diff --git a/mmv1/api/resource.go b/mmv1/api/resource.go index a363e6d05765..a750741ecce9 100644 --- a/mmv1/api/resource.go +++ b/mmv1/api/resource.go @@ -1014,6 +1014,9 @@ func (r Resource) IsInIdentity(t Type) bool { return false } +// ==================== +// Iam Methods +// ==================== func (r Resource) IamParentResourceName() string { var parentResourceName string @@ -1028,6 +1031,7 @@ func (r Resource) IamParentResourceName() string { return parentResourceName } +// For example: "projects/{{project}}/schemas/{{name}}" func (r Resource) IamResourceUri() string { var resourceUri string if r.IamPolicy != nil { @@ -1039,13 +1043,15 @@ func (r Resource) IamResourceUri() string { return resourceUri } -func (r Resource) IamImportUrl() string { - r.IamResourceUri() +// For example: "projects/%s/schemas/%s" +func (r Resource) IamResourceUriFormat() string { return regexp.MustCompile(`\{\{%?(\w+)\}\}`).ReplaceAllString(r.IamResourceUri(), "%s") } +// For example: the uri "projects/{{project}}/schemas/{{name}}" +// The paramerters are "project", "schema". func (r Resource) IamResourceParams() []string { - resourceUri := strings.ReplaceAll(r.IamResourceUri(), "{{name}}", fmt.Sprintf("{{%s}}}", r.IamParentResourceName())) + resourceUri := strings.ReplaceAll(r.IamResourceUri(), "{{name}}", fmt.Sprintf("{{%s}}", r.IamParentResourceName())) return r.ExtractIdentifiers(resourceUri) } @@ -1054,7 +1060,9 @@ func (r Resource) IsInIamResourceParams(param string) bool { return slices.Contains(r.IamResourceParams(), param) } -func (r Resource) IamStringQualifiers() string { +// For example: for the uri "projects/{{project}}/schemas/{{name}}", +// the string qualifiers are "u.project, u.schema" +func (r Resource) IamResourceUriStringQualifiers() string { var transformed []string for _, param := range r.IamResourceParams() { transformed = append(transformed, fmt.Sprintf("u.%s", google.Camelize(param, "lower"))) @@ -1062,6 +1070,8 @@ func (r Resource) IamStringQualifiers() string { return strings.Join(transformed[:], ", ") } +// For example, for the url "projects/{{project}}/schemas/{{schema}}", +// the identifiers are "project", "schema". // def extract_identifiers(url) func (r Resource) ExtractIdentifiers(url string) []string { matches := regexp.MustCompile(`\{\{%?(\w+)\}\}`).FindAllStringSubmatch(url, -1) @@ -1072,6 +1082,7 @@ func (r Resource) ExtractIdentifiers(url string) []string { return result } +// For example, "projects/{{project}}/schemas/{{name}}", "{{project}}/{{name}}", "{{name}}" func (r Resource) RawImportIdFormatsFromIam() []string { var importFormat []string @@ -1085,6 +1096,7 @@ func (r Resource) RawImportIdFormatsFromIam() []string { return ImportIdFormats(importFormat, r.Identity, r.BaseUrl) } +// For example, projects/(?P[^/]+)/schemas/(?P[^/]+)", "(?P[^/]+)/(?P[^/]+)", "(?P[^/]+) func (r Resource) ImportIdRegexesFromIam() string { var transformed []string @@ -1098,6 +1110,7 @@ func (r Resource) ImportIdRegexesFromIam() string { return strings.Join(transformed[:], "\", \"") } +// For example, "projects/{{project}}/schemas/{{name}}", "{{project}}/{{name}}", "{{name}}" func (r Resource) ImportIdFormatsFromIam() []string { importIdFormats := r.RawImportIdFormatsFromIam() var transformed []string @@ -1107,6 +1120,7 @@ func (r Resource) ImportIdFormatsFromIam() []string { return transformed } +// For example, projects/{{project}}/schemas/{{schema}} func (r Resource) FirstIamImportIdFormat() string { importIdFormats := r.ImportIdFormatsFromIam() if len(importIdFormats) == 0 { @@ -1133,6 +1147,7 @@ func (r Resource) IamSelfLinkIdentifiers() []string { return r.ExtractIdentifiers(selfLink) } +// Returns the resource properties that are idenfifires in the selflink url func (r Resource) IamSelfLinkProperties() []*Type { params := r.IamSelfLinkIdentifiers() @@ -1143,6 +1158,7 @@ func (r Resource) IamSelfLinkProperties() []*Type { return urlProperties } +// Returns the attributes from the selflink url func (r Resource) IamAttributes() []string { var attributes []string ids := r.IamSelfLinkIdentifiers() @@ -1161,6 +1177,19 @@ func (r Resource) IamAttributes() []string { return attributes } +// Since most resources define a "basic" config as their first example, +// we can reuse that config to create a resource to test IAM resources with. +func (r Resource) FirstTestExample() resource.Examples { + examples := google.Reject(r.Examples, func(e resource.Examples) bool { + return e.SkipTest + }) + examples = google.Reject(examples, func(e resource.Examples) bool { + return (r.ProductMetadata.VersionObjOrClosest(r.TargetVersionName).CompareTo(r.ProductMetadata.VersionObjOrClosest(e.MinVersion)) < 0) + }) + + return examples[0] +} + func (r Resource) ExamplePrimaryResourceId() string { examples := google.Reject(r.Examples, func(e resource.Examples) bool { return e.SkipTest @@ -1185,6 +1214,50 @@ func (r Resource) IamParentSourceType() string { return t } +func (r Resource) IamImportQualifiersForTest() string { + var importFormat string + if len(r.IamPolicy.ImportFormat) > 0 { + importFormat = r.IamPolicy.ImportFormat[0] + } else { + importFormat = r.IamPolicy.SelfLink + if importFormat == "" { + importFormat = r.SelfLinkUrl() + } + } + + params := r.ExtractIdentifiers(importFormat) + var importQualifiers []string + for i, param := range params { + if param == "project" { + if i != len(params)-1 { + // If the last parameter is project then we want to create a new project to use for the test, so don't default from the environment + importQualifiers = append(importQualifiers, "envvar.GetTestProjectFromEnv()") + } else { + importQualifiers = append(importQualifiers, `context["project_id"]`) + } + } else if param == "zone" && r.IamPolicy.SubstituteZoneValue { + importQualifiers = append(importQualifiers, "envvar.GetTestZoneFromEnv()") + } else if param == "region" || param == "location" { + example := r.FirstTestExample() + if example.RegionOverride == "" { + importQualifiers = append(importQualifiers, "envvar.GetTestRegionFromEnv()") + } else { + importQualifiers = append(importQualifiers, example.RegionOverride) + } + } else if param == "universe_domain" { + importQualifiers = append(importQualifiers, "envvar.GetTestUniverseDomainFromEnv()") + } else { + break + } + } + + if len(importQualifiers) == 0 { + return "" + } + + return strings.Join(importQualifiers, ", ") +} + func OrderProperties(props []*Type) []*Type { req := google.Select(props, func(p *Type) bool { return p.Required diff --git a/mmv1/api/resource/iam_policy.go b/mmv1/api/resource/iam_policy.go index 84f90f7e0c8f..812e18ab8170 100644 --- a/mmv1/api/resource/iam_policy.go +++ b/mmv1/api/resource/iam_policy.go @@ -40,9 +40,9 @@ type IamPolicy struct { // While Compute subnetwork uses {resource}/getIamPolicy MethodNameSeparator string `yaml:"method_name_separator"` - // The terraform type of the parent resource if it is not the same as the - // IAM resource. The IAP product needs these as its IAM policies refer - // to compute resources + // The terraform type (e.g. 'google_endpoints_service') of the parent resource + // if it is not the same as the IAM resource. The IAP product needs these + // as its IAM policies refer to compute resources. ParentResourceType string `yaml:"parent_resource_type"` // Some resources allow retrieving the IAM policy with GET requests, @@ -84,7 +84,7 @@ type IamPolicy struct { // Some resources (IAP) use fields named differently from the parent resource. // We need to use the parent's attributes to create an IAM policy, but they may not be - // named as the IAM IAM resource expects. + // named as the IAM resource expects. // This allows us to specify a file (relative to MM root) containing a partial terraform // config with the test/example attributes of the IAM resource. ExampleConfigBody string `yaml:"example_config_body"` diff --git a/mmv1/provider/template_data.go b/mmv1/provider/template_data.go index ba75aea2e1e4..63ab416c88f8 100644 --- a/mmv1/provider/template_data.go +++ b/mmv1/provider/template_data.go @@ -185,6 +185,11 @@ func (td *TemplateData) GenerateIamDatasourceDocumentationFile(filePath string, } func (td *TemplateData) GenerateIamPolicyTestFile(filePath string, resource api.Resource) { + templatePath := "templates/terraform/examples/base_configs/iam_test_file.go.tmpl" + templates := []string{ + templatePath, + } + td.GenerateFile(filePath, templatePath, resource, false, templates...) } func (td *TemplateData) GenerateFile(filePath, templatePath string, input any, goFormat bool, templates ...string) { diff --git a/mmv1/templates/terraform/datasource_iam.html.markdown.tmpl b/mmv1/templates/terraform/datasource_iam.html.markdown.tmpl index 4bc96a6a7851..db0d361cc133 100644 --- a/mmv1/templates/terraform/datasource_iam.html.markdown.tmpl +++ b/mmv1/templates/terraform/datasource_iam.html.markdown.tmpl @@ -69,7 +69,7 @@ data "{{ $.IamTerraformName }}_policy" "policy" { {{ if eq $.MinVersionObj.Name "beta" }} provider = google-beta {{- end }} -{{- $.CustomTemplate $.IamPolicy.ExampleConfigBody}} +{{- $.CustomTemplate $.IamPolicy.ExampleConfigBody false }} } ``` diff --git a/mmv1/templates/terraform/examples/base_configs/iam_test_file.go.tmpl b/mmv1/templates/terraform/examples/base_configs/iam_test_file.go.tmpl new file mode 100644 index 000000000000..044f937ee1cc --- /dev/null +++ b/mmv1/templates/terraform/examples/base_configs/iam_test_file.go.tmpl @@ -0,0 +1,674 @@ +{{/* <% if hc_downstream */}} +// 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 {{ lower $.ProductMetadata.Name }}_test + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + + "{{ $.ImportPath }}/acctest" + "{{ $.ImportPath }}/envvar" + "{{ $.ImportPath }}/tpgresource" +) +{{ $example := $.FirstTestExample }} +func TestAcc{{ $.ResourceName }}IamBindingGenerated(t *testing.T) { + t.Parallel() + +{{/* iam_context.go.erb */}} + + acctest.VcrTest(t, resource.TestCase{ + PreCheck: func() { acctest.AccTestPreCheck(t) }, +{{- if eq $.MinVersionObj.Name "beta" }} + ProtoV5ProviderFactories: acctest.ProtoV5ProviderBetaFactories(t), +{{- else }} + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), + {{- if $example.ExternalProviders }} + ExternalProviders: map[string]resource.ExternalProvider{ + {{- range $provider := $example.ExternalProviders }} + "{{$provider}}": {}, + {{- end }} + }, + {{- end }} +{{- end }} + Steps: []resource.TestStep{ + { + Config: testAcc{{ $.ResourceName }}IamBinding_basicGenerated(context), + }, +{{- if not $.IamPolicy.SkipImportTest }} + { + ResourceName: "{{ $.IamTerraformName }}_binding.foo", + ImportStateId: fmt.Sprintf("{{ $.IamResourceUriFormat }} {{ $.IamPolicy.AllowedIamRole }}", {{ $.IamImportQualifiersForTest }}, {{ $example.PrimaryResourceName }}), + ImportState: true, + ImportStateVerify: true, + }, +{{- end }} + { + // Test Iam Binding update + Config: testAcc{{ $.ResourceName }}IamBinding_updateGenerated(context), + }, +{{- if not $.IamPolicy.SkipImportTest }} + { + ResourceName: "{{ $.IamTerraformName }}_binding.foo", + ImportStateId: fmt.Sprintf("{{ $.IamResourceUriFormat }} {{ $.IamPolicy.AllowedIamRole }}", {{ $.IamImportQualifiersForTest }}, {{ $example.PrimaryResourceName }}), + ImportState: true, + ImportStateVerify: true, + }, +{{- end }} + }, + }) +} + +func TestAcc{{ $.ResourceName }}IamMemberGenerated(t *testing.T) { + t.Parallel() + +{{/* iam_context.go.erb */}} + + acctest.VcrTest(t, resource.TestCase{ + PreCheck: func() { acctest.AccTestPreCheck(t) }, +{{- if eq $.MinVersionObj.Name "beta" }} + ProtoV5ProviderFactories: acctest.ProtoV5ProviderBetaFactories(t), +{{- else }} + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), +{{- end }} +{{- if $example.ExternalProviders }} + ExternalProviders: map[string]resource.ExternalProvider{ + {{- range $provider := $example.ExternalProviders }} + "{{$provider}}": {}, + {{- end }} + }, +{{- end }} + Steps: []resource.TestStep{ + { + // Test Iam Member creation (no update for member, no need to test) + Config: testAcc{{ $.ResourceName }}IamMember_basicGenerated(context), + }, +{{- if not $.IamPolicy.SkipImportTest }} + { + ResourceName: "{{ $.IamTerraformName }}_member.foo", + ImportStateId: fmt.Sprintf("{{ $.IamResourceUriFormat }} {{ $.IamPolicy.AllowedIamRole }} user:admin@hashicorptest.com", {{ $.IamImportQualifiersForTest }}, {{ $example.PrimaryResourceName }}), + ImportState: true, + ImportStateVerify: true, + }, +{{- end }} + }, + }) +} + +func TestAcc{{ $.ResourceName }}IamPolicyGenerated(t *testing.T) { + t.Parallel() + +{{- if $.IamPolicy.AdminIamRole }} + // This may skip test, so do it first + sa := envvar.GetTestServiceAccountFromEnv(t) +{{- end }} +{{/* iam_context.go.erb */}} +{{- if $.IamPolicy.AdminIamRole }} + context["service_account"] = sa +{{- end }} + + acctest.VcrTest(t, resource.TestCase{ + PreCheck: func() { acctest.AccTestPreCheck(t) }, +{{- if eq $.MinVersionObj.Name "beta" }} + ProtoV5ProviderFactories: acctest.ProtoV5ProviderBetaFactories(t), +{{- else }} + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), +{{- end }} +{{- if $example.ExternalProviders }} + ExternalProviders: map[string]resource.ExternalProvider{ + {{- range $provider := $example.ExternalProviders }} + "{{$provider}}": {}, + {{- end }} + }, +{{- end }} + Steps: []resource.TestStep{ + { + Config: testAcc{{ $.ResourceName }}IamPolicy_basicGenerated(context), + Check: resource.TestCheckResourceAttrSet("data.{{ $.IamTerraformName }}_policy.foo", "policy_data"), + }, +{{- if not $.IamPolicy.SkipImportTest }} + { + ResourceName: "{{ $.IamTerraformName }}_policy.foo", + ImportStateId: fmt.Sprintf("{{ $.IamResourceUriFormat }}", {{ $.IamImportQualifiersForTest }}, {{ $example.PrimaryResourceName }}), + ImportState: true, + ImportStateVerify: true, + }, +{{- end }} + { + Config: testAcc{{ $.ResourceName }}IamPolicy_emptyBinding(context), + }, +{{- if not $.IamPolicy.SkipImportTest }} + { + ResourceName: "{{ $.IamTerraformName }}_policy.foo", + ImportStateId: fmt.Sprintf("{{ $.IamResourceUriFormat }}", {{ $.IamImportQualifiersForTest }}, {{ $example.PrimaryResourceName }}), + ImportState: true, + ImportStateVerify: true, + }, +{{- end }} + }, + }) +} + +{{- if $.IamPolicy.IamConditionsRequestType }} +func TestAcc{{ $.ResourceName }}IamBindingGenerated_withCondition(t *testing.T) { + t.Parallel() + +{{/* iam_context.go.erb */}} + + acctest.VcrTest(t, resource.TestCase{ + PreCheck: func() { acctest.AccTestPreCheck(t) }, +{{- if eq $.MinVersionObj.Name "beta" }} + ProtoV5ProviderFactories: acctest.ProtoV5ProviderBetaFactories(t), +{{- else }} + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), +{{- end }} +{{- if $example.ExternalProviders }} + ExternalProviders: map[string]resource.ExternalProvider{ + {{- range $provider := $example.ExternalProviders }} + "{{$provider}}": {}, + {{- end }} + }, +{{- end }} + Steps: []resource.TestStep{ + { + Config: testAcc{{ $.ResourceName }}IamBinding_withConditionGenerated(context), + }, +{{- if not $.IamPolicy.SkipImportTest }} + { + ResourceName: "{{ $.IamTerraformName }}_binding.foo", + ImportStateId: fmt.Sprintf("{{ $.IamResourceUriFormat }} {{ $.IamPolicy.AllowedIamRole }}", {{ $.IamImportQualifiersForTest }}, {{ $example.PrimaryResourceName }}, context["condition_title"]), + ImportState: true, + ImportStateVerify: true, + }, +{{- end }} + }, + }) +} + +func TestAcc{{ $.ResourceName }}IamBindingGenerated_withAndWithoutCondition(t *testing.T) { + // Multiple fine-grained resources + acctest.SkipIfVcr(t) + t.Parallel() + +{{/* iam_context.go.erb */}} + + acctest.VcrTest(t, resource.TestCase{ + PreCheck: func() { acctest.AccTestPreCheck(t) }, +{{- if eq $.MinVersionObj.Name "beta" }} + ProtoV5ProviderFactories: acctest.ProtoV5ProviderBetaFactories(t), +{{- else }} + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), +{{- end }} +{{- if $example.ExternalProviders }} + ExternalProviders: map[string]resource.ExternalProvider{ + {{- range $provider := $example.ExternalProviders }} + "{{$provider}}": {}, + {{- end }} + }, +{{- end }} + Steps: []resource.TestStep{ + { + Config: testAcc{{ $.ResourceName }}IamBinding_withAndWithoutConditionGenerated(context), + }, +{{- if not $.IamPolicy.SkipImportTest }} + { + ResourceName: "{{ $.IamTerraformName }}_binding.foo", + ImportStateId: fmt.Sprintf("{{ $.IamResourceUriFormat }} {{ $.IamPolicy.AllowedIamRole }}", {{ $.IamImportQualifiersForTest }}, {{ $example.PrimaryResourceName }}), + ImportState: true, + ImportStateVerify: true, + }, + { + ResourceName: "{{ $.IamTerraformName }}_binding.foo2", + ImportStateId: fmt.Sprintf("{{ $.IamResourceUriFormat }} {{ $.IamPolicy.AllowedIamRole }}", {{ $.IamImportQualifiersForTest }}, {{ $example.PrimaryResourceName }}, context["condition_title"]), + ImportState: true, + ImportStateVerify: true, + }, + { + ResourceName: "{{ $.IamTerraformName }}_binding.foo3", + ImportStateId: fmt.Sprintf("{{ $.IamResourceUriFormat }} {{ $.IamPolicy.AllowedIamRole }}", {{ $.IamImportQualifiersForTest }}, {{ $example.PrimaryResourceName }}, context["condition_title_no_desc"]), + ImportState: true, + ImportStateVerify: true, + }, +{{- end }} + }, + }) +} + +func TestAcc{{ $.ResourceName }}IamMemberGenerated_withCondition(t *testing.T) { + t.Parallel() + +{{/* iam_context.go.erb */}} + + acctest.VcrTest(t, resource.TestCase{ + PreCheck: func() { acctest.AccTestPreCheck(t) }, +{{- if eq $.MinVersionObj.Name "beta" }} + ProtoV5ProviderFactories: acctest.ProtoV5ProviderBetaFactories(t), +{{- else }} + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), +{{- end }} +{{- if $example.ExternalProviders }} + ExternalProviders: map[string]resource.ExternalProvider{ + {{- range $provider := $example.ExternalProviders }} + "{{$provider}}": {}, + {{- end }} + }, +{{- end }} + Steps: []resource.TestStep{ + { + Config: testAcc{{ $.ResourceName }}IamMember_withConditionGenerated(context), + }, +{{- if not $.IamPolicy.SkipImportTest }} + { + ResourceName: "{{ $.IamTerraformName }}_member.foo", + ImportStateId: fmt.Sprintf("{{ $.IamResourceUriFormat }} {{ $.IamPolicy.AllowedIamRole }}", {{ $.IamImportQualifiersForTest }}, {{ $example.PrimaryResourceName }}, context["condition_title"]), + ImportState: true, + ImportStateVerify: true, + }, +{{- end }} + }, + }) +} + +func TestAcc{{ $.ResourceName }}IamMemberGenerated_withAndWithoutCondition(t *testing.T) { + // Multiple fine-grained resources + acctest.SkipIfVcr(t) + t.Parallel() + +{{/* iam_context.go.erb */}} + + acctest.VcrTest(t, resource.TestCase{ + PreCheck: func() { acctest.AccTestPreCheck(t) }, +{{- if eq $.MinVersionObj.Name "beta" }} + ProtoV5ProviderFactories: acctest.ProtoV5ProviderBetaFactories(t), +{{- else }} + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), +{{- end }} +{{- if $example.ExternalProviders }} + ExternalProviders: map[string]resource.ExternalProvider{ + {{- range $provider := $example.ExternalProviders }} + "{{$provider}}": {}, + {{- end }} + }, +{{- end }} + Steps: []resource.TestStep{ + { + Config: testAcc{{ $.ResourceName }}IamMember_withAndWithoutConditionGenerated(context), + }, +{{- if not $.IamPolicy.SkipImportTest }} + { + ResourceName: "{{ $.IamTerraformName }}_member.foo", + ImportStateId: fmt.Sprintf("{{ $.IamResourceUriFormat }} {{ $.IamPolicy.AllowedIamRole }}", {{ $.IamImportQualifiersForTest }}, {{ $example.PrimaryResourceName }}), + ImportState: true, + ImportStateVerify: true, + }, + { + ResourceName: "{{ $.IamTerraformName }}_member.foo2", + ImportStateId: fmt.Sprintf("{{ $.IamResourceUriFormat }} {{ $.IamPolicy.AllowedIamRole }}", {{ $.IamImportQualifiersForTest }}, {{ $example.PrimaryResourceName }}, context["condition_title"]), + ImportState: true, + ImportStateVerify: true, + }, + { + ResourceName: "{{ $.IamTerraformName }}_member.foo3", + ImportStateId: fmt.Sprintf("{{ $.IamResourceUriFormat }} {{ $.IamPolicy.AllowedIamRole }}", {{ $.IamImportQualifiersForTest }}, {{ $example.PrimaryResourceName }}, context["condition_title_no_desc"]), + ImportState: true, + ImportStateVerify: true, + }, +{{- end }} + }, + }) +} + +func TestAcc{{ $.ResourceName }}IamPolicyGenerated_withCondition(t *testing.T) { + t.Parallel() + +{{- if $.IamPolicy.AdminIamRole }} + // This may skip test, so do it first + sa := envvar.GetTestServiceAccountFromEnv(t) +{{- end }} +{{/* iam_context.go.erb */}} +{{- if $.IamPolicy.AdminIamRole }} + context["service_account"] = sa +{{- end }} + +{{- if $.IamPolicy.AdminIamRole }} + // Test should have 2 bindings: one with a description and one without. Any < chars are converted to a unicode character by the API. + expectedPolicyData := acctest.Nprintf(`{"bindings":[{"condition":{"description":"%{condition_desc}","expression":"%{condition_expr}","title":"%{condition_title}"},"members":["user:admin@hashicorptest.com"],"role":"%{role}"},{"condition":{"expression":"%{condition_expr}","title":"%{condition_title}-no-description"},"members":["user:admin@hashicorptest.com"],"role":"%{role}"}]}`, context) +{{- else }} + // Test should have 3 bindings: one with a description and one without, and a third for an admin role. Any < chars are converted to a unicode character by the API. + expectedPolicyData := acctest.Nprintf(`{"bindings":[{"members":["serviceAccount:%{service_account}"],"role":"%{admin_role}"},{"condition":{"description":"%{condition_desc}","expression":"%{condition_expr}","title":"%{condition_title}"},"members":["user:admin@hashicorptest.com"],"role":"%{role}"},{"condition":{"expression":"%{condition_expr}","title":"%{condition_title}-no-description"},"members":["user:admin@hashicorptest.com"],"role":"%{role}"}]}`, context) +{{- end }} + expectedPolicyData = strings.Replace(expectedPolicyData, "<", "\\u003c", -1) + + acctest.VcrTest(t, resource.TestCase{ + PreCheck: func() { acctest.AccTestPreCheck(t) }, +{{- if eq $.MinVersionObj.Name "beta" }} + ProtoV5ProviderFactories: acctest.ProtoV5ProviderBetaFactories(t), +{{- else }} + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), +{{- end }} +{{- if $example.ExternalProviders }} + ExternalProviders: map[string]resource.ExternalProvider{ + {{- range $provider := $example.ExternalProviders }} + "{{$provider}}": {}, + {{- end }} + }, +{{- end }} + Steps: []resource.TestStep{ + { + Config: testAcc{{ $.ResourceName }}IamPolicy_withConditionGenerated(context), + Check: resource.ComposeAggregateTestCheckFunc( + // TODO(SarahFrench) - uncomment once https://github.com/GoogleCloudPlatform/magic-modules/pull/6466 merged + // resource.TestCheckResourceAttr("data.google_iam_policy.foo", "policy_data", expectedPolicyData), + resource.TestCheckResourceAttr("{{ $.IamTerraformName }}_policy.foo", "policy_data", expectedPolicyData), + resource.TestCheckResourceAttrWith("data.google_iam_policy.foo", "policy_data", tpgresource.CheckGoogleIamPolicy), + ), + }, +{{- if not $.IamPolicy.SkipImportTest }} + { + ResourceName: "{{ $.IamTerraformName }}_policy.foo", + ImportStateId: fmt.Sprintf("{{ $.IamResourceUriFormat }}", {{ $.IamImportQualifiersForTest }}, {{ $example.PrimaryResourceName }}), + ImportState: true, + ImportStateVerify: true, + }, +{{- end }} + }, + }) +} +{{- end }} + +func testAcc{{ $.ResourceName }}IamMember_basicGenerated(context map[string]interface{}) string { + return acctest.Nprintf(` +{{/* example.config_test_body */}} + +resource "{{ $.IamTerraformName }}_member" "foo" { +{{- if eq $.MinVersionObj.Name "beta" }} + provider = google-beta +{{- end }} +{{- $.CustomTemplate $.IamPolicy.ExampleConfigBody false }} + role = "%{role}" + member = "user:admin@hashicorptest.com" +} +`, context) +} + +func testAcc{{ $.ResourceName }}IamPolicy_basicGenerated(context map[string]interface{}) string { + return acctest.Nprintf(` +{{/* example.config_test_body */}} + +data "google_iam_policy" "foo" { +{{- if eq $.MinVersionObj.Name "beta" }} + provider = google-beta +{{- end }} + binding { + role = "%{role}" + members = ["user:admin@hashicorptest.com"] + } +{{- if $.IamPolicy.AdminIamRole }} + binding { + role = "%{admin_role}" + members = ["serviceAccount:%{service_account}"] + } +{{- end }} +} + +resource "{{ $.IamTerraformName }}_policy" "foo" { +{{- if eq $.MinVersionObj.Name "beta" }} + provider = google-beta +{{- end }} +{{- $.CustomTemplate $.IamPolicy.ExampleConfigBody false }} + policy_data = data.google_iam_policy.foo.policy_data +} + +data "{{ $.IamTerraformName }}_policy" "foo" { +{{- if eq $.MinVersionObj.Name "beta" }} + provider = google-beta +{{- end }} +{{- $.CustomTemplate $.IamPolicy.ExampleConfigBody false }} + depends_on = [ + {{ $.IamTerraformName }}_policy.foo + ] +} +`, context) +} + +func testAcc{{ $.ResourceName }}IamPolicy_emptyBinding(context map[string]interface{}) string { + return acctest.Nprintf(` +{{/* example.config_test_body */}} + +data "google_iam_policy" "foo" { +{{- if eq $.MinVersionObj.Name "beta" }} + provider = google-beta +{{- end }} +} + +resource "{{ $.IamTerraformName }}_policy" "foo" { +{{- if eq $.MinVersionObj.Name "beta" }} + provider = google-beta +{{- end }} +{{- $.CustomTemplate $.IamPolicy.ExampleConfigBody false }} + policy_data = data.google_iam_policy.foo.policy_data +} +`, context) +} + +func testAcc{{ $.ResourceName }}IamBinding_basicGenerated(context map[string]interface{}) string { + return acctest.Nprintf(` +{{/* example.config_test_body */}} + +resource "{{ $.IamTerraformName }}_binding" "foo" { +{{- if eq $.MinVersionObj.Name "beta" }} + provider = google-beta +{{- end }} +{{- $.CustomTemplate $.IamPolicy.ExampleConfigBody false }} + role = "%{role}" + members = ["user:admin@hashicorptest.com"] +} +`, context) +} + +func testAcc{{ $.ResourceName }}IamBinding_updateGenerated(context map[string]interface{}) string { + return acctest.Nprintf(` +{{/* example.config_test_body */}} + +resource "{{ $.IamTerraformName }}_binding" "foo" { +{{- if eq $.MinVersionObj.Name "beta" }} + provider = google-beta +{{- end }} +{{- $.CustomTemplate $.IamPolicy.ExampleConfigBody false }} + role = "%{role}" + members = ["user:admin@hashicorptest.com", "user:gterraformtest1@gmail.com"] +} +`, context) +} + +{{- if $.IamPolicy.IamConditionsRequestType }} +func testAcc{{ $.ResourceName }}IamBinding_withConditionGenerated(context map[string]interface{}) string { + return acctest.Nprintf(` +{{/* example.config_test_body */}} + +resource "{{ $.IamTerraformName }}_binding" "foo" { +{{- if eq $.MinVersionObj.Name "beta" }} + provider = google-beta +{{- end }} +{{- $.CustomTemplate $.IamPolicy.ExampleConfigBody false }} + role = "%{role}" + members = ["user:admin@hashicorptest.com"] + condition { + title = "%{condition_title}" + description = "%{condition_desc}" + expression = "%{condition_expr}" + } +} +`, context) +} + +func testAcc{{ $.ResourceName }}IamBinding_withAndWithoutConditionGenerated(context map[string]interface{}) string { + return acctest.Nprintf(` +{{/* example.config_test_body */}} + +resource "{{ $.IamTerraformName }}_binding" "foo" { +{{- if eq $.MinVersionObj.Name "beta" }} + provider = google-beta +{{- end }} +{{- $.CustomTemplate $.IamPolicy.ExampleConfigBody false }} + role = "%{role}" + members = ["user:admin@hashicorptest.com"] +} + +resource "{{ $.IamTerraformName }}_binding" "foo2" { +{{- if eq $.MinVersionObj.Name "beta" }} + provider = google-beta +{{- end }} +{{- $.CustomTemplate $.IamPolicy.ExampleConfigBody false }} + role = "%{role}" + members = ["user:admin@hashicorptest.com"] + condition { + title = "%{condition_title}" + description = "%{condition_desc}" + expression = "%{condition_expr}" + } +} + +resource "{{ $.IamTerraformName }}_binding" "foo3" { +{{- if eq $.MinVersionObj.Name "beta" }} + provider = google-beta +{{- end }} +{{- $.CustomTemplate $.IamPolicy.ExampleConfigBody false }} + role = "%{role}" + members = ["user:admin@hashicorptest.com"] + condition { + # Check that lack of description doesn't cause any issues + # Relates to issue : https://github.com/hashicorp/terraform-provider-google/issues/8701 + title = "%{condition_title_no_desc}" + expression = "%{condition_expr_no_desc}" + } +} +`, context) +} + +func testAcc{{ $.ResourceName }}IamMember_withConditionGenerated(context map[string]interface{}) string { + return acctest.Nprintf(` +{{/* example.config_test_body */}} + +resource "{{ $.IamTerraformName }}_member" "foo" { +{{- if eq $.MinVersionObj.Name "beta" }} + provider = google-beta +{{- end }} +{{- $.CustomTemplate $.IamPolicy.ExampleConfigBody false }} + role = "%{role}" + member = "user:admin@hashicorptest.com" + condition { + title = "%{condition_title}" + description = "%{condition_desc}" + expression = "%{condition_expr}" + } +} +`, context) +} + +func testAcc{{ $.ResourceName }}IamMember_withAndWithoutConditionGenerated(context map[string]interface{}) string { + return acctest.Nprintf(` +{{/* example.config_test_body */}} + +resource "{{ $.IamTerraformName }}_member" "foo" { +{{- if eq $.MinVersionObj.Name "beta" }} + provider = google-beta +{{- end }} +{{- $.CustomTemplate $.IamPolicy.ExampleConfigBody false }} + role = "%{role}" + member = "user:admin@hashicorptest.com" +} + +resource "{{ $.IamTerraformName }}_member" "foo2" { +{{- if eq $.MinVersionObj.Name "beta" }} + provider = google-beta +{{- end }} +{{- $.CustomTemplate $.IamPolicy.ExampleConfigBody false }} + role = "%{role}" + member = "user:admin@hashicorptest.com" + condition { + title = "%{condition_title}" + description = "%{condition_desc}" + expression = "%{condition_expr}" + } +} + +resource "{{ $.IamTerraformName }}_member" "foo3" { +{{- if eq $.MinVersionObj.Name "beta" }} + provider = google-beta +{{- end }} +{{- $.CustomTemplate $.IamPolicy.ExampleConfigBody false }} + role = "%{role}" + member = "user:admin@hashicorptest.com" + condition { + # Check that lack of description doesn't cause any issues + # Relates to issue : https://github.com/hashicorp/terraform-provider-google/issues/8701 + title = "%{condition_title_no_desc}" + expression = "%{condition_expr_no_desc}" + } +} +`, context) +} + +func testAcc{{ $.ResourceName }}IamPolicy_withConditionGenerated(context map[string]interface{}) string { + return acctest.Nprintf(` +{{/* example.config_test_body */}} + +data "google_iam_policy" "foo" { +{{- if eq $.MinVersionObj.Name "beta" }} + provider = google-beta +{{- end }} + binding { + role = "%{role}" + members = ["user:admin@hashicorptest.com"] + condition { + # Check that lack of description doesn't cause any issues + # Relates to issue : https://github.com/hashicorp/terraform-provider-google/issues/8701 + title = "%{condition_title_no_desc}" + expression = "%{condition_expr_no_desc}" + } + } + binding { + role = "%{role}" + members = ["user:admin@hashicorptest.com"] + condition { + title = "%{condition_title}" + description = "%{condition_desc}" + expression = "%{condition_expr}" + } + } +{{- if $.IamPolicy.AdminIamRole }} + binding { + role = "%{admin_role}" + members = ["serviceAccount:%{service_account}"] + } +{{- end }} +} + +resource "{{ $.IamTerraformName }}_policy" "foo" { +{{- if eq $.MinVersionObj.Name "beta" }} + provider = google-beta +{{- end }} +{{- $.CustomTemplate $.IamPolicy.ExampleConfigBody false }} + policy_data = data.google_iam_policy.foo.policy_data +} +`, context) +} +{{- end }} diff --git a/mmv1/templates/terraform/iam_policy.go.tmpl b/mmv1/templates/terraform/iam_policy.go.tmpl index 5979b02aab47..1c476b0631c6 100644 --- a/mmv1/templates/terraform/iam_policy.go.tmpl +++ b/mmv1/templates/terraform/iam_policy.go.tmpl @@ -309,7 +309,7 @@ func (u *{{ $.ResourceName }}IamUpdater) SetResourceIamPolicy(policy *cloudresou } func (u *{{ $.ResourceName }}IamUpdater) qualify{{ $.Name }}Url(methodIdentifier string) (string, error) { - urlTemplate := fmt.Sprintf("{{"{{"}}{{ $.ProductMetadata.Name }}BasePath{{"}}"}}%s{{ $.IamPolicy.MethodNameSeparator }}%s", fmt.Sprintf("{{ $.IamImportUrl }}", {{ $.IamStringQualifiers }}), methodIdentifier) + urlTemplate := fmt.Sprintf("{{"{{"}}{{ $.ProductMetadata.Name }}BasePath{{"}}"}}%s{{ $.IamPolicy.MethodNameSeparator }}%s", fmt.Sprintf("{{ $.IamResourceUriFormat }}", {{ $.IamResourceUriStringQualifiers }}), methodIdentifier) url, err := tpgresource.ReplaceVars(u.d, u.Config, urlTemplate) if err != nil { return "", err @@ -318,7 +318,7 @@ func (u *{{ $.ResourceName }}IamUpdater) qualify{{ $.Name }}Url(methodIdentifier } func (u *{{ $.ResourceName }}IamUpdater) GetResourceId() string { - return fmt.Sprintf("{{ $.IamImportUrl }}", {{ $.IamStringQualifiers }}) + return fmt.Sprintf("{{ $.IamResourceUriFormat }}", {{ $.IamResourceUriStringQualifiers }}) } func (u *{{ $.ResourceName }}IamUpdater) GetMutexKey() string { diff --git a/mmv1/templates/terraform/resource_iam.html.markdown.tmpl b/mmv1/templates/terraform/resource_iam.html.markdown.tmpl index bd98136f76da..88bd230b9359 100644 --- a/mmv1/templates/terraform/resource_iam.html.markdown.tmpl +++ b/mmv1/templates/terraform/resource_iam.html.markdown.tmpl @@ -204,7 +204,7 @@ The following arguments are supported: {{ range $param := $.IamSelfLinkProperties }} {{- if eq $param.Name "name" -}} -* `{{if $.IamPolicy.ParentResourceAttribute}}{{$.IamPolicy.ParentResourceAttribute}}{{else}}{{underscore $.Name}}{{end}}` - (Required) Used to find the parent resource to bind the IAM policy to +* `{{ $.IamParentResourceName }}` - (Required) Used to find the parent resource to bind the IAM policy to {{- else if or (eq (underscore $param.Name) "region") (eq (underscore $param.Name) "zone") }} * `{{ underscore $param.Name }}` - (Optional) {{ $param.Description }} Used to find the parent resource to bind the IAM policy to. If not specified, the value will be parsed from the identifier of the parent resource. If no {{ underscore $param.Name }} is provided in the parent identifier and no