From f8b4c8dfab99db82772f47d02944acdc2021ae8a Mon Sep 17 00:00:00 2001 From: "alvarez.mauriciotm@gmail.com" Date: Tue, 8 Sep 2026 12:57:09 -0700 Subject: [PATCH 1/7] fix: preserve list resource scope identity --- mmv1/templates/terraform/list_resource_method.go.tmpl | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/mmv1/templates/terraform/list_resource_method.go.tmpl b/mmv1/templates/terraform/list_resource_method.go.tmpl index e49922ab7a8d..d0a7ad6f5ca2 100644 --- a/mmv1/templates/terraform/list_resource_method.go.tmpl +++ b/mmv1/templates/terraform/list_resource_method.go.tmpl @@ -122,6 +122,14 @@ func List{{ $.ResourceName }}s(config *transport_tpg.Config, if err = Resource{{ $.ResourceName }}Flatten(d, config, res, config, {{ if $.HasProject }}project, {{ end }}userAgent, billingProject, url, headers); err != nil { return err } +{{- /* Copy list scope values onto each item so URL-scope identity fields are preserved. */ -}} +{{- range $scope := $.ListScopeProperties }} + if {{ $scope.CamelizeProperty }} != "" { + if err := d.Set("{{ underscore $scope.Name }}", {{ $scope.CamelizeProperty }}); err != nil { + return fmt.Errorf("error setting {{ underscore $scope.Name }}: %w", err) + } + } +{{- end }} id, err := tpgresource.ReplaceVars{{if $.LegacyLongFormProject -}}ForId{{ end -}}(d, config, "{{ $.IdFormat -}}") if err != nil { return fmt.Errorf("error constructing id: %w", err) From 4a13051f9ef8529df04cc2e24e77aeb5e5acf26c Mon Sep 17 00:00:00 2001 From: vr-ibm Date: Fri, 31 Jul 2026 16:03:56 -0500 Subject: [PATCH 2/7] secretmanager: add list resource for google_secret_manager_secret_version --- ...ogle_secret_manager_secret_version.go.tmpl | 179 ++++++++++++++++++ ...secret_manager_secret_version_test.go.tmpl | 86 +++++++++ ...ecret_manager_secret_version.html.markdown | 54 ++++++ 3 files changed, 319 insertions(+) create mode 100644 mmv1/third_party/terraform/services/secretmanager/list_google_secret_manager_secret_version.go.tmpl create mode 100644 mmv1/third_party/terraform/services/secretmanager/list_google_secret_manager_secret_version_test.go.tmpl create mode 100644 mmv1/third_party/terraform/website/docs/list-resources/google_secret_manager_secret_version.html.markdown diff --git a/mmv1/third_party/terraform/services/secretmanager/list_google_secret_manager_secret_version.go.tmpl b/mmv1/third_party/terraform/services/secretmanager/list_google_secret_manager_secret_version.go.tmpl new file mode 100644 index 000000000000..1cef2633a7e7 --- /dev/null +++ b/mmv1/third_party/terraform/services/secretmanager/list_google_secret_manager_secret_version.go.tmpl @@ -0,0 +1,179 @@ +{{- if ne $.TargetVersionName "ga" }} +// Copyright (c) IBM Corp. 2014, 2026 +// SPDX-License-Identifier: MPL-2.0 + +package secretmanager + +import ( + "context" + "errors" + "fmt" + "regexp" + + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/list" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + + "github.com/hashicorp/terraform-provider-google/google/registry" + "github.com/hashicorp/terraform-provider-google/google/tpgresource" + transport_tpg "github.com/hashicorp/terraform-provider-google/google/transport" +) + +func init() { + registry.FrameworkListResource{ + Name: "google_secret_manager_secret_version", + ProductName: "secretmanager", + Func: NewGoogleSecretManagerSecretVersionListResource, + }.Register() +} + +type GoogleSecretManagerSecretVersionListResource struct { + tpgresource.ListResourceMetadata +} + +type GoogleSecretManagerSecretVersionListModel struct { + Project types.String `tfsdk:"project"` + Secret types.String `tfsdk:"secret"` +} + +func NewGoogleSecretManagerSecretVersionListResource() list.ListResource { + listR := &GoogleSecretManagerSecretVersionListResource{} + listR.TypeName = "google_secret_manager_secret_version" + listR.SDKv2Resource = ResourceSecretManagerSecretVersion() + listR.ListConfigFields = []tpgresource.ListConfigField{ + {Name: "project", Kind: tpgresource.ListConfigKindString, Optional: true}, + {Name: "secret", Kind: tpgresource.ListConfigKindString, Optional: false}, + } + return listR +} + +func (listR *GoogleSecretManagerSecretVersionListResource) List(ctx context.Context, listReq list.ListRequest, stream *list.ListResultsStream) { + errStreamClosed := errors.New("stream closed") + + var data GoogleSecretManagerSecretVersionListModel + diags := listReq.Config.Get(ctx, &data) + if diags.HasError() { + stream.Results = list.ListResultsStreamDiagnostics(diags) + return + } + if listR.Client == nil { + diags = append(diags, diag.NewErrorDiagnostic( + "Provider not configured", + "The Google provider client is not available; ensure the provider is configured (e.g. credentials and default project).", + )) + stream.Results = list.ListResultsStreamDiagnostics(diags) + return + } + + project := listR.GetProject(data.Project) + secret := data.Secret.ValueString() + + stream.Results = func(push func(list.ListResult) bool) { + err := ListSecretManagerSecretVersions(listR.Client, project, secret, func(rd *schema.ResourceData) error { + result := listReq.NewListResult(ctx) + + if err := listR.SetResult(ctx, listReq.IncludeResource, &result, rd, "version"); err != nil { + return err + } + + if !push(result) { + return errStreamClosed + } + return nil + }) + if err == nil || errors.Is(err, errStreamClosed) { + return + } + diags.AddError("API Error", err.Error()) + result := listReq.NewListResult(ctx) + result.Diagnostics = diags + push(result) + } +} + +func ListSecretManagerSecretVersions(config *transport_tpg.Config, project, secret string, callback func(*schema.ResourceData) error) error { + if config == nil { + return fmt.Errorf("provider client is not configured") + } + + d := ResourceSecretManagerSecretVersion().Data(&terraform.InstanceState{}) + + if project != "" { + if err := d.Set("project", project); err != nil { + return fmt.Errorf("error setting project on temporary resource data: %w", err) + } + } + + // Normalize secret to full resource name + fullSecret := secret + if matched, _ := regexp.MatchString(`^projects/[^/]+/secrets/[^/]+$`, secret); !matched { + fullSecret = fmt.Sprintf("projects/%s/secrets/%s", project, secret) + } + if err := d.Set("secret", fullSecret); err != nil { + return fmt.Errorf("error setting secret on temporary resource data: %w", err) + } + + url := fmt.Sprintf("%sprojects/%s/secrets/%s/versions", transport_tpg.BaseUrl(Product, config), project, secret) + + billingProject := project + if bp, err := tpgresource.GetBillingProject(d, config); err == nil { + billingProject = bp + } + + userAgent, err := tpgresource.GenerateUserAgentString(d, config.UserAgent) + if err != nil { + return err + } + + secretRegex := regexp.MustCompile(`projects/(.+)/secrets/(.+)/versions/(.+)$`) + + return transport_tpg.ListPages(transport_tpg.ListPagesOptions{ + Config: config, + TempData: d, + Resource: ResourceSecretManagerSecretVersion(), + ListURL: url, + BillingProject: billingProject, + ItemName: "versions", + UserAgent: userAgent, + Flattener: func(res map[string]interface{}, d *schema.ResourceData, config *transport_tpg.Config) error { + name, ok := res["name"].(string) + if !ok || name == "" { + return fmt.Errorf("missing name in secret version list response") + } + + matches := secretRegex.FindStringSubmatch(name) + if matches == nil { + return fmt.Errorf("secret version name %q does not match expected format", name) + } + // matches[1]=project, matches[2]=secret, matches[3]=version + versionProject := matches[1] + versionSecret := matches[2] + versionNum := matches[3] + + if err := d.Set("project", versionProject); err != nil { + return err + } + if err := d.Set("secret", fmt.Sprintf("projects/%s/secrets/%s", versionProject, versionSecret)); err != nil { + return err + } + if err := d.Set("version", versionNum); err != nil { + return err + } + if err := d.Set("name", name); err != nil { + return err + } + if state, ok := res["state"].(string); ok { + if err := d.Set("enabled", state == "ENABLED"); err != nil { + return err + } + } + + d.SetId(name) + return nil + }, + Callback: callback, + }) +} +{{- end }} diff --git a/mmv1/third_party/terraform/services/secretmanager/list_google_secret_manager_secret_version_test.go.tmpl b/mmv1/third_party/terraform/services/secretmanager/list_google_secret_manager_secret_version_test.go.tmpl new file mode 100644 index 000000000000..c1f4fddfd3cb --- /dev/null +++ b/mmv1/third_party/terraform/services/secretmanager/list_google_secret_manager_secret_version_test.go.tmpl @@ -0,0 +1,86 @@ +{{- if ne $.TargetVersionName "ga" }} +package secretmanager_test + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/knownvalue" + "github.com/hashicorp/terraform-plugin-testing/querycheck" + "github.com/hashicorp/terraform-plugin-testing/tfversion" + + "github.com/hashicorp/terraform-provider-google/google/acctest" + "github.com/hashicorp/terraform-provider-google/google/envvar" + _ "github.com/hashicorp/terraform-provider-google/google/services/secretmanager" +) + +func TestAccSecretManagerSecretVersionListResource_basic(t *testing.T) { + t.Parallel() + + project := envvar.GetTestProjectFromEnv() + secretName := fmt.Sprintf("tf-test-secret-%s", acctest.RandString(t, 10)) + + acctest.VcrTest(t, resource.TestCase{ + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.SkipBelow(tfversion.Version1_14_0), + }, + PreCheck: func() { acctest.AccTestPreCheck(t) }, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), + Steps: []resource.TestStep{ + { + Config: testAccSecretManagerSecretVersionListResourceBasic(project, secretName), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("google_secret_manager_secret.test", "secret_id", secretName), + resource.TestCheckResourceAttrSet("google_secret_manager_secret_version.test", "version"), + ), + }, + { + Query: true, + Config: testAccSecretManagerSecretVersionListResourceQuery(project, secretName), + QueryResultChecks: []querycheck.QueryResultCheck{ + querycheck.ExpectLengthAtLeast("google_secret_manager_secret_version.all", 1), + querycheck.ExpectIdentity("google_secret_manager_secret_version.all", map[string]knownvalue.Check{ + "project": knownvalue.StringExact(project), + "secret": knownvalue.StringExact(fmt.Sprintf("projects/%s/secrets/%s", project, secretName)), + "version": knownvalue.NotNull(), + }), + }, + }, + }, + }) +} + +func testAccSecretManagerSecretVersionListResourceBasic(project, secretName string) string { + return fmt.Sprintf(` +resource "google_secret_manager_secret" "test" { + secret_id = %q + project = %q + + replication { + auto {} + } +} + +resource "google_secret_manager_secret_version" "test" { + secret = google_secret_manager_secret.test.id + secret_data = "my-secret-data" +} +`, secretName, project) +} + +func testAccSecretManagerSecretVersionListResourceQuery(project, secretName string) string { + return fmt.Sprintf(` +provider "google" {} + +list "google_secret_manager_secret_version" "all" { + provider = google + + config { + project = %q + secret = %q + } +} +`, project, secretName) +} +{{- end }} diff --git a/mmv1/third_party/terraform/website/docs/list-resources/google_secret_manager_secret_version.html.markdown b/mmv1/third_party/terraform/website/docs/list-resources/google_secret_manager_secret_version.html.markdown new file mode 100644 index 000000000000..08ca7a0d281e --- /dev/null +++ b/mmv1/third_party/terraform/website/docs/list-resources/google_secret_manager_secret_version.html.markdown @@ -0,0 +1,54 @@ +--- +subcategory: "Secret Manager" +description: |- + List Secret Manager secret versions for use with terraform query + and .tfquery.hcl files. +--- + +# google_secret_manager_secret_version (list) + +Lists Secret Manager secret versions for use with +[`terraform query`](https://developer.hashicorp.com/terraform/cli/commands/query) and +`.tfquery.hcl` files. Results correspond to existing +[`google_secret_manager_secret_version`](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/secret_manager_secret_version) +managed resources. + +For how list resources work in this provider, Terraform version requirements, and shared +`list` block arguments, refer to the guide +[Use list resources with terraform query (Google Cloud provider)](https://registry.terraform.io/providers/hashicorp/google/latest/docs/guides/using_list_resources_with_terraform_query). + +## Example + +```hcl +list "google_secret_manager_secret_version" "all" { + provider = google + + config { + # Optional. Defaults to the provider project when omitted. + # project = "my-project" + secret = "my-secret" + } +} +``` + +Run `terraform query` from the directory that contains the `.tfquery.hcl` file. + +## Configuration (`config` block) + +* `project` - (Optional) Project ID containing the secret. If unset, the provider project is used. + +* `secret` - (Required) Secret name or full resource name (`projects/{project}/secrets/{secret}`). + +## Results + +By default each result includes resource identity for `google_secret_manager_secret_version`: + +* `project` - Project ID. + +* `secret` - Full secret resource name. + +* `version` - Version number. + +With `include_resource = true` on the `list` block, results also include the full resource-style +attributes documented for the managed +[`google_secret_manager_secret_version` resource](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/secret_manager_secret_version#attributes-reference). From 7c688daef238ef1ecec201100312ec83b9fa5f10 Mon Sep 17 00:00:00 2001 From: "alvarez.mauriciotm@gmail.com" Date: Tue, 8 Sep 2026 12:00:49 -0700 Subject: [PATCH 3/7] fix: generate secret version list resource for GA --- .../list_google_secret_manager_secret_version.go.tmpl | 2 -- .../list_google_secret_manager_secret_version_test.go.tmpl | 2 -- 2 files changed, 4 deletions(-) diff --git a/mmv1/third_party/terraform/services/secretmanager/list_google_secret_manager_secret_version.go.tmpl b/mmv1/third_party/terraform/services/secretmanager/list_google_secret_manager_secret_version.go.tmpl index 1cef2633a7e7..fb0e9d77ee3c 100644 --- a/mmv1/third_party/terraform/services/secretmanager/list_google_secret_manager_secret_version.go.tmpl +++ b/mmv1/third_party/terraform/services/secretmanager/list_google_secret_manager_secret_version.go.tmpl @@ -1,4 +1,3 @@ -{{- if ne $.TargetVersionName "ga" }} // Copyright (c) IBM Corp. 2014, 2026 // SPDX-License-Identifier: MPL-2.0 @@ -176,4 +175,3 @@ func ListSecretManagerSecretVersions(config *transport_tpg.Config, project, secr Callback: callback, }) } -{{- end }} diff --git a/mmv1/third_party/terraform/services/secretmanager/list_google_secret_manager_secret_version_test.go.tmpl b/mmv1/third_party/terraform/services/secretmanager/list_google_secret_manager_secret_version_test.go.tmpl index c1f4fddfd3cb..72d0df27afbc 100644 --- a/mmv1/third_party/terraform/services/secretmanager/list_google_secret_manager_secret_version_test.go.tmpl +++ b/mmv1/third_party/terraform/services/secretmanager/list_google_secret_manager_secret_version_test.go.tmpl @@ -1,4 +1,3 @@ -{{- if ne $.TargetVersionName "ga" }} package secretmanager_test import ( @@ -83,4 +82,3 @@ list "google_secret_manager_secret_version" "all" { } `, project, secretName) } -{{- end }} From 4df64dc707f34a90cfd467516f3f5e6b67642f37 Mon Sep 17 00:00:00 2001 From: "alvarez.mauriciotm@gmail.com" Date: Tue, 8 Sep 2026 12:04:55 -0700 Subject: [PATCH 4/7] fix: rename static list resource files --- ...rsion.go.tmpl => list_google_secret_manager_secret_version.go} | 0 ....go.tmpl => list_google_secret_manager_secret_version_test.go} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename mmv1/third_party/terraform/services/secretmanager/{list_google_secret_manager_secret_version.go.tmpl => list_google_secret_manager_secret_version.go} (100%) rename mmv1/third_party/terraform/services/secretmanager/{list_google_secret_manager_secret_version_test.go.tmpl => list_google_secret_manager_secret_version_test.go} (100%) diff --git a/mmv1/third_party/terraform/services/secretmanager/list_google_secret_manager_secret_version.go.tmpl b/mmv1/third_party/terraform/services/secretmanager/list_google_secret_manager_secret_version.go similarity index 100% rename from mmv1/third_party/terraform/services/secretmanager/list_google_secret_manager_secret_version.go.tmpl rename to mmv1/third_party/terraform/services/secretmanager/list_google_secret_manager_secret_version.go diff --git a/mmv1/third_party/terraform/services/secretmanager/list_google_secret_manager_secret_version_test.go.tmpl b/mmv1/third_party/terraform/services/secretmanager/list_google_secret_manager_secret_version_test.go similarity index 100% rename from mmv1/third_party/terraform/services/secretmanager/list_google_secret_manager_secret_version_test.go.tmpl rename to mmv1/third_party/terraform/services/secretmanager/list_google_secret_manager_secret_version_test.go From 07fee4410c711e947b3a5d3ccaf547fc14434a4d Mon Sep 17 00:00:00 2001 From: "alvarez.mauriciotm@gmail.com" Date: Tue, 8 Sep 2026 12:22:25 -0700 Subject: [PATCH 5/7] fix: set identity on secret version list results --- .../list_google_secret_manager_secret_version.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/mmv1/third_party/terraform/services/secretmanager/list_google_secret_manager_secret_version.go b/mmv1/third_party/terraform/services/secretmanager/list_google_secret_manager_secret_version.go index fb0e9d77ee3c..38130718d3d1 100644 --- a/mmv1/third_party/terraform/services/secretmanager/list_google_secret_manager_secret_version.go +++ b/mmv1/third_party/terraform/services/secretmanager/list_google_secret_manager_secret_version.go @@ -163,6 +163,13 @@ func ListSecretManagerSecretVersions(config *transport_tpg.Config, project, secr if err := d.Set("name", name); err != nil { return err } + if err := tpgresource.SetResourceIdentityAttributes(d, map[string]interface{}{ + "project": versionProject, + "secret": fmt.Sprintf("projects/%s/secrets/%s", versionProject, versionSecret), + "version": versionNum, + }); err != nil { + return err + } if state, ok := res["state"].(string); ok { if err := d.Set("enabled", state == "ENABLED"); err != nil { return err From 26888c0dd8512de202c58c3b07adabd10196595e Mon Sep 17 00:00:00 2001 From: "alvarez.mauriciotm@gmail.com" Date: Wed, 9 Sep 2026 08:51:59 -0700 Subject: [PATCH 6/7] fix: preserve populated list resource identity --- .../terraform/tpgresource/list_resource.go | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/mmv1/third_party/terraform/tpgresource/list_resource.go b/mmv1/third_party/terraform/tpgresource/list_resource.go index 2dec34b042aa..7482ff3984a2 100644 --- a/mmv1/third_party/terraform/tpgresource/list_resource.go +++ b/mmv1/third_party/terraform/tpgresource/list_resource.go @@ -182,11 +182,23 @@ func SetResourceIdentityAttributes(d *schema.ResourceData, attrs map[string]inte // It panics if SDKv2Resource, Identity, or the identity schema is empty (wiring error). func (listR *ListResourceMetadata) setResourceIdentity(rd *schema.ResourceData) error { idSchema := listR.SDKv2Resource.Identity.SchemaMap() - attrs := make(map[string]interface{}, len(idSchema)) + identity, err := rd.Identity() + if err != nil { + return fmt.Errorf("error getting resource identity: %w", err) + } + if identity == nil { + return fmt.Errorf("resource identity is unavailable") + } + for attr := range idSchema { - attrs[attr] = rd.Get(attr) + if value, ok := identity.GetOk(attr); ok && value != "" { + continue + } + if err := identity.Set(attr, rd.Get(attr)); err != nil { + return fmt.Errorf("error setting resource identity field %q: %w", attr, err) + } } - return SetResourceIdentityAttributes(rd, attrs) + return nil } // ListResultDisplayName returns the first non-empty label from rd for keys in order. Use a From bed40b1b5f2449680f5971450e2d6486a187c917 Mon Sep 17 00:00:00 2001 From: "alvarez.mauriciotm@gmail.com" Date: Tue, 8 Sep 2026 16:42:54 -0700 Subject: [PATCH 7/7] fix: preserve identity in list results --- .../terraform/tpgresource/list_resource.go | 21 +++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/mmv1/third_party/terraform/tpgresource/list_resource.go b/mmv1/third_party/terraform/tpgresource/list_resource.go index 7482ff3984a2..bd76afb8b27b 100644 --- a/mmv1/third_party/terraform/tpgresource/list_resource.go +++ b/mmv1/third_party/terraform/tpgresource/list_resource.go @@ -228,14 +228,22 @@ func ListResultDisplayName(rd *schema.ResourceData, keys ...string) (string, err // via ListResultDisplayName when it is still empty; omit or pass no keys to skip. Non-empty keys // produce an error if no key yields a non-empty display label. func (listR *ListResourceMetadata) SetResult(ctx context.Context, includeResource bool, result *list.ListResult, rd *schema.ResourceData, displayNameKeys ...string) error { - if err := listR.setResourceIdentity(rd); err != nil { + identityRD := rd + identity, identityErr := rd.Identity() + if identityErr != nil || identity == nil { + identityRD = listR.SDKv2Resource.TestResourceData() + if err := listR.setResourceIdentityFrom(identityRD, rd); err != nil { + return err + } + } else if err := listR.setResourceIdentity(rd); err != nil { return err } - tfTypeIdentity, err := rd.TfTypeIdentityState() + tfTypeIdentity, err := identityRD.TfTypeIdentityState() if err != nil { return fmt.Errorf("error converting identity state: %w", err) } + if err := result.Identity.Set(ctx, *tfTypeIdentity); err != nil { return errors.New("error setting identity on list result") } @@ -260,3 +268,12 @@ func (listR *ListResourceMetadata) SetResult(ctx context.Context, includeResourc return nil } + +func (listR *ListResourceMetadata) setResourceIdentityFrom(identityRD, sourceRD *schema.ResourceData) error { + idSchema := listR.SDKv2Resource.Identity.SchemaMap() + attrs := make(map[string]interface{}, len(idSchema)) + for attr := range idSchema { + attrs[attr] = sourceRD.Get(attr) + } + return SetResourceIdentityAttributes(identityRD, attrs) +}