Repository: GoogleCloudPlatform/terraform-validator
Pull request #1244 (2023-01-03 21:48:06)

Attached clusters
<!-- Put a description of what this PR is for here, along with any references to issues that this resolves or contributes to -->
- Added new resource for attached clusters `google_container_attached_cluster`
- ~Added new datasource for retrieving valid attached cluster platform versions `google_container_attached_versions`~
- ~Added new datasource for retrieving a manifest to apply to the user cluster for boot-strapping `google_container_attached_install_manifest`~



<!--
Replace each [ ] with [X] to check it. Switch to the preview view to make it easier to click on links.
These steps will speed up the review process, and we appreciate you spending time on them before sending
your code to be reviewed.
-->
If this PR is for Terraform, I acknowledge that I have:

- [X] Searched through the [issue tracker](https://github.com/hashicorp/terraform-provider-google/issues) for an open issue that this either resolves or contributes to, commented on it to claim it, and written "fixes {url}" or "part of {url}" in this PR description. If there were no relevant open issues, I opened one and commented that I would like to work on it (not necessary for very small changes).
- [X] [Generated Terraform](https://github.com/GoogleCloudPlatform/magic-modules#generating-the-terraform-providers), and ran `make test` and `make lint` to ensure it passes unit and linter tests.
- [X] Ensured that all new fields I added that can be set by a user appear in at least one [example](https://github.com/GoogleCloudPlatform/magic-modules/tree/main/mmv1/templates/terraform/examples) (for generated resources) or [third_party test](https://github.com/GoogleCloudPlatform/magic-modules/tree/main/mmv1/third_party/terraform/tests) (for handwritten resources or update tests).
- [X] [Ran](https://github.com/hashicorp/terraform-provider-google/blob/main/.github/CONTRIBUTING.md#tests) relevant acceptance tests (If the acceptance tests do not yet pass or you are unable to run them, please let your reviewer know).
- [X] Read the [Release Notes Guide](https://github.com/GoogleCloudPlatform/magic-modules/blob/main/.ci/RELEASE_NOTES_GUIDE.md) before writing my release note below.

<!-- AUTOCHANGELOG for Downstream PRs.

Please select one of the following "release-note:" headings:
    - release-note:enhancement
    - release-note:bug
    - release-note:note
    - release-note:new-resource
    - release-note:new-datasource
    - release-note:deprecation
    - release-note:breaking-change
    - release-note:none
    
Unless you choose release-note:none, please add a release note.

See .ci/RELEASE_NOTES_GUIDE.md for writing good release notes.

You can add more release note blocks if you want more than one CHANGELOG
entry for this PR.
-->
**Release Note Template for Downstream PRs (will be copied)**

```release-note:new-resource
`google_container_attached_cluster`
```

Derived from https://github.com/GoogleCloudPlatform/magic-modules/pull/6858


Patch:
diff --git a/converters/google/resources/config.go b/converters/google/resources/config.go
index 70041010..3712f4f3 100644
--- a/converters/google/resources/config.go
+++ b/converters/google/resources/config.go
@@ -200,6 +200,7 @@ type Config struct {
 	CloudTasksBasePath           string
 	ComputeBasePath              string
 	ContainerAnalysisBasePath    string
+	ContainerAttachedBasePath    string
 	DataCatalogBasePath          string
 	DataFusionBasePath           string
 	DataLossPreventionBasePath   string
@@ -301,6 +302,7 @@ const CloudSchedulerBasePathKey = "CloudScheduler"
 const CloudTasksBasePathKey = "CloudTasks"
 const ComputeBasePathKey = "Compute"
 const ContainerAnalysisBasePathKey = "ContainerAnalysis"
+const ContainerAttachedBasePathKey = "ContainerAttached"
 const DataCatalogBasePathKey = "DataCatalog"
 const DataFusionBasePathKey = "DataFusion"
 const DataLossPreventionBasePathKey = "DataLossPrevention"
@@ -396,6 +398,7 @@ var DefaultBasePaths = map[string]string{
 	CloudTasksBasePathKey:           "https://cloudtasks.googleapis.com/v2/",
 	ComputeBasePathKey:              "https://compute.googleapis.com/compute/v1/",
 	ContainerAnalysisBasePathKey:    "https://containeranalysis.googleapis.com/v1/",
+	ContainerAttachedBasePathKey:    "https://{{location}}-gkemulticloud.googleapis.com/v1/",
 	DataCatalogBasePathKey:          "https://datacatalog.googleapis.com/v1/",
 	DataFusionBasePathKey:           "https://datafusion.googleapis.com/v1/",
 	DataLossPreventionBasePathKey:   "https://dlp.googleapis.com/v2/",
@@ -1253,6 +1256,7 @@ func ConfigureBasePaths(c *Config) {
 	c.CloudTasksBasePath = DefaultBasePaths[CloudTasksBasePathKey]
 	c.ComputeBasePath = DefaultBasePaths[ComputeBasePathKey]
 	c.ContainerAnalysisBasePath = DefaultBasePaths[ContainerAnalysisBasePathKey]
+	c.ContainerAttachedBasePath = DefaultBasePaths[ContainerAttachedBasePathKey]
 	c.DataCatalogBasePath = DefaultBasePaths[DataCatalogBasePathKey]
 	c.DataFusionBasePath = DefaultBasePaths[DataFusionBasePathKey]
 	c.DataLossPreventionBasePath = DefaultBasePaths[DataLossPreventionBasePathKey]
diff --git a/converters/google/resources/containerattached_cluster.go b/converters/google/resources/containerattached_cluster.go
new file mode 100644
index 00000000..1fa08a98
--- /dev/null
+++ b/converters/google/resources/containerattached_cluster.go
@@ -0,0 +1,358 @@
+// ----------------------------------------------------------------------------
+//
+//     ***     AUTO GENERATED CODE    ***    Type: MMv1     ***
+//
+// ----------------------------------------------------------------------------
+//
+//     This file is automatically generated by Magic Modules and manual
+//     changes will be clobbered when the file is regenerated.
+//
+//     Please read more about how to change this file in
+//     .github/CONTRIBUTING.md.
+//
+// ----------------------------------------------------------------------------
+
+package google
+
+import (
+	"reflect"
+
+	"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
+)
+
+func suppressAttachedClustersLoggingConfigDiff(_, old, new string, d *schema.ResourceData) bool {
+	if old == new {
+		return true
+	}
+	_, n := d.GetChange("logging_config.0.component_config.0.enable_components")
+	if isEmptyValue(reflect.ValueOf(n)) {
+		return true
+	}
+	return false
+}
+
+const ContainerAttachedClusterAssetType string = "{{location}}-gkemulticloud.googleapis.com/Cluster"
+
+func resourceConverterContainerAttachedCluster() ResourceConverter {
+	return ResourceConverter{
+		AssetType: ContainerAttachedClusterAssetType,
+		Convert:   GetContainerAttachedClusterCaiObject,
+	}
+}
+
+func GetContainerAttachedClusterCaiObject(d TerraformResourceData, config *Config) ([]Asset, error) {
+	name, err := assetName(d, config, "//{{location}}-gkemulticloud.googleapis.com/projects/{{project}}/locations/{{location}}/attachedClusters/{{name}}")
+	if err != nil {
+		return []Asset{}, err
+	}
+	if obj, err := GetContainerAttachedClusterApiObject(d, config); err == nil {
+		return []Asset{{
+			Name: name,
+			Type: ContainerAttachedClusterAssetType,
+			Resource: &AssetResource{
+				Version:              "v1",
+				DiscoveryDocumentURI: "https://www.googleapis.com/discovery/v1/apis/{{location}}-gkemulticloud/v1/rest",
+				DiscoveryName:        "Cluster",
+				Data:                 obj,
+			},
+		}}, nil
+	} else {
+		return []Asset{}, err
+	}
+}
+
+func GetContainerAttachedClusterApiObject(d TerraformResourceData, config *Config) (map[string]interface{}, error) {
+	obj := make(map[string]interface{})
+	nameProp, err := expandContainerAttachedClusterName(d.Get("name"), d, config)
+	if err != nil {
+		return nil, err
+	} else if v, ok := d.GetOkExists("name"); !isEmptyValue(reflect.ValueOf(nameProp)) && (ok || !reflect.DeepEqual(v, nameProp)) {
+		obj["name"] = nameProp
+	}
+	descriptionProp, err := expandContainerAttachedClusterDescription(d.Get("description"), d, config)
+	if err != nil {
+		return nil, err
+	} else if v, ok := d.GetOkExists("description"); !isEmptyValue(reflect.ValueOf(descriptionProp)) && (ok || !reflect.DeepEqual(v, descriptionProp)) {
+		obj["description"] = descriptionProp
+	}
+	oidcConfigProp, err := expandContainerAttachedClusterOidcConfig(d.Get("oidc_config"), d, config)
+	if err != nil {
+		return nil, err
+	} else if v, ok := d.GetOkExists("oidc_config"); !isEmptyValue(reflect.ValueOf(oidcConfigProp)) && (ok || !reflect.DeepEqual(v, oidcConfigProp)) {
+		obj["oidcConfig"] = oidcConfigProp
+	}
+	platformVersionProp, err := expandContainerAttachedClusterPlatformVersion(d.Get("platform_version"), d, config)
+	if err != nil {
+		return nil, err
+	} else if v, ok := d.GetOkExists("platform_version"); !isEmptyValue(reflect.ValueOf(platformVersionProp)) && (ok || !reflect.DeepEqual(v, platformVersionProp)) {
+		obj["platformVersion"] = platformVersionProp
+	}
+	distributionProp, err := expandContainerAttachedClusterDistribution(d.Get("distribution"), d, config)
+	if err != nil {
+		return nil, err
+	} else if v, ok := d.GetOkExists("distribution"); !isEmptyValue(reflect.ValueOf(distributionProp)) && (ok || !reflect.DeepEqual(v, distributionProp)) {
+		obj["distribution"] = distributionProp
+	}
+	fleetProp, err := expandContainerAttachedClusterFleet(d.Get("fleet"), d, config)
+	if err != nil {
+		return nil, err
+	} else if v, ok := d.GetOkExists("fleet"); !isEmptyValue(reflect.ValueOf(fleetProp)) && (ok || !reflect.DeepEqual(v, fleetProp)) {
+		obj["fleet"] = fleetProp
+	}
+	annotationsProp, err := expandContainerAttachedClusterAnnotations(d.Get("annotations"), d, config)
+	if err != nil {
+		return nil, err
+	} else if v, ok := d.GetOkExists("annotations"); !isEmptyValue(reflect.ValueOf(annotationsProp)) && (ok || !reflect.DeepEqual(v, annotationsProp)) {
+		obj["annotations"] = annotationsProp
+	}
+	loggingConfigProp, err := expandContainerAttachedClusterLoggingConfig(d.Get("logging_config"), d, config)
+	if err != nil {
+		return nil, err
+	} else if v, ok := d.GetOkExists("logging_config"); ok || !reflect.DeepEqual(v, loggingConfigProp) {
+		obj["loggingConfig"] = loggingConfigProp
+	}
+	authorizationProp, err := expandContainerAttachedClusterAuthorization(d.Get("authorization"), d, config)
+	if err != nil {
+		return nil, err
+	} else if v, ok := d.GetOkExists("authorization"); !isEmptyValue(reflect.ValueOf(authorizationProp)) && (ok || !reflect.DeepEqual(v, authorizationProp)) {
+		obj["authorization"] = authorizationProp
+	}
+	monitoringConfigProp, err := expandContainerAttachedClusterMonitoringConfig(d.Get("monitoring_config"), d, config)
+	if err != nil {
+		return nil, err
+	} else if v, ok := d.GetOkExists("monitoring_config"); !isEmptyValue(reflect.ValueOf(monitoringConfigProp)) && (ok || !reflect.DeepEqual(v, monitoringConfigProp)) {
+		obj["monitoringConfig"] = monitoringConfigProp
+	}
+
+	return obj, nil
+}
+
+func expandContainerAttachedClusterName(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
+	return v, nil
+}
+
+func expandContainerAttachedClusterDescription(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
+	return v, nil
+}
+
+func expandContainerAttachedClusterOidcConfig(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
+	l := v.([]interface{})
+	if len(l) == 0 || l[0] == nil {
+		return nil, nil
+	}
+	raw := l[0]
+	original := raw.(map[string]interface{})
+	transformed := make(map[string]interface{})
+
+	transformedIssuerUrl, err := expandContainerAttachedClusterOidcConfigIssuerUrl(original["issuer_url"], d, config)
+	if err != nil {
+		return nil, err
+	} else if val := reflect.ValueOf(transformedIssuerUrl); val.IsValid() && !isEmptyValue(val) {
+		transformed["issuerUrl"] = transformedIssuerUrl
+	}
+
+	transformedJwks, err := expandContainerAttachedClusterOidcConfigJwks(original["jwks"], d, config)
+	if err != nil {
+		return nil, err
+	} else if val := reflect.ValueOf(transformedJwks); val.IsValid() && !isEmptyValue(val) {
+		transformed["jwks"] = transformedJwks
+	}
+
+	return transformed, nil
+}
+
+func expandContainerAttachedClusterOidcConfigIssuerUrl(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
+	return v, nil
+}
+
+func expandContainerAttachedClusterOidcConfigJwks(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
+	return v, nil
+}
+
+func expandContainerAttachedClusterPlatformVersion(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
+	return v, nil
+}
+
+func expandContainerAttachedClusterDistribution(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
+	return v, nil
+}
+
+func expandContainerAttachedClusterFleet(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
+	l := v.([]interface{})
+	if len(l) == 0 || l[0] == nil {
+		return nil, nil
+	}
+	raw := l[0]
+	original := raw.(map[string]interface{})
+	transformed := make(map[string]interface{})
+
+	transformedMembership, err := expandContainerAttachedClusterFleetMembership(original["membership"], d, config)
+	if err != nil {
+		return nil, err
+	} else if val := reflect.ValueOf(transformedMembership); val.IsValid() && !isEmptyValue(val) {
+		transformed["membership"] = transformedMembership
+	}
+
+	transformedProject, err := expandContainerAttachedClusterFleetProject(original["project"], d, config)
+	if err != nil {
+		return nil, err
+	} else if val := reflect.ValueOf(transformedProject); val.IsValid() && !isEmptyValue(val) {
+		transformed["project"] = transformedProject
+	}
+
+	return transformed, nil
+}
+
+func expandContainerAttachedClusterFleetMembership(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
+	return v, nil
+}
+
+func expandContainerAttachedClusterFleetProject(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
+	return v, nil
+}
+
+func expandContainerAttachedClusterAnnotations(v interface{}, d TerraformResourceData, config *Config) (map[string]string, error) {
+	if v == nil {
+		return map[string]string{}, nil
+	}
+	m := make(map[string]string)
+	for k, val := range v.(map[string]interface{}) {
+		m[k] = val.(string)
+	}
+	return m, nil
+}
+
+func expandContainerAttachedClusterLoggingConfig(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
+	l := v.([]interface{})
+	if len(l) == 0 || l[0] == nil {
+		transformed := make(map[string]interface{})
+		return transformed, nil
+	}
+	raw := l[0]
+	original := raw.(map[string]interface{})
+	transformed := make(map[string]interface{})
+
+	transformedComponentConfig, err := expandContainerAttachedClusterLoggingConfigComponentConfig(original["component_config"], d, config)
+	if err != nil {
+		return nil, err
+	} else {
+		transformed["componentConfig"] = transformedComponentConfig
+	}
+
+	return transformed, nil
+}
+
+func expandContainerAttachedClusterLoggingConfigComponentConfig(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
+	l := v.([]interface{})
+	if len(l) == 0 {
+		return nil, nil
+	}
+
+	if l[0] == nil {
+		transformed := make(map[string]interface{})
+		return transformed, nil
+	}
+	raw := l[0]
+	original := raw.(map[string]interface{})
+	transformed := make(map[string]interface{})
+
+	transformedEnableComponents, err := expandContainerAttachedClusterLoggingConfigComponentConfigEnableComponents(original["enable_components"], d, config)
+	if err != nil {
+		return nil, err
+	} else {
+		transformed["enableComponents"] = transformedEnableComponents
+	}
+
+	return transformed, nil
+}
+
+func expandContainerAttachedClusterLoggingConfigComponentConfigEnableComponents(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
+	return v, nil
+}
+
+type attachedClusterUser struct {
+	Username string `json:"username"`
+}
+
+// The custom expander transforms input into something like this:
+// authorization {
+//    admin_users [
+//      { username = "user1" },
+//      { username = "user2" }
+//    ]
+// }
+// The custom flattener transforms input back into something like this:
+// authorization {
+//    admin_users = [
+//      "user1",
+//      "user2"
+//    ]
+// }
+func expandContainerAttachedClusterAuthorization(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
+	l := v.([]interface{})
+	if len(l) == 0 || l[0] == nil {
+		return nil, nil
+	}
+	raw := l[0]
+	orig := raw.(map[string]interface{})["admin_users"].([]interface{})
+	transformed := make(map[string][]interface{})
+	transformed["admin_users"] = make([]interface{}, len(orig))
+	for i, u := range orig {
+		if u != nil {
+			transformed["admin_users"][i] = attachedClusterUser{Username: u.(string)}
+		}
+	}
+	return transformed, nil
+}
+
+func expandContainerAttachedClusterMonitoringConfig(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
+	l := v.([]interface{})
+	if len(l) == 0 {
+		return nil, nil
+	}
+
+	if l[0] == nil {
+		transformed := make(map[string]interface{})
+		return transformed, nil
+	}
+	raw := l[0]
+	original := raw.(map[string]interface{})
+	transformed := make(map[string]interface{})
+
+	transformedManagedPrometheusConfig, err := expandContainerAttachedClusterMonitoringConfigManagedPrometheusConfig(original["managed_prometheus_config"], d, config)
+	if err != nil {
+		return nil, err
+	} else if val := reflect.ValueOf(transformedManagedPrometheusConfig); val.IsValid() && !isEmptyValue(val) {
+		transformed["managedPrometheusConfig"] = transformedManagedPrometheusConfig
+	}
+
+	return transformed, nil
+}
+
+func expandContainerAttachedClusterMonitoringConfigManagedPrometheusConfig(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
+	l := v.([]interface{})
+	if len(l) == 0 {
+		return nil, nil
+	}
+
+	if l[0] == nil {
+		transformed := make(map[string]interface{})
+		return transformed, nil
+	}
+	raw := l[0]
+	original := raw.(map[string]interface{})
+	transformed := make(map[string]interface{})
+
+	transformedEnabled, err := expandContainerAttachedClusterMonitoringConfigManagedPrometheusConfigEnabled(original["enabled"], d, config)
+	if err != nil {
+		return nil, err
+	} else if val := reflect.ValueOf(transformedEnabled); val.IsValid() && !isEmptyValue(val) {
+		transformed["enabled"] = transformedEnabled
+	}
+
+	return transformed, nil
+}
+
+func expandContainerAttachedClusterMonitoringConfigManagedPrometheusConfigEnabled(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
+	return v, nil
+}
diff --git a/go.mod b/go.mod
index 324938e1..e54320ee 100644
--- a/go.mod
+++ b/go.mod
@@ -26,7 +26,7 @@ require (
 	github.com/hashicorp/go-cleanhttp v0.5.2
 	github.com/hashicorp/terraform-json v0.14.0
 	github.com/hashicorp/terraform-plugin-sdk/v2 v2.24.0
-	github.com/hashicorp/terraform-provider-google v1.20.1-0.20230103170911-71edccc94c58
+	github.com/hashicorp/terraform-provider-google v1.20.1-0.20230103214347-54daef483d98
 	github.com/mitchellh/go-homedir v1.1.0
 	github.com/pkg/errors v0.9.1
 	github.com/sirupsen/logrus v1.9.0
diff --git a/go.sum b/go.sum
index 748d9616..c6adee08 100644
--- a/go.sum
+++ b/go.sum
@@ -514,8 +514,8 @@ github.com/hashicorp/terraform-plugin-log v0.7.0 h1:SDxJUyT8TwN4l5b5/VkiTIaQgY6R
 github.com/hashicorp/terraform-plugin-log v0.7.0/go.mod h1:p4R1jWBXRTvL4odmEkFfDdhUjHf9zcs/BCoNHAc7IK4=
 github.com/hashicorp/terraform-plugin-sdk/v2 v2.24.0 h1:FtCLTiTcykdsURXPt/ku7fYXm3y19nbzbZcUxHx9RbI=
 github.com/hashicorp/terraform-plugin-sdk/v2 v2.24.0/go.mod h1:80wf5oad1tW+oLnbXS4UTYmDCrl7BuN1Q+IA91X1a4Y=
-github.com/hashicorp/terraform-provider-google v1.20.1-0.20230103170911-71edccc94c58 h1:4COu8Sfa93TrUD6AAqGXahkDjlpaV7dpfD6VkwElhzM=
-github.com/hashicorp/terraform-provider-google v1.20.1-0.20230103170911-71edccc94c58/go.mod h1:U6fCCwkZ6COAY1NchAI8XWuC2IjBB4Va7qa6wL7Xk6s=
+github.com/hashicorp/terraform-provider-google v1.20.1-0.20230103214347-54daef483d98 h1:w5tEEAzckSsGoiReKefH5n45m+UWYMBAO3fs3npfnQM=
+github.com/hashicorp/terraform-provider-google v1.20.1-0.20230103214347-54daef483d98/go.mod h1:U6fCCwkZ6COAY1NchAI8XWuC2IjBB4Va7qa6wL7Xk6s=
 github.com/hashicorp/terraform-registry-address v0.0.0-20220623143253-7d51757b572c h1:D8aRO6+mTqHfLsK/BC3j5OAoogv1WLRWzY1AaTo3rBg=
 github.com/hashicorp/terraform-registry-address v0.0.0-20220623143253-7d51757b572c/go.mod h1:Wn3Na71knbXc1G8Lh+yu/dQWWJeFQEpDeJMtWMtlmNI=
 github.com/hashicorp/terraform-svchost v0.0.0-20200729002733-f050f53b9734 h1:HKLsbzeOsfXmKNpr3GiT18XAblV0BjCbzL8KQAMZGa0=


Test patch:
diff --git a/converters/google/resources/config_test_utils.go b/converters/google/resources/config_test_utils.go
index 1dc58dcd..3c25a8f7 100644
--- a/converters/google/resources/config_test_utils.go
+++ b/converters/google/resources/config_test_utils.go
@@ -47,6 +47,7 @@ func configureTestBasePaths(c *Config, url string) {
 	c.CloudTasksBasePath = url
 	c.ComputeBasePath = url
 	c.ContainerAnalysisBasePath = url
+	c.ContainerAttachedBasePath = url
 	c.DataCatalogBasePath = url
 	c.DataFusionBasePath = url
 	c.DataLossPreventionBasePath = url