From 6aceb9727bfae18ba9cac4d714ac12e786a17f54 Mon Sep 17 00:00:00 2001 From: malhotrasagar2212 Date: Thu, 25 Jun 2026 11:47:58 -0400 Subject: [PATCH 1/9] Added option project field in ListCall Config --- .../resource_iam_member_list.go | 33 ++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/mmv1/third_party/terraform/tpgiamresource/resource_iam_member_list.go b/mmv1/third_party/terraform/tpgiamresource/resource_iam_member_list.go index 643dd2f700d5..f5c7a3414a60 100644 --- a/mmv1/third_party/terraform/tpgiamresource/resource_iam_member_list.go +++ b/mmv1/third_party/terraform/tpgiamresource/resource_iam_member_list.go @@ -38,6 +38,7 @@ type IamMemberListCallConfig struct { ParentResourceField string EnableRoleFilter bool EnableMemberFilter bool + EnableProjectField bool } // IamMemberListResource lists IAM member rows by reading IAM policies on one or more policy targets. @@ -81,9 +82,17 @@ func NewIamMemberListResource(typeName string, memberResource *schema.Resource, }) } + if listCallConfig.EnableProjectField { + listConfigFields = append(listConfigFields, tpgresource.ListConfigField{ + Name: "project", + Kind: tpgresource.ListConfigKindString, + Optional: true, + }) + } + iamResourceSchema := make(map[string]*schema.Schema) for _, field := range listConfigFields { - if field.Name == "role" || field.Name == "member" { + if field.Name == "role" || field.Name == "member" || field.Name == "project" { continue } schemaField, ok := memberResource.Schema[field.Name] @@ -155,6 +164,28 @@ func (r *IamMemberListResource) discoverPolicyTargets(ctx context.Context, req l } } + if r.listCallConfig.EnableProjectField { + var project types.String + diags := req.Config.GetAttribute(ctx, path.Root("project"), &project) + if diags.HasError() { + return nil, fmt.Errorf("%s", diags.Errors()[0].Detail()) + } + projectVal := "" + if !project.IsNull() && !project.IsUnknown() { + projectVal = project.ValueString() + } else if r.Client != nil { + projectVal = r.Client.Project + } + + if projectVal == "" { + return nil, fmt.Errorf("project must be set in the list config or configured on the provider") + } + + if err := baseRd.Set("project", projectVal); err != nil { + return nil, fmt.Errorf("setting project: %w", err) + } + } + if r.listCallConfig.ListPagesOptions.Callback == nil { return []*schema.ResourceData{baseRd}, nil } From 552aded011325fa36b170e73bd75d93f3be2e8f6 Mon Sep 17 00:00:00 2001 From: malhotrasagar2212 Date: Thu, 25 Jun 2026 14:46:10 -0400 Subject: [PATCH 2/9] Added ressource identity and list implementation --- .../pubsub/iam_pubsub_subscription.go.tmpl | 263 ++++++++++-------- 1 file changed, 152 insertions(+), 111 deletions(-) diff --git a/mmv1/third_party/terraform/services/pubsub/iam_pubsub_subscription.go.tmpl b/mmv1/third_party/terraform/services/pubsub/iam_pubsub_subscription.go.tmpl index 3b31867cc935..0249ff15f10e 100644 --- a/mmv1/third_party/terraform/services/pubsub/iam_pubsub_subscription.go.tmpl +++ b/mmv1/third_party/terraform/services/pubsub/iam_pubsub_subscription.go.tmpl @@ -1,161 +1,202 @@ package pubsub import ( - "fmt" - - "github.com/hashicorp/errwrap" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - {{- if ne $.Compiler "terraformgoogleconversion-codegen"}} - "github.com/hashicorp/terraform-provider-google/google/registry" - {{- end}} - "github.com/hashicorp/terraform-provider-google/google/tpgiamresource" - "github.com/hashicorp/terraform-provider-google/google/tpgresource" - transport_tpg "github.com/hashicorp/terraform-provider-google/google/transport" - "google.golang.org/api/cloudresourcemanager/v1" - "google.golang.org/api/pubsub/v1" + "fmt" + + "github.com/hashicorp/terraform-plugin-framework/list" + "github.com/hashicorp/errwrap" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + {{- if ne $.Compiler "terraformgoogleconversion-codegen"}} + "github.com/hashicorp/terraform-provider-google/google/registry" + {{- end}} + "github.com/hashicorp/terraform-provider-google/google/tpgiamresource" + "github.com/hashicorp/terraform-provider-google/google/tpgresource" + transport_tpg "github.com/hashicorp/terraform-provider-google/google/transport" + "google.golang.org/api/cloudresourcemanager/v1" + "google.golang.org/api/pubsub/v1" ) var IamPubsubSubscriptionSchema = map[string]*schema.Schema{ - "subscription": { - Type: schema.TypeString, - Required: true, - ForceNew: true, - DiffSuppressFunc: tpgresource.CompareSelfLinkOrResourceName, - }, - "project": { - Type: schema.TypeString, - Optional: true, - Computed: true, - ForceNew: true, - }, + "subscription": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + DiffSuppressFunc: tpgresource.CompareSelfLinkOrResourceName, + }, + "project": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + }, } type PubsubSubscriptionIamUpdater struct { - subscription string - d tpgresource.TerraformResourceData - Config *transport_tpg.Config + subscription string + d tpgresource.TerraformResourceData + Config *transport_tpg.Config } func NewPubsubSubscriptionIamUpdater(d tpgresource.TerraformResourceData, config *transport_tpg.Config) (tpgiamresource.ResourceIamUpdater, error) { - project, err := tpgresource.GetProject(d, config) - if err != nil { - return nil, err - } - - subscription := GetComputedSubscriptionName(project, d.Get("subscription").(string)) - - return &PubsubSubscriptionIamUpdater{ - subscription: subscription, - d: d, - Config: config, - }, nil + project, err := tpgresource.GetProject(d, config) + if err != nil { + return nil, err + } + + subscription := GetComputedSubscriptionName(project, d.Get("subscription").(string)) + + return &PubsubSubscriptionIamUpdater{ + subscription: subscription, + d: d, + Config: config, + }, nil } func PubsubSubscriptionIdParseFunc(d *schema.ResourceData, _ *transport_tpg.Config) error { - if err := d.Set("subscription", d.Id()); err != nil { - return fmt.Errorf("Error setting subscription: %s", err) - } - return nil + if err := d.Set("subscription", d.Id()); err != nil { + return fmt.Errorf("Error setting subscription: %s", err) + } + return nil } func (u *PubsubSubscriptionIamUpdater) GetResourceIamPolicy() (*cloudresourcemanager.Policy, error) { - userAgent, err := tpgresource.GenerateUserAgentString(u.d, u.Config.UserAgent) - if err != nil { - return nil, err - } + userAgent, err := tpgresource.GenerateUserAgentString(u.d, u.Config.UserAgent) + if err != nil { + return nil, err + } - p, err := NewClient(u.Config, userAgent).Projects.Subscriptions.GetIamPolicy(u.subscription).Do() + p, err := NewClient(u.Config, userAgent).Projects.Subscriptions.GetIamPolicy(u.subscription).Do() - if err != nil { - return nil, errwrap.Wrapf(fmt.Sprintf("Error retrieving IAM policy for %s: {{"{{"}}err{{"}}"}}", u.DescribeResource()), err) - } + if err != nil { + return nil, errwrap.Wrapf(fmt.Sprintf("Error retrieving IAM policy for %s: {{"{{"}}err{{"}}"}}", u.DescribeResource()), err) + } - v1Policy, err := pubsubToResourceManagerPolicy(p) - if err != nil { - return nil, err - } + v1Policy, err := pubsubToResourceManagerPolicy(p) + if err != nil { + return nil, err + } - return v1Policy, nil + return v1Policy, nil } func (u *PubsubSubscriptionIamUpdater) SetResourceIamPolicy(policy *cloudresourcemanager.Policy) error { - userAgent, err := tpgresource.GenerateUserAgentString(u.d, u.Config.UserAgent) - if err != nil { - return err - } + userAgent, err := tpgresource.GenerateUserAgentString(u.d, u.Config.UserAgent) + if err != nil { + return err + } - pubsubPolicy, err := resourceManagerToPubsubPolicy(policy) - if err != nil { - return err - } + pubsubPolicy, err := resourceManagerToPubsubPolicy(policy) + if err != nil { + return err + } - _, err = NewClient(u.Config, userAgent).Projects.Subscriptions.SetIamPolicy(u.subscription, &pubsub.SetIamPolicyRequest{ - Policy: pubsubPolicy, - }).Do() + _, err = NewClient(u.Config, userAgent).Projects.Subscriptions.SetIamPolicy(u.subscription, &pubsub.SetIamPolicyRequest{ + Policy: pubsubPolicy, + }).Do() - if err != nil { - return errwrap.Wrapf(fmt.Sprintf("Error setting IAM policy for %s: {{"{{"}}err{{"}}"}}", u.DescribeResource()), err) - } + if err != nil { + return errwrap.Wrapf(fmt.Sprintf("Error setting IAM policy for %s: {{"{{"}}err{{"}}"}}", u.DescribeResource()), err) + } - return nil + return nil } func (u *PubsubSubscriptionIamUpdater) GetResourceId() string { - return u.subscription + return u.subscription } func (u *PubsubSubscriptionIamUpdater) GetMutexKey() string { - return fmt.Sprintf("iam-pubsub-subscription-%s", u.subscription) + return fmt.Sprintf("iam-pubsub-subscription-%s", u.subscription) } func (u *PubsubSubscriptionIamUpdater) DescribeResource() string { - return fmt.Sprintf("pubsub subscription %q", u.subscription) + return fmt.Sprintf("pubsub subscription %q", u.subscription) } // v1 and v2 policy are identical func resourceManagerToPubsubPolicy(in *cloudresourcemanager.Policy) (*pubsub.Policy, error) { - out := &pubsub.Policy{} - err := tpgresource.Convert(in, out) - if err != nil { - return nil, errwrap.Wrapf("Cannot convert a v1 policy to a pubsub policy: {{"{{"}}err{{"}}"}}", err) - } - return out, nil + out := &pubsub.Policy{} + err := tpgresource.Convert(in, out) + if err != nil { + return nil, errwrap.Wrapf("Cannot convert a v1 policy to a pubsub policy: {{"{{"}}err{{"}}"}}", err) + } + return out, nil } func pubsubToResourceManagerPolicy(in *pubsub.Policy) (*cloudresourcemanager.Policy, error) { - out := &cloudresourcemanager.Policy{} - err := tpgresource.Convert(in, out) - if err != nil { - return nil, errwrap.Wrapf("Cannot convert a pubsub policy to a v1 policy: {{"{{"}}err{{"}}"}}", err) - } - return out, nil + out := &cloudresourcemanager.Policy{} + err := tpgresource.Convert(in, out) + if err != nil { + return nil, errwrap.Wrapf("Cannot convert a pubsub policy to a v1 policy: {{"{{"}}err{{"}}"}}", err) + } + return out, nil } + +// PubsubSubscriptionIamParentResourceIdentityParser resolves the parent topic id from import identity. +func PubsubSubscriptionIamParentResourceIdentityParser(d *schema.ResourceData, identity *schema.IdentityData, config *transport_tpg.Config) (string, error) { + return tpgiamresource.ParseIamResourceIdentity(d, identity, config, tpgiamresource.IamResourceIdentityConfig{ + Params: []tpgiamresource.IamIdentityParam{ + {Key: "project", IdentityKey: "project"}, + {Key: "subscription", IdentityKey: "subscription"}, + }, + UriFormat: "projects/%s/subscriptions/%s", + }) +} + +func PubsubSubscriptionIamMemberResource() *schema.Resource { + return tpgiamresource.ResourceIamMember( + IamPubsubSubscriptionSchema, + NewPubsubSubscriptionIamUpdater, + PubsubSubscriptionIdParseFunc, + tpgiamresource.IamWithParentResourceIdentity(PubsubSubscriptionIamParentResourceIdentityParser), + ) +} + +func NewPubsubSubscriptionIamMemberListResource() list.ListResource { + return tpgiamresource.NewIamMemberListResource( + "google_pubsub_subscription_iam_member", + PubsubSubscriptionIamMemberResource(), + NewPubsubSubscriptionIamUpdater, + tpgiamresource.IamMemberListCallConfig{ + ParentResourceField: "subscription", + EnableRoleFilter: true, + EnableMemberFilter: true, + EnableProjectField: true, + }, + ) +} + {{- if ne $.Compiler "terraformgoogleconversion-codegen"}} func init() { - registry.Schema{ - Name: "google_pubsub_subscription_iam_member", - ProductName: "pubsub", - Type: registry.SchemaTypeIAMResource, - Schema: tpgiamresource.ResourceIamMember(IamPubsubSubscriptionSchema, NewPubsubSubscriptionIamUpdater, PubsubSubscriptionIdParseFunc), - }.Register() - registry.Schema{ - Name: "google_pubsub_subscription_iam_binding", - ProductName: "pubsub", - Type: registry.SchemaTypeIAMResource, - Schema: tpgiamresource.ResourceIamBinding(IamPubsubSubscriptionSchema, NewPubsubSubscriptionIamUpdater, PubsubSubscriptionIdParseFunc), - }.Register() - registry.Schema{ - Name: "google_pubsub_subscription_iam_policy", - ProductName: "pubsub", - Type: registry.SchemaTypeIAMResource, - Schema: tpgiamresource.ResourceIamPolicy(IamPubsubSubscriptionSchema, NewPubsubSubscriptionIamUpdater, PubsubSubscriptionIdParseFunc), - }.Register() - registry.Schema{ - Name: "google_pubsub_subscription_iam_policy", - ProductName: "pubsub", - Type: registry.SchemaTypeIAMDataSource, - Schema: tpgiamresource.DataSourceIamPolicy(IamPubsubSubscriptionSchema, NewPubsubSubscriptionIamUpdater), - }.Register() + registry.Schema{ + Name: "google_pubsub_subscription_iam_member", + ProductName: "pubsub", + Type: registry.SchemaTypeIAMResource, + Schema: PubsubSubscriptionIamMemberResource(), + }.Register() + registry.FrameworkListResource{ + Name: "google_storage_bucket_iam_member", + ProductName: "pubsub", + Func: NewPubsubSubscriptionIamMemberListResource, + }.Register() + registry.Schema{ + Name: "google_pubsub_subscription_iam_binding", + ProductName: "pubsub", + Type: registry.SchemaTypeIAMResource, + Schema: tpgiamresource.ResourceIamBinding(IamPubsubSubscriptionSchema, NewPubsubSubscriptionIamUpdater, PubsubSubscriptionIdParseFunc), + }.Register() + registry.Schema{ + Name: "google_pubsub_subscription_iam_policy", + ProductName: "pubsub", + Type: registry.SchemaTypeIAMResource, + Schema: tpgiamresource.ResourceIamPolicy(IamPubsubSubscriptionSchema, NewPubsubSubscriptionIamUpdater, PubsubSubscriptionIdParseFunc), + }.Register() + registry.Schema{ + Name: "google_pubsub_subscription_iam_policy", + ProductName: "pubsub", + Type: registry.SchemaTypeIAMDataSource, + Schema: tpgiamresource.DataSourceIamPolicy(IamPubsubSubscriptionSchema, NewPubsubSubscriptionIamUpdater), + }.Register() } -{{- end}} +{{- end}} \ No newline at end of file From fa3afabd6a2ba56fa5fa73df8cd24c1e1b5bf415 Mon Sep 17 00:00:00 2001 From: malhotrasagar2212 Date: Thu, 25 Jun 2026 15:03:03 -0400 Subject: [PATCH 3/9] Added test --- ...t_google_pubsub_subscription_iam_member.go | 109 ++++++++++++++++++ 1 file changed, 109 insertions(+) create mode 100644 mmv1/third_party/terraform/services/pubsub/list_google_pubsub_subscription_iam_member.go diff --git a/mmv1/third_party/terraform/services/pubsub/list_google_pubsub_subscription_iam_member.go b/mmv1/third_party/terraform/services/pubsub/list_google_pubsub_subscription_iam_member.go new file mode 100644 index 000000000000..8bb67cdf4401 --- /dev/null +++ b/mmv1/third_party/terraform/services/pubsub/list_google_pubsub_subscription_iam_member.go @@ -0,0 +1,109 @@ +package pubsub_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" +) + +func TestAccPubsubSubscriptionIamMemberListResource_queryIdentity(t *testing.T) { + t.Parallel() + + project := envvar.GetTestProjectFromEnv() + suffix := acctest.RandString(t, 10) + topic := "tf-test-topic-" + suffix + subscription := "tf-test-sub-" + suffix + subscriptionId := fmt.Sprintf("projects/%s/subscriptions/%s", project, subscription) + account := "tf-test-pubsub-iam-" + suffix + role := "roles/pubsub.viewer" + member := "serviceAccount:" + envvar.ServiceAccountCanonicalEmail(account) + + fmt.Printf("\n[Expected------]\n") + fmt.Printf("project=%q\n", project) + fmt.Printf("subscription=%q\n", subscription) + fmt.Printf("role=%q\n", role) + fmt.Printf("member=%q\n", member) + fmt.Printf("condition_title=\n\n") + + acctest.VcrTest(t, resource.TestCase{ + PreCheck: func() { acctest.AccTestPreCheck(t) }, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.SkipBelow(tfversion.Version1_14_0), + }, + Steps: []resource.TestStep{ + { + Config: testAccPubsubSubscriptionIamMember(project, topic, account, subscription, role), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("google_pubsub_subscription_iam_member.test", "subscription", subscription), + resource.TestCheckResourceAttr("google_pubsub_subscription_iam_member.test", "role", role), + resource.TestCheckResourceAttr("google_pubsub_subscription_iam_member.test", "member", member), + ), + }, + { + Query: true, + Config: testAccPubsubSubscriptionIamMemberListQueryWithFilters(subscription, project, role, member), + QueryResultChecks: []querycheck.QueryResultCheck{ + querycheck.ExpectLength("google_pubsub_subscription_iam_member.test", 1), + querycheck.ExpectIdentity("google_pubsub_subscription_iam_member.test", map[string]knownvalue.Check{ + "subscription": knownvalue.StringExact(subscriptionId), + "role": knownvalue.StringExact(role), + "member": knownvalue.StringExact(member), + "project": knownvalue.StringExact(project), + "condition_title": knownvalue.Null(), + }), + }, + }, + }, + }) +} + +func testAccPubsubSubscriptionIamMember(project, topic, account, subscription, role string) string { + return fmt.Sprintf(` +resource "google_pubsub_topic" "topic" { + project = "%s" + name = "%s" +} + +resource "google_service_account" "test-account" { + project = "%s" + account_id = "%s" + display_name = "Pubsub subscription IAM Testing Account" +} + +resource "google_pubsub_subscription" "test-sub" { + project = "%s" + name = "%s" + topic = google_pubsub_topic.topic.id +} +resource "google_pubsub_subscription_iam_member" "test" { + project= "%s" + subscription = google_pubsub_subscription.test-sub.name + role = "%s" + member = "serviceAccount:${google_service_account.test-account.email}" +} +`, project, topic, project, account, project, subscription, project, role) +} + +func testAccPubsubSubscriptionIamMemberListQueryWithFilters(subscription, project, role, member string) string { + return fmt.Sprintf(` +list "google_pubsub_subscription_iam_member" "test" { + provider = google + include_resource = true + + config { + subscription = %q + project = %q + role = %q + member = %q + } +} +`, subscription, project, role, member) +} From e0a1ca452167a080ead438a6ac6a275bf4c429e3 Mon Sep 17 00:00:00 2001 From: malhotrasagar2212 Date: Thu, 25 Jun 2026 15:21:55 -0400 Subject: [PATCH 4/9] removed debug log code --- ...list_google_pubsub_subscription_iam_member_test.go} | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) rename mmv1/third_party/terraform/services/pubsub/{list_google_pubsub_subscription_iam_member.go => list_google_pubsub_subscription_iam_member_test.go} (89%) diff --git a/mmv1/third_party/terraform/services/pubsub/list_google_pubsub_subscription_iam_member.go b/mmv1/third_party/terraform/services/pubsub/list_google_pubsub_subscription_iam_member_test.go similarity index 89% rename from mmv1/third_party/terraform/services/pubsub/list_google_pubsub_subscription_iam_member.go rename to mmv1/third_party/terraform/services/pubsub/list_google_pubsub_subscription_iam_member_test.go index 8bb67cdf4401..7289dcf7df35 100644 --- a/mmv1/third_party/terraform/services/pubsub/list_google_pubsub_subscription_iam_member.go +++ b/mmv1/third_party/terraform/services/pubsub/list_google_pubsub_subscription_iam_member_test.go @@ -20,18 +20,10 @@ func TestAccPubsubSubscriptionIamMemberListResource_queryIdentity(t *testing.T) suffix := acctest.RandString(t, 10) topic := "tf-test-topic-" + suffix subscription := "tf-test-sub-" + suffix - subscriptionId := fmt.Sprintf("projects/%s/subscriptions/%s", project, subscription) account := "tf-test-pubsub-iam-" + suffix role := "roles/pubsub.viewer" member := "serviceAccount:" + envvar.ServiceAccountCanonicalEmail(account) - fmt.Printf("\n[Expected------]\n") - fmt.Printf("project=%q\n", project) - fmt.Printf("subscription=%q\n", subscription) - fmt.Printf("role=%q\n", role) - fmt.Printf("member=%q\n", member) - fmt.Printf("condition_title=\n\n") - acctest.VcrTest(t, resource.TestCase{ PreCheck: func() { acctest.AccTestPreCheck(t) }, ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), @@ -53,7 +45,7 @@ func TestAccPubsubSubscriptionIamMemberListResource_queryIdentity(t *testing.T) QueryResultChecks: []querycheck.QueryResultCheck{ querycheck.ExpectLength("google_pubsub_subscription_iam_member.test", 1), querycheck.ExpectIdentity("google_pubsub_subscription_iam_member.test", map[string]knownvalue.Check{ - "subscription": knownvalue.StringExact(subscriptionId), + "subscription": knownvalue.StringExact(subscription), "role": knownvalue.StringExact(role), "member": knownvalue.StringExact(member), "project": knownvalue.StringExact(project), From 56904238e7fc2f5052b637b89808d9839992ed86 Mon Sep 17 00:00:00 2001 From: malhotrasagar2212 Date: Thu, 25 Jun 2026 15:53:11 -0400 Subject: [PATCH 5/9] Added resource identity tests --- .../iam_pubsub_subscription_identity_test.go | 57 +++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 mmv1/third_party/terraform/services/pubsub/iam_pubsub_subscription_identity_test.go diff --git a/mmv1/third_party/terraform/services/pubsub/iam_pubsub_subscription_identity_test.go b/mmv1/third_party/terraform/services/pubsub/iam_pubsub_subscription_identity_test.go new file mode 100644 index 000000000000..6be4bef5ef91 --- /dev/null +++ b/mmv1/third_party/terraform/services/pubsub/iam_pubsub_subscription_identity_test.go @@ -0,0 +1,57 @@ +package pubsub + +import "testing" + +func TestPubsubSubscriptionIamMemberResource_HasIdentity(t *testing.T) { + resource := PubsubSubscriptionIamMemberResource() + if resource.Identity == nil { + t.Fatalf("expected google_pubsub_subscription_iam_member resource identity to be configured") + } +} + +func TestStorageBucketIamParentResourceIdentityParser(t *testing.T) { + tests := []struct { + name string + projectValue string + subscriptionValue string + want string + }{ + { + name: "short subscription name", + projectValue: "my-project", + subscriptionValue: "my-subscription", + want: "projects/my-project/subscriptions/my-subscription", + }, + { + name: "canonical subscription name", + projectValue: "my-project", + subscriptionValue: "projects/my-project/subscriptions/my-subscription", + want: "projects/my-project/subscriptions/my-subscription", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resource := PubsubSubscriptionIamMemberResource() + rd := resource.TestResourceData() + identity, err := rd.Identity() + if err != nil { + t.Fatalf("creating identity: %v", err) + } + if err := identity.Set("project", tt.projectValue); err != nil { + t.Fatalf("setting identity project: %v", err) + } + if err := identity.Set("subscription", tt.subscriptionValue); err != nil { + t.Fatalf("setting identity subscription: %v", err) + } + + got, err := PubsubSubscriptionIamParentResourceIdentityParser(rd, identity, nil) + if err != nil { + t.Fatalf("parsing identity: %v", err) + } + if got != tt.want { + t.Fatalf("unexpected parsed id: got %q, want %q", got, tt.want) + } + }) + } +} From cb0d1b31b2421afc824345956df634575e8be50c Mon Sep 17 00:00:00 2001 From: malhotrasagar2212 Date: Thu, 25 Jun 2026 16:11:05 -0400 Subject: [PATCH 6/9] Added resource identity documentation --- .../r/pubsub_subscription_iam.html.markdown | 26 ++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/mmv1/third_party/terraform/website/docs/r/pubsub_subscription_iam.html.markdown b/mmv1/third_party/terraform/website/docs/r/pubsub_subscription_iam.html.markdown index b28583049195..cc096b0cf92a 100644 --- a/mmv1/third_party/terraform/website/docs/r/pubsub_subscription_iam.html.markdown +++ b/mmv1/third_party/terraform/website/docs/r/pubsub_subscription_iam.html.markdown @@ -156,4 +156,28 @@ The [`terraform import` command](https://developer.hashicorp.com/terraform/cli/c ``` $ terraform import google_pubsub_subscription_iam_policy.default projects/{{project_id}}/subscriptions/{{subscription}} -``` \ No newline at end of file +``` + +#### Import via resource identity + +`google_pubsub_subscription_iam_member` also supports plannable import via [resource identity](https://developer.hashicorp.com/terraform/language/block/import#identity) (Terraform 1.12+): + +```tf +import { + to = google_pubsub_subscription_iam_member.viewer + identity = { + project = "my-project" + subscription = "my-subscription" + role = "roles/pubsub.viewer" + member = "user:jane@example.com" + } +} +``` + +Identity attributes: + +* `project` - (Required) The project ID the Pub/Sub belongs to. +* `subscription` - (Required) The Pub/Sub subscription name. Both `my-subscription` and `projects/my-project/subscriptions/my-subscription` formats are accepted. +* `role` - (Required) The IAM role being granted. +* `member` - (Required) The identity that the role is granted to. +* `condition_title` - (Optional) Title of the IAM condition, when importing a conditional binding. \ No newline at end of file From 7f2e3e6edea5b46b24e2979a332c199b9c9c00d9 Mon Sep 17 00:00:00 2001 From: malhotrasagar2212 Date: Thu, 25 Jun 2026 17:07:13 -0400 Subject: [PATCH 7/9] Added list documentation --- ...bsub_subscription_iam_member.html.markdown | 66 +++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 mmv1/third_party/terraform/website/docs/list-resources/google_pubsub_subscription_iam_member.html.markdown diff --git a/mmv1/third_party/terraform/website/docs/list-resources/google_pubsub_subscription_iam_member.html.markdown b/mmv1/third_party/terraform/website/docs/list-resources/google_pubsub_subscription_iam_member.html.markdown new file mode 100644 index 000000000000..12a880c140ce --- /dev/null +++ b/mmv1/third_party/terraform/website/docs/list-resources/google_pubsub_subscription_iam_member.html.markdown @@ -0,0 +1,66 @@ +--- +subcategory: "Cloud Pub/Sun" +description: |- + List IAM member bindings for a Pub/Sub subscription for use with terraform query + and .tfquery.hcl files. +--- + +# google_pubsub_subscription_iam_member (list) + +Lists IAM **member bindings** for a Pub/Sub subscription for use with +[`terraform query`](https://developer.hashicorp.com/terraform/cli/commands/query) and +**`.tfquery.hcl`** files. Results correspond to existing +[`google_pubsub_subscription_iam_member`](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/pubsub_subscription_iam) +managed resources. + +For how list resources work in this provider, file layout, 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_pubsub_subscription_iam_member" "all" { + provider = google + + config { + project = "my-project" + subscription = "my-subscription" + # role = "roles/pubsub.viewer" + # member = "userLjanem@example.com" + } +} +``` + +Run `terraform query` from the directory that contains the `.tfquery.hcl` file. + +## Configuration (`config` block) + +* `project` - (Optional) Project ID for the Pub/Sub subscription. If unset, the provider's + configured default project is used (same idea as the managed resource). + +* `subscription` - (Required) The Pub/sub subscription name to list IAm members from. + For example, `my-subscription`. + +* `role` - (Optional) If set, only bindings with this exact role are returned. + For example, `roles/pubsub.viewer`. If unset, bindings for all roles are returned. + +* `member` - (Optional) If set, only bindings where this principle is a member + are returned. For example, `user:jane@example.com`. If unset, bindings for + all roles are returned. + + +## Results + +By default each result includes **resource identity** for `google_pubsub_subscription_iam_member` (see +[Resource identity](https://developer.hashicorp.com/terraform/language/resources/identities)): + +* `project` - Project ID the Pub/Sub subscription belongs to. +* `subscription` - The Pub/Sub subscription name. +* `role` - The Iam role, e.g. `roles/pubsub.viewer`. +* `member` The principal, e.g. `user:jane@example.com`. + +With `include_resource = true` on the `list` block, results also include the full resource-style +attributes documented for the managed +[`google_pubsub_subscription_iam_member` resource](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/pubsub_subscription_iam#attributes-reference) +(for example `etag` where present in state). From 38d20ccbe52530d468126b2aa0e8aa2c0ded1552 Mon Sep 17 00:00:00 2001 From: malhotrasagar2212 Date: Thu, 25 Jun 2026 22:23:29 -0400 Subject: [PATCH 8/9] Fixed typo --- .../terraform/services/pubsub/iam_pubsub_subscription.go.tmpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mmv1/third_party/terraform/services/pubsub/iam_pubsub_subscription.go.tmpl b/mmv1/third_party/terraform/services/pubsub/iam_pubsub_subscription.go.tmpl index 0249ff15f10e..093aab88fc51 100644 --- a/mmv1/third_party/terraform/services/pubsub/iam_pubsub_subscription.go.tmpl +++ b/mmv1/third_party/terraform/services/pubsub/iam_pubsub_subscription.go.tmpl @@ -176,7 +176,7 @@ func init() { Schema: PubsubSubscriptionIamMemberResource(), }.Register() registry.FrameworkListResource{ - Name: "google_storage_bucket_iam_member", + Name: "google_pubsub_subscription_iam_member", ProductName: "pubsub", Func: NewPubsubSubscriptionIamMemberListResource, }.Register() From 3273563b4d8436f5c087e52c999426688727a0b0 Mon Sep 17 00:00:00 2001 From: malhotrasagar2212 Date: Thu, 25 Jun 2026 22:27:29 -0400 Subject: [PATCH 9/9] updated project to skip from iam list schema when absent --- .../terraform/tpgiamresource/resource_iam_member_list.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/mmv1/third_party/terraform/tpgiamresource/resource_iam_member_list.go b/mmv1/third_party/terraform/tpgiamresource/resource_iam_member_list.go index f5c7a3414a60..2b924cddf64f 100644 --- a/mmv1/third_party/terraform/tpgiamresource/resource_iam_member_list.go +++ b/mmv1/third_party/terraform/tpgiamresource/resource_iam_member_list.go @@ -92,11 +92,14 @@ func NewIamMemberListResource(typeName string, memberResource *schema.Resource, iamResourceSchema := make(map[string]*schema.Schema) for _, field := range listConfigFields { - if field.Name == "role" || field.Name == "member" || field.Name == "project" { + if field.Name == "role" || field.Name == "member" { continue } schemaField, ok := memberResource.Schema[field.Name] if !ok { + if field.Name == "project" { + continue + } panic(fmt.Sprintf("tpgiamresource: list config field %q not found in member resource schema", field.Name)) }