From 4fdbe6da98f86e256846e1898aa260a734f9ec52 Mon Sep 17 00:00:00 2001
From: abheda-crest <105624942+abheda-crest@users.noreply.github.com>
Date: Thu, 19 Sep 2024 21:57:35 +0530
Subject: [PATCH] Add support for regional secrets list datasource
`google_secret_manager_regional_secrets` (#11743)
---
.../provider/provider_mmv1_resources.go.erb | 1 +
..._source_secret_manager_regional_secrets.go | 176 ++++++++++++
...ce_secret_manager_regional_secrets_test.go | 257 ++++++++++++++++++
...ret_manager_regional_secrets.html.markdown | 82 ++++++
4 files changed, 516 insertions(+)
create mode 100644 mmv1/third_party/terraform/services/secretmanagerregional/data_source_secret_manager_regional_secrets.go
create mode 100644 mmv1/third_party/terraform/services/secretmanagerregional/data_source_secret_manager_regional_secrets_test.go
create mode 100644 mmv1/third_party/terraform/website/docs/d/secret_manager_regional_secrets.html.markdown
diff --git a/mmv1/third_party/terraform/provider/provider_mmv1_resources.go.erb b/mmv1/third_party/terraform/provider/provider_mmv1_resources.go.erb
index 847cc147f54d..e0031d4aa923 100644
--- a/mmv1/third_party/terraform/provider/provider_mmv1_resources.go.erb
+++ b/mmv1/third_party/terraform/provider/provider_mmv1_resources.go.erb
@@ -182,6 +182,7 @@ var handwrittenDatasources = map[string]*schema.Resource{
<% end -%>
"google_secret_manager_regional_secret_version": secretmanagerregional.DataSourceSecretManagerRegionalRegionalSecretVersion(),
"google_secret_manager_regional_secret": secretmanagerregional.DataSourceSecretManagerRegionalRegionalSecret(),
+ "google_secret_manager_regional_secrets": secretmanagerregional.DataSourceSecretManagerRegionalRegionalSecrets(),
"google_secret_manager_secret": secretmanager.DataSourceSecretManagerSecret(),
"google_secret_manager_secrets": secretmanager.DataSourceSecretManagerSecrets(),
"google_secret_manager_secret_version": secretmanager.DataSourceSecretManagerSecretVersion(),
diff --git a/mmv1/third_party/terraform/services/secretmanagerregional/data_source_secret_manager_regional_secrets.go b/mmv1/third_party/terraform/services/secretmanagerregional/data_source_secret_manager_regional_secrets.go
new file mode 100644
index 000000000000..b2cda48defee
--- /dev/null
+++ b/mmv1/third_party/terraform/services/secretmanagerregional/data_source_secret_manager_regional_secrets.go
@@ -0,0 +1,176 @@
+package secretmanagerregional
+
+import (
+ "fmt"
+ "strings"
+
+ "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
+ "github.com/hashicorp/terraform-provider-google/google/tpgresource"
+ transport_tpg "github.com/hashicorp/terraform-provider-google/google/transport"
+)
+
+func DataSourceSecretManagerRegionalRegionalSecrets() *schema.Resource {
+ dsSchema := tpgresource.DatasourceSchemaFromResourceSchema(ResourceSecretManagerRegionalRegionalSecret().Schema)
+ return &schema.Resource{
+ Read: dataSourceSecretManagerRegionalRegionalSecretsRead,
+ Schema: map[string]*schema.Schema{
+ "project": {
+ Type: schema.TypeString,
+ Optional: true,
+ Computed: true,
+ },
+ "location": {
+ Type: schema.TypeString,
+ Required: true,
+ },
+ "filter": {
+ Type: schema.TypeString,
+ Description: `Filter string, adhering to the rules in List-operation filtering (https://cloud.google.com/secret-manager/docs/filtering).
+List only secrets matching the filter. If filter is empty, all regional secrets are listed from the specified location.`,
+ Optional: true,
+ },
+ "secrets": {
+ Type: schema.TypeList,
+ Computed: true,
+ Elem: &schema.Resource{
+ Schema: dsSchema,
+ },
+ },
+ },
+ }
+}
+
+func dataSourceSecretManagerRegionalRegionalSecretsRead(d *schema.ResourceData, meta interface{}) error {
+ config := meta.(*transport_tpg.Config)
+ userAgent, err := tpgresource.GenerateUserAgentString(d, config.UserAgent)
+ if err != nil {
+ return err
+ }
+
+ url, err := tpgresource.ReplaceVars(d, config, "{{SecretManagerRegionalBasePath}}projects/{{project}}/locations/{{location}}/secrets")
+ if err != nil {
+ return err
+ }
+
+ filter, has_filter := d.GetOk("filter")
+
+ if has_filter {
+ url, err = transport_tpg.AddQueryParams(url, map[string]string{"filter": filter.(string)})
+ if err != nil {
+ return err
+ }
+ }
+
+ billingProject := ""
+
+ project, err := tpgresource.GetProject(d, config)
+ if err != nil {
+ return fmt.Errorf("Error fetching project for Secret: %s", err)
+ }
+ billingProject = project
+
+ // err == nil indicates that the billing_project value was found
+ if bp, err := tpgresource.GetBillingProject(d, config); err == nil {
+ billingProject = bp
+ }
+
+ // To handle pagination locally
+ allSecrets := make([]interface{}, 0)
+ token := ""
+ for paginate := true; paginate; {
+ if token != "" {
+ url, err = transport_tpg.AddQueryParams(url, map[string]string{"pageToken": token})
+ if err != nil {
+ return err
+ }
+ }
+ secrets, err := transport_tpg.SendRequest(transport_tpg.SendRequestOptions{
+ Config: config,
+ Method: "GET",
+ Project: billingProject,
+ RawURL: url,
+ UserAgent: userAgent,
+ })
+ if err != nil {
+ return transport_tpg.HandleNotFoundError(err, d, fmt.Sprintf("SecretManagerRegionalSecrets %q", d.Id()))
+ }
+ secretsInterface := secrets["secrets"]
+ if secretsInterface == nil {
+ break
+ }
+ allSecrets = append(allSecrets, secretsInterface.([]interface{})...)
+ tokenInterface := secrets["nextPageToken"]
+ if tokenInterface == nil {
+ paginate = false
+ } else {
+ paginate = true
+ token = tokenInterface.(string)
+ }
+ }
+
+ if err := d.Set("project", project); err != nil {
+ return fmt.Errorf("error setting project: %s", err)
+ }
+
+ if err := d.Set("filter", filter); err != nil {
+ return fmt.Errorf("error setting filter: %s", err)
+ }
+
+ if err := d.Set("secrets", flattenSecretManagerRegionalRegionalSecretsSecrets(allSecrets, d, config)); err != nil {
+ return fmt.Errorf("error setting secrets: %s", err)
+ }
+
+ // Store the ID now
+ id, err := tpgresource.ReplaceVars(d, config, "projects/{{project}}/locations/{{location}}/secrets")
+ if err != nil {
+ return fmt.Errorf("Error constructing id: %s", err)
+ }
+ if has_filter {
+ id += "/filter=" + filter.(string)
+ }
+ d.SetId(id)
+
+ return nil
+}
+
+func flattenSecretManagerRegionalRegionalSecretsSecrets(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} {
+ if v == nil {
+ return v
+ }
+ l := v.([]interface{})
+ transformed := make([]interface{}, 0, len(l))
+
+ for _, raw := range l {
+ original := raw.(map[string]interface{})
+ if len(original) < 1 {
+ // Do not include empty json objects coming back from the api
+ continue
+ }
+
+ transformed = append(transformed, map[string]interface{}{
+ "annotations": flattenSecretManagerRegionalRegionalSecretEffectiveAnnotations(original["annotations"], d, config),
+ "effective_annotations": flattenSecretManagerRegionalRegionalSecretEffectiveAnnotations(original["annotations"], d, config),
+ "expire_time": flattenSecretManagerRegionalRegionalSecretExpireTime(original["expireTime"], d, config),
+ "labels": flattenSecretManagerRegionalRegionalSecretEffectiveLabels(original["labels"], d, config),
+ "effective_labels": flattenSecretManagerRegionalRegionalSecretEffectiveLabels(original["labels"], d, config),
+ "terraform_labels": flattenSecretManagerRegionalRegionalSecretEffectiveLabels(original["labels"], d, config),
+ "version_aliases": flattenSecretManagerRegionalRegionalSecretVersionAliases(original["versionAliases"], d, config),
+ "rotation": flattenSecretManagerRegionalRegionalSecretRotation(original["rotation"], d, config),
+ "topics": flattenSecretManagerRegionalRegionalSecretTopics(original["topics"], d, config),
+ "version_destroy_ttl": flattenSecretManagerRegionalRegionalSecretVersionDestroyTtl(original["versionDestroyTtl"], d, config),
+ "customer_managed_encryption": flattenSecretManagerRegionalRegionalSecretCustomerManagedEncryption(original["customerManagedEncryption"], d, config),
+ "create_time": flattenSecretManagerRegionalRegionalSecretCreateTime(original["createTime"], d, config),
+ "name": flattenSecretManagerRegionalRegionalSecretName(original["name"], d, config),
+ "project": getDataFromName(original["name"], 1),
+ "location": getDataFromName(original["name"], 3),
+ "secret_id": getDataFromName(original["name"], 5),
+ })
+ }
+ return transformed
+}
+
+func getDataFromName(v interface{}, part int) string {
+ name := v.(string)
+ split := strings.Split(name, "/")
+ return split[part]
+}
diff --git a/mmv1/third_party/terraform/services/secretmanagerregional/data_source_secret_manager_regional_secrets_test.go b/mmv1/third_party/terraform/services/secretmanagerregional/data_source_secret_manager_regional_secrets_test.go
new file mode 100644
index 000000000000..a3ed4127b7ac
--- /dev/null
+++ b/mmv1/third_party/terraform/services/secretmanagerregional/data_source_secret_manager_regional_secrets_test.go
@@ -0,0 +1,257 @@
+package secretmanagerregional_test
+
+import (
+ "errors"
+ "fmt"
+ "strconv"
+
+ "testing"
+
+ "github.com/hashicorp/terraform-plugin-testing/helper/resource"
+ "github.com/hashicorp/terraform-plugin-testing/terraform"
+ "github.com/hashicorp/terraform-provider-google/google/acctest"
+)
+
+func TestAccDataSourceSecretManagerRegionalRegionalSecrets_basic(t *testing.T) {
+ t.Parallel()
+
+ context := map[string]interface{}{
+ "random_suffix": acctest.RandString(t, 10),
+ }
+
+ acctest.VcrTest(t, resource.TestCase{
+ PreCheck: func() { acctest.AccTestPreCheck(t) },
+ ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t),
+ CheckDestroy: testAccCheckSecretManagerRegionalRegionalSecretDestroyProducer(t),
+ Steps: []resource.TestStep{
+ {
+ Config: testAccDataSourceSecretManagerRegionalRegionalSecrets_basic(context),
+ Check: resource.ComposeTestCheckFunc(
+ checkListDataSourceStateMatchesResourceStateWithIgnores(
+ "data.google_secret_manager_regional_secrets.foo",
+ "google_secret_manager_regional_secret.foo",
+ map[string]struct{}{
+ "id": {},
+ "project": {},
+ },
+ ),
+ ),
+ },
+ },
+ })
+}
+
+func testAccDataSourceSecretManagerRegionalRegionalSecrets_basic(context map[string]interface{}) string {
+ return acctest.Nprintf(`
+provider "google" {
+ add_terraform_attribution_label = false
+}
+
+resource "google_secret_manager_regional_secret" "foo" {
+ secret_id = "tf-test-secret-%{random_suffix}"
+ location = "us-central1"
+
+ labels = {
+ label = "my-label"
+ }
+
+ annotations = {
+ key1 = "value1"
+ }
+}
+
+data "google_secret_manager_regional_secrets" "foo" {
+ location = "us-central1"
+ depends_on = [
+ google_secret_manager_regional_secret.foo
+ ]
+}
+`, context)
+}
+
+func TestAccDataSourceSecretManagerRegionalRegionalSecrets_filter(t *testing.T) {
+ t.Parallel()
+
+ context := map[string]interface{}{
+ "random_suffix": acctest.RandString(t, 10),
+ }
+
+ acctest.VcrTest(t, resource.TestCase{
+ PreCheck: func() { acctest.AccTestPreCheck(t) },
+ ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t),
+ CheckDestroy: testAccCheckSecretManagerRegionalRegionalSecretDestroyProducer(t),
+ Steps: []resource.TestStep{
+ {
+ Config: testAccDataSourceSecretManagerRegionalRegionalSecrets_filter(context),
+ Check: resource.ComposeTestCheckFunc(
+ checkListDataSourceStateMatchesResourceStateWithIgnoresForAppliedFilter(
+ "data.google_secret_manager_regional_secrets.foo",
+ "google_secret_manager_regional_secret.foo",
+ "google_secret_manager_regional_secret.bar",
+ map[string]struct{}{
+ "id": {},
+ "project": {},
+ },
+ ),
+ ),
+ },
+ },
+ })
+}
+
+func testAccDataSourceSecretManagerRegionalRegionalSecrets_filter(context map[string]interface{}) string {
+ return acctest.Nprintf(`
+provider "google" {
+ add_terraform_attribution_label = false
+}
+
+resource "google_secret_manager_regional_secret" "foo" {
+ secret_id = "tf-test-secret-1-%{random_suffix}"
+ location = "us-central1"
+
+ labels = {
+ label = "my-label1"
+ }
+
+ annotations = {
+ key1 = "value1"
+ }
+}
+
+resource "google_secret_manager_regional_secret" "bar" {
+ secret_id = "tf-test-secret-2-%{random_suffix}"
+ location = "us-central1"
+
+ labels = {
+ label= "my-label2"
+ }
+
+ annotations = {
+ key1 = "value1"
+ }
+}
+
+data "google_secret_manager_regional_secrets" "foo" {
+ location = "us-central1"
+ filter = "labels.label=my-label1"
+ depends_on = [
+ google_secret_manager_regional_secret.foo,
+ google_secret_manager_regional_secret.bar
+ ]
+}
+`, context)
+}
+
+// This function checks data source state matches for resourceName secret manager regional secret state
+func checkListDataSourceStateMatchesResourceStateWithIgnores(dataSourceName, resourceName string, ignoreFields map[string]struct{}) func(*terraform.State) error {
+ return func(s *terraform.State) error {
+ ds, ok := s.RootModule().Resources[dataSourceName]
+ if !ok {
+ return fmt.Errorf("can't find %s in state", dataSourceName)
+ }
+
+ rs, ok := s.RootModule().Resources[resourceName]
+ if !ok {
+ return fmt.Errorf("can't find %s in state", resourceName)
+ }
+
+ dsAttr := ds.Primary.Attributes
+ rsAttr := rs.Primary.Attributes
+
+ err := checkFieldsMatchForDataSourceStateAndResourceState(dsAttr, rsAttr, ignoreFields)
+ if err != nil {
+ return err
+ }
+ return nil
+ }
+}
+
+// This function checks whether all the attributes of the secret manager secret resource and the attributes of the secret manager secret inside the data source list are the same
+func checkFieldsMatchForDataSourceStateAndResourceState(dsAttr, rsAttr map[string]string, ignoreFields map[string]struct{}) error {
+ totalSecrets, err := strconv.Atoi(dsAttr["secrets.#"])
+ if err != nil {
+ return errors.New("Couldn't convert length of secrets list to integer")
+ }
+ index := "-1"
+ for i := 0; i < totalSecrets; i++ {
+ if dsAttr["secrets."+strconv.Itoa(i)+".name"] == rsAttr["name"] {
+ index = strconv.Itoa(i)
+ }
+ }
+
+ if index == "-1" {
+ return errors.New("The newly created secret is not found in the data source")
+ }
+
+ errMsg := ""
+ // Data sources are often derived from resources, so iterate over the resource fields to
+ // make sure all fields are accounted for in the data source.
+ // If a field exists in the data source but not in the resource, its expected value should
+ // be checked separately.
+ for k := range rsAttr {
+ if _, ok := ignoreFields[k]; ok {
+ continue
+ }
+ if k == "%" {
+ continue
+ }
+ if dsAttr["secrets."+index+"."+k] != rsAttr[k] {
+ // ignore data sources where an empty list is being compared against a null list.
+ if k[len(k)-1:] == "#" && (dsAttr["secrets."+index+"."+k] == "" || dsAttr["secrets."+index+"."+k] == "0") && (rsAttr[k] == "" || rsAttr[k] == "0") {
+ continue
+ }
+ errMsg += fmt.Sprintf("%s is %s; want %s\n", k, dsAttr["secrets."+index+"."+k], rsAttr[k])
+ }
+ }
+
+ if errMsg != "" {
+ return errors.New(errMsg)
+ }
+
+ return nil
+}
+
+// This function checks state match for resourceName and asserts the absence of resourceName2 in data source
+func checkListDataSourceStateMatchesResourceStateWithIgnoresForAppliedFilter(dataSourceName, resourceName, resourceName2 string, ignoreFields map[string]struct{}) func(*terraform.State) error {
+ return func(s *terraform.State) error {
+ ds, ok := s.RootModule().Resources[dataSourceName]
+ if !ok {
+ return fmt.Errorf("can't find %s in state", dataSourceName)
+ }
+
+ rs, ok := s.RootModule().Resources[resourceName]
+ if !ok {
+ return fmt.Errorf("can't find %s in state", resourceName)
+ }
+
+ rs2, ok := s.RootModule().Resources[resourceName2]
+ if !ok {
+ return fmt.Errorf("can't find %s in state", resourceName2)
+ }
+
+ dsAttr := ds.Primary.Attributes
+ rsAttr := rs.Primary.Attributes
+ rsAttr2 := rs2.Primary.Attributes
+
+ err := checkFieldsMatchForDataSourceStateAndResourceState(dsAttr, rsAttr, ignoreFields)
+ if err != nil {
+ return err
+ }
+ err = checkResourceAbsentInDataSourceAfterFilterApplied(dsAttr, rsAttr2)
+ return err
+ }
+}
+
+// This function asserts the absence of the secret manager secret resource which would not be included in the data source list due to the filter applied.
+func checkResourceAbsentInDataSourceAfterFilterApplied(dsAttr, rsAttr map[string]string) error {
+ totalSecrets, err := strconv.Atoi(dsAttr["secrets.#"])
+ if err != nil {
+ return errors.New("Couldn't convert length of secrets list to integer")
+ }
+ for i := 0; i < totalSecrets; i++ {
+ if dsAttr["secrets."+strconv.Itoa(i)+".name"] == rsAttr["name"] {
+ return errors.New("The resource is present in the data source even after the filter is applied")
+ }
+ }
+ return nil
+}
diff --git a/mmv1/third_party/terraform/website/docs/d/secret_manager_regional_secrets.html.markdown b/mmv1/third_party/terraform/website/docs/d/secret_manager_regional_secrets.html.markdown
new file mode 100644
index 000000000000..5a0f2e9a1051
--- /dev/null
+++ b/mmv1/third_party/terraform/website/docs/d/secret_manager_regional_secrets.html.markdown
@@ -0,0 +1,82 @@
+---
+subcategory: "Secret Manager"
+description: |-
+ List the Secret Manager Regional Secrets.
+---
+
+# google_secret_manager_regional_secrets
+
+Use this data source to list the Secret Manager Regional Secrets.
+
+## Example Usage
+
+```hcl
+data "google_secret_manager_regional_secrets" "secrets" {
+ location = "us-central1"
+}
+```
+
+## Argument Reference
+
+The following arguments are supported:
+
+* `project` - (optional) The ID of the project.
+
+* `filter` - (optional) Filter string, adhering to the rules in [List-operation filtering](https://cloud.google.com/secret-manager/docs/filtering). List only secrets matching the filter. If filter is empty, all regional secrets are listed from the specified location.
+
+* `location` - (Required) The location of the regional secret.
+
+## Attributes Reference
+
+In addition to the arguments listed above, the following computed attributes are exported:
+
+* `secrets` - A list of regional secrets present in the specified location and matching the filter. Structure is [defined below](#nested_secrets).
+
+The `secrets` block supports:
+
+* `labels` - The labels assigned to this regional secret.
+
+* `annotations` - Custom metadata about the regional secret.
+
+* `version_aliases` - Mapping from version alias to version name.
+
+* `topics` -
+ A list of up to 10 Pub/Sub topics to which messages are published when control plane operations are called on the regional secret or its versions.
+ Structure is [documented below](#nested_topics).
+
+* `expire_time` - Timestamp in UTC when the regional secret is scheduled to expire.
+
+* `create_time` - The time at which the regional secret was created.
+
+* `rotation` -
+ The rotation time and period for a regional secret.
+ Structure is [documented below](#nested_rotation).
+
+* `project` - The ID of the project in which the resource belongs.
+
+* `location` - The location in which the resource belongs.
+
+* `secret_id` - The unique name of the resource.
+
+* `name` - The resource name of the regional secret. Format: `projects/{{project}}/locations/{{location}}/secrets/{{secret_id}}`
+
+* `version_destroy_ttl` - The version destroy ttl for the regional secret version.
+
+* `customer_managed_encryption` -
+ Customer Managed Encryption for the regional secret.
+ Structure is [documented below](#nested_customer_managed_encryption_user_managed).
+
+The `topics` block supports:
+
+* `name` - The resource name of the Pub/Sub topic that will be published to.
+
+The `rotation` block supports:
+
+* `next_rotation_time` - Timestamp in UTC at which the secret is scheduled to rotate.
+
+* `rotation_period` - The Duration between rotation notifications.
+
+The `customer_managed_encryption` block supports:
+
+* `kms_key_name` -
+ Describes the Cloud KMS encryption key that will be used to protect destination secret.