From 6f041184ca591debfa6a70fb309ef9439230d093 Mon Sep 17 00:00:00 2001 From: abheda-crest Date: Tue, 24 Dec 2024 16:13:00 +0530 Subject: [PATCH] Add support for parameter manager parameters datasource --- .../provider/provider_mmv1_resources.go.tmpl | 1 + ...ata_source_parameter_manager_parameters.go | 165 ++++++++++++ ...ource_parameter_manager_parameters_test.go | 246 ++++++++++++++++++ ...parameter_manager_parameters.html.markdown | 60 +++++ 4 files changed, 472 insertions(+) create mode 100644 mmv1/third_party/terraform/services/parametermanager/data_source_parameter_manager_parameters.go create mode 100644 mmv1/third_party/terraform/services/parametermanager/data_source_parameter_manager_parameters_test.go create mode 100644 mmv1/third_party/terraform/website/docs/d/parameter_manager_parameters.html.markdown diff --git a/mmv1/third_party/terraform/provider/provider_mmv1_resources.go.tmpl b/mmv1/third_party/terraform/provider/provider_mmv1_resources.go.tmpl index 876f24f36bb0..49855f42d2f6 100644 --- a/mmv1/third_party/terraform/provider/provider_mmv1_resources.go.tmpl +++ b/mmv1/third_party/terraform/provider/provider_mmv1_resources.go.tmpl @@ -183,6 +183,7 @@ var handwrittenDatasources = map[string]*schema.Resource{ "google_oracle_database_cloud_vm_clusters": oracledatabase.DataSourceOracleDatabaseCloudVmClusters(), "google_oracle_database_cloud_vm_cluster": oracledatabase.DataSourceOracleDatabaseCloudVmCluster(), "google_organization": resourcemanager.DataSourceGoogleOrganization(), + "google_parameter_manager_parameters": parametermanager.DataSourceParameterManagerParameters(), "google_privateca_certificate_authority": privateca.DataSourcePrivatecaCertificateAuthority(), "google_privileged_access_manager_entitlement": privilegedaccessmanager.DataSourceGooglePrivilegedAccessManagerEntitlement(), "google_project": resourcemanager.DataSourceGoogleProject(), diff --git a/mmv1/third_party/terraform/services/parametermanager/data_source_parameter_manager_parameters.go b/mmv1/third_party/terraform/services/parametermanager/data_source_parameter_manager_parameters.go new file mode 100644 index 000000000000..deb703b19583 --- /dev/null +++ b/mmv1/third_party/terraform/services/parametermanager/data_source_parameter_manager_parameters.go @@ -0,0 +1,165 @@ +package parametermanager + +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 DataSourceParameterManagerParameters() *schema.Resource { + + dsSchema := tpgresource.DatasourceSchemaFromResourceSchema(ResourceParameterManagerParameter().Schema) + + return &schema.Resource{ + Read: dataSourceParameterManagerParametersRead, + 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 parameters are listed.`, + Optional: true, + }, + "parameters": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: dsSchema, + }, + }, + }, + } +} + +func dataSourceParameterManagerParametersRead(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, "{{ParameterManagerBasePath}}projects/{{project}}/locations/global/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 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("ParameterManagerParameters %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", flattenParameterManagerParameterParameters(allParameters, d, config)); err != nil { + return fmt.Errorf("error setting 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/global/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 flattenParameterManagerParameterParameters(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": flattenParameterManagerParameterFormat(original["format"], d, config), + "labels": flattenParameterManagerParameterEffectiveLabels(original["labels"], d, config), + "effective_labels": flattenParameterManagerParameterEffectiveLabels(original["labels"], d, config), + "terraform_labels": flattenParameterManagerParameterEffectiveLabels(original["labels"], d, config), + "create_time": flattenParameterManagerParameterCreateTime(original["createTime"], d, config), + "update_time": flattenParameterManagerParameterUpdateTime(original["updateTime"], d, config), + "policy_member": flattenParameterManagerParameterPolicyMember(original["policyMember"], d, config), + "name": flattenParameterManagerParameterName(original["name"], d, config), + "project": getDataFromName(original["name"], 1), + "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/mmv1/third_party/terraform/services/parametermanager/data_source_parameter_manager_parameters_test.go b/mmv1/third_party/terraform/services/parametermanager/data_source_parameter_manager_parameters_test.go new file mode 100644 index 000000000000..bcb8c8f0cc3a --- /dev/null +++ b/mmv1/third_party/terraform/services/parametermanager/data_source_parameter_manager_parameters_test.go @@ -0,0 +1,246 @@ +package parametermanager_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 TestAccDataSourceParameterManagerParameters_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: testAccCheckParameterManagerParameterDestroyProducer(t), + Steps: []resource.TestStep{ + { + Config: testAccDataSourceParameterManagerParameters_basic(context), + Check: resource.ComposeTestCheckFunc( + checkListDataSourceStateMatchesResourceStateWithIgnores( + "data.google_parameter_manager_parameters.parameters-datasource", + "google_parameter_manager_parameter.parameters", + map[string]struct{}{ + "id": {}, + "project": {}, + }, + ), + ), + }, + }, + }) +} + +func testAccDataSourceParameterManagerParameters_basic(context map[string]interface{}) string { + return acctest.Nprintf(` +provider "google" { + add_terraform_attribution_label = false +} + +resource "google_parameter_manager_parameter" "parameters" { + parameter_id = "tf_test_parameter%{random_suffix}" + format = "YAML" + + labels = { + key1 = "val1" + key2 = "val2" + key3 = "val3" + key4 = "val4" + key5 = "val5" + } +} + +data "google_parameter_manager_parameters" "parameters-datasource" { + depends_on = [ + google_parameter_manager_parameter.parameters + ] +} +`, context) +} + +func TestAccDataSourceParameterManagerParameters_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: testAccCheckParameterManagerParameterDestroyProducer(t), + Steps: []resource.TestStep{ + { + Config: testAccDataSourceParameterManagerParameters_filter(context), + Check: resource.ComposeTestCheckFunc( + checkListDataSourceStateMatchesResourceStateWithIgnoresForAppliedFilter( + "data.google_parameter_manager_parameters.parameters-datasource-filter", + "google_parameter_manager_parameter.parameters-1", + "google_parameter_manager_parameter.parameters-2", + map[string]struct{}{ + "id": {}, + "project": {}, + }, + ), + ), + }, + }, + }) +} + +func testAccDataSourceParameterManagerParameters_filter(context map[string]interface{}) string { + return acctest.Nprintf(` +provider "google" { + add_terraform_attribution_label = false +} + +resource "google_parameter_manager_parameter" "parameters-1" { + parameter_id = "tf_test_parameter%{random_suffix}" + format = "JSON" + + labels = { + key1 = "val1" + } +} + +resource "google_parameter_manager_parameter" "parameters-2" { + parameter_id = "tf_test_parameter_2_%{random_suffix}" + format = "YAML" + + labels = { + keyoth1 = "valoth1" + } +} + +data "google_parameter_manager_parameters" "parameters-datasource-filter" { + filter = "format:JSON" + depends_on = [ + google_parameter_manager_parameter.parameters-1, + google_parameter_manager_parameter.parameters-2 + ] +} +`, context) +} + +// This function checks data source state matches for resourceName parameter manager 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 parameter resource and the attributes of the parameter manager 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 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 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 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 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/mmv1/third_party/terraform/website/docs/d/parameter_manager_parameters.html.markdown b/mmv1/third_party/terraform/website/docs/d/parameter_manager_parameters.html.markdown new file mode 100644 index 000000000000..78e95d254d7e --- /dev/null +++ b/mmv1/third_party/terraform/website/docs/d/parameter_manager_parameters.html.markdown @@ -0,0 +1,60 @@ +--- +subcategory: "Parameter Manager" +description: |- + List the Parameter Manager Parameters. +--- + +# google_parameter_manager_parameters + +Use this data source to list the Parameter Manager Parameters. + +## Example Usage + +```hcl +data "google_parameter_manager_parameters" "parameters" { +} +``` + +## 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 parameters are listed. + +## Attributes Reference + +In addition to the arguments listed above, the following computed attributes are +exported: + +* `parameters` - A list of parameters matching the filter. Structure is [defined below](#nested_parameters). + +The `parameters` block supports: + +* `format` - The format type of the parameter. + +* `labels` - The labels assigned to the parameter. + +* `create_time` - The time at which the parameter was created. + +* `update_time` - The time at which the 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 parameter. Format: `projects/{{project}}/locations/global/parameters/{{parameter_id}}` + +* `policy_member` - An object containing a unique resource identity tied to the 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/global/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/global/parameters/{{parameter_id}}` \ No newline at end of file