diff --git a/.changelog/6608.txt b/.changelog/6608.txt new file mode 100644 index 0000000000..c27d23104b --- /dev/null +++ b/.changelog/6608.txt @@ -0,0 +1,3 @@ +```release-note:new-resource +google_org_policy_custom_constraint +``` diff --git a/google-beta/config.go b/google-beta/config.go index a889f39dff..038e9e89e5 100644 --- a/google-beta/config.go +++ b/google-beta/config.go @@ -230,6 +230,7 @@ type Config struct { NetworkManagementBasePath string NetworkServicesBasePath string NotebooksBasePath string + OrgPolicyBasePath string OSConfigBasePath string OSLoginBasePath string PrivatecaBasePath string @@ -332,6 +333,7 @@ const MonitoringBasePathKey = "Monitoring" const NetworkManagementBasePathKey = "NetworkManagement" const NetworkServicesBasePathKey = "NetworkServices" const NotebooksBasePathKey = "Notebooks" +const OrgPolicyBasePathKey = "OrgPolicy" const OSConfigBasePathKey = "OSConfig" const OSLoginBasePathKey = "OSLogin" const PrivatecaBasePathKey = "Privateca" @@ -428,6 +430,7 @@ var DefaultBasePaths = map[string]string{ NetworkManagementBasePathKey: "https://networkmanagement.googleapis.com/v1/", NetworkServicesBasePathKey: "https://networkservices.googleapis.com/v1/", NotebooksBasePathKey: "https://notebooks.googleapis.com/v1/", + OrgPolicyBasePathKey: "https://orgpolicy.googleapis.com/v2/", OSConfigBasePathKey: "https://osconfig.googleapis.com/v1beta/", OSLoginBasePathKey: "https://oslogin.googleapis.com/v1/", PrivatecaBasePathKey: "https://privateca.googleapis.com/v1/", @@ -1300,6 +1303,7 @@ func ConfigureBasePaths(c *Config) { c.NetworkManagementBasePath = DefaultBasePaths[NetworkManagementBasePathKey] c.NetworkServicesBasePath = DefaultBasePaths[NetworkServicesBasePathKey] c.NotebooksBasePath = DefaultBasePaths[NotebooksBasePathKey] + c.OrgPolicyBasePath = DefaultBasePaths[OrgPolicyBasePathKey] c.OSConfigBasePath = DefaultBasePaths[OSConfigBasePathKey] c.OSLoginBasePath = DefaultBasePaths[OSLoginBasePathKey] c.PrivatecaBasePath = DefaultBasePaths[PrivatecaBasePathKey] diff --git a/google-beta/provider.go b/google-beta/provider.go index 9d72c44a42..ba28472faa 100644 --- a/google-beta/provider.go +++ b/google-beta/provider.go @@ -613,6 +613,14 @@ func Provider() *schema.Provider { "GOOGLE_NOTEBOOKS_CUSTOM_ENDPOINT", }, DefaultBasePaths[NotebooksBasePathKey]), }, + "org_policy_custom_endpoint": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validateCustomEndpoint, + DefaultFunc: schema.MultiEnvDefaultFunc([]string{ + "GOOGLE_ORG_POLICY_CUSTOM_ENDPOINT", + }, DefaultBasePaths[OrgPolicyBasePathKey]), + }, "os_config_custom_endpoint": { Type: schema.TypeString, Optional: true, @@ -956,9 +964,9 @@ func Provider() *schema.Provider { return provider } -// Generated resources: 264 +// Generated resources: 265 // Generated IAM resources: 171 -// Total generated resources: 435 +// Total generated resources: 436 func ResourceMap() map[string]*schema.Resource { resourceMap, _ := ResourceMapWithErrors() return resourceMap @@ -1320,6 +1328,7 @@ func ResourceMapWithErrors() (map[string]*schema.Resource, error) { "google_notebooks_runtime_iam_member": ResourceIamMember(NotebooksRuntimeIamSchema, NotebooksRuntimeIamUpdaterProducer, NotebooksRuntimeIdParseFunc), "google_notebooks_runtime_iam_policy": ResourceIamPolicy(NotebooksRuntimeIamSchema, NotebooksRuntimeIamUpdaterProducer, NotebooksRuntimeIdParseFunc), "google_notebooks_location": resourceNotebooksLocation(), + "google_org_policy_custom_constraint": resourceOrgPolicyCustomConstraint(), "google_os_config_patch_deployment": resourceOSConfigPatchDeployment(), "google_os_config_guest_policies": resourceOSConfigGuestPolicies(), "google_os_login_ssh_public_key": resourceOSLoginSSHPublicKey(), @@ -1688,6 +1697,7 @@ func providerConfigure(ctx context.Context, d *schema.ResourceData, p *schema.Pr config.NetworkManagementBasePath = d.Get("network_management_custom_endpoint").(string) config.NetworkServicesBasePath = d.Get("network_services_custom_endpoint").(string) config.NotebooksBasePath = d.Get("notebooks_custom_endpoint").(string) + config.OrgPolicyBasePath = d.Get("org_policy_custom_endpoint").(string) config.OSConfigBasePath = d.Get("os_config_custom_endpoint").(string) config.OSLoginBasePath = d.Get("os_login_custom_endpoint").(string) config.PrivatecaBasePath = d.Get("privateca_custom_endpoint").(string) diff --git a/google-beta/provider_dcl_endpoints.go b/google-beta/provider_dcl_endpoints.go index 032b13be16..d75b9a5788 100644 --- a/google-beta/provider_dcl_endpoints.go +++ b/google-beta/provider_dcl_endpoints.go @@ -112,15 +112,6 @@ var NetworkConnectivityEndpointEntry = &schema.Schema{ }, ""), } -var OrgPolicyEndpointEntryKey = "org_policy_custom_endpoint" -var OrgPolicyEndpointEntry = &schema.Schema{ - Type: schema.TypeString, - Optional: true, - DefaultFunc: schema.MultiEnvDefaultFunc([]string{ - "GOOGLE_ORG_POLICY_CUSTOM_ENDPOINT", - }, ""), -} - var RecaptchaEnterpriseEndpointEntryKey = "recaptcha_enterprise_custom_endpoint" var RecaptchaEnterpriseEndpointEntry = &schema.Schema{ Type: schema.TypeString, @@ -141,7 +132,6 @@ type DCLConfig struct { FirebaserulesBasePath string GKEHubFeatureBasePath string NetworkConnectivityBasePath string - OrgPolicyBasePath string RecaptchaEnterpriseBasePath string } @@ -156,7 +146,6 @@ func configureDCLProvider(provider *schema.Provider) { provider.Schema[FirebaserulesEndpointEntryKey] = FirebaserulesEndpointEntry provider.Schema[GKEHubFeatureEndpointEntryKey] = GKEHubFeatureEndpointEntry provider.Schema[NetworkConnectivityEndpointEntryKey] = NetworkConnectivityEndpointEntry - provider.Schema[OrgPolicyEndpointEntryKey] = OrgPolicyEndpointEntry provider.Schema[RecaptchaEnterpriseEndpointEntryKey] = RecaptchaEnterpriseEndpointEntry } @@ -171,7 +160,6 @@ func providerDCLConfigure(d *schema.ResourceData, config *Config) interface{} { config.FirebaserulesBasePath = d.Get(FirebaserulesEndpointEntryKey).(string) config.GKEHubFeatureBasePath = d.Get(GKEHubFeatureEndpointEntryKey).(string) config.NetworkConnectivityBasePath = d.Get(NetworkConnectivityEndpointEntryKey).(string) - config.OrgPolicyBasePath = d.Get(OrgPolicyEndpointEntryKey).(string) config.RecaptchaEnterpriseBasePath = d.Get(RecaptchaEnterpriseEndpointEntryKey).(string) config.CloudBuildWorkerPoolBasePath = d.Get(CloudBuildWorkerPoolEndpointEntryKey).(string) return config diff --git a/google-beta/resource_org_policy_custom_constraint.go b/google-beta/resource_org_policy_custom_constraint.go new file mode 100644 index 0000000000..bb60d60bcc --- /dev/null +++ b/google-beta/resource_org_policy_custom_constraint.go @@ -0,0 +1,428 @@ +// ---------------------------------------------------------------------------- +// +// *** AUTO GENERATED CODE *** Type: MMv1 *** +// +// ---------------------------------------------------------------------------- +// +// This file is automatically generated by Magic Modules and manual +// changes will be clobbered when the file is regenerated. +// +// Please read more about how to change this file in +// .github/CONTRIBUTING.md. +// +// ---------------------------------------------------------------------------- + +package google + +import ( + "fmt" + "log" + "reflect" + "time" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func resourceOrgPolicyCustomConstraint() *schema.Resource { + return &schema.Resource{ + Create: resourceOrgPolicyCustomConstraintCreate, + Read: resourceOrgPolicyCustomConstraintRead, + Update: resourceOrgPolicyCustomConstraintUpdate, + Delete: resourceOrgPolicyCustomConstraintDelete, + + Importer: &schema.ResourceImporter{ + State: resourceOrgPolicyCustomConstraintImport, + }, + + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(20 * time.Minute), + Update: schema.DefaultTimeout(20 * time.Minute), + Delete: schema.DefaultTimeout(20 * time.Minute), + }, + + Schema: map[string]*schema.Schema{ + "action_type": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validateEnum([]string{"ALLOW", "DENY"}), + Description: `The action to take if the condition is met. Possible values: ["ALLOW", "DENY"]`, + }, + "condition": { + Type: schema.TypeString, + Required: true, + Description: `A CEL condition that refers to a supported service resource, for example 'resource.management.autoUpgrade == false'. For details about CEL usage, see [Common Expression Language](https://cloud.google.com/resource-manager/docs/organization-policy/creating-managing-custom-constraints#common_expression_language).`, + }, + "method_types": { + Type: schema.TypeList, + Required: true, + Description: `A list of RESTful methods for which to enforce the constraint. Can be 'CREATE', 'UPDATE', or both. Not all Google Cloud services support both methods. To see supported methods for each service, find the service in [Supported services](https://cloud.google.com/resource-manager/docs/organization-policy/custom-constraint-supported-services).`, + MinItems: 1, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: `Immutable. The name of the custom constraint. This is unique within the organization.`, + }, + "parent": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: `The parent of the resource, an organization. Format should be 'organizations/{organization_id}'.`, + }, + "resource_types": { + Type: schema.TypeList, + Required: true, + ForceNew: true, + Description: `Immutable. The fully qualified name of the Google Cloud REST resource containing the object and field you want to restrict. For example, 'container.googleapis.com/NodePool'.`, + MinItems: 1, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "description": { + Type: schema.TypeString, + Optional: true, + Description: `A human-friendly description of the constraint to display as an error message when the policy is violated.`, + }, + "display_name": { + Type: schema.TypeString, + Optional: true, + Description: `A human-friendly name for the constraint.`, + }, + "update_time": { + Type: schema.TypeString, + Computed: true, + Description: `Output only. The timestamp representing when the constraint was last updated.`, + }, + }, + UseJSONNumber: true, + } +} + +func resourceOrgPolicyCustomConstraintCreate(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + userAgent, err := generateUserAgentString(d, config.userAgent) + if err != nil { + return err + } + + obj := make(map[string]interface{}) + nameProp, err := expandOrgPolicyCustomConstraintName(d.Get("name"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("name"); !isEmptyValue(reflect.ValueOf(nameProp)) && (ok || !reflect.DeepEqual(v, nameProp)) { + obj["name"] = nameProp + } + displayNameProp, err := expandOrgPolicyCustomConstraintDisplayName(d.Get("display_name"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("display_name"); !isEmptyValue(reflect.ValueOf(displayNameProp)) && (ok || !reflect.DeepEqual(v, displayNameProp)) { + obj["displayName"] = displayNameProp + } + descriptionProp, err := expandOrgPolicyCustomConstraintDescription(d.Get("description"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("description"); !isEmptyValue(reflect.ValueOf(descriptionProp)) && (ok || !reflect.DeepEqual(v, descriptionProp)) { + obj["description"] = descriptionProp + } + conditionProp, err := expandOrgPolicyCustomConstraintCondition(d.Get("condition"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("condition"); !isEmptyValue(reflect.ValueOf(conditionProp)) && (ok || !reflect.DeepEqual(v, conditionProp)) { + obj["condition"] = conditionProp + } + actionTypeProp, err := expandOrgPolicyCustomConstraintActionType(d.Get("action_type"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("action_type"); !isEmptyValue(reflect.ValueOf(actionTypeProp)) && (ok || !reflect.DeepEqual(v, actionTypeProp)) { + obj["actionType"] = actionTypeProp + } + methodTypesProp, err := expandOrgPolicyCustomConstraintMethodTypes(d.Get("method_types"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("method_types"); !isEmptyValue(reflect.ValueOf(methodTypesProp)) && (ok || !reflect.DeepEqual(v, methodTypesProp)) { + obj["methodTypes"] = methodTypesProp + } + resourceTypesProp, err := expandOrgPolicyCustomConstraintResourceTypes(d.Get("resource_types"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("resource_types"); !isEmptyValue(reflect.ValueOf(resourceTypesProp)) && (ok || !reflect.DeepEqual(v, resourceTypesProp)) { + obj["resourceTypes"] = resourceTypesProp + } + + url, err := replaceVars(d, config, "{{OrgPolicyBasePath}}{{parent}}/customConstraints") + if err != nil { + return err + } + + log.Printf("[DEBUG] Creating new CustomConstraint: %#v", obj) + billingProject := "" + + // err == nil indicates that the billing_project value was found + if bp, err := getBillingProject(d, config); err == nil { + billingProject = bp + } + + res, err := sendRequestWithTimeout(config, "POST", billingProject, url, userAgent, obj, d.Timeout(schema.TimeoutCreate)) + if err != nil { + return fmt.Errorf("Error creating CustomConstraint: %s", err) + } + + // Store the ID now + id, err := replaceVars(d, config, "{{parent}}/customConstraints/{{name}}") + if err != nil { + return fmt.Errorf("Error constructing id: %s", err) + } + d.SetId(id) + + log.Printf("[DEBUG] Finished creating CustomConstraint %q: %#v", d.Id(), res) + + return resourceOrgPolicyCustomConstraintRead(d, meta) +} + +func resourceOrgPolicyCustomConstraintRead(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + userAgent, err := generateUserAgentString(d, config.userAgent) + if err != nil { + return err + } + + url, err := replaceVars(d, config, "{{OrgPolicyBasePath}}{{parent}}/customConstraints/{{name}}") + if err != nil { + return err + } + + billingProject := "" + + // err == nil indicates that the billing_project value was found + if bp, err := getBillingProject(d, config); err == nil { + billingProject = bp + } + + res, err := sendRequest(config, "GET", billingProject, url, userAgent, nil) + if err != nil { + return handleNotFoundError(err, d, fmt.Sprintf("OrgPolicyCustomConstraint %q", d.Id())) + } + + if err := d.Set("name", flattenOrgPolicyCustomConstraintName(res["name"], d, config)); err != nil { + return fmt.Errorf("Error reading CustomConstraint: %s", err) + } + if err := d.Set("display_name", flattenOrgPolicyCustomConstraintDisplayName(res["displayName"], d, config)); err != nil { + return fmt.Errorf("Error reading CustomConstraint: %s", err) + } + if err := d.Set("description", flattenOrgPolicyCustomConstraintDescription(res["description"], d, config)); err != nil { + return fmt.Errorf("Error reading CustomConstraint: %s", err) + } + if err := d.Set("condition", flattenOrgPolicyCustomConstraintCondition(res["condition"], d, config)); err != nil { + return fmt.Errorf("Error reading CustomConstraint: %s", err) + } + if err := d.Set("action_type", flattenOrgPolicyCustomConstraintActionType(res["actionType"], d, config)); err != nil { + return fmt.Errorf("Error reading CustomConstraint: %s", err) + } + if err := d.Set("method_types", flattenOrgPolicyCustomConstraintMethodTypes(res["methodTypes"], d, config)); err != nil { + return fmt.Errorf("Error reading CustomConstraint: %s", err) + } + if err := d.Set("resource_types", flattenOrgPolicyCustomConstraintResourceTypes(res["resourceTypes"], d, config)); err != nil { + return fmt.Errorf("Error reading CustomConstraint: %s", err) + } + if err := d.Set("update_time", flattenOrgPolicyCustomConstraintUpdateTime(res["updateTime"], d, config)); err != nil { + return fmt.Errorf("Error reading CustomConstraint: %s", err) + } + + return nil +} + +func resourceOrgPolicyCustomConstraintUpdate(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + userAgent, err := generateUserAgentString(d, config.userAgent) + if err != nil { + return err + } + + billingProject := "" + + obj := make(map[string]interface{}) + displayNameProp, err := expandOrgPolicyCustomConstraintDisplayName(d.Get("display_name"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("display_name"); !isEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, displayNameProp)) { + obj["displayName"] = displayNameProp + } + descriptionProp, err := expandOrgPolicyCustomConstraintDescription(d.Get("description"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("description"); !isEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, descriptionProp)) { + obj["description"] = descriptionProp + } + conditionProp, err := expandOrgPolicyCustomConstraintCondition(d.Get("condition"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("condition"); !isEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, conditionProp)) { + obj["condition"] = conditionProp + } + actionTypeProp, err := expandOrgPolicyCustomConstraintActionType(d.Get("action_type"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("action_type"); !isEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, actionTypeProp)) { + obj["actionType"] = actionTypeProp + } + methodTypesProp, err := expandOrgPolicyCustomConstraintMethodTypes(d.Get("method_types"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("method_types"); !isEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, methodTypesProp)) { + obj["methodTypes"] = methodTypesProp + } + + obj, err = resourceOrgPolicyCustomConstraintUpdateEncoder(d, meta, obj) + if err != nil { + return err + } + + url, err := replaceVars(d, config, "{{OrgPolicyBasePath}}{{parent}}/customConstraints/{{name}}") + if err != nil { + return err + } + + log.Printf("[DEBUG] Updating CustomConstraint %q: %#v", d.Id(), obj) + + // err == nil indicates that the billing_project value was found + if bp, err := getBillingProject(d, config); err == nil { + billingProject = bp + } + + res, err := sendRequestWithTimeout(config, "PATCH", billingProject, url, userAgent, obj, d.Timeout(schema.TimeoutUpdate)) + + if err != nil { + return fmt.Errorf("Error updating CustomConstraint %q: %s", d.Id(), err) + } else { + log.Printf("[DEBUG] Finished updating CustomConstraint %q: %#v", d.Id(), res) + } + + return resourceOrgPolicyCustomConstraintRead(d, meta) +} + +func resourceOrgPolicyCustomConstraintDelete(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + userAgent, err := generateUserAgentString(d, config.userAgent) + if err != nil { + return err + } + + billingProject := "" + + url, err := replaceVars(d, config, "{{OrgPolicyBasePath}}{{parent}}/customConstraints/{{name}}") + if err != nil { + return err + } + + var obj map[string]interface{} + log.Printf("[DEBUG] Deleting CustomConstraint %q", d.Id()) + + // err == nil indicates that the billing_project value was found + if bp, err := getBillingProject(d, config); err == nil { + billingProject = bp + } + + res, err := sendRequestWithTimeout(config, "DELETE", billingProject, url, userAgent, obj, d.Timeout(schema.TimeoutDelete)) + if err != nil { + return handleNotFoundError(err, d, "CustomConstraint") + } + + log.Printf("[DEBUG] Finished deleting CustomConstraint %q: %#v", d.Id(), res) + return nil +} + +func resourceOrgPolicyCustomConstraintImport(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { + config := meta.(*Config) + if err := parseImportId([]string{ + "(?P.+)/customConstraints/(?P[^/]+)", + }, d, config); err != nil { + return nil, err + } + + // Replace import id for the resource id + id, err := replaceVars(d, config, "{{parent}}/customConstraints/{{name}}") + if err != nil { + return nil, fmt.Errorf("Error constructing id: %s", err) + } + d.SetId(id) + + return []*schema.ResourceData{d}, nil +} + +func flattenOrgPolicyCustomConstraintName(v interface{}, d *schema.ResourceData, config *Config) interface{} { + if v == nil { + return v + } + return NameFromSelfLinkStateFunc(v) +} + +func flattenOrgPolicyCustomConstraintDisplayName(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func flattenOrgPolicyCustomConstraintDescription(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func flattenOrgPolicyCustomConstraintCondition(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func flattenOrgPolicyCustomConstraintActionType(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func flattenOrgPolicyCustomConstraintMethodTypes(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func flattenOrgPolicyCustomConstraintResourceTypes(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func flattenOrgPolicyCustomConstraintUpdateTime(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func expandOrgPolicyCustomConstraintName(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return replaceVars(d, config, "{{parent}}/customConstraints/{{name}}") +} + +func expandOrgPolicyCustomConstraintDisplayName(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandOrgPolicyCustomConstraintDescription(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandOrgPolicyCustomConstraintCondition(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandOrgPolicyCustomConstraintActionType(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandOrgPolicyCustomConstraintMethodTypes(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandOrgPolicyCustomConstraintResourceTypes(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func resourceOrgPolicyCustomConstraintUpdateEncoder(d *schema.ResourceData, meta interface{}, obj map[string]interface{}) (map[string]interface{}, error) { + // need to send resource_types in all PATCH requests + resourceTypesProp := d.Get("resource_types") + if v, ok := d.GetOkExists("resource_types"); !isEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, resourceTypesProp)) { + obj["resourceTypes"] = resourceTypesProp + } + + return obj, nil +} diff --git a/google-beta/resource_org_policy_custom_constraint_generated_test.go b/google-beta/resource_org_policy_custom_constraint_generated_test.go new file mode 100644 index 0000000000..a6fb7db550 --- /dev/null +++ b/google-beta/resource_org_policy_custom_constraint_generated_test.go @@ -0,0 +1,156 @@ +// ---------------------------------------------------------------------------- +// +// *** AUTO GENERATED CODE *** Type: MMv1 *** +// +// ---------------------------------------------------------------------------- +// +// This file is automatically generated by Magic Modules and manual +// changes will be clobbered when the file is regenerated. +// +// Please read more about how to change this file in +// .github/CONTRIBUTING.md. +// +// ---------------------------------------------------------------------------- + +package google + +import ( + "fmt" + "strings" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" +) + +func TestAccOrgPolicyCustomConstraint_orgPolicyCustomConstraintBasicExample(t *testing.T) { + t.Parallel() + + context := map[string]interface{}{ + "org_id": getTestOrgFromEnv(t), + "random_suffix": randString(t, 10), + } + + vcrTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProvidersOiCS, + CheckDestroy: testAccCheckOrgPolicyCustomConstraintDestroyProducer(t), + Steps: []resource.TestStep{ + { + Config: testAccOrgPolicyCustomConstraint_orgPolicyCustomConstraintBasicExample(context), + }, + { + ResourceName: "google_org_policy_custom_constraint.constraint", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"parent"}, + }, + }, + }) +} + +func testAccOrgPolicyCustomConstraint_orgPolicyCustomConstraintBasicExample(context map[string]interface{}) string { + return Nprintf(` +resource "google_org_policy_custom_constraint" "constraint" { + provider = google-beta + + name = "custom.disableGkeAutoUpgrade" + parent = "organizations/%{org_id}" + + action_type = "ALLOW" + condition = "resource.management.autoUpgrade == false" + method_types = ["CREATE", "UPDATE"] + resource_types = ["container.googleapis.com/NodePool"] +} +`, context) +} + +func TestAccOrgPolicyCustomConstraint_orgPolicyCustomConstraintFullExample(t *testing.T) { + t.Parallel() + + context := map[string]interface{}{ + "org_id": getTestOrgTargetFromEnv(t), + "random_suffix": randString(t, 10), + } + + vcrTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProvidersOiCS, + CheckDestroy: testAccCheckOrgPolicyCustomConstraintDestroyProducer(t), + Steps: []resource.TestStep{ + { + Config: testAccOrgPolicyCustomConstraint_orgPolicyCustomConstraintFullExample(context), + }, + { + ResourceName: "google_org_policy_custom_constraint.constraint", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"parent"}, + }, + }, + }) +} + +func testAccOrgPolicyCustomConstraint_orgPolicyCustomConstraintFullExample(context map[string]interface{}) string { + return Nprintf(` +resource "google_org_policy_custom_constraint" "constraint" { + provider = google-beta + + name = "custom.disableGkeAutoUpgrade" + parent = "organizations/%{org_id}" + display_name = "Disable GKE auto upgrade" + description = "Only allow GKE NodePool resource to be created or updated if AutoUpgrade is not enabled where this custom constraint is enforced." + + action_type = "ALLOW" + condition = "resource.management.autoUpgrade == false" + method_types = ["CREATE", "UPDATE"] + resource_types = ["container.googleapis.com/NodePool"] +} + +resource "google_org_policy_policy" "bool" { + provider = google-beta + + name = "organizations/%{org_id}/policies/${google_org_policy_custom_constraint.constraint.name}" + parent = "organizations/%{org_id}" + + spec { + rules { + enforce = "TRUE" + } + } +} +`, context) +} + +func testAccCheckOrgPolicyCustomConstraintDestroyProducer(t *testing.T) func(s *terraform.State) error { + return func(s *terraform.State) error { + for name, rs := range s.RootModule().Resources { + if rs.Type != "google_org_policy_custom_constraint" { + continue + } + if strings.HasPrefix(name, "data.") { + continue + } + + config := googleProviderConfig(t) + + url, err := replaceVarsForTest(config, rs, "{{OrgPolicyBasePath}}{{parent}}/customConstraints/{{name}}") + if err != nil { + return err + } + + billingProject := "" + + if config.BillingProject != "" { + billingProject = config.BillingProject + } + + _, err = sendRequest(config, "GET", billingProject, url, config.userAgent, nil) + if err == nil { + return fmt.Errorf("OrgPolicyCustomConstraint still exists at %s", url) + } + } + + return nil + } +} diff --git a/google-beta/resource_org_policy_custom_constraint_sweeper_test.go b/google-beta/resource_org_policy_custom_constraint_sweeper_test.go new file mode 100644 index 0000000000..e6a844c82b --- /dev/null +++ b/google-beta/resource_org_policy_custom_constraint_sweeper_test.go @@ -0,0 +1,124 @@ +// ---------------------------------------------------------------------------- +// +// *** AUTO GENERATED CODE *** Type: MMv1 *** +// +// ---------------------------------------------------------------------------- +// +// This file is automatically generated by Magic Modules and manual +// changes will be clobbered when the file is regenerated. +// +// Please read more about how to change this file in +// .github/CONTRIBUTING.md. +// +// ---------------------------------------------------------------------------- + +package google + +import ( + "context" + "log" + "strings" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +func init() { + resource.AddTestSweepers("OrgPolicyCustomConstraint", &resource.Sweeper{ + Name: "OrgPolicyCustomConstraint", + F: testSweepOrgPolicyCustomConstraint, + }) +} + +// At the time of writing, the CI only passes us-central1 as the region +func testSweepOrgPolicyCustomConstraint(region string) error { + resourceName := "OrgPolicyCustomConstraint" + log.Printf("[INFO][SWEEPER_LOG] Starting sweeper for %s", resourceName) + + config, err := sharedConfigForRegion(region) + if err != nil { + log.Printf("[INFO][SWEEPER_LOG] error getting shared config for region: %s", err) + return err + } + + err = config.LoadAndValidate(context.Background()) + if err != nil { + log.Printf("[INFO][SWEEPER_LOG] error loading: %s", err) + return err + } + + t := &testing.T{} + billingId := getTestBillingAccountFromEnv(t) + + // Setup variables to replace in list template + d := &ResourceDataMock{ + FieldsInSchema: map[string]interface{}{ + "project": config.Project, + "region": region, + "location": region, + "zone": "-", + "billing_account": billingId, + }, + } + + listTemplate := strings.Split("https://orgpolicy.googleapis.com/v2/{{parent}}/customConstraints", "?")[0] + listUrl, err := replaceVars(d, config, listTemplate) + if err != nil { + log.Printf("[INFO][SWEEPER_LOG] error preparing sweeper list url: %s", err) + return nil + } + + res, err := sendRequest(config, "GET", config.Project, listUrl, config.userAgent, nil) + if err != nil { + log.Printf("[INFO][SWEEPER_LOG] Error in response from request %s: %s", listUrl, err) + return nil + } + + resourceList, ok := res["customConstraints"] + if !ok { + log.Printf("[INFO][SWEEPER_LOG] Nothing found in response.") + return nil + } + + rl := resourceList.([]interface{}) + + log.Printf("[INFO][SWEEPER_LOG] Found %d items in %s list response.", len(rl), resourceName) + // Keep count of items that aren't sweepable for logging. + nonPrefixCount := 0 + for _, ri := range rl { + obj := ri.(map[string]interface{}) + if obj["name"] == nil { + log.Printf("[INFO][SWEEPER_LOG] %s resource name was nil", resourceName) + return nil + } + + name := GetResourceNameFromSelfLink(obj["name"].(string)) + // Skip resources that shouldn't be sweeped + if !isSweepableTestResource(name) { + nonPrefixCount++ + continue + } + + deleteTemplate := "https://orgpolicy.googleapis.com/v2/{{parent}}/customConstraints/{{name}}" + deleteUrl, err := replaceVars(d, config, deleteTemplate) + if err != nil { + log.Printf("[INFO][SWEEPER_LOG] error preparing delete url: %s", err) + return nil + } + deleteUrl = deleteUrl + name + + // Don't wait on operations as we may have a lot to delete + _, err = sendRequest(config, "DELETE", config.Project, deleteUrl, config.userAgent, nil) + if err != nil { + log.Printf("[INFO][SWEEPER_LOG] Error deleting for url %s : %s", deleteUrl, err) + } else { + log.Printf("[INFO][SWEEPER_LOG] Sent delete request for %s resource: %s", resourceName, name) + } + } + + if nonPrefixCount > 0 { + log.Printf("[INFO][SWEEPER_LOG] %d items were non-sweepable and skipped.", nonPrefixCount) + } + + return nil +} diff --git a/google-beta/resource_org_policy_custom_constraint_test.go b/google-beta/resource_org_policy_custom_constraint_test.go new file mode 100644 index 0000000000..a8c3bbe3b5 --- /dev/null +++ b/google-beta/resource_org_policy_custom_constraint_test.go @@ -0,0 +1,74 @@ +package google + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +func TestAccOrgPolicyCustomConstraint_update(t *testing.T) { + t.Parallel() + + context := map[string]interface{}{ + "org_id": getTestOrgFromEnv(t), + "random_suffix": randString(t, 10), + } + + vcrTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckOrgPolicyCustomConstraintDestroyProducer(t), + Steps: []resource.TestStep{ + { + Config: testAccOrgPolicyCustomConstraint_v1(context), + }, + { + ResourceName: "google_org_policy_custom_constraint.constraint", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"parent"}, + }, + { + Config: testAccOrgPolicyCustomConstraint_v2(context), + }, + { + ResourceName: "google_org_policy_custom_constraint.constraint", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"parent"}, + }, + }, + }) +} + +func testAccOrgPolicyCustomConstraint_v1(context map[string]interface{}) string { + return Nprintf(` +resource "google_org_policy_custom_constraint" "constraint" { + name = "custom.tfTest%{random_suffix}" + parent = "organizations/%{org_id}" + display_name = "Disable GKE auto upgrade" + description = "Only allow GKE NodePool resource to be created or updated if AutoUpgrade is not enabled where this custom constraint is enforced." + + action_type = "ALLOW" + condition = "resource.management.autoUpgrade == false" + method_types = ["CREATE", "UPDATE"] + resource_types = ["container.googleapis.com/NodePool"] +} +`, context) +} + +func testAccOrgPolicyCustomConstraint_v2(context map[string]interface{}) string { + return Nprintf(` +resource "google_org_policy_custom_constraint" "constraint" { + name = "custom.tfTest%{random_suffix}" + parent = "organizations/%{org_id}" + display_name = "Updated" + description = "Updated" + + action_type = "DENY" + condition = "resource.management.autoUpgrade == true" + method_types = ["CREATE"] + resource_types = ["container.googleapis.com/NodePool"] +} +`, context) +} diff --git a/website/docs/r/org_policy_custom_constraint.html.markdown b/website/docs/r/org_policy_custom_constraint.html.markdown new file mode 100644 index 0000000000..76855db061 --- /dev/null +++ b/website/docs/r/org_policy_custom_constraint.html.markdown @@ -0,0 +1,152 @@ +--- +# ---------------------------------------------------------------------------- +# +# *** AUTO GENERATED CODE *** Type: MMv1 *** +# +# ---------------------------------------------------------------------------- +# +# This file is automatically generated by Magic Modules and manual +# changes will be clobbered when the file is regenerated. +# +# Please read more about how to change this file in +# .github/CONTRIBUTING.md. +# +# ---------------------------------------------------------------------------- +subcategory: "Organization Policy" +page_title: "Google: google_org_policy_custom_constraint" +description: |- + Custom constraints are created by administrators to provide more granular and customizable control over the specific fields that are restricted by your organization policies. +--- + +# google\_org\_policy\_custom\_constraint + +Custom constraints are created by administrators to provide more granular and customizable control over the specific fields that are restricted by your organization policies. + +~> **Warning:** This resource is in beta, and should be used with the terraform-provider-google-beta provider. +See [Provider Versions](https://terraform.io/docs/providers/google/guides/provider_versions.html) for more details on beta resources. + +To get more information about CustomConstraint, see: + +* [API documentation](https://cloud.google.com/resource-manager/docs/reference/orgpolicy/rest/v2/organizations.constraints) +* How-to Guides + * [Official Documentation](https://cloud.google.com/resource-manager/docs/organization-policy/creating-managing-custom-constraints) + * [Supported Services](https://cloud.google.com/resource-manager/docs/organization-policy/custom-constraint-supported-services) + +## Example Usage - Org Policy Custom Constraint Basic + + +```hcl +resource "google_org_policy_custom_constraint" "constraint" { + provider = google-beta + + name = "custom.disableGkeAutoUpgrade" + parent = "organizations/123456789" + + action_type = "ALLOW" + condition = "resource.management.autoUpgrade == false" + method_types = ["CREATE", "UPDATE"] + resource_types = ["container.googleapis.com/NodePool"] +} +``` +## Example Usage - Org Policy Custom Constraint Full + + +```hcl +resource "google_org_policy_custom_constraint" "constraint" { + provider = google-beta + + name = "custom.disableGkeAutoUpgrade" + parent = "organizations/123456789" + display_name = "Disable GKE auto upgrade" + description = "Only allow GKE NodePool resource to be created or updated if AutoUpgrade is not enabled where this custom constraint is enforced." + + action_type = "ALLOW" + condition = "resource.management.autoUpgrade == false" + method_types = ["CREATE", "UPDATE"] + resource_types = ["container.googleapis.com/NodePool"] +} + +resource "google_org_policy_policy" "bool" { + provider = google-beta + + name = "organizations/123456789/policies/${google_org_policy_custom_constraint.constraint.name}" + parent = "organizations/123456789" + + spec { + rules { + enforce = "TRUE" + } + } +} +``` + +## Argument Reference + +The following arguments are supported: + + +* `name` - + (Required) + Immutable. The name of the custom constraint. This is unique within the organization. + +* `condition` - + (Required) + A CEL condition that refers to a supported service resource, for example `resource.management.autoUpgrade == false`. For details about CEL usage, see [Common Expression Language](https://cloud.google.com/resource-manager/docs/organization-policy/creating-managing-custom-constraints#common_expression_language). + +* `action_type` - + (Required) + The action to take if the condition is met. + Possible values are `ALLOW` and `DENY`. + +* `method_types` - + (Required) + A list of RESTful methods for which to enforce the constraint. Can be `CREATE`, `UPDATE`, or both. Not all Google Cloud services support both methods. To see supported methods for each service, find the service in [Supported services](https://cloud.google.com/resource-manager/docs/organization-policy/custom-constraint-supported-services). + +* `resource_types` - + (Required) + Immutable. The fully qualified name of the Google Cloud REST resource containing the object and field you want to restrict. For example, `container.googleapis.com/NodePool`. + +* `parent` - + (Required) + The parent of the resource, an organization. Format should be `organizations/{organization_id}`. + + +- - - + + +* `display_name` - + (Optional) + A human-friendly name for the constraint. + +* `description` - + (Optional) + A human-friendly description of the constraint to display as an error message when the policy is violated. + + +## Attributes Reference + +In addition to the arguments listed above, the following computed attributes are exported: + +* `id` - an identifier for the resource with format `{{parent}}/customConstraints/{{name}}` + +* `update_time` - + Output only. The timestamp representing when the constraint was last updated. + + +## Timeouts + +This resource provides the following +[Timeouts](/docs/configuration/resources.html#timeouts) configuration options: + +- `create` - Default is 20 minutes. +- `update` - Default is 20 minutes. +- `delete` - Default is 20 minutes. + +## Import + + +CustomConstraint can be imported using any of these accepted formats: + +``` +$ terraform import google_org_policy_custom_constraint.default {{parent}}/customConstraints/{{name}} +```