diff --git a/mmv1/third_party/terraform/services/storage/list_google_storage_bucket.go.tmpl b/mmv1/third_party/terraform/services/storage/list_google_storage_bucket.go.tmpl new file mode 100644 index 000000000000..138e158bad10 --- /dev/null +++ b/mmv1/third_party/terraform/services/storage/list_google_storage_bucket.go.tmpl @@ -0,0 +1,152 @@ +// Copyright (c) IBM Corp. 2014, 2026 +// SPDX-License-Identifier: MPL-2.0 + +package storage + +import ( + "context" + "errors" + "fmt" + + "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" + + "google.golang.org/api/storage/v1" + + "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_storage_bucket", + ProductName: "storage", + Func: NewGoogleStorageBucketListResource, + }.Register() +} + + +type GoogleStorageBucketListResource struct { + tpgresource.ListResourceMetadata +} + +type GoogleStorageBucketListModel struct { + Project types.String `tfsdk:"project"` +} + +func NewGoogleStorageBucketListResource() list.ListResource { + listR := &GoogleStorageBucketListResource{} + listR.TypeName = "google_storage_bucket" + listR.SDKv2Resource = ResourceStorageBucket() + listR.ListConfigFields = []tpgresource.ListConfigField{ + {Name: "project", Kind: tpgresource.ListConfigKindString, Optional: true}, + } + return listR +} + +func (listR *GoogleStorageBucketListResource) List(ctx context.Context, listReq list.ListRequest, stream *list.ListResultsStream) { + errStorageBucketListStreamClosed := errors.New("stream closed") + + var data GoogleStorageBucketListModel + 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) + + stream.Results = func(push func(list.ListResult) bool) { + err := ListStorageBuckets(listR.Client, project, func(rd *schema.ResourceData) error { + result := listReq.NewListResult(ctx) + + if err := listR.SetResult(ctx, listReq.IncludeResource, &result, rd, "name"); err != nil { + return err + } + + if !push(result) { + return errStorageBucketListStreamClosed + } + return nil + }) + if err != nil { + if errors.Is(err, errStorageBucketListStreamClosed) { + return + } + diags.AddError("API Error", err.Error()) + result := listReq.NewListResult(ctx) + result.Diagnostics = diags + push(result) + } + } +} + +func flattenStorageBucketListItem(res map[string]interface{}, d *schema.ResourceData, config *transport_tpg.Config) error { + var bucket storage.Bucket + if err := tpgresource.Convert(res, &bucket); err != nil { + return fmt.Errorf("error converting storage bucket list response: %w", err) + } + if bucket.Name == "" { + return fmt.Errorf("missing name in storage bucket list response") + } + if err := d.Set("name", bucket.Name); err != nil { + return err + } + userAgent, err := tpgresource.GenerateUserAgentString(d, config.UserAgent) + if err != nil { + return err + } + return setStorageBucket(d, config, &bucket, bucket.Name, userAgent) +} + +func ListStorageBuckets(config *transport_tpg.Config, project string, callback func(rd *schema.ResourceData) error) error { + if config == nil { + return fmt.Errorf("provider client is not configured") + } + d := ResourceStorageBucket().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) + } + } + url, err := tpgresource.ReplaceVars(d, config, "{{"{{"}}StorageBasePath{{"}}"}}b?project={{"{{"}}project{{"}}"}}") + if err != nil { + return err + } + + billingProject := "" + if bp, err := tpgresource.GetBillingProject(d, config); err == nil { + billingProject = bp + } + + userAgent, err := tpgresource.GenerateUserAgentString(d, config.UserAgent) + if err != nil { + return err + } + + return transport_tpg.ListPages(transport_tpg.ListPagesOptions{ + Config: config, + TempData: d, + Resource: ResourceStorageBucket(), + ListURL: url, + BillingProject: billingProject, + UserAgent: userAgent, + Flattener: flattenStorageBucketListItem, + Callback: callback, + }) +} + + + diff --git a/mmv1/third_party/terraform/services/storage/list_google_storage_bucket_test.go b/mmv1/third_party/terraform/services/storage/list_google_storage_bucket_test.go new file mode 100644 index 000000000000..dcdca0c6a387 --- /dev/null +++ b/mmv1/third_party/terraform/services/storage/list_google_storage_bucket_test.go @@ -0,0 +1,103 @@ +// Copyright (c) IBM Corp. 2014, 2026 +// SPDX-License-Identifier: MPL-2.0 + +package storage_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/querycheck/queryfilter" + "github.com/hashicorp/terraform-plugin-testing/tfjsonpath" + "github.com/hashicorp/terraform-plugin-testing/tfversion" + + "github.com/hashicorp/terraform-provider-google/google/acctest" + "github.com/hashicorp/terraform-provider-google/google/envvar" +) + +func TestAccStorageBucketListResource_queryIdentity(t *testing.T) { + t.Parallel() + + project := envvar.GetTestProjectFromEnv() + bucketName := "tf-test-" + 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: testAccStorageBucketBasic(bucketName, project), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("google_storage_bucket.test", "project", project), + resource.TestCheckResourceAttr("google_storage_bucket.test", "name", bucketName), + ), + }, + { + Query: true, + Config: testAccStorageBucketListQuery(project, true), + QueryResultChecks: []querycheck.QueryResultCheck{ + querycheck.ExpectIdentity("google_storage_bucket.all_in_project", map[string]knownvalue.Check{ + "name": knownvalue.StringExact(bucketName), + "project": knownvalue.StringExact(project), + }), + querycheck.ExpectLengthAtLeast("google_storage_bucket.all_in_project", 1), + querycheck.ExpectResourceKnownValues( + "google_storage_bucket.all_in_project", + queryfilter.ByDisplayName(knownvalue.StringExact(bucketName)), + []querycheck.KnownValueCheck{ + { + Path: tfjsonpath.New("name"), + KnownValue: knownvalue.StringExact(bucketName), + }, + { + Path: tfjsonpath.New("project"), + KnownValue: knownvalue.StringExact(project), + }, + { + Path: tfjsonpath.New("location"), + KnownValue: knownvalue.StringExact("US"), + }, + }, + ), + }, + }, + }, + }) +} + +func testAccStorageBucketBasic(name, project string) string { + return fmt.Sprintf(` +resource "google_storage_bucket" "test" { + name = %q + location = "US" + project = %q +} +`, name, project) +} + +func testAccStorageBucketListQuery(project string, includeResource bool) string { + includeResourceBlock := "" + if includeResource { + includeResourceBlock = " include_resource = true" + } + + return fmt.Sprintf(` +provider "google" {} + +list "google_storage_bucket" "all_in_project" { + provider = google +%s + limit = 1000 + + config { + project = %q + } +} +`, includeResourceBlock, project) +} diff --git a/mmv1/third_party/terraform/services/storage/resource_storage_bucket.go.tmpl b/mmv1/third_party/terraform/services/storage/resource_storage_bucket.go.tmpl index 07df37a4688a..5834b154cc3b 100644 --- a/mmv1/third_party/terraform/services/storage/resource_storage_bucket.go.tmpl +++ b/mmv1/third_party/terraform/services/storage/resource_storage_bucket.go.tmpl @@ -67,6 +67,22 @@ func ResourceStorageBucket() *schema.Resource { tpgresource.DefaultProviderDeletionPolicy("DELETE"), ), + Identity: &schema.ResourceIdentity{ + Version: 1, + SchemaFunc: func() map[string]*schema.Schema { + return map[string]*schema.Schema{ + "project": { + Type: schema.TypeString, + OptionalForImport: true, + }, + "name": { + Type: schema.TypeString, + RequiredForImport: true, + }, + } + }, + }, + Timeouts: &schema.ResourceTimeout{ Create: schema.DefaultTimeout(10 * time.Minute), Update: schema.DefaultTimeout(4 * time.Minute), @@ -1365,6 +1381,35 @@ func resourceStorageBucketDelete(d *schema.ResourceData, meta interface{}) error } func resourceStorageBucketStateImporter(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { + if d.Id() == "" { + identity, err := d.Identity() + if err != nil { + return nil, fmt.Errorf("Error reading import identity: %s", err) + } + if identity == nil { + return nil, fmt.Errorf("import requires bucket name") + } + + nameValue, ok := identity.GetOk("name") + if !ok || nameValue.(string) == "" { + return nil, fmt.Errorf("import requires bucket name") + } + + name := nameValue.(string) + if err := d.Set("name", name); err != nil { + return nil, fmt.Errorf("Error setting name: %s", err) + } + + if projectValue, ok := identity.GetOk("project"); ok && projectValue.(string) != "" { + if err := d.Set("project", projectValue.(string)); err != nil { + return nil, fmt.Errorf("Error setting project: %s", err) + } + d.SetId(fmt.Sprintf("%s/%s", projectValue.(string), name)) + } else { + d.SetId(name) + } + } + // We need to support project/bucket_name and bucket_name formats. This will allow // importing a bucket that is in a different project than the provider default. // ParseImportID can't be used because having no project will cause an error but it @@ -2459,12 +2504,12 @@ func setStorageBucket(d *schema.ResourceData, config *transport_tpg.Config, res if err := tpgresource.SetLabels(res.Labels, d, "labels"); err != nil { return fmt.Errorf("Error setting labels: %s", err) } - if err := tpgresource.SetLabels(res.Labels, d, "terraform_labels"); err != nil { - return fmt.Errorf("Error setting terraform_labels: %s", err) - } if err := d.Set("effective_labels", res.Labels); err != nil { return fmt.Errorf("Error setting labels: %s", err) } + if err := tpgresource.SetLabels(res.Labels, d, "terraform_labels"); err != nil { + return fmt.Errorf("Error setting terraform_labels: %s", err) + } if err := d.Set("website", flattenBucketWebsite(res.Website)); err != nil { return fmt.Errorf("Error setting website: %s", err) } @@ -2529,7 +2574,11 @@ func setStorageBucket(d *schema.ResourceData, config *transport_tpg.Config, res d.SetId(res.Id) - return nil + + return tpgresource.SetResourceIdentityAttributes(d, map[string]interface{}{ + "name": res.Name, + "project": d.Get("project").(string), +}) } func hierachicalNamespaceDiffSuppress(k, old, new string, r *schema.ResourceData) bool { diff --git a/mmv1/third_party/terraform/services/storage/resource_storage_bucket_test.go b/mmv1/third_party/terraform/services/storage/resource_storage_bucket_test.go index dbacfc98a85d..77ca9a934f76 100644 --- a/mmv1/third_party/terraform/services/storage/resource_storage_bucket_test.go +++ b/mmv1/third_party/terraform/services/storage/resource_storage_bucket_test.go @@ -13,6 +13,7 @@ import ( "github.com/hashicorp/terraform-plugin-testing/helper/resource" "github.com/hashicorp/terraform-plugin-testing/plancheck" "github.com/hashicorp/terraform-plugin-testing/terraform" + "github.com/hashicorp/terraform-plugin-testing/tfversion" "github.com/hashicorp/terraform-provider-google/google/acctest" "github.com/hashicorp/terraform-provider-google/google/envvar" @@ -67,6 +68,39 @@ func TestAccStorageBucket_basic(t *testing.T) { }) } +func TestAccStorageBucket_importBlockWithResourceIdentity(t *testing.T) { + t.Parallel() + + bucketName := acctest.TestBucketName(t) + project := envvar.GetTestProjectFromEnv() + + acctest.VcrTest(t, resource.TestCase{ + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.SkipBelow(tfversion.Version1_12_0), + }, + PreCheck: func() { acctest.AccTestPreCheck(t) }, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), + CheckDestroy: testAccStorageBucketDestroyProducer(t), + Steps: []resource.TestStep{ + { + Config: testAccStorageBucket_basic(bucketName), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr( + "google_storage_bucket.bucket", "project", project), + resource.TestCheckResourceAttr( + "google_storage_bucket.bucket", "name", bucketName), + ), + }, + { + ResourceName: "google_storage_bucket.bucket", + RefreshState: true, + ExpectNonEmptyPlan: true, + ImportStateKind: resource.ImportBlockWithResourceIdentity, + }, + }, + }) +} + func TestAccStorageBucket_basicWithAutoclass(t *testing.T) { t.Parallel() diff --git a/mmv1/third_party/terraform/website/docs/list-resources/google_storage_bucket.html.markdown b/mmv1/third_party/terraform/website/docs/list-resources/google_storage_bucket.html.markdown new file mode 100644 index 000000000000..45a55906e5b2 --- /dev/null +++ b/mmv1/third_party/terraform/website/docs/list-resources/google_storage_bucket.html.markdown @@ -0,0 +1,50 @@ +--- +subcategory: "Cloud Storage" +description: |- + List Google Cloud Storage buckets in a project for use with terraform query + and .tfquery.hcl files. +--- + +# google_storage_bucket (list) + +Lists Google Cloud Storage **buckets** in a project for use with +[`terraform query`](https://developer.hashicorp.com/terraform/cli/commands/query) and +**`.tfquery.hcl`** files. Results correspond to existing +[`google_storage_bucket`](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/storage_bucket) +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_storage_bucket" "all" { + provider = google + + config { + # Optional. Defaults to the provider project when omitted. + # project = "other-project" + } +} +``` + +Run `terraform query` from the directory that contains the `.tfquery.hcl` file. + +## Configuration (`config` block) + +* `project` - (Optional) Project ID to list buckets from. If unset, the provider's configured + default project is used, matching the managed resource behavior. + +## Results + +By default each result includes **resource identity** for `google_storage_bucket` (see +[Resource identity](https://developer.hashicorp.com/terraform/language/block/import#identity)): + +* `name` - Bucket name. +* `project` - Project ID. + +With `include_resource = true` on the `list` block, results also include the full resource-style +attributes documented for the managed +[`google_storage_bucket` resource](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/storage_bucket#attributes-reference). \ No newline at end of file diff --git a/mmv1/third_party/terraform/website/docs/r/storage_bucket.html.markdown b/mmv1/third_party/terraform/website/docs/r/storage_bucket.html.markdown index b1fab6bc9651..43f9fce19819 100644 --- a/mmv1/third_party/terraform/website/docs/r/storage_bucket.html.markdown +++ b/mmv1/third_party/terraform/website/docs/r/storage_bucket.html.markdown @@ -453,6 +453,18 @@ import { } ``` +In Terraform v1.12.0 and later, use an [`identity` block](https://developer.hashicorp.com/terraform/language/block/import#identity) to import Storage buckets using identity values. For example: + +```tf +import { + identity = { + project = "{{project_id}}" + name = "{{bucket}}" + } + to = google_storage_bucket.default +} +``` + When using the [`terraform import` command](https://developer.hashicorp.com/terraform/cli/commands/import), Storage buckets can be imported using one of the formats above. For example: ```