diff --git a/.changelog/12651.txt b/.changelog/12651.txt new file mode 100644 index 0000000000..279ed0e6c8 --- /dev/null +++ b/.changelog/12651.txt @@ -0,0 +1,3 @@ +```release-note:new-datasource +`google_parameter_manager_regional_parameters` +``` \ No newline at end of file diff --git a/google-beta/provider/provider_mmv1_resources.go b/google-beta/provider/provider_mmv1_resources.go index c7b64120a8..dc979b11e1 100644 --- a/google-beta/provider/provider_mmv1_resources.go +++ b/google-beta/provider/provider_mmv1_resources.go @@ -322,6 +322,7 @@ var handwrittenDatasources = map[string]*schema.Resource{ "google_organization": resourcemanager.DataSourceGoogleOrganization(), "google_parameter_manager_parameter": parametermanager.DataSourceParameterManagerParameter(), "google_parameter_manager_regional_parameter": parametermanagerregional.DataSourceParameterManagerRegionalRegionalParameter(), + "google_parameter_manager_regional_parameters": parametermanagerregional.DataSourceParameterManagerRegionalRegionalParameters(), "google_privateca_certificate_authority": privateca.DataSourcePrivatecaCertificateAuthority(), "google_privileged_access_manager_entitlement": privilegedaccessmanager.DataSourceGooglePrivilegedAccessManagerEntitlement(), "google_project": resourcemanager.DataSourceGoogleProject(), diff --git a/google-beta/services/parametermanagerregional/data_source_parameter_manager_regional_parameters.go b/google-beta/services/parametermanagerregional/data_source_parameter_manager_regional_parameters.go new file mode 100644 index 0000000000..47c02d0baa --- /dev/null +++ b/google-beta/services/parametermanagerregional/data_source_parameter_manager_regional_parameters.go @@ -0,0 +1,172 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 +package parametermanagerregional + +import ( + "fmt" + "strings" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-provider-google-beta/google-beta/tpgresource" + transport_tpg "github.com/hashicorp/terraform-provider-google-beta/google-beta/transport" +) + +func DataSourceParameterManagerRegionalRegionalParameters() *schema.Resource { + + dsSchema := tpgresource.DatasourceSchemaFromResourceSchema(ResourceParameterManagerRegionalRegionalParameter().Schema) + + return &schema.Resource{ + Read: dataSourceParameterManagerRegionalRegionalParametersRead, + Schema: map[string]*schema.Schema{ + "project": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "filter": { + Type: schema.TypeString, + Description: `Filter string, adhering to the rules in List-operation filtering. List only parameters matching the filter. +If filter is empty, all regional parameters are listed from specific location.`, + Optional: true, + }, + "parameters": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: dsSchema, + }, + }, + "location": { + Type: schema.TypeString, + Required: true, + }, + }, + } +} + +func dataSourceParameterManagerRegionalRegionalParametersRead(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, "{{ParameterManagerRegionalBasePath}}projects/{{project}}/locations/{{location}}/parameters") + 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 Regional Parameters: %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 the pagination locally + allParameters := 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 + } + } + parameters, 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("ParameterManagerRegionalParameters %q", d.Id())) + } + parametersInterface := parameters["parameters"] + if parametersInterface != nil { + allParameters = append(allParameters, parametersInterface.([]interface{})...) + } + tokenInterface := parameters["nextPageToken"] + if tokenInterface == nil { + paginate = false + } else { + paginate = true + token = tokenInterface.(string) + } + } + + if err := d.Set("parameters", flattenParameterManagerRegionalRegionalParameterParameters(allParameters, d, config)); err != nil { + return fmt.Errorf("error setting regional parameters: %s", err) + } + + 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) + } + + // Store the ID now + id, err := tpgresource.ReplaceVars(d, config, "projects/{{project}}/locations/{{location}}/parameters") + if err != nil { + return fmt.Errorf("error constructing id: %s", err) + } + if has_filter { + id += "/filter=" + filter.(string) + } + d.SetId(id) + + return nil +} + +func flattenParameterManagerRegionalRegionalParameterParameters(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{}{ + "format": flattenParameterManagerRegionalRegionalParameterFormat(original["format"], d, config), + "labels": flattenParameterManagerRegionalRegionalParameterEffectiveLabels(original["labels"], d, config), + "effective_labels": flattenParameterManagerRegionalRegionalParameterEffectiveLabels(original["labels"], d, config), + "terraform_labels": flattenParameterManagerRegionalRegionalParameterEffectiveLabels(original["labels"], d, config), + "create_time": flattenParameterManagerRegionalRegionalParameterCreateTime(original["createTime"], d, config), + "update_time": flattenParameterManagerRegionalRegionalParameterUpdateTime(original["updateTime"], d, config), + "policy_member": flattenParameterManagerRegionalRegionalParameterPolicyMember(original["policyMember"], d, config), + "name": flattenParameterManagerRegionalRegionalParameterName(original["name"], d, config), + "project": getDataFromName(original["name"], 1), + "location": getDataFromName(original["name"], 3), + "parameter_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/google-beta/services/parametermanagerregional/data_source_parameter_manager_regional_parameters_test.go b/google-beta/services/parametermanagerregional/data_source_parameter_manager_regional_parameters_test.go new file mode 100644 index 0000000000..3874d94ba0 --- /dev/null +++ b/google-beta/services/parametermanagerregional/data_source_parameter_manager_regional_parameters_test.go @@ -0,0 +1,259 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 +package parametermanagerregional_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-beta/google-beta/acctest" +) + +func TestAccDataSourceParameterManagerRegionalRegionalParameters_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.ProtoV5ProviderBetaFactories(t), + CheckDestroy: testAccCheckParameterManagerRegionalRegionalParameterDestroyProducer(t), + Steps: []resource.TestStep{ + { + Config: testAccDataSourceParameterManagerRegionalRegionalParameters_basic(context), + Check: resource.ComposeTestCheckFunc( + checkListDataSourceStateMatchesResourceStateWithIgnores( + "data.google_parameter_manager_regional_parameters.regional-parameters-datasource", + "google_parameter_manager_regional_parameter.regional-parameters", + map[string]struct{}{ + "id": {}, + "project": {}, + }, + ), + ), + }, + }, + }) +} + +func testAccDataSourceParameterManagerRegionalRegionalParameters_basic(context map[string]interface{}) string { + return acctest.Nprintf(` +provider "google-beta" { + add_terraform_attribution_label = false +} + +resource "google_parameter_manager_regional_parameter" "regional-parameters" { + provider = google-beta + parameter_id = "tf_test_regional_parameter%{random_suffix}" + format = "YAML" + location = "us-central1" + + labels = { + key1 = "val1" + key2 = "val2" + key3 = "val3" + key4 = "val4" + key5 = "val5" + } +} + +data "google_parameter_manager_regional_parameters" "regional-parameters-datasource" { + provider = google-beta + depends_on = [ + google_parameter_manager_regional_parameter.regional-parameters + ] + location = "us-central1" +} +`, context) +} + +func TestAccDataSourceParameterManagerRegionalRegionalParameters_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.ProtoV5ProviderBetaFactories(t), + CheckDestroy: testAccCheckParameterManagerRegionalRegionalParameterDestroyProducer(t), + Steps: []resource.TestStep{ + { + Config: testAccDataSourceParameterManagerRegionalRegionalParameters_filter(context), + Check: resource.ComposeTestCheckFunc( + checkListDataSourceStateMatchesResourceStateWithIgnoresForAppliedFilter( + "data.google_parameter_manager_regional_parameters.regional-parameters-datasource-filter", + "google_parameter_manager_regional_parameter.regional-parameters-1", + "google_parameter_manager_regional_parameter.regional-parameters-2", + map[string]struct{}{ + "id": {}, + "project": {}, + }, + ), + ), + }, + }, + }) +} + +func testAccDataSourceParameterManagerRegionalRegionalParameters_filter(context map[string]interface{}) string { + return acctest.Nprintf(` +provider "google-beta" { + add_terraform_attribution_label = false +} + +resource "google_parameter_manager_regional_parameter" "regional-parameters-1" { + provider = google-beta + parameter_id = "tf_test_regional_parameter%{random_suffix}" + format = "JSON" + location = "us-central1" + + labels = { + key1 = "val1" + } +} + +resource "google_parameter_manager_regional_parameter" "regional-parameters-2" { + provider = google-beta + parameter_id = "tf_test_regional_parameter_2_%{random_suffix}" + format = "YAML" + location = "us-central1" + + labels = { + keyoth1 = "valoth1" + } +} + +data "google_parameter_manager_regional_parameters" "regional-parameters-datasource-filter" { + provider = google-beta + filter = "format:JSON" + location = "us-central1" + + depends_on = [ + google_parameter_manager_regional_parameter.regional-parameters-1, + google_parameter_manager_regional_parameter.regional-parameters-2 + ] +} +`, context) +} + +// This function checks data source state matches for resourceName parameter manager regional parameter 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 parameter manager regional parameter resource and the attributes of the parameter manager regional parameter inside the data source list are the same +func checkFieldsMatchForDataSourceStateAndResourceState(dsAttr, rsAttr map[string]string, ignoreFields map[string]struct{}) error { + totalParameters, err := strconv.Atoi(dsAttr["parameters.#"]) + if err != nil { + return errors.New("couldn't convert length of regional parameters list to integer") + } + index := "-1" + for i := 0; i < totalParameters; i++ { + if dsAttr["parameters."+strconv.Itoa(i)+".name"] == rsAttr["name"] { + index = strconv.Itoa(i) + } + } + + if index == "-1" { + return errors.New("the newly created regional parameter 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["parameters."+index+"."+k] != rsAttr[k] { + // ignore data sources where an empty list is being compared against a null list. + if k[len(k)-1:] == "#" && (dsAttr["parameters."+index+"."+k] == "" || dsAttr["parameters."+index+"."+k] == "0") && (rsAttr[k] == "" || rsAttr[k] == "0") { + continue + } + errMsg += fmt.Sprintf("%s is %s; want %s\n", k, dsAttr["parameters."+index+"."+k], rsAttr[k]) + } + } + + if errMsg != "" { + return errors.New(errMsg) + } + + return nil +} + +// This function checks state match for resourceName and asserts the absense 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 parameter manager regional parameter resource which would not be included in the data source list due to the filter applied. +func checkResourceAbsentInDataSourceAfterFilterApplied(dsAttr, rsAttr map[string]string) error { + totalParameters, err := strconv.Atoi(dsAttr["parameters.#"]) + if err != nil { + return errors.New("couldn't convert length of regional parameters list to integer") + } + for i := 0; i < totalParameters; i++ { + if dsAttr["parameters."+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/website/docs/d/parameter_manager_regional_parameters.html.markdown b/website/docs/d/parameter_manager_regional_parameters.html.markdown new file mode 100644 index 0000000000..4ab44990f5 --- /dev/null +++ b/website/docs/d/parameter_manager_regional_parameters.html.markdown @@ -0,0 +1,67 @@ +--- +subcategory: "Parameter Manager" +description: |- + List the Parameter Manager Regional Parameters. +--- + +# google_parameter_manager_regional_parameters + +Use this data source to list the Parameter Manager Regional Parameters + +~> **Warning:** This datasource is in beta, and should be used with the terraform-provider-google-beta provider. +See [Provider Versions](https://terraform.io/docs/providers/google/guides/provider_versions.html) for more details on beta datasources. + +## Example Usage + +```hcl +data "google_parameter_manager_regional_parameters" "regional-parameters" { + 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. List only parameters matching the filter. If filter is empty, all regional parameters are listed. + +- `location` - (Required) The location of regional parameter. + + +## Attributes Reference + +In addition to the arguments listed above, the following computed attributes are +exported: + +- `parameters` - A list of regional parameters matching the filter. Structure is [defined below](#nested_parameters). + +The `parameters` block supports: + +- `format` - The format type of the regional parameter. + +- `labels` - The labels assigned to the regional parameter. + +- `create_time` - The time at which the regional parameter was created. + +- `update_time` - The time at which the regional parameter was updated. + +- `project` - The ID of the project in which the resource belongs. + +- `parameter_id` - The unique name of the resource. + +- `name` - The resource name of the regional parameter. Format: `projects/{{project}}/locations/{{location}}/parameters/{{parameter_id}}` + +- `policy_member` - An object containing a unique resource identity tied to the regional parameter. Structure is [documented below](#nested_policy_member). + +The `policy_member` block contains: + +* `iam_policy_uid_principal` - IAM policy binding member referring to a Google Cloud resource by system-assigned unique identifier. +If a resource is deleted and recreated with the same name, the binding will not be applicable to the +new resource. Format: +`principal://parametermanager.googleapis.com/projects/{{project}}/uid/locations/{{location}}/parameters/{{uid}}` + +* `iam_policy_name_principal` - AM policy binding member referring to a Google Cloud resource by user-assigned name. If a resource is deleted and recreated with the same name, the binding will be applicable to the +new resource. Format: +`principal://parametermanager.googleapis.com/projects/{{project}}/name/locations/{{location}}/parameters/{{parameter_id}}` \ No newline at end of file