diff --git a/.changelog/12008.txt b/.changelog/12008.txt new file mode 100644 index 00000000000..3784a417d29 --- /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/services/orgpolicy/resource_org_policy_policy.go b/google/services/orgpolicy/resource_org_policy_policy.go index 00684bb57a2..6c244ef8372 100644 --- a/google/services/orgpolicy/resource_org_policy_policy.go +++ b/google/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/google/tpgresource" transport_tpg "github.com/hashicorp/terraform-provider-google/google/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/services/orgpolicy/resource_org_policy_policy_test.go b/google/services/orgpolicy/resource_org_policy_policy_test.go index 5c05a9953dd..cea1e0c5adc 100644 --- a/google/services/orgpolicy/resource_org_policy_policy_test.go +++ b/google/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 1c6bde0cd2e..3ed33c5cdc2 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')".