From 24f330bc2eecf666802e5c5983bc71feb39da500 Mon Sep 17 00:00:00 2001 From: Modular Magician Date: Tue, 10 Dec 2024 16:00:28 +0000 Subject: [PATCH] Add `parameters` to `google_org_policy_policy` (#12008) [upstream:971f4cc87fb960871a449798d060c749e831e43c] Signed-off-by: Modular Magician --- .changelog/12008.txt | 3 + .../orgpolicy/resource_org_policy_policy.go | 101 +++++++++++++++-- .../resource_org_policy_policy_test.go | 103 ++++++++++++++++++ .../docs/r/org_policy_policy.html.markdown | 31 ++++++ 4 files changed, 228 insertions(+), 10 deletions(-) create mode 100644 .changelog/12008.txt diff --git a/.changelog/12008.txt b/.changelog/12008.txt new file mode 100644 index 0000000000..3784a417d2 --- /dev/null +++ b/.changelog/12008.txt @@ -0,0 +1,3 @@ +```release-note:enhancement +orgpolicy: added `parameters` fields to `google_org_policy_policy` resource (beta) +``` \ No newline at end of file diff --git a/google-beta/services/orgpolicy/resource_org_policy_policy.go b/google-beta/services/orgpolicy/resource_org_policy_policy.go index 346b562b16..4bcbd6761c 100644 --- a/google-beta/services/orgpolicy/resource_org_policy_policy.go +++ b/google-beta/services/orgpolicy/resource_org_policy_policy.go @@ -18,6 +18,7 @@ package orgpolicy import ( + "encoding/json" "fmt" "log" "net/http" @@ -27,6 +28,8 @@ import ( "time" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/structure" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" "github.com/hashicorp/terraform-provider-google-beta/google-beta/tpgresource" transport_tpg "github.com/hashicorp/terraform-provider-google-beta/google-beta/transport" @@ -139,6 +142,13 @@ func ResourceOrgPolicyPolicy() *schema.Resource { Optional: true, Description: `If '"TRUE"', then the 'Policy' is enforced. If '"FALSE"', then any configuration is acceptable. This field can be set only in Policies for boolean constraints.`, }, + "parameters": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringIsJSON, + StateFunc: func(v interface{}) string { s, _ := structure.NormalizeJsonString(v); return s }, + Description: `Optional. Required for Managed Constraints if parameters defined in constraints. Pass parameter values when policy enforcement is enabled. Ensure that parameter value types match those defined in the constraint definition. For example: { \"allowedLocations\" : [\"us-east1\", \"us-west1\"], \"allowAll\" : true }`, + }, "values": { Type: schema.TypeList, Optional: true, @@ -250,6 +260,13 @@ func ResourceOrgPolicyPolicy() *schema.Resource { Optional: true, Description: `If '"TRUE"', then the 'Policy' is enforced. If '"FALSE"', then any configuration is acceptable. This field can be set only in Policies for boolean constraints.`, }, + "parameters": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringIsJSON, + StateFunc: func(v interface{}) string { s, _ := structure.NormalizeJsonString(v); return s }, + Description: `Optional. Required for Managed Constraints if parameters defined in constraints. Pass parameter values when policy enforcement is enabled. Ensure that parameter value types match those defined in the constraint definition. For example: { \"allowedLocations\" : [\"us-east1\", \"us-west1\"], \"allowAll\" : true }`, + }, "values": { Type: schema.TypeList, Optional: true, @@ -609,11 +626,12 @@ func flattenOrgPolicyPolicySpecRules(v interface{}, d *schema.ResourceData, conf continue } transformed = append(transformed, map[string]interface{}{ - "values": flattenOrgPolicyPolicySpecRulesValues(original["values"], d, config), - "allow_all": flattenOrgPolicyPolicySpecRulesAllowAll(original["allowAll"], d, config), - "deny_all": flattenOrgPolicyPolicySpecRulesDenyAll(original["denyAll"], d, config), - "enforce": flattenOrgPolicyPolicySpecRulesEnforce(original["enforce"], d, config), - "condition": flattenOrgPolicyPolicySpecRulesCondition(original["condition"], d, config), + "values": flattenOrgPolicyPolicySpecRulesValues(original["values"], d, config), + "allow_all": flattenOrgPolicyPolicySpecRulesAllowAll(original["allowAll"], d, config), + "deny_all": flattenOrgPolicyPolicySpecRulesDenyAll(original["denyAll"], d, config), + "enforce": flattenOrgPolicyPolicySpecRulesEnforce(original["enforce"], d, config), + "parameters": flattenOrgPolicyPolicySpecRulesParameters(original["parameters"], d, config), + "condition": flattenOrgPolicyPolicySpecRulesCondition(original["condition"], d, config), }) } return transformed @@ -662,6 +680,18 @@ func flattenOrgPolicyPolicySpecRulesEnforce(v interface{}, d *schema.ResourceDat return strings.ToUpper(strconv.FormatBool(v.(bool))) } +func flattenOrgPolicyPolicySpecRulesParameters(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + if v == nil { + return nil + } + b, err := json.Marshal(v) + if err != nil { + // TODO: return error once https://github.com/GoogleCloudPlatform/magic-modules/issues/3257 is fixed. + log.Printf("[ERROR] failed to marshal schema to JSON: %v", err) + } + return string(b) +} + func flattenOrgPolicyPolicySpecRulesCondition(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { if v == nil { return nil @@ -747,11 +777,12 @@ func flattenOrgPolicyPolicyDryRunSpecRules(v interface{}, d *schema.ResourceData continue } transformed = append(transformed, map[string]interface{}{ - "values": flattenOrgPolicyPolicyDryRunSpecRulesValues(original["values"], d, config), - "allow_all": flattenOrgPolicyPolicyDryRunSpecRulesAllowAll(original["allowAll"], d, config), - "deny_all": flattenOrgPolicyPolicyDryRunSpecRulesDenyAll(original["denyAll"], d, config), - "enforce": flattenOrgPolicyPolicyDryRunSpecRulesEnforce(original["enforce"], d, config), - "condition": flattenOrgPolicyPolicyDryRunSpecRulesCondition(original["condition"], d, config), + "values": flattenOrgPolicyPolicyDryRunSpecRulesValues(original["values"], d, config), + "allow_all": flattenOrgPolicyPolicyDryRunSpecRulesAllowAll(original["allowAll"], d, config), + "deny_all": flattenOrgPolicyPolicyDryRunSpecRulesDenyAll(original["denyAll"], d, config), + "enforce": flattenOrgPolicyPolicyDryRunSpecRulesEnforce(original["enforce"], d, config), + "parameters": flattenOrgPolicyPolicyDryRunSpecRulesParameters(original["parameters"], d, config), + "condition": flattenOrgPolicyPolicyDryRunSpecRulesCondition(original["condition"], d, config), }) } return transformed @@ -800,6 +831,18 @@ func flattenOrgPolicyPolicyDryRunSpecRulesEnforce(v interface{}, d *schema.Resou return strings.ToUpper(strconv.FormatBool(v.(bool))) } +func flattenOrgPolicyPolicyDryRunSpecRulesParameters(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + if v == nil { + return nil + } + b, err := json.Marshal(v) + if err != nil { + // TODO: return error once https://github.com/GoogleCloudPlatform/magic-modules/issues/3257 is fixed. + log.Printf("[ERROR] failed to marshal schema to JSON: %v", err) + } + return string(b) +} + func flattenOrgPolicyPolicyDryRunSpecRulesCondition(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { if v == nil { return nil @@ -944,6 +987,13 @@ func expandOrgPolicyPolicySpecRules(v interface{}, d tpgresource.TerraformResour transformed["enforce"] = transformedEnforce } + transformedParameters, err := expandOrgPolicyPolicySpecRulesParameters(original["parameters"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedParameters); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["parameters"] = transformedParameters + } + transformedCondition, err := expandOrgPolicyPolicySpecRulesCondition(original["condition"], d, config) if err != nil { return nil, err @@ -1026,6 +1076,18 @@ func expandOrgPolicyPolicySpecRulesEnforce(v interface{}, d tpgresource.Terrafor return b, nil } +func expandOrgPolicyPolicySpecRulesParameters(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + b := []byte(v.(string)) + if len(b) == 0 { + return nil, nil + } + m := make(map[string]interface{}) + if err := json.Unmarshal(b, &m); err != nil { + return nil, err + } + return m, nil +} + func expandOrgPolicyPolicySpecRulesCondition(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { l := v.([]interface{}) if len(l) == 0 || l[0] == nil { @@ -1183,6 +1245,13 @@ func expandOrgPolicyPolicyDryRunSpecRules(v interface{}, d tpgresource.Terraform transformed["enforce"] = transformedEnforce } + transformedParameters, err := expandOrgPolicyPolicyDryRunSpecRulesParameters(original["parameters"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedParameters); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["parameters"] = transformedParameters + } + transformedCondition, err := expandOrgPolicyPolicyDryRunSpecRulesCondition(original["condition"], d, config) if err != nil { return nil, err @@ -1265,6 +1334,18 @@ func expandOrgPolicyPolicyDryRunSpecRulesEnforce(v interface{}, d tpgresource.Te return b, nil } +func expandOrgPolicyPolicyDryRunSpecRulesParameters(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + b := []byte(v.(string)) + if len(b) == 0 { + return nil, nil + } + m := make(map[string]interface{}) + if err := json.Unmarshal(b, &m); err != nil { + return nil, err + } + return m, nil +} + func expandOrgPolicyPolicyDryRunSpecRulesCondition(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { l := v.([]interface{}) if len(l) == 0 || l[0] == nil { diff --git a/google-beta/services/orgpolicy/resource_org_policy_policy_test.go b/google-beta/services/orgpolicy/resource_org_policy_policy_test.go index dc11046590..54c577d464 100644 --- a/google-beta/services/orgpolicy/resource_org_policy_policy_test.go +++ b/google-beta/services/orgpolicy/resource_org_policy_policy_test.go @@ -460,3 +460,106 @@ func testAccCheckOrgPolicyPolicyDestroyProducer(t *testing.T) func(s *terraform. return nil } } +func TestAccOrgPolicyPolicy_EnforceParameterizedMCPolicy(t *testing.T) { + // Skip this test as no constraints yet launched in production, verified functionality with manual testing. + t.Skip() + t.Parallel() + + context := map[string]interface{}{ + "org_id": envvar.GetTestOrgFromEnv(t), + "random_suffix": acctest.RandString(t, 10), + } + + acctest.VcrTest(t, resource.TestCase{ + PreCheck: func() { acctest.AccTestPreCheck(t) }, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), + CheckDestroy: testAccCheckOrgPolicyPolicyDestroyProducer(t), + Steps: []resource.TestStep{ + { + Config: testAccOrgPolicyPolicy_EnforceParameterizedMCPolicy(context), + }, + { + ResourceName: "google_org_policy_policy.primary", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"name", "spec.0.rules.0.condition.0.expression"}, + }, + }, + }) +} +func testAccOrgPolicyPolicy_EnforceParameterizedMCPolicy(context map[string]interface{}) string { + return acctest.Nprintf(` +resource "google_org_policy_policy" "primary" { + name = "projects/${google_project.basic.name}/policies/essentialcontacts.managed.allowedContactDomains" + parent = "projects/${google_project.basic.name}" + + spec { + rules { + enforce = "TRUE" + parameters = "{\"allowedDomains\": [\"@google.com\"]}" + } + } +} + +resource "google_project" "basic" { + project_id = "tf-test-id%{random_suffix}" + name = "tf-test-id%{random_suffix}" + org_id = "%{org_id}" + deletion_policy = "DELETE" +} + + +`, context) +} + +func TestAccOrgPolicyPolicy_EnforceParameterizedMCDryRunPolicy(t *testing.T) { + // Skip this test as no constraints yet launched in production, verified functionality with manual testing. + t.Skip() + t.Parallel() + + context := map[string]interface{}{ + "org_id": envvar.GetTestOrgFromEnv(t), + "random_suffix": acctest.RandString(t, 10), + } + + acctest.VcrTest(t, resource.TestCase{ + PreCheck: func() { acctest.AccTestPreCheck(t) }, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), + CheckDestroy: testAccCheckOrgPolicyPolicyDestroyProducer(t), + Steps: []resource.TestStep{ + { + Config: testAccOrgPolicyPolicy_EnforceParameterizedMCDryRunPolicy(context), + }, + { + ResourceName: "google_org_policy_policy.primary", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"name", "spec.0.rules.0.condition.0.expression"}, + }, + }, + }) +} +func testAccOrgPolicyPolicy_EnforceParameterizedMCDryRunPolicy(context map[string]interface{}) string { + return acctest.Nprintf(` +resource "google_org_policy_policy" "primary" { + name = "projects/${google_project.basic.name}/policies/essentialcontacts.managed.allowedContactDomains" + parent = "projects/${google_project.basic.name}" + + dry_run_spec { + rules { + enforce = "TRUE" + parameters = "{\"allowedDomains\": [\"@google.com\"]}" + } + } +} + +resource "google_project" "basic" { + project_id = "tf-test-id%{random_suffix}" + name = "tf-test-id%{random_suffix}" + org_id = "%{org_id}" + deletion_policy = "DELETE" +} + + +`, context) +} diff --git a/website/docs/r/org_policy_policy.html.markdown b/website/docs/r/org_policy_policy.html.markdown index 1c6bde0cd2..3ed33c5cdc 100644 --- a/website/docs/r/org_policy_policy.html.markdown +++ b/website/docs/r/org_policy_policy.html.markdown @@ -157,6 +157,29 @@ resource "google_org_policy_policy" "primary" { } } ``` +## Example Usage - Org Policy Policy Parameters Enforce + + +```hcl +resource "google_org_policy_policy" "primary" { + name = "projects/${google_project.basic.name}/policies/compute.managed.restrictDiskCreation" + parent = "projects/${google_project.basic.name}" + + spec { + rules { + enforce = "TRUE" + parameters = jsonencode({"isSizeLimitCheck" : true, "allowedDiskTypes" : ["pd-ssd", "pd-standard"]}) + } + } +} + +resource "google_project" "basic" { + project_id = "id" + name = "id" + org_id = "123456789" + deletion_policy = "DELETE" +} +``` ## Argument Reference @@ -229,6 +252,10 @@ The following arguments are supported: (Optional) If `"TRUE"`, then the `Policy` is enforced. If `"FALSE"`, then any configuration is acceptable. This field can be set only in Policies for boolean constraints. +* `parameters` - + (Optional) + Optional. Required for Managed Constraints if parameters defined in constraints. Pass parameter values when policy enforcement is enabled. Ensure that parameter value types match those defined in the constraint definition. For example: { \"allowedLocations\" : [\"us-east1\", \"us-west1\"], \"allowAll\" : true } + * `condition` - (Optional) A condition which determines whether this rule is used in the evaluation of the policy. When set, the `expression` field in the `Expr' must include from 1 to 10 subexpressions, joined by the "||" or "&&" operators. Each subexpression must be of the form "resource.matchTag('/tag_key_short_name, 'tag_value_short_name')". or "resource.matchTagId('tagKeys/key_id', 'tagValues/value_id')". where key_name and value_name are the resource names for Label Keys and Values. These names are available from the Tag Manager Service. An example expression is: "resource.matchTag('123456789/environment, 'prod')". or "resource.matchTagId('tagKeys/123', 'tagValues/456')". @@ -306,6 +333,10 @@ The following arguments are supported: (Optional) If `"TRUE"`, then the `Policy` is enforced. If `"FALSE"`, then any configuration is acceptable. This field can be set only in Policies for boolean constraints. +* `parameters` - + (Optional) + Optional. Required for Managed Constraints if parameters defined in constraints. Pass parameter values when policy enforcement is enabled. Ensure that parameter value types match those defined in the constraint definition. For example: { \"allowedLocations\" : [\"us-east1\", \"us-west1\"], \"allowAll\" : true } + * `condition` - (Optional) A condition which determines whether this rule is used in the evaluation of the policy. When set, the `expression` field in the `Expr' must include from 1 to 10 subexpressions, joined by the "||" or "&&" operators. Each subexpression must be of the form "resource.matchTag('/tag_key_short_name, 'tag_value_short_name')". or "resource.matchTagId('tagKeys/key_id', 'tagValues/value_id')". where key_name and value_name are the resource names for Label Keys and Values. These names are available from the Tag Manager Service. An example expression is: "resource.matchTag('123456789/environment, 'prod')". or "resource.matchTagId('tagKeys/123', 'tagValues/456')".