diff --git a/.changelog/1068.txt b/.changelog/1068.txt new file mode 100644 index 000000000..386bbe106 --- /dev/null +++ b/.changelog/1068.txt @@ -0,0 +1,3 @@ +```release-note:new-resource +harness_governance_rule - Added Governance Rule resource in Harness terraform provider +``` \ No newline at end of file diff --git a/docs/data-sources/governance_rule.md b/docs/data-sources/governance_rule.md new file mode 100644 index 000000000..a548c03cf --- /dev/null +++ b/docs/data-sources/governance_rule.md @@ -0,0 +1,34 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "harness_governance_rule Data Source - terraform-provider-harness" +subcategory: "Next Gen" +description: |- + Datasource for looking up a rule. +--- + +# harness_governance_rule (Data Source) + +Datasource for looking up a rule. + +## Example Usage + +```terraform +data "harness_governance_rule" "example" { + rule_id = "rule_id" +} +``` + + +## Schema + +### Required + +- `rule_id` (String) Id of rule. + +### Read-Only + +- `cloud_provider` (String) The cloud provider for the rule. +- `description` (String) Description for rule. +- `id` (String) The ID of this resource. +- `name` (String) Name of the rule. +- `rules_yaml` (String) Policy YAML of the rule. diff --git a/docs/resources/governance_rule.md b/docs/resources/governance_rule.md new file mode 100644 index 000000000..7bee4abfa --- /dev/null +++ b/docs/resources/governance_rule.md @@ -0,0 +1,40 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "harness_governance_rule Resource - terraform-provider-harness" +subcategory: "Next Gen" +description: |- + Resource for creating, updating, and managing rule. +--- + +# harness_governance_rule (Resource) + +Resource for creating, updating, and managing rule. + + + + +## Schema + +### Required + +- `cloud_provider` (String) The cloud provider for the rule. It should be either AWS, AZURE or GCP. +- `name` (String) Name of the rule. +- `rules_yaml` (String) The policy YAML of the rule + +### Optional + +- `description` (String) Description for rule. + +### Read-Only + +- `id` (String) The ID of this resource. +- `rule_id` (String) Id of the rule. + +## Import + +Import is supported using the following syntax: + +```shell +# Import governance enforcement +terraform import harness_governance_rule.example +``` diff --git a/examples/data-sources/harness_governance_rule/data-source.tf b/examples/data-sources/harness_governance_rule/data-source.tf new file mode 100644 index 000000000..3c4abb454 --- /dev/null +++ b/examples/data-sources/harness_governance_rule/data-source.tf @@ -0,0 +1,3 @@ +data "harness_governance_rule" "example" { + rule_id = "rule_id" +} diff --git a/examples/resources/harness_governance_rule/import.sh b/examples/resources/harness_governance_rule/import.sh new file mode 100644 index 000000000..0c2823426 --- /dev/null +++ b/examples/resources/harness_governance_rule/import.sh @@ -0,0 +1,2 @@ +# Import governance enforcement +terraform import harness_governance_rule.example diff --git a/examples/resources/harness_governance_rule/resources.tf b/examples/resources/harness_governance_rule/resources.tf new file mode 100644 index 000000000..bf89bbd46 --- /dev/null +++ b/examples/resources/harness_governance_rule/resources.tf @@ -0,0 +1,7 @@ +resource "harness_governance_rule" "example" { + identifier = "identifier" + name = "name" + cloud_provider = "AWS/AZURE/GCP" + description = "description" + rules_yaml = "policies:\n - name: aws-list-ec2\n resource: aws.ec2" +} \ No newline at end of file diff --git a/go.mod b/go.mod index 411e2d170..0b580ac22 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( github.com/antihax/optional v1.0.0 github.com/aws/aws-sdk-go v1.46.4 github.com/docker/docker v24.0.5+incompatible - github.com/harness/harness-go-sdk v0.4.5 + github.com/harness/harness-go-sdk v0.4.8 github.com/harness/harness-openapi-go-client v0.0.21 github.com/hashicorp/go-cleanhttp v0.5.2 github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320 diff --git a/go.sum b/go.sum index de6f6eff2..e0b86c198 100644 --- a/go.sum +++ b/go.sum @@ -54,8 +54,8 @@ github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/harness/harness-go-sdk v0.4.5 h1:nXcZ5XNP+PFfRgo9A3yuQi5zTQbinWpooZq3VRBTN2E= -github.com/harness/harness-go-sdk v0.4.5/go.mod h1:a/1HYTgVEuNEoh3Z3IsOHZdlUNxl94KcX57ZSNVGll0= +github.com/harness/harness-go-sdk v0.4.8 h1:ufWTQgFKq5xGLGPEm8CMuEHv0v0Gveri2TzDTdPqDMw= +github.com/harness/harness-go-sdk v0.4.8/go.mod h1:a/1HYTgVEuNEoh3Z3IsOHZdlUNxl94KcX57ZSNVGll0= github.com/harness/harness-openapi-go-client v0.0.21 h1:VtJnpQKZvCAlaCmUPbNR69OT3c5WRdhNN5TOgUwtwZ4= github.com/harness/harness-openapi-go-client v0.0.21/go.mod h1:u0vqYb994BJGotmEwJevF4L3BNAdU9i8ui2d22gmLPA= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 19971e779..6a8cecb70 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -9,6 +9,7 @@ import ( dbschema "github.com/harness/terraform-provider-harness/internal/service/platform/db_schema" "github.com/harness/terraform-provider-harness/internal/service/platform/gitx/webhook" governance_enforcement "github.com/harness/terraform-provider-harness/internal/service/platform/governance/enforcement" + governance_rule "github.com/harness/terraform-provider-harness/internal/service/platform/governance/rule" "github.com/harness/terraform-provider-harness/internal/service/platform/notification_rule" "github.com/harness/terraform-provider-harness/internal/service/platform/feature_flag" @@ -282,6 +283,7 @@ func Provider(version string) func() *schema.Provider { "harness_platform_gitops_app_project": gitops_project.DataSourceGitOpsProject(), "harness_platform_gitx_webhook": webhook.DataSourceWebhook(), "harness_governance_rule_enforcement": governance_enforcement.DatasourceRuleEnforcement(), + "harness_governance_rule": governance_rule.DatasourceRule(), }, ResourcesMap: map[string]*schema.Resource{ "harness_platform_template": pl_template.ResourceTemplate(), @@ -425,6 +427,7 @@ func Provider(version string) func() *schema.Provider { "harness_platform_connector_custom_secret_manager": connector.ResourceConnectorCSM(), "harness_platform_gitx_webhook": webhook.ResourceWebhook(), "harness_governance_rule_enforcement": governance_enforcement.ResourceRuleEnforcement(), + "harness_governance_rule": governance_rule.ResourceRule(), }, } diff --git a/internal/service/platform/governance/rule/rule.go b/internal/service/platform/governance/rule/rule.go new file mode 100644 index 000000000..301127ba2 --- /dev/null +++ b/internal/service/platform/governance/rule/rule.go @@ -0,0 +1,162 @@ +package governance_rule + +import ( + "context" + "net/http" + + "github.com/harness/harness-go-sdk/harness/nextgen" + "github.com/harness/terraform-provider-harness/helpers" + "github.com/harness/terraform-provider-harness/internal" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" +) + +func ResourceRule() *schema.Resource { + resource := &schema.Resource{ + Description: "Resource for creating, updating, and managing rule.", + ReadContext: resourceRuleRead, + CreateContext: resourceRuleCreateOrUpdate, + UpdateContext: resourceRuleCreateOrUpdate, + DeleteContext: resourceRuleDelete, + Importer: helpers.AccountLevelResourceImporter, + Schema: map[string]*schema.Schema{ + "name": { + Description: "Name of the rule.", + Type: schema.TypeString, + Required: true, + }, + "description": { + Description: "Description for rule.", + Type: schema.TypeString, + Optional: true, + }, + "cloud_provider": { + Description: "The cloud provider for the rule. It should be either AWS, AZURE or GCP.", + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice([]string{"AWS", "GCP", "AZURE"}, false), + }, + "rules_yaml": { + Description: "The policy YAML of the rule", + Type: schema.TypeString, + Required: true, + }, + "rule_id": { + Description: "Id of the rule.", + Type: schema.TypeString, + Computed: true, + }, + }, + } + + return resource +} + +func resourceRuleRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + c, ctx := meta.(*internal.Session).GetPlatformClientWithContext(ctx) + + id := d.Id() + resp, httpResp, err := c.RuleApi.GetPolicies(ctx, readRuleRequest(id), c.AccountId, nil) + + if err != nil { + return helpers.HandleReadApiError(err, d, httpResp) + } + + if resp.Data != nil { + err := readRuleResponse(d, resp.Data) + if err != nil { + return helpers.HandleReadApiError(err, d, httpResp) + } + } + + return nil +} + +func readRuleRequest(id string) nextgen.ListDto { + return nextgen.ListDto{ + Query: &nextgen.RuleRequest{ + PolicyIds: []string{id}, + }, + } +} + +func readRuleResponse(d *schema.ResourceData, ruleList *nextgen.RuleList) error { + rule := ruleList.Rules[0] + + d.Set("name", rule.Name) + d.Set("cloud_provider", rule.CloudProvider) + d.Set("description", rule.Description) + d.Set("rules_yaml", rule.RulesYaml) + + return nil +} + +func resourceRuleCreateOrUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + c, ctx := meta.(*internal.Session).GetPlatformClientWithContext(ctx) + + var err error + var resp nextgen.ResponseDtoRule + var httpResp *http.Response + + id := d.Id() + + if id == "" { + resp, httpResp, err = c.RuleApi.CreateNewRule(ctx, buildRule(d, false), c.AccountId) + } else { + resp, httpResp, err = c.RuleApi.UpdateRule(ctx, buildRule(d, true), c.AccountId) + } + + if err != nil { + return helpers.HandleApiError(err, d, httpResp) + } + + if resp.Data != nil { + createOrUpdateRuleResponse(d, resp.Data) + } + + return nil +} + +func buildRule(d *schema.ResourceData, update bool) nextgen.CreateRuleDto { + rule := &nextgen.CcmRule{ + Name: d.Get("name").(string), + CloudProvider: d.Get("cloud_provider").(string), + Description: d.Get("description").(string), + RulesYaml: d.Get("rules_yaml").(string), + IsOOTB: false, + } + + if update { + rule.Uuid = d.Id() + } + + return nextgen.CreateRuleDto{ + Rule: rule, + } +} + +func createOrUpdateRuleResponse(d *schema.ResourceData, rule *nextgen.CcmRule) error { + d.SetId(rule.Uuid) + d.Set("rule_id", rule.Uuid) + d.Set("name", rule.Name) + d.Set("cloud_provider", rule.CloudProvider) + d.Set("description", rule.Description) + d.Set("rules_yaml", rule.RulesYaml) + + return nil +} + +func resourceRuleDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + c, ctx := meta.(*internal.Session).GetPlatformClientWithContext(ctx) + + id := d.Id() + + _, httpResp, err := c.RuleApi.DeleteRule(ctx, c.AccountId, id) + + if err != nil { + return helpers.HandleApiError(err, d, httpResp) + } + + return nil +} diff --git a/internal/service/platform/governance/rule/rule_data_source.go b/internal/service/platform/governance/rule/rule_data_source.go new file mode 100644 index 000000000..f433f9336 --- /dev/null +++ b/internal/service/platform/governance/rule/rule_data_source.go @@ -0,0 +1,68 @@ +package governance_rule + +import ( + "context" + + "github.com/harness/terraform-provider-harness/helpers" + "github.com/harness/terraform-provider-harness/internal" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func DatasourceRule() *schema.Resource { + return &schema.Resource{ + Description: "Datasource for looking up a rule.", + + ReadContext: resourceRuleReadDataSource, + + Schema: map[string]*schema.Schema{ + "rule_id": { + Description: "Id of rule.", + Type: schema.TypeString, + Required: true, + }, + "name": { + Description: "Name of the rule.", + Type: schema.TypeString, + Computed: true, + }, + "rules_yaml": { + Description: "Policy YAML of the rule.", + Type: schema.TypeString, + Computed: true, + }, + "cloud_provider": { + Description: "The cloud provider for the rule.", + Type: schema.TypeString, + Computed: true, + }, + "description": { + Description: "Description for rule.", + Type: schema.TypeString, + Computed: true, + }, + }, + } +} + +func resourceRuleReadDataSource(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + c, ctx := meta.(*internal.Session).GetPlatformClientWithContext(ctx) + + id := d.Get("rule_id").(string) + resp, httpResp, err := c.RuleApi.GetPolicies(ctx, readRuleRequest(id), c.AccountId, nil) + + if err != nil { + return helpers.HandleReadApiError(err, d, httpResp) + } + + if resp.Data != nil { + err := readRuleResponse(d, resp.Data) + if err != nil { + return helpers.HandleReadApiError(err, d, httpResp) + } + } + + d.SetId(id) + + return nil +} diff --git a/internal/service/platform/governance/rule/rule_data_source_test.go b/internal/service/platform/governance/rule/rule_data_source_test.go new file mode 100644 index 000000000..63f016329 --- /dev/null +++ b/internal/service/platform/governance/rule/rule_data_source_test.go @@ -0,0 +1,48 @@ +package governance_rule_test + +import ( + "fmt" + "testing" + + "github.com/harness/harness-go-sdk/harness/utils" + "github.com/harness/terraform-provider-harness/internal/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +func TestAccDataSourceRule(t *testing.T) { + var ( + name = fmt.Sprintf("%s_%s", t.Name(), utils.RandStringBytes(4)) + resourceName = "data.harness_governance_rule.test" + ) + + resource.UnitTest(t, resource.TestCase{ + PreCheck: func() { acctest.TestAccPreCheck(t) }, + ProviderFactories: acctest.ProviderFactories, + Steps: []resource.TestStep{ + { + Config: testAccDataSourceRule(name), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "name", name), + resource.TestCheckResourceAttr(resourceName, "cloud_provider", "AWS"), + resource.TestCheckResourceAttr(resourceName, "rules_yaml", "policies:\n - name: aws-list-ec2\n resource: aws.ec2"), + resource.TestCheckResourceAttr(resourceName, "description", "Dummy"), + ), + }, + }, + }) +} + +func testAccDataSourceRule(name string) string { + return fmt.Sprintf(` + resource "harness_governance_rule" "test" { + name = "%[1]s" + cloud_provider = "AWS" + rules_yaml = "policies:\n - name: aws-list-ec2\n resource: aws.ec2" + description = "Dummy" + } + + data "harness_governance_rule" "test" { + rule_id = harness_governance_rule.test.rule_id + } + `, name) +} diff --git a/internal/service/platform/governance/rule/rule_test.go b/internal/service/platform/governance/rule/rule_test.go new file mode 100644 index 000000000..7cac2fbd3 --- /dev/null +++ b/internal/service/platform/governance/rule/rule_test.go @@ -0,0 +1,97 @@ +package governance_rule_test + +import ( + "fmt" + "testing" + + "github.com/harness/harness-go-sdk/harness/nextgen" + "github.com/harness/harness-go-sdk/harness/utils" + "github.com/harness/terraform-provider-harness/internal/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" +) + +func TestAccResourceRule(t *testing.T) { + name := fmt.Sprintf("%s_%s", t.Name(), utils.RandStringBytes(5)) + updatedName := fmt.Sprintf("%s_updated", name) + resourceName := "harness_governance_rule.test" + + resource.UnitTest(t, resource.TestCase{ + PreCheck: func() { acctest.TestAccPreCheck(t) }, + ProviderFactories: acctest.ProviderFactories, + CheckDestroy: testAccRuleDestroy(resourceName), + Steps: []resource.TestStep{ + { + Config: testAccResourceRule(name), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "name", name), + resource.TestCheckResourceAttr(resourceName, "cloud_provider", "AWS"), + resource.TestCheckResourceAttr(resourceName, "description", "Dummy"), + resource.TestCheckResourceAttr(resourceName, "rules_yaml", "policies:\n - name: aws-list-ec2\n resource: aws.ec2"), + ), + }, + { + Config: testAccResourceRule(updatedName), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "name", updatedName), + resource.TestCheckResourceAttr(resourceName, "cloud_provider", "AWS"), + resource.TestCheckResourceAttr(resourceName, "description", "Dummy"), + resource.TestCheckResourceAttr(resourceName, "rules_yaml", "policies:\n - name: aws-list-ec2\n resource: aws.ec2"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: false, + ImportStateVerifyIgnore: []string{"identifier"}, + ImportStateIdFunc: acctest.AccountLevelResourceImportStateIdFunc(resourceName), + }, + }, + }) +} + +func testAccResourceRule(name string) string { + return fmt.Sprintf(` + resource "harness_governance_rule" "test" { + name = "%[1]s" + cloud_provider = "AWS" + description = "Dummy" + rules_yaml = "policies:\n - name: aws-list-ec2\n resource: aws.ec2" + } + `, name) +} + +func testAccRuleDestroy(resourceName string) resource.TestCheckFunc { + return func(state *terraform.State) error { + rule, _ := testGetRule(resourceName, state) + if rule != nil { + return fmt.Errorf("Found rule: %s", rule.Name) + } + return nil + } +} + +func testGetRule(resourceName string, state *terraform.State) (*nextgen.CcmRule, error) { + r := acctest.TestAccGetResource(resourceName, state) + c, ctx := acctest.TestAccGetPlatformClientWithContext() + ruleId := r.Primary.ID + + resp, _, err := c.RuleApi.GetPolicies(ctx, readRuleRequest(ruleId), c.AccountId, nil) + + if err != nil { + return nil, err + } + + if len(resp.Data.Rules) > 0 { + return &resp.Data.Rules[0], nil + } + return nil, nil +} + +func readRuleRequest(id string) nextgen.ListDto { + return nextgen.ListDto{ + Query: &nextgen.RuleRequest{ + PolicyIds: []string{id}, + }, + } +}