diff --git a/.changelog/8704.txt b/.changelog/8704.txt new file mode 100644 index 00000000000..c077664e099 --- /dev/null +++ b/.changelog/8704.txt @@ -0,0 +1,3 @@ +```release-note:new-resource +`google_iam_deny_policy` (ga only) +``` diff --git a/google/provider/provider.go b/google/provider/provider.go index 3f4b329ff73..6fcd69ba43f 100644 --- a/google/provider/provider.go +++ b/google/provider/provider.go @@ -966,9 +966,9 @@ func DatasourceMapWithErrors() (map[string]*schema.Resource, error) { }) } -// Generated resources: 310 +// Generated resources: 311 // Generated IAM resources: 204 -// Total generated resources: 514 +// Total generated resources: 515 func ResourceMap() map[string]*schema.Resource { resourceMap, _ := ResourceMapWithErrors() return resourceMap @@ -1318,6 +1318,7 @@ func ResourceMapWithErrors() (map[string]*schema.Resource, error) { "google_healthcare_fhir_store": healthcare.ResourceHealthcareFhirStore(), "google_healthcare_hl7_v2_store": healthcare.ResourceHealthcareHl7V2Store(), "google_iam_access_boundary_policy": iam2.ResourceIAM2AccessBoundaryPolicy(), + "google_iam_deny_policy": iam2.ResourceIAM2DenyPolicy(), "google_iam_workload_identity_pool": iambeta.ResourceIAMBetaWorkloadIdentityPool(), "google_iam_workload_identity_pool_provider": iambeta.ResourceIAMBetaWorkloadIdentityPoolProvider(), "google_iam_workforce_pool": iamworkforcepool.ResourceIAMWorkforcePoolWorkforcePool(), diff --git a/google/services/iam2/resource_iam_deny_policy.go b/google/services/iam2/resource_iam_deny_policy.go new file mode 100644 index 00000000000..34968070e24 --- /dev/null +++ b/google/services/iam2/resource_iam_deny_policy.go @@ -0,0 +1,682 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +// ---------------------------------------------------------------------------- +// +// *** 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 iam2 + +import ( + "fmt" + "log" + "reflect" + "time" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + + "github.com/hashicorp/terraform-provider-google/google/tpgresource" + transport_tpg "github.com/hashicorp/terraform-provider-google/google/transport" +) + +func ResourceIAM2DenyPolicy() *schema.Resource { + return &schema.Resource{ + Create: resourceIAM2DenyPolicyCreate, + Read: resourceIAM2DenyPolicyRead, + Update: resourceIAM2DenyPolicyUpdate, + Delete: resourceIAM2DenyPolicyDelete, + + Importer: &schema.ResourceImporter{ + State: resourceIAM2DenyPolicyImport, + }, + + 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{ + "name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: `The name of the policy.`, + }, + "parent": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: `The attachment point is identified by its URL-encoded full resource name.`, + }, + "rules": { + Type: schema.TypeList, + Required: true, + Description: `Rules to be applied.`, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "deny_rule": { + Type: schema.TypeList, + Optional: true, + Description: `A deny rule in an IAM deny policy.`, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "denial_condition": { + Type: schema.TypeList, + Optional: true, + Description: `User defined CEVAL expression. A CEVAL expression is used to specify match criteria such as origin.ip, source.region_code and contents in the request header.`, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "expression": { + Type: schema.TypeString, + Required: true, + Description: `Textual representation of an expression in Common Expression Language syntax.`, + }, + "description": { + Type: schema.TypeString, + Optional: true, + Description: `Description of the expression. This is a longer text which describes the expression, +e.g. when hovered over it in a UI.`, + }, + "location": { + Type: schema.TypeString, + Optional: true, + Description: `String indicating the location of the expression for error reporting, +e.g. a file name and a position in the file.`, + }, + "title": { + Type: schema.TypeString, + Optional: true, + Description: `Title for the expression, i.e. a short string describing its purpose. +This can be used e.g. in UIs which allow to enter the expression.`, + }, + }, + }, + }, + "denied_permissions": { + Type: schema.TypeList, + Optional: true, + Description: `The permissions that are explicitly denied by this rule. Each permission uses the format '{service-fqdn}/{resource}.{verb}', +where '{service-fqdn}' is the fully qualified domain name for the service. For example, 'iam.googleapis.com/roles.list'.`, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "denied_principals": { + Type: schema.TypeList, + Optional: true, + Description: `The identities that are prevented from using one or more permissions on Google Cloud resources.`, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "exception_permissions": { + Type: schema.TypeList, + Optional: true, + Description: `Specifies the permissions that this rule excludes from the set of denied permissions given by deniedPermissions. +If a permission appears in deniedPermissions and in exceptionPermissions then it will not be denied. +The excluded permissions can be specified using the same syntax as deniedPermissions.`, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "exception_principals": { + Type: schema.TypeList, + Optional: true, + Description: `The identities that are excluded from the deny rule, even if they are listed in the deniedPrincipals. +For example, you could add a Google group to the deniedPrincipals, then exclude specific users who belong to that group.`, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + }, + }, + }, + "description": { + Type: schema.TypeString, + Optional: true, + Description: `The description of the rule.`, + }, + }, + }, + }, + "display_name": { + Type: schema.TypeString, + Optional: true, + Description: `The display name of the rule.`, + }, + "etag": { + Type: schema.TypeString, + Computed: true, + Description: `The hash of the resource. Used internally during updates.`, + }, + }, + UseJSONNumber: true, + } +} + +func resourceIAM2DenyPolicyCreate(d *schema.ResourceData, meta interface{}) error { + config := meta.(*transport_tpg.Config) + userAgent, err := tpgresource.GenerateUserAgentString(d, config.UserAgent) + if err != nil { + return err + } + + obj := make(map[string]interface{}) + displayNameProp, err := expandIAM2DenyPolicyDisplayName(d.Get("display_name"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("display_name"); !tpgresource.IsEmptyValue(reflect.ValueOf(displayNameProp)) && (ok || !reflect.DeepEqual(v, displayNameProp)) { + obj["displayName"] = displayNameProp + } + etagProp, err := expandIAM2DenyPolicyEtag(d.Get("etag"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("etag"); !tpgresource.IsEmptyValue(reflect.ValueOf(etagProp)) && (ok || !reflect.DeepEqual(v, etagProp)) { + obj["etag"] = etagProp + } + rulesProp, err := expandIAM2DenyPolicyRules(d.Get("rules"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("rules"); !tpgresource.IsEmptyValue(reflect.ValueOf(rulesProp)) && (ok || !reflect.DeepEqual(v, rulesProp)) { + obj["rules"] = rulesProp + } + + url, err := tpgresource.ReplaceVars(d, config, "{{IAM2BasePath}}policies/{{parent}}/denypolicies?policyId={{name}}") + if err != nil { + return err + } + + log.Printf("[DEBUG] Creating new DenyPolicy: %#v", obj) + billingProject := "" + + // err == nil indicates that the billing_project value was found + if bp, err := tpgresource.GetBillingProject(d, config); err == nil { + billingProject = bp + } + + res, err := transport_tpg.SendRequest(transport_tpg.SendRequestOptions{ + Config: config, + Method: "POST", + Project: billingProject, + RawURL: url, + UserAgent: userAgent, + Body: obj, + Timeout: d.Timeout(schema.TimeoutCreate), + }) + if err != nil { + return fmt.Errorf("Error creating DenyPolicy: %s", err) + } + + // Store the ID now + id, err := tpgresource.ReplaceVars(d, config, "{{parent}}/{{name}}") + if err != nil { + return fmt.Errorf("Error constructing id: %s", err) + } + d.SetId(id) + + err = IAM2OperationWaitTime( + config, res, "Creating DenyPolicy", userAgent, + d.Timeout(schema.TimeoutCreate)) + + if err != nil { + // The resource didn't actually create + d.SetId("") + return fmt.Errorf("Error waiting to create DenyPolicy: %s", err) + } + + log.Printf("[DEBUG] Finished creating DenyPolicy %q: %#v", d.Id(), res) + + return resourceIAM2DenyPolicyRead(d, meta) +} + +func resourceIAM2DenyPolicyRead(d *schema.ResourceData, meta interface{}) error { + config := meta.(*transport_tpg.Config) + userAgent, err := tpgresource.GenerateUserAgentString(d, config.UserAgent) + if err != nil { + return err + } + + url, err := tpgresource.ReplaceVars(d, config, "{{IAM2BasePath}}policies/{{parent}}/denypolicies/{{name}}") + if err != nil { + return err + } + + billingProject := "" + + // err == nil indicates that the billing_project value was found + if bp, err := tpgresource.GetBillingProject(d, config); err == nil { + billingProject = bp + } + + res, err := transport_tpg.SendRequest(transport_tpg.SendRequestOptions{ + Config: config, + Method: "GET", + Project: billingProject, + RawURL: url, + UserAgent: userAgent, + }) + if err != nil { + return transport_tpg.HandleNotFoundError(err, d, fmt.Sprintf("IAM2DenyPolicy %q", d.Id())) + } + + if err := d.Set("display_name", flattenIAM2DenyPolicyDisplayName(res["displayName"], d, config)); err != nil { + return fmt.Errorf("Error reading DenyPolicy: %s", err) + } + if err := d.Set("etag", flattenIAM2DenyPolicyEtag(res["etag"], d, config)); err != nil { + return fmt.Errorf("Error reading DenyPolicy: %s", err) + } + if err := d.Set("rules", flattenIAM2DenyPolicyRules(res["rules"], d, config)); err != nil { + return fmt.Errorf("Error reading DenyPolicy: %s", err) + } + + return nil +} + +func resourceIAM2DenyPolicyUpdate(d *schema.ResourceData, meta interface{}) error { + config := meta.(*transport_tpg.Config) + userAgent, err := tpgresource.GenerateUserAgentString(d, config.UserAgent) + if err != nil { + return err + } + + billingProject := "" + + obj := make(map[string]interface{}) + displayNameProp, err := expandIAM2DenyPolicyDisplayName(d.Get("display_name"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("display_name"); !tpgresource.IsEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, displayNameProp)) { + obj["displayName"] = displayNameProp + } + etagProp, err := expandIAM2DenyPolicyEtag(d.Get("etag"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("etag"); !tpgresource.IsEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, etagProp)) { + obj["etag"] = etagProp + } + rulesProp, err := expandIAM2DenyPolicyRules(d.Get("rules"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("rules"); !tpgresource.IsEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, rulesProp)) { + obj["rules"] = rulesProp + } + + url, err := tpgresource.ReplaceVars(d, config, "{{IAM2BasePath}}policies/{{parent}}/denypolicies/{{name}}") + if err != nil { + return err + } + + log.Printf("[DEBUG] Updating DenyPolicy %q: %#v", d.Id(), obj) + + // err == nil indicates that the billing_project value was found + if bp, err := tpgresource.GetBillingProject(d, config); err == nil { + billingProject = bp + } + + res, err := transport_tpg.SendRequest(transport_tpg.SendRequestOptions{ + Config: config, + Method: "PUT", + Project: billingProject, + RawURL: url, + UserAgent: userAgent, + Body: obj, + Timeout: d.Timeout(schema.TimeoutUpdate), + }) + + if err != nil { + return fmt.Errorf("Error updating DenyPolicy %q: %s", d.Id(), err) + } else { + log.Printf("[DEBUG] Finished updating DenyPolicy %q: %#v", d.Id(), res) + } + + err = IAM2OperationWaitTime( + config, res, "Updating DenyPolicy", userAgent, + d.Timeout(schema.TimeoutUpdate)) + + if err != nil { + return err + } + + return resourceIAM2DenyPolicyRead(d, meta) +} + +func resourceIAM2DenyPolicyDelete(d *schema.ResourceData, meta interface{}) error { + config := meta.(*transport_tpg.Config) + userAgent, err := tpgresource.GenerateUserAgentString(d, config.UserAgent) + if err != nil { + return err + } + + billingProject := "" + + url, err := tpgresource.ReplaceVars(d, config, "{{IAM2BasePath}}policies/{{parent}}/denypolicies/{{name}}") + if err != nil { + return err + } + + var obj map[string]interface{} + log.Printf("[DEBUG] Deleting DenyPolicy %q", d.Id()) + + // err == nil indicates that the billing_project value was found + if bp, err := tpgresource.GetBillingProject(d, config); err == nil { + billingProject = bp + } + + res, err := transport_tpg.SendRequest(transport_tpg.SendRequestOptions{ + Config: config, + Method: "DELETE", + Project: billingProject, + RawURL: url, + UserAgent: userAgent, + Body: obj, + Timeout: d.Timeout(schema.TimeoutDelete), + }) + if err != nil { + return transport_tpg.HandleNotFoundError(err, d, "DenyPolicy") + } + + err = IAM2OperationWaitTime( + config, res, "Deleting DenyPolicy", userAgent, + d.Timeout(schema.TimeoutDelete)) + + if err != nil { + return err + } + + log.Printf("[DEBUG] Finished deleting DenyPolicy %q: %#v", d.Id(), res) + return nil +} + +func resourceIAM2DenyPolicyImport(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { + config := meta.(*transport_tpg.Config) + if err := tpgresource.ParseImportId([]string{ + "(?P[^/]+)/(?P[^/]+)", + }, d, config); err != nil { + return nil, err + } + + // Replace import id for the resource id + id, err := tpgresource.ReplaceVars(d, config, "{{parent}}/{{name}}") + if err != nil { + return nil, fmt.Errorf("Error constructing id: %s", err) + } + d.SetId(id) + + return []*schema.ResourceData{d}, nil +} + +func flattenIAM2DenyPolicyDisplayName(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func flattenIAM2DenyPolicyEtag(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func flattenIAM2DenyPolicyRules(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + if v == nil { + return v + } + l := v.([]interface{}) + transformed := make([]interface{}, 0, len(l)) + for _, raw := range l { + original := raw.(map[string]interface{}) + if len(original) < 1 { + // Do not include empty json objects coming back from the api + continue + } + transformed = append(transformed, map[string]interface{}{ + "description": flattenIAM2DenyPolicyRulesDescription(original["description"], d, config), + "deny_rule": flattenIAM2DenyPolicyRulesDenyRule(original["denyRule"], d, config), + }) + } + return transformed +} +func flattenIAM2DenyPolicyRulesDescription(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func flattenIAM2DenyPolicyRulesDenyRule(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + if v == nil { + return nil + } + original := v.(map[string]interface{}) + if len(original) == 0 { + return nil + } + transformed := make(map[string]interface{}) + transformed["denied_principals"] = + flattenIAM2DenyPolicyRulesDenyRuleDeniedPrincipals(original["deniedPrincipals"], d, config) + transformed["exception_principals"] = + flattenIAM2DenyPolicyRulesDenyRuleExceptionPrincipals(original["exceptionPrincipals"], d, config) + transformed["denied_permissions"] = + flattenIAM2DenyPolicyRulesDenyRuleDeniedPermissions(original["deniedPermissions"], d, config) + transformed["exception_permissions"] = + flattenIAM2DenyPolicyRulesDenyRuleExceptionPermissions(original["exceptionPermissions"], d, config) + transformed["denial_condition"] = + flattenIAM2DenyPolicyRulesDenyRuleDenialCondition(original["denialCondition"], d, config) + return []interface{}{transformed} +} +func flattenIAM2DenyPolicyRulesDenyRuleDeniedPrincipals(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func flattenIAM2DenyPolicyRulesDenyRuleExceptionPrincipals(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func flattenIAM2DenyPolicyRulesDenyRuleDeniedPermissions(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func flattenIAM2DenyPolicyRulesDenyRuleExceptionPermissions(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func flattenIAM2DenyPolicyRulesDenyRuleDenialCondition(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + if v == nil { + return nil + } + original := v.(map[string]interface{}) + if len(original) == 0 { + return nil + } + transformed := make(map[string]interface{}) + transformed["expression"] = + flattenIAM2DenyPolicyRulesDenyRuleDenialConditionExpression(original["expression"], d, config) + transformed["title"] = + flattenIAM2DenyPolicyRulesDenyRuleDenialConditionTitle(original["title"], d, config) + transformed["description"] = + flattenIAM2DenyPolicyRulesDenyRuleDenialConditionDescription(original["description"], d, config) + transformed["location"] = + flattenIAM2DenyPolicyRulesDenyRuleDenialConditionLocation(original["location"], d, config) + return []interface{}{transformed} +} +func flattenIAM2DenyPolicyRulesDenyRuleDenialConditionExpression(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func flattenIAM2DenyPolicyRulesDenyRuleDenialConditionTitle(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func flattenIAM2DenyPolicyRulesDenyRuleDenialConditionDescription(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func flattenIAM2DenyPolicyRulesDenyRuleDenialConditionLocation(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func expandIAM2DenyPolicyDisplayName(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandIAM2DenyPolicyEtag(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandIAM2DenyPolicyRules(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + l := v.([]interface{}) + req := make([]interface{}, 0, len(l)) + for _, raw := range l { + if raw == nil { + continue + } + original := raw.(map[string]interface{}) + transformed := make(map[string]interface{}) + + transformedDescription, err := expandIAM2DenyPolicyRulesDescription(original["description"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedDescription); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["description"] = transformedDescription + } + + transformedDenyRule, err := expandIAM2DenyPolicyRulesDenyRule(original["deny_rule"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedDenyRule); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["denyRule"] = transformedDenyRule + } + + req = append(req, transformed) + } + return req, nil +} + +func expandIAM2DenyPolicyRulesDescription(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandIAM2DenyPolicyRulesDenyRule(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + l := v.([]interface{}) + if len(l) == 0 || l[0] == nil { + return nil, nil + } + raw := l[0] + original := raw.(map[string]interface{}) + transformed := make(map[string]interface{}) + + transformedDeniedPrincipals, err := expandIAM2DenyPolicyRulesDenyRuleDeniedPrincipals(original["denied_principals"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedDeniedPrincipals); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["deniedPrincipals"] = transformedDeniedPrincipals + } + + transformedExceptionPrincipals, err := expandIAM2DenyPolicyRulesDenyRuleExceptionPrincipals(original["exception_principals"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedExceptionPrincipals); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["exceptionPrincipals"] = transformedExceptionPrincipals + } + + transformedDeniedPermissions, err := expandIAM2DenyPolicyRulesDenyRuleDeniedPermissions(original["denied_permissions"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedDeniedPermissions); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["deniedPermissions"] = transformedDeniedPermissions + } + + transformedExceptionPermissions, err := expandIAM2DenyPolicyRulesDenyRuleExceptionPermissions(original["exception_permissions"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedExceptionPermissions); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["exceptionPermissions"] = transformedExceptionPermissions + } + + transformedDenialCondition, err := expandIAM2DenyPolicyRulesDenyRuleDenialCondition(original["denial_condition"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedDenialCondition); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["denialCondition"] = transformedDenialCondition + } + + return transformed, nil +} + +func expandIAM2DenyPolicyRulesDenyRuleDeniedPrincipals(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandIAM2DenyPolicyRulesDenyRuleExceptionPrincipals(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandIAM2DenyPolicyRulesDenyRuleDeniedPermissions(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandIAM2DenyPolicyRulesDenyRuleExceptionPermissions(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandIAM2DenyPolicyRulesDenyRuleDenialCondition(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + l := v.([]interface{}) + if len(l) == 0 || l[0] == nil { + return nil, nil + } + raw := l[0] + original := raw.(map[string]interface{}) + transformed := make(map[string]interface{}) + + transformedExpression, err := expandIAM2DenyPolicyRulesDenyRuleDenialConditionExpression(original["expression"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedExpression); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["expression"] = transformedExpression + } + + transformedTitle, err := expandIAM2DenyPolicyRulesDenyRuleDenialConditionTitle(original["title"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedTitle); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["title"] = transformedTitle + } + + transformedDescription, err := expandIAM2DenyPolicyRulesDenyRuleDenialConditionDescription(original["description"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedDescription); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["description"] = transformedDescription + } + + transformedLocation, err := expandIAM2DenyPolicyRulesDenyRuleDenialConditionLocation(original["location"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedLocation); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["location"] = transformedLocation + } + + return transformed, nil +} + +func expandIAM2DenyPolicyRulesDenyRuleDenialConditionExpression(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandIAM2DenyPolicyRulesDenyRuleDenialConditionTitle(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandIAM2DenyPolicyRulesDenyRuleDenialConditionDescription(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandIAM2DenyPolicyRulesDenyRuleDenialConditionLocation(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} diff --git a/google/services/iam2/resource_iam_deny_policy_generated_test.go b/google/services/iam2/resource_iam_deny_policy_generated_test.go new file mode 100644 index 00000000000..df826869c50 --- /dev/null +++ b/google/services/iam2/resource_iam_deny_policy_generated_test.go @@ -0,0 +1,144 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +// ---------------------------------------------------------------------------- +// +// *** 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 iam2_test + +import ( + "fmt" + "strings" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + + "github.com/hashicorp/terraform-provider-google/google/acctest" + "github.com/hashicorp/terraform-provider-google/google/envvar" + "github.com/hashicorp/terraform-provider-google/google/tpgresource" + transport_tpg "github.com/hashicorp/terraform-provider-google/google/transport" +) + +func TestAccIAM2DenyPolicy_iamDenyPolicyBasicExample(t *testing.T) { + t.Parallel() + + context := map[string]interface{}{ + "org_id": envvar.GetTestOrgFromEnv(t), + "billing_account": envvar.GetTestBillingAccountFromEnv(t), + "random_suffix": acctest.RandString(t, 10), + } + + acctest.VcrTest(t, resource.TestCase{ + PreCheck: func() { acctest.AccTestPreCheck(t) }, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), + CheckDestroy: testAccCheckIAM2DenyPolicyDestroyProducer(t), + Steps: []resource.TestStep{ + { + Config: testAccIAM2DenyPolicy_iamDenyPolicyBasicExample(context), + }, + { + ResourceName: "google_iam_deny_policy.example", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"name", "parent"}, + }, + }, + }) +} + +func testAccIAM2DenyPolicy_iamDenyPolicyBasicExample(context map[string]interface{}) string { + return acctest.Nprintf(` +resource "google_project" "project" { + project_id = "tf-test-my-project%{random_suffix}" + name = "tf-test-my-project%{random_suffix}" + org_id = "%{org_id}" + billing_account = "%{billing_account}" +} + +resource "google_iam_deny_policy" "example" { + parent = urlencode("cloudresourcemanager.googleapis.com/projects/${google_project.project.project_id}") + name = "tf-test-my-deny-policy%{random_suffix}" + display_name = "A deny rule" + rules { + description = "First rule" + deny_rule { + denied_principals = ["principalSet://goog/public:all"] + denial_condition { + title = "Some expr" + expression = "!resource.matchTag('12345678/env', 'test')" + } + denied_permissions = ["cloudresourcemanager.googleapis.com/projects.delete"] + } + } + rules { + description = "Second rule" + deny_rule { + denied_principals = ["principalSet://goog/public:all"] + denial_condition { + title = "Some expr" + expression = "!resource.matchTag('12345678/env', 'test')" + } + denied_permissions = ["cloudresourcemanager.googleapis.com/projects.delete"] + exception_principals = ["principal://iam.googleapis.com/projects/-/serviceAccounts/${google_service_account.test-account.email}"] + } + } +} + +resource "google_service_account" "test-account" { + account_id = "tf-test-svc-acc%{random_suffix}" + display_name = "Test Service Account" + project = google_project.project.project_id +} +`, context) +} + +func testAccCheckIAM2DenyPolicyDestroyProducer(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_iam_deny_policy" { + continue + } + if strings.HasPrefix(name, "data.") { + continue + } + + config := acctest.GoogleProviderConfig(t) + + url, err := tpgresource.ReplaceVarsForTest(config, rs, "{{IAM2BasePath}}policies/{{parent}}/denypolicies/{{name}}") + if err != nil { + return err + } + + billingProject := "" + + if config.BillingProject != "" { + billingProject = config.BillingProject + } + + _, err = transport_tpg.SendRequest(transport_tpg.SendRequestOptions{ + Config: config, + Method: "GET", + Project: billingProject, + RawURL: url, + UserAgent: config.UserAgent, + }) + if err == nil { + return fmt.Errorf("IAM2DenyPolicy still exists at %s", url) + } + } + + return nil + } +} diff --git a/google/services/iam2/resource_iam_deny_policy_sweeper.go b/google/services/iam2/resource_iam_deny_policy_sweeper.go new file mode 100644 index 00000000000..a566088ea2f --- /dev/null +++ b/google/services/iam2/resource_iam_deny_policy_sweeper.go @@ -0,0 +1,139 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +// ---------------------------------------------------------------------------- +// +// *** 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 iam2 + +import ( + "context" + "log" + "strings" + "testing" + + "github.com/hashicorp/terraform-provider-google/google/envvar" + "github.com/hashicorp/terraform-provider-google/google/sweeper" + "github.com/hashicorp/terraform-provider-google/google/tpgresource" + transport_tpg "github.com/hashicorp/terraform-provider-google/google/transport" +) + +func init() { + sweeper.AddTestSweepers("IAM2DenyPolicy", testSweepIAM2DenyPolicy) +} + +// At the time of writing, the CI only passes us-central1 as the region +func testSweepIAM2DenyPolicy(region string) error { + resourceName := "IAM2DenyPolicy" + log.Printf("[INFO][SWEEPER_LOG] Starting sweeper for %s", resourceName) + + config, err := sweeper.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 := envvar.GetTestBillingAccountFromEnv(t) + + // Setup variables to replace in list template + d := &tpgresource.ResourceDataMock{ + FieldsInSchema: map[string]interface{}{ + "project": config.Project, + "region": region, + "location": region, + "zone": "-", + "billing_account": billingId, + }, + } + + listTemplate := strings.Split("https://iam.googleapis.com/v2/policies/{{parent}}/denypolicies", "?")[0] + listUrl, err := tpgresource.ReplaceVars(d, config, listTemplate) + if err != nil { + log.Printf("[INFO][SWEEPER_LOG] error preparing sweeper list url: %s", err) + return nil + } + + res, err := transport_tpg.SendRequest(transport_tpg.SendRequestOptions{ + Config: config, + Method: "GET", + Project: config.Project, + RawURL: listUrl, + UserAgent: config.UserAgent, + }) + if err != nil { + log.Printf("[INFO][SWEEPER_LOG] Error in response from request %s: %s", listUrl, err) + return nil + } + + resourceList, ok := res["denyPolicies"] + 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 := tpgresource.GetResourceNameFromSelfLink(obj["name"].(string)) + // Skip resources that shouldn't be sweeped + if !sweeper.IsSweepableTestResource(name) { + nonPrefixCount++ + continue + } + + deleteTemplate := "https://iam.googleapis.com/v2/policies/{{parent}}/denypolicies/{{name}}" + deleteUrl, err := tpgresource.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 = transport_tpg.SendRequest(transport_tpg.SendRequestOptions{ + Config: config, + Method: "DELETE", + Project: config.Project, + RawURL: deleteUrl, + UserAgent: config.UserAgent, + }) + 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/services/iam2/resource_iam_deny_policy_test.go b/google/services/iam2/resource_iam_deny_policy_test.go index be124ace112..0fe43fcfdcc 100644 --- a/google/services/iam2/resource_iam_deny_policy_test.go +++ b/google/services/iam2/resource_iam_deny_policy_test.go @@ -1,3 +1,222 @@ // Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 package iam2_test + +import ( + "github.com/hashicorp/terraform-provider-google/google/acctest" + "github.com/hashicorp/terraform-provider-google/google/envvar" + + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +func TestAccIAM2DenyPolicy_iamDenyPolicyUpdate(t *testing.T) { + t.Parallel() + + context := map[string]interface{}{ + "org_id": envvar.GetTestOrgFromEnv(t), + "billing_account": envvar.GetTestBillingAccountFromEnv(t), + "random_suffix": acctest.RandString(t, 10), + } + + acctest.VcrTest(t, resource.TestCase{ + PreCheck: func() { acctest.AccTestPreCheck(t) }, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), + CheckDestroy: testAccCheckIAM2DenyPolicyDestroyProducer(t), + Steps: []resource.TestStep{ + { + Config: testAccIAM2DenyPolicy_iamDenyPolicyUpdate(context), + }, + { + ResourceName: "google_iam_deny_policy.example", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"name", "parent"}, + }, + { + Config: testAccIAM2DenyPolicy_iamDenyPolicyUpdate2(context), + }, + { + ResourceName: "google_iam_deny_policy.example", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"name", "parent"}, + }, + { + Config: testAccIAM2DenyPolicy_iamDenyPolicyUpdate(context), + }, + { + ResourceName: "google_iam_deny_policy.example", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"name", "parent"}, + }, + }, + }) +} + +func TestAccIAM2DenyPolicy_iamDenyPolicyFolderParent(t *testing.T) { + 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: testAccCheckIAM2DenyPolicyDestroyProducer(t), + Steps: []resource.TestStep{ + { + Config: testAccIAM2DenyPolicy_iamDenyPolicyFolder(context), + }, + { + ResourceName: "google_iam_deny_policy.example", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"name", "parent"}, + }, + { + Config: testAccIAM2DenyPolicy_iamDenyPolicyFolderUpdate(context), + }, + { + ResourceName: "google_iam_deny_policy.example", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"name", "parent"}, + }, + }, + }) +} + +func testAccIAM2DenyPolicy_iamDenyPolicyUpdate(context map[string]interface{}) string { + return acctest.Nprintf(` +resource "google_project" "project" { + project_id = "tf-test%{random_suffix}" + name = "tf-test%{random_suffix}" + org_id = "%{org_id}" + billing_account = "%{billing_account}" +} + +resource "google_iam_deny_policy" "example" { + parent = urlencode("cloudresourcemanager.googleapis.com/projects/${google_project.project.project_id}") + name = "tf-test-my-deny-policy%{random_suffix}" + display_name = "A deny rule" + rules { + description = "First rule" + deny_rule { + denied_principals = ["principal://iam.googleapis.com/projects/-/serviceAccounts/${google_service_account.test-account.email}"] + denial_condition { + title = "Some expr" + expression = "!resource.matchTag('12345678/env', 'test')" + } + denied_permissions = ["cloudresourcemanager.googleapis.com/projects.delete"] + } + } + rules { + description = "Second rule" + deny_rule { + denied_principals = ["principalSet://goog/public:all"] + denial_condition { + title = "Some expr" + expression = "!resource.matchTag('12345678/env', 'test')" + } + denied_permissions = ["cloudresourcemanager.googleapis.com/projects.delete"] + exception_principals = ["principal://iam.googleapis.com/projects/-/serviceAccounts/${google_service_account.test-account.email}"] + } + } +} + +resource "google_service_account" "test-account" { + account_id = "tf-test-deny-account%{random_suffix}" + display_name = "Test Service Account" + project = google_project.project.project_id +} +`, context) +} + +func testAccIAM2DenyPolicy_iamDenyPolicyUpdate2(context map[string]interface{}) string { + return acctest.Nprintf(` +resource "google_project" "project" { + project_id = "tf-test%{random_suffix}" + name = "tf-test%{random_suffix}" + org_id = "%{org_id}" + billing_account = "%{billing_account}" +} + +resource "google_iam_deny_policy" "example" { + parent = urlencode("cloudresourcemanager.googleapis.com/projects/${google_project.project.project_id}") + name = "tf-test-my-deny-policy%{random_suffix}" + display_name = "A deny rule" + rules { + description = "Second rule" + deny_rule { + denied_principals = ["principalSet://goog/public:all"] + denial_condition { + title = "Some other expr" + expression = "!resource.matchTag('87654321/env', 'test')" + location = "/some/file" + description = "A denial condition" + } + denied_permissions = ["cloudresourcemanager.googleapis.com/projects.delete"] + } + } +} + +resource "google_service_account" "test-account" { + account_id = "tf-test-deny-account%{random_suffix}" + display_name = "Test Service Account" + project = google_project.project.project_id +} +`, context) +} + +func testAccIAM2DenyPolicy_iamDenyPolicyFolder(context map[string]interface{}) string { + return acctest.Nprintf(` +resource "google_iam_deny_policy" "example" { + parent = urlencode("cloudresourcemanager.googleapis.com/${google_folder.folder.id}") + name = "tf-test-my-deny-policy%{random_suffix}" + display_name = "A deny rule" + rules { + description = "Second rule" + deny_rule { + denied_principals = ["principalSet://goog/public:all"] + denial_condition { + title = "Some expr" + expression = "!resource.matchTag('12345678/env', 'test')" + } + denied_permissions = ["cloudresourcemanager.googleapis.com/projects.delete"] + } + } +} + +resource "google_folder" "folder" { + display_name = "tf-test-%{random_suffix}" + parent = "organizations/%{org_id}" +} +`, context) +} + +func testAccIAM2DenyPolicy_iamDenyPolicyFolderUpdate(context map[string]interface{}) string { + return acctest.Nprintf(` +resource "google_iam_deny_policy" "example" { + parent = urlencode("cloudresourcemanager.googleapis.com/${google_folder.folder.id}") + name = "tf-test-my-deny-policy%{random_suffix}" + display_name = "A deny rule" + rules { + description = "Second rule" + deny_rule { + denied_principals = ["principalSet://goog/public:all"] + denied_permissions = ["cloudresourcemanager.googleapis.com/projects.delete"] + } + } +} + +resource "google_folder" "folder" { + display_name = "tf-test-%{random_suffix}" + parent = "organizations/%{org_id}" +} +`, context) +} diff --git a/website/docs/r/iam_deny_policy.html.markdown b/website/docs/r/iam_deny_policy.html.markdown index ea0ca48199a..0301de7da03 100644 --- a/website/docs/r/iam_deny_policy.html.markdown +++ b/website/docs/r/iam_deny_policy.html.markdown @@ -21,12 +21,10 @@ description: |- Represents a collection of denial policies to apply to a given resource. -~> **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 DenyPolicy, see: -* [API documentation](https://cloud.google.com/iam/docs/reference/rest/v2beta/policies) +* [API documentation](https://cloud.google.com/iam/docs/reference/rest/v2/policies) * How-to Guides * [Permissions supported in deny policies](https://cloud.google.com/iam/docs/deny-permissions-support) @@ -35,7 +33,6 @@ To get more information about DenyPolicy, see: ```hcl resource "google_project" "project" { - provider = google-beta project_id = "my-project" name = "my-project" org_id = "123456789" @@ -43,7 +40,6 @@ resource "google_project" "project" { } resource "google_iam_deny_policy" "example" { - provider = google-beta parent = urlencode("cloudresourcemanager.googleapis.com/projects/${google_project.project.project_id}") name = "my-deny-policy" display_name = "A deny rule" @@ -73,7 +69,6 @@ resource "google_iam_deny_policy" "example" { } resource "google_service_account" "test-account" { - provider = google-beta account_id = "svc-acc" display_name = "Test Service Account" project = google_project.project.project_id