Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions mmv1/templates/terraform/list_resource_method.go.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
// 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 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
}
}

d.SetId(name)
return nil
},
Callback: callback,
})
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
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)
}
39 changes: 34 additions & 5 deletions mmv1/third_party/terraform/tpgresource/list_resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -216,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")
}
Expand All @@ -248,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)
}
Loading
Loading