From 1714db3d363af204ffa9a19a34a16628b003835b Mon Sep 17 00:00:00 2001 From: Lin Lin Date: Tue, 9 Jun 2026 02:00:39 +0000 Subject: [PATCH 1/6] Update google_network_services_authz_extension IAP example documentation This change updates the documentation for the 'google_network_services_authz_extension' resource by adding a 'metadata' block to the IAP example and enabling its generation. Changes: - Added 'metadata' block with 'iapPolicyVersion' to 'network_services_authz_extension_iap.tf.tmpl'. - Enabled documentation for the IAP example in 'AuthzExtension.yaml' by removing 'skip_docs'. - Synchronized missing 'AuthzExtension' resource definition and supporting templates. --- .../networkservices/AuthzExtension.yaml | 178 ++++++++++++++++++ .../network_services_authz_extensions.tmpl | 15 ++ .../custom_flatten/name_from_self_link.tmpl | 2 +- ...ork_services_authz_extension_basic.tf.tmpl | 23 +++ ...thz_extension_basic_with_auth_grpc.tf.tmpl | 26 +++ ...twork_services_authz_extension_iap.tf.tmpl | 10 + 6 files changed, 253 insertions(+), 1 deletion(-) create mode 100644 mmv1/products/networkservices/AuthzExtension.yaml create mode 100644 mmv1/templates/terraform/custom_expand/network_services_authz_extensions.tmpl create mode 100644 mmv1/templates/terraform/examples/network_services_authz_extension_basic.tf.tmpl create mode 100644 mmv1/templates/terraform/examples/network_services_authz_extension_basic_with_auth_grpc.tf.tmpl create mode 100644 mmv1/templates/terraform/examples/network_services_authz_extension_iap.tf.tmpl diff --git a/mmv1/products/networkservices/AuthzExtension.yaml b/mmv1/products/networkservices/AuthzExtension.yaml new file mode 100644 index 000000000000..14cc9b895961 --- /dev/null +++ b/mmv1/products/networkservices/AuthzExtension.yaml @@ -0,0 +1,178 @@ +# Copyright 2024 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +--- +name: 'AuthzExtension' +description: | + AuthzExtension is a resource that allows traffic forwarding to a callout backend service to make an authorization decision. +references: + guides: + api: 'https://cloud.google.com/service-extensions/docs/reference/rest/v1beta1/projects.locations.authzExtensions' +docs: +base_url: 'projects/{{project}}/locations/{{location}}/authzExtensions' +self_link: 'projects/{{project}}/locations/{{location}}/authzExtensions/{{name}}' +create_url: 'projects/{{project}}/locations/{{location}}/authzExtensions?authzExtensionId={{name}}' +update_verb: 'PATCH' +update_mask: true +import_format: + - 'projects/{{project}}/locations/{{location}}/authzExtensions/{{name}}' + - '{{name}}' +timeouts: + insert_minutes: 30 + update_minutes: 30 + delete_minutes: 30 +autogen_async: true +async: + actions: ['create', 'delete', 'update'] + type: 'OpAsync' + operation: + base_url: '{{op_id}}' + timeouts: + insert_minutes: 30 + update_minutes: 30 + delete_minutes: 30 + result: + resource_inside_response: false +custom_code: +sweeper: +examples: + - name: 'network_services_authz_extension_basic' + primary_resource_id: 'default' + vars: + resource_name: 'my-authz-ext' + backend_name: 'authz-service' + test_env_vars: + project: 'PROJECT_NAME' + - name: 'network_services_authz_extension_basic_with_auth_grpc' + min_version: 'beta' + primary_resource_id: 'default' + vars: + resource_name: 'my-authz-ext-with-grpc' + backend_name: 'authz-service-grpc' + test_env_vars: + project: 'PROJECT_NAME' + - name: 'network_services_authz_extension_iap' + primary_resource_id: 'default' + vars: + resource_name: 'my-authz-ext' +parameters: + - name: 'name' + type: String + description: | + Identifier. Name of the AuthzExtension resource. + required: true + custom_flatten: 'templates/terraform/custom_flatten/name_from_self_link.tmpl' + custom_expand: 'templates/terraform/custom_expand/network_services_authz_extensions.tmpl' + - name: 'location' + type: String + description: | + The location of the resource. + url_param_only: true + required: true +properties: + - name: 'createTime' + type: Time + description: | + The timestamp when the resource was created. + output: true + - name: 'updateTime' + type: Time + description: | + The timestamp when the resource was updated. + output: true + - name: 'description' + type: String + description: | + A human-readable description of the resource. + - name: 'labels' + type: KeyValueLabels + description: | + Set of labels associated with the AuthzExtension resource. + - name: 'loadBalancingScheme' + type: Enum + description: | + Required when the service points to a backend service. All backend services and forwarding rules referenced by + this extension must share the same load balancing scheme. For more information, refer to + [Backend services overview](https://cloud.google.com/load-balancing/docs/backend-service). + enum_values: + - 'INTERNAL_MANAGED' + - 'EXTERNAL_MANAGED' + - name: 'authority' + type: String + description: | + The :authority header in the gRPC request sent from Envoy to the extension service. + - name: 'service' + type: ResourceRef + description: | + The service that runs the extension. + The following values and formats are accepted: + * `iap.googleapis.com` when the policyProfile is set to REQUEST_AUTHZ + * `modelarmor.{{region}}.rep.googleapis.com` when the policyProfile is set to CONTENT_AUTHZ + * A fully qualified domain name that can be resolved by the dataplane + * Backend service resource URI of the form `https://www.googleapis.com/compute/v1/projects/{{project}}/regions/{{region}}/backendServices/{{name}}` or `https://www.googleapis.com/compute/v1/projects/{{project}}/global/backendServices/{{name}}}}` + required: true + diff_suppress_func: 'tpgresource.ProjectNumberDiffSuppress' + resource: 'BackendService' + imports: 'selfLink' + - name: 'timeout' + type: String + description: | + Specifies the timeout for each individual message on the stream. The timeout must be between 10-10000 milliseconds. + required: true + diff_suppress_func: 'tpgresource.DurationDiffSuppress' + - name: 'failOpen' + type: Boolean + description: | + Determines how the proxy behaves if the call to the extension fails or times out. + When set to TRUE, request or response processing continues without error. Any subsequent extensions in the extension chain are also executed. When set to FALSE or the default setting of FALSE is used, one of the following happens: + * If response headers have not been delivered to the downstream client, a generic 500 error is returned to the client. The error response can be tailored by configuring a custom error response in the load balancer. + * If response headers have been delivered, then the HTTP stream to the downstream client is reset. + default_from_api: true + send_empty_value: true + - name: 'metadata' + type: KeyValuePairs + description: | + The metadata provided here is included as part of the metadata_context (of type google.protobuf.Struct) in the ProcessingRequest message sent to the extension server. The metadata is available under the namespace com.google.authz_extension.. The following variables are supported in the metadata Struct: + + {forwarding_rule_id} - substituted with the forwarding rule's fully qualified resource name. + - name: 'forwardHeaders' + type: Array + description: | + List of the HTTP headers to forward to the extension (from the client). If omitted, all headers are sent. Each element is a string indicating the header name. + item_type: + type: String + - name: 'wireFormat' + type: Enum + description: | + The format of communication supported by the callout extension. Applicable only when the policyProfile is REQUEST_AUTHZ. + This field is supported only for regional AuthzExtension resources. If not specified, the default value + EXT_PROC_GRPC is used. Global AuthzExtension resources use the EXT_PROC_GRPC wire format. + + Supported values: + - WIRE_FORMAT_UNSPECIFIED: + No wire format is explicitly specified. The backend automatically + defaults this value to EXT_PROC_GRPC. + - EXT_PROC_GRPC: + Uses Envoy's External Processing (ext_proc) gRPC API over a single + gRPC stream. The backend service must support HTTP/2 or H2C. + All supported events for a client request are sent over the same + gRPC stream. This is the default wire format. + - EXT_AUTHZ_GRPC: + Uses Envoy's external authorization (ext_authz) gRPC API. + The backend service must support HTTP/2 or H2C. + This option is only supported for regional AuthzExtension resources. + default_from_api: true + enum_values: + - 'WIRE_FORMAT_UNSPECIFIED' + - 'EXT_PROC_GRPC' + - 'EXT_AUTHZ_GRPC' diff --git a/mmv1/templates/terraform/custom_expand/network_services_authz_extensions.tmpl b/mmv1/templates/terraform/custom_expand/network_services_authz_extensions.tmpl new file mode 100644 index 000000000000..377c74156c3c --- /dev/null +++ b/mmv1/templates/terraform/custom_expand/network_services_authz_extensions.tmpl @@ -0,0 +1,15 @@ +{{/* + The license inside this block applies to this file + Copyright 2024 Google Inc. + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ -}} +func expand{{$.GetPrefix}}{{$.TitlelizeProperty}}(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return fmt.Sprintf("projects/%s/locations/%s/authzExtensions/%s", d.Get("project"), d.Get("location"), v), nil +} diff --git a/mmv1/templates/terraform/custom_flatten/name_from_self_link.tmpl b/mmv1/templates/terraform/custom_flatten/name_from_self_link.tmpl index 24b2086d705e..9093852372a5 100644 --- a/mmv1/templates/terraform/custom_flatten/name_from_self_link.tmpl +++ b/mmv1/templates/terraform/custom_flatten/name_from_self_link.tmpl @@ -14,5 +14,5 @@ func flatten{{$.GetPrefix}}{{$.TitlelizeProperty}}(v interface{}, d *schema.Reso if v == nil { return v } - return tpgresource.NameFromSelfLinkStateFunc(v) + return tpgresource.GetResourceNameFromSelfLink(v.(string)) } diff --git a/mmv1/templates/terraform/examples/network_services_authz_extension_basic.tf.tmpl b/mmv1/templates/terraform/examples/network_services_authz_extension_basic.tf.tmpl new file mode 100644 index 000000000000..c2050c447ea5 --- /dev/null +++ b/mmv1/templates/terraform/examples/network_services_authz_extension_basic.tf.tmpl @@ -0,0 +1,23 @@ +resource "google_compute_region_backend_service" "default" { + name = "{{index $.Vars "backend_name"}}" + project = "{{index $.TestEnvVars "project"}}" + region = "us-west1" + + protocol = "HTTP2" + load_balancing_scheme = "INTERNAL_MANAGED" + port_name = "grpc" +} + +resource "google_network_services_authz_extension" "{{$.PrimaryResourceId}}" { + name = "{{index $.Vars "resource_name"}}" + project = "{{index $.TestEnvVars "project"}}" + location = "us-west1" + + description = "my description" + load_balancing_scheme = "INTERNAL_MANAGED" + authority = "ext11.com" + service = google_compute_region_backend_service.default.self_link + timeout = "0.1s" + fail_open = false + forward_headers = ["Authorization"] +} \ No newline at end of file diff --git a/mmv1/templates/terraform/examples/network_services_authz_extension_basic_with_auth_grpc.tf.tmpl b/mmv1/templates/terraform/examples/network_services_authz_extension_basic_with_auth_grpc.tf.tmpl new file mode 100644 index 000000000000..b3434578ba12 --- /dev/null +++ b/mmv1/templates/terraform/examples/network_services_authz_extension_basic_with_auth_grpc.tf.tmpl @@ -0,0 +1,26 @@ +resource "google_compute_region_backend_service" "default" { + provider = google-beta + name = "{{index $.Vars "backend_name"}}" + project = "{{index $.TestEnvVars "project"}}" + region = "us-west1" + + protocol = "HTTP2" + load_balancing_scheme = "INTERNAL_MANAGED" + port_name = "grpc" +} + +resource "google_network_services_authz_extension" "{{$.PrimaryResourceId}}" { + provider = google-beta + name = "{{index $.Vars "resource_name"}}" + project = "{{index $.TestEnvVars "project"}}" + location = "us-west1" + + description = "my description" + load_balancing_scheme = "INTERNAL_MANAGED" + wire_format = "EXT_AUTHZ_GRPC" + authority = "ext11.com" + service = google_compute_region_backend_service.default.self_link + timeout = "0.1s" + fail_open = false + forward_headers = ["Authorization"] +} diff --git a/mmv1/templates/terraform/examples/network_services_authz_extension_iap.tf.tmpl b/mmv1/templates/terraform/examples/network_services_authz_extension_iap.tf.tmpl new file mode 100644 index 000000000000..14b94264bd92 --- /dev/null +++ b/mmv1/templates/terraform/examples/network_services_authz_extension_iap.tf.tmpl @@ -0,0 +1,10 @@ +resource "google_network_services_authz_extension" "{{$.PrimaryResourceId}}" { + name = "{{index $.Vars "resource_name"}}" + location = "us-west1" + + service = "iap.googleapis.com" + timeout = "0.1s" + metadata = { + "iapPolicyVersion" = "V1" + } +} From e0efe6c8aec8955f5aa938f40aa91a7c15aaf053 Mon Sep 17 00:00:00 2001 From: tmgho122 Date: Thu, 11 Jun 2026 10:48:53 -0500 Subject: [PATCH 2/6] Fix inconsistent result after apply error when adding CLOUD_IAM_GROUP users with capitalized domain names. (#17533) --- .../services/sql/resource_sql_user.go | 23 ++++ .../services/sql/resource_sql_user_test.go | 114 +++++++++++++++++- 2 files changed, 132 insertions(+), 5 deletions(-) diff --git a/mmv1/third_party/terraform/services/sql/resource_sql_user.go b/mmv1/third_party/terraform/services/sql/resource_sql_user.go index af6495642993..d46954bbc028 100644 --- a/mmv1/third_party/terraform/services/sql/resource_sql_user.go +++ b/mmv1/third_party/terraform/services/sql/resource_sql_user.go @@ -26,6 +26,22 @@ func diffSuppressIamUserName(_, old, new string, d *schema.ResourceData) bool { return true } + // MySQL casts all hostnames to lowercase. For MySQL Cloud IAM Groups + // make sure comparison checks lowercase everything after the "@" symbol. + // Only MySQL has "%" populated for empty hostnames so we can use + // that to identify MySQL Cloud IAM Groups. + if strings.Contains(userType, "CLOUD_IAM_GROUP") && d.Get("host") == "%" { + splitName := strings.SplitN(new, "@", 2) + if len(splitName) == 2 { + groupUsername := splitName[0] + groupHostname := splitName[1] + groupName := groupUsername + "@" + strings.ToLower(groupHostname) + if old == groupName { + return true + } + } + } + return false } @@ -392,6 +408,13 @@ func resourceSqlUserRead(d *schema.ResourceData, meta interface{}) error { var username string if !(strings.Contains(databaseInstance.DatabaseVersion, "POSTGRES") || currentUser.Type == "CLOUD_IAM_GROUP") { username = strings.Split(name, "@")[0] + } else if strings.Contains(databaseInstance.DatabaseVersion, "MYSQL") && currentUser.Type == "CLOUD_IAM_GROUP" { + splitName := strings.SplitN(name, "@", 2) + if len(splitName) == 2 { + groupUsername := splitName[0] + groupHostname := splitName[1] + username = groupUsername + "@" + strings.ToLower(groupHostname) + } } else { username = name } diff --git a/mmv1/third_party/terraform/services/sql/resource_sql_user_test.go b/mmv1/third_party/terraform/services/sql/resource_sql_user_test.go index 23f15bbda041..917a4075020a 100644 --- a/mmv1/third_party/terraform/services/sql/resource_sql_user_test.go +++ b/mmv1/third_party/terraform/services/sql/resource_sql_user_test.go @@ -136,7 +136,66 @@ func TestAccSqlUser_iamGroupUser(t *testing.T) { CheckDestroy: testAccSqlUserDestroyProducer(t), Steps: []resource.TestStep{ { - Config: testGoogleSqlUser_iamGroupUser(instance), + Config: testGoogleSqlUser_iamGroupUser("iam-group-auth-test-group@google.com", instance), + Check: resource.ComposeTestCheckFunc( + testAccCheckGoogleSqlUserExists(t, "google_sql_user.user"), + ), + }, + { + ResourceName: "google_sql_user.user", + ImportStateId: fmt.Sprintf("%s/%s/iam-group-auth-test-group@google.com", envvar.GetTestProjectFromEnv(), instance), + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"password"}, + }, + }, + }) +} + +func TestAccSqlUser_iamGroupUser_capitalizedHostName(t *testing.T) { + // Multiple fine-grained resources + acctest.SkipIfVcr(t) + t.Parallel() + + instance := fmt.Sprintf("tf-test-%d", acctest.RandInt(t)) + acctest.VcrTest(t, resource.TestCase{ + PreCheck: func() { acctest.AccTestPreCheck(t) }, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), + CheckDestroy: testAccSqlUserDestroyProducer(t), + Steps: []resource.TestStep{ + { + Config: testGoogleSqlUser_iamGroupUser("iam-group-auth-test-group@GOOGLE.com", instance), + Check: resource.ComposeTestCheckFunc( + testAccCheckGoogleSqlUserExists(t, "google_sql_user.user"), + ), + }, + { + ResourceName: "google_sql_user.user", + ImportStateId: fmt.Sprintf("%s/%s/iam-group-auth-test-group@google.com", envvar.GetTestProjectFromEnv(), instance), + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"password"}, + }, + }, + }) +} + +func TestAccSqlUser_postgres_iamGroupUser(t *testing.T) { + // Multiple fine-grained resources + acctest.SkipIfVcr(t) + t.Parallel() + + instance := fmt.Sprintf("tf-test-%d", acctest.RandInt(t)) + acctest.VcrTest(t, resource.TestCase{ + PreCheck: func() { acctest.AccTestPreCheck(t) }, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), + ExternalProviders: map[string]resource.ExternalProvider{ + "time": {}, + }, + CheckDestroy: testAccSqlUserDestroyProducer(t), + Steps: []resource.TestStep{ + { + Config: testGoogleSqlUser_postgres_iamGroupUser("iam-group-auth-test-group@google.com", instance), Check: resource.ComposeTestCheckFunc( testAccCheckGoogleSqlUserExists(t, "google_sql_user.user"), ), @@ -260,6 +319,10 @@ func testAccCheckGoogleSqlUserExists(t *testing.T, n string) resource.TestCheckF name := rs.Primary.Attributes["name"] instance := rs.Primary.Attributes["instance"] host := rs.Primary.Attributes["host"] + databaseInstance, err := sql.NewClient(config, config.UserAgent).Instances.Get(config.Project, instance).Do() + if err != nil { + return err + } users, err := sql.NewClient(config, config.UserAgent).Users.List(config.Project, instance).Do() @@ -268,7 +331,16 @@ func testAccCheckGoogleSqlUserExists(t *testing.T, n string) resource.TestCheckF } for _, user := range users.Items { - if user.Name == name && user.Host == host { + username := name + if user.Type == "CLOUD_IAM_GROUP" && strings.Contains(databaseInstance.DatabaseVersion, "MYSQL") { + splitName := strings.SplitN(name, "@", 2) + if len(splitName) == 2 { + groupUsername := splitName[0] + groupHostname := splitName[1] + username = groupUsername + "@" + strings.ToLower(groupHostname) + } + } + if user.Name == username && user.Host == host { return nil } } @@ -1092,7 +1164,7 @@ resource "google_project_iam_member" "sa_user" { `, instance, instance, instance, instance) } -func testGoogleSqlUser_iamGroupUser(instance string) string { +func testGoogleSqlUser_iamGroupUser(username, instance string) string { return fmt.Sprintf(` resource "google_sql_database_instance" "instance" { name = "%s" @@ -1109,9 +1181,41 @@ resource "google_sql_database_instance" "instance" { } resource "google_sql_user" "user" { - name = "iam-group-auth-test-group@google.com" + name = "%s" instance = google_sql_database_instance.instance.name type = "CLOUD_IAM_GROUP" } -`, instance) +`, instance, username) +} + +func testGoogleSqlUser_postgres_iamGroupUser(username, instance string) string { + return fmt.Sprintf(` +resource "google_sql_database_instance" "instance" { + name = "%s" + region = "us-central1" + database_version = "POSTGRES_9_6" + deletion_protection = false + settings { + tier = "db-f1-micro" + database_flags { + name = "cloudsql.iam_authentication" + value = "on" + } + } +} + +# TODO: Remove with resolution of https://github.com/hashicorp/terraform-provider-google/issues/14233 +resource "time_sleep" "wait_60_seconds" { + depends_on = [google_sql_database_instance.instance] + + create_duration = "60s" +} + +resource "google_sql_user" "user" { + depends_on = [time_sleep.wait_60_seconds] + name = "%s" + instance = google_sql_database_instance.instance.name + type = "CLOUD_IAM_GROUP" +} +`, instance, username) } From d362b52742722629013d100170208ced4c0e7e20 Mon Sep 17 00:00:00 2001 From: Prem Kamal Jain <108341999+premkjain@users.noreply.github.com> Date: Thu, 11 Jun 2026 22:09:08 +0530 Subject: [PATCH 3/6] Update BackupDR Beta endpoint to v1beta (#17896) --- mmv1/products/backupdr/product.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mmv1/products/backupdr/product.yaml b/mmv1/products/backupdr/product.yaml index 4a72f271d356..6ab840c3297f 100644 --- a/mmv1/products/backupdr/product.yaml +++ b/mmv1/products/backupdr/product.yaml @@ -18,7 +18,7 @@ versions: - name: 'ga' base_url: 'https://backupdr.googleapis.com/v1/' - name: 'beta' - base_url: 'https://backupdr.googleapis.com/v1/' + base_url: 'https://backupdr.googleapis.com/v1beta/' scopes: - 'https://www.googleapis.com/auth/cloud-platform' async: From 996a331fee6e6b49b24aed136d0c3a945fecd72f Mon Sep 17 00:00:00 2001 From: Nathan Herring Date: Thu, 11 Jun 2026 10:30:53 -0700 Subject: [PATCH 4/6] Fix GPU diff in Cloud Run Service (#17886) --- .../terraform/constants/cloud_run_service.go.tmpl | 11 +++++++++++ .../services/cloudrun/cloud_run_service_gpu.tf.tmpl | 9 ++++----- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/mmv1/templates/terraform/constants/cloud_run_service.go.tmpl b/mmv1/templates/terraform/constants/cloud_run_service.go.tmpl index 0b748a4b3ba6..6bf502a7558d 100644 --- a/mmv1/templates/terraform/constants/cloud_run_service.go.tmpl +++ b/mmv1/templates/terraform/constants/cloud_run_service.go.tmpl @@ -9,6 +9,7 @@ func revisionNameCustomizeDiff(_ context.Context, diff *schema.ResourceDiff, v i var cloudRunGoogleProvidedTemplateAnnotations = regexp.MustCompile(`template\.0\.metadata\.0\.annotations\.run\.googleapis\.com/sandbox`) var cloudRunGoogleProvidedTemplateAnnotations_autoscaling_maxscale = regexp.MustCompile(`template\.0\.metadata\.0\.annotations\.autoscaling\.knative\.dev/maxScale`) +var cloudRunGoogleProvidedTemplateAnnotations_gpu_zonal_redundancy_disabled = regexp.MustCompile(`template\.0\.metadata\.0\.annotations\.run\.googleapis\.com/gpu-zonal-redundancy-disabled`) func cloudrunTemplateAnnotationDiffSuppress(k, old, new string, d *schema.ResourceData) bool { // Suppress diffs for the annotations provided by API @@ -21,6 +22,16 @@ func cloudrunTemplateAnnotationDiffSuppress(k, old, new string, d *schema.Resour return true } + if cloudRunGoogleProvidedTemplateAnnotations_gpu_zonal_redundancy_disabled.MatchString(k) && new == "" { + if limitsRaw, ok := d.GetOk("template.0.spec.0.containers.0.resources.0.limits"); ok { + if limits, ok := limitsRaw.(map[string]interface{}); ok { + if _, hasGpu := limits["nvidia.com/gpu"]; hasGpu { + return true + } + } + } + } + // For other keys, don't suppress diff. return false } diff --git a/mmv1/templates/terraform/samples/services/cloudrun/cloud_run_service_gpu.tf.tmpl b/mmv1/templates/terraform/samples/services/cloudrun/cloud_run_service_gpu.tf.tmpl index 06b5c816cfb7..23aae8245bfa 100644 --- a/mmv1/templates/terraform/samples/services/cloudrun/cloud_run_service_gpu.tf.tmpl +++ b/mmv1/templates/terraform/samples/services/cloudrun/cloud_run_service_gpu.tf.tmpl @@ -2,17 +2,16 @@ resource "google_cloud_run_service" "{{$.PrimaryResourceId}}" { name = "{{index $.ResourceIdVars "cloud_run_service_name"}}" location = "us-central1" - metadata { - annotations = { - "run.googleapis.com/launch-stage" = "BETA" - } - } template { metadata { annotations = { "autoscaling.knative.dev/maxScale": "1" "run.googleapis.com/cpu-throttling": "false" + # Explicitly disable zonal redundancy to bypass quota limits in the HashiCorp test project. + # Alternatively, if quota is granted to the test project, this annotation can be removed + # to properly test the DiffSuppressFunc. + "run.googleapis.com/gpu-zonal-redundancy-disabled": "true" } } spec { From 3fb918e4dd107128c50e00f66e6dc03660a40c5d Mon Sep 17 00:00:00 2001 From: jcromanu <14983382+jcromanu@users.noreply.github.com> Date: Thu, 11 Jun 2026 12:15:16 -0600 Subject: [PATCH 5/6] Migrate flattenAccessConfigs, flattenIpv6AccessConfigs ,flattenAliasIpRange , flattenNetworkInterfacesflattenIpv6AliasRange functions in compute_instance_helpers.go.tmpl to use direct HTTP rather than a client library (#17706) --- .../compute/compute_instance_helpers.go.tmpl | 324 ++++++++++++++---- .../data_source_google_compute_instance.go | 6 +- .../compute/resource_compute_instance.go.tmpl | 21 +- ...resource_compute_instance_template.go.tmpl | 6 +- ...e_compute_region_instance_template.go.tmpl | 12 +- .../pkg/services/compute/compute_instance.go | 18 +- 6 files changed, 315 insertions(+), 72 deletions(-) diff --git a/mmv1/third_party/terraform/services/compute/compute_instance_helpers.go.tmpl b/mmv1/third_party/terraform/services/compute/compute_instance_helpers.go.tmpl index 00c290626ceb..0c487365286d 100644 --- a/mmv1/third_party/terraform/services/compute/compute_instance_helpers.go.tmpl +++ b/mmv1/third_party/terraform/services/compute/compute_instance_helpers.go.tmpl @@ -1,7 +1,9 @@ package compute import ( + "encoding/json" "fmt" + "log" "reflect" "strconv" @@ -53,7 +55,7 @@ func expandAliasIpRanges(ranges []interface{}) []*compute.AliasIpRange { return ipRanges } -func flattenAliasIpRange(d *schema.ResourceData, ranges []*compute.AliasIpRange, i int) []map[string]interface{} { +func flattenAliasIpRange(d *schema.ResourceData, ranges []interface{}, i int) []map[string]interface{} { prefix := fmt.Sprintf("network_interface.%d", i) configData := []map[string]interface{}{} @@ -62,10 +64,22 @@ func flattenAliasIpRange(d *schema.ResourceData, ranges []*compute.AliasIpRange, } apiData := make([]map[string]interface{}, 0, len(ranges)) - for _, ipRange := range ranges { + for _, raw := range ranges { + ipRange, ok := raw.(map[string]interface{}) + if !ok { + continue + } + ipCidrRange, ok := ipRange["ipCidrRange"].(string) + if !ok && ipRange["ipCidrRange"] != nil { + log.Printf("[WARN] flattenAliasIpRange: unexpected type for ipCidrRange: %T", ipRange["ipCidrRange"]) + } + subnetworkRangeName, ok := ipRange["subnetworkRangeName"].(string) + if !ok && ipRange["subnetworkRangeName"] != nil { + log.Printf("[WARN] flattenAliasIpRange: unexpected type for subnetworkRangeName: %T", ipRange["subnetworkRangeName"]) + } apiData = append(apiData, map[string]interface{}{ - "ip_cidr_range": ipRange.IpCidrRange, - "subnetwork_range_name": ipRange.SubnetworkRangeName, + "ip_cidr_range": ipCidrRange, + "subnetwork_range_name": subnetworkRangeName, }) } @@ -78,7 +92,7 @@ func flattenAliasIpRange(d *schema.ResourceData, ranges []*compute.AliasIpRange, } {{ if ne $.TargetVersionName `ga` -}} -func flattenIpv6AliasRange(d *schema.ResourceData, ranges []*compute.AliasIpRange, i int) []map[string]interface{} { +func flattenIpv6AliasRange(d *schema.ResourceData, ranges []interface{}, i int) []map[string]interface{} { prefix := fmt.Sprintf("network_interface.%d", i) configData := []map[string]interface{}{} @@ -87,10 +101,22 @@ func flattenIpv6AliasRange(d *schema.ResourceData, ranges []*compute.AliasIpRang } apiData := make([]map[string]interface{}, 0, len(ranges)) - for _, ipRange := range ranges { + for _, raw := range ranges { + ipRange, ok := raw.(map[string]interface{}) + if !ok { + continue + } + ipCidrRange, ok := ipRange["ipCidrRange"].(string) + if !ok && ipRange["ipCidrRange"] != nil { + log.Printf("[WARN] flattenIpv6AliasRange: unexpected type for ipCidrRange: %T", ipRange["ipCidrRange"]) + } + subnetworkRangeName, ok := ipRange["subnetworkRangeName"].(string) + if !ok && ipRange["subnetworkRangeName"] != nil { + log.Printf("[WARN] flattenIpv6AliasRange: unexpected type for subnetworkRangeName: %T", ipRange["subnetworkRangeName"]) + } apiData = append(apiData, map[string]interface{}{ - "ip_cidr_range": ipRange.IpCidrRange, - "subnetwork_range_name": ipRange.SubnetworkRangeName, + "ip_cidr_range": ipCidrRange, + "subnetwork_range_name": subnetworkRangeName, }) } @@ -520,118 +546,286 @@ func flattenComputePreemptionNoticeDuration(v *compute.Duration) []interface{} { } {{- end }} -func flattenAccessConfigs(accessConfigs []*compute.AccessConfig) ([]map[string]interface{}, string) { - flattened := make([]map[string]interface{}, len(accessConfigs)) +func flattenAccessConfigs(accessConfigs []interface{}) ([]map[string]interface{}, string) { + flattened := make([]map[string]interface{}, 0, len(accessConfigs)) natIP := "" - for i, ac := range accessConfigs { - flattened[i] = map[string]interface{}{ - "nat_ip": ac.NatIP, - "network_tier": ac.NetworkTier, + for _, raw := range accessConfigs { + ac, ok := raw.(map[string]interface{}) + if !ok { + continue + } + natIPVal, ok := ac["natIP"].(string) + if !ok && ac["natIP"] != nil { + log.Printf("[WARN] flattenAccessConfigs: unexpected type for natIP: %T", ac["natIP"]) + } + networkTier, ok := ac["networkTier"].(string) + if !ok && ac["networkTier"] != nil { + log.Printf("[WARN] flattenAccessConfigs: unexpected type for networkTier: %T", ac["networkTier"]) + } + entry := map[string]interface{}{ + "nat_ip": natIPVal, + "network_tier": networkTier, } - if ac.SetPublicPtr { - flattened[i]["public_ptr_domain_name"] = ac.PublicPtrDomainName + if setPublicPtr, ok := ac["setPublicPtr"].(bool); ok && setPublicPtr { + entry["public_ptr_domain_name"] = ac["publicPtrDomainName"] + } else if !ok && ac["setPublicPtr"] != nil { + log.Printf("[WARN] flattenAccessConfigs: unexpected type for setPublicPtr: %T", ac["setPublicPtr"]) } if natIP == "" { - natIP = ac.NatIP + natIP = natIPVal } {{- if ne $.TargetVersionName "ga" }} - if ac.SecurityPolicy != "" { - flattened[i]["security_policy"] = ac.SecurityPolicy + if sp, ok := ac["securityPolicy"].(string); ok && sp != "" { + entry["security_policy"] = sp + } else if !ok && ac["securityPolicy"] != nil { + log.Printf("[WARN] flattenAccessConfigs: unexpected type for securityPolicy: %T", ac["securityPolicy"]) } {{- end }} + flattened = append(flattened, entry) } return flattened, natIP } -func flattenIpv6AccessConfigs(ipv6AccessConfigs []*compute.AccessConfig) []map[string]interface{} { - flattened := make([]map[string]interface{}, len(ipv6AccessConfigs)) - for i, ac := range ipv6AccessConfigs { - flattened[i] = map[string]interface{}{ - "network_tier": ac.NetworkTier, +func flattenIpv6AccessConfigs(ipv6AccessConfigs []interface{}) []map[string]interface{} { + flattened := make([]map[string]interface{}, 0, len(ipv6AccessConfigs)) + for _, raw := range ipv6AccessConfigs { + ac, ok := raw.(map[string]interface{}) + if !ok { + continue + } + networkTier, ok := ac["networkTier"].(string) + if !ok && ac["networkTier"] != nil { + log.Printf("[WARN] flattenIpv6AccessConfigs: unexpected type for networkTier: %T", ac["networkTier"]) + } + publicPtrDomainName, ok := ac["publicPtrDomainName"].(string) + if !ok && ac["publicPtrDomainName"] != nil { + log.Printf("[WARN] flattenIpv6AccessConfigs: unexpected type for publicPtrDomainName: %T", ac["publicPtrDomainName"]) } - flattened[i]["public_ptr_domain_name"] = ac.PublicPtrDomainName - flattened[i]["external_ipv6"] = ac.ExternalIpv6 - flattened[i]["external_ipv6_prefix_length"] = strconv.FormatInt(ac.ExternalIpv6PrefixLength, 10) - flattened[i]["name"] = ac.Name + externalIpv6, ok := ac["externalIpv6"].(string) + if !ok && ac["externalIpv6"] != nil { + log.Printf("[WARN] flattenIpv6AccessConfigs: unexpected type for externalIpv6: %T", ac["externalIpv6"]) + } + name, ok := ac["name"].(string) + if !ok && ac["name"] != nil { + log.Printf("[WARN] flattenIpv6AccessConfigs: unexpected type for name: %T", ac["name"]) + } + entry := map[string]interface{}{ + "network_tier": networkTier, + } + entry["public_ptr_domain_name"] = publicPtrDomainName + entry["external_ipv6"] = externalIpv6 + entry["external_ipv6_prefix_length"] = strconv.FormatInt(flattenNetworkInterfaceInt64(ac["externalIpv6PrefixLength"]), 10) + entry["name"] = name {{- if ne $.TargetVersionName "ga" }} - if ac.SecurityPolicy != "" { - flattened[i]["security_policy"] = ac.SecurityPolicy + if sp, ok := ac["securityPolicy"].(string); ok && sp != "" { + entry["security_policy"] = sp + } else if !ok && ac["securityPolicy"] != nil { + log.Printf("[WARN] flattenIpv6AccessConfigs: unexpected type for securityPolicy: %T", ac["securityPolicy"]) } {{- end }} + flattened = append(flattened, entry) } return flattened } -func flattenNetworkInterfaces(d *schema.ResourceData, config *transport_tpg.Config, networkInterfaces []*compute.NetworkInterface) ([]map[string]interface{}, string, string, string, error) { +// flattenNetworkInterfaceInt64 coerces a numeric value decoded from a JSON +// response (typically float64) into an int64. It tolerates the int variants in +// case the value originated from a typed struct converted via ConvertToMap. +func flattenNetworkInterfaceInt64(v interface{}) int64 { + switch t := v.(type) { + case float64: + return int64(t) + case int64: + return t + case int: + return int64(t) + } + return 0 +} + +func flattenNetworkInterfaces(d *schema.ResourceData, config *transport_tpg.Config, networkInterfaces []interface{}) ([]map[string]interface{}, string, string, string, error) { flattened := make([]map[string]interface{}, len(networkInterfaces)) var region, internalIP, externalIP string - for i, iface := range networkInterfaces { + for i, raw := range networkInterfaces { + iface, ok := raw.(map[string]interface{}) + if !ok { + return nil, "", "", "", fmt.Errorf("expected map[string]interface{} for network interface element at index %d, got %T", i, raw) + } + + accessConfigs, ok := iface["accessConfigs"].([]interface{}) + if !ok && iface["accessConfigs"] != nil { + log.Printf("[WARN] flattenNetworkInterfaces: unexpected type for accessConfigs at index %d: %T", i, iface["accessConfigs"]) + } + if accessConfigs == nil { + accessConfigs = []interface{}{} + } + ipv6AccessConfigs, ok := iface["ipv6AccessConfigs"].([]interface{}) + if !ok && iface["ipv6AccessConfigs"] != nil { + log.Printf("[WARN] flattenNetworkInterfaces: unexpected type for ipv6AccessConfigs at index %d: %T", i, iface["ipv6AccessConfigs"]) + } + if ipv6AccessConfigs == nil { + ipv6AccessConfigs = []interface{}{} + } + aliasIpRanges, ok := iface["aliasIpRanges"].([]interface{}) + if !ok && iface["aliasIpRanges"] != nil { + log.Printf("[WARN] flattenNetworkInterfaces: unexpected type for aliasIpRanges at index %d: %T", i, iface["aliasIpRanges"]) + } + if aliasIpRanges == nil { + aliasIpRanges = []interface{}{} + } + var ac []map[string]interface{} - ac, externalIP = flattenAccessConfigs(iface.AccessConfigs) + ac, externalIP = flattenAccessConfigs(accessConfigs) - subnet, err := tpgresource.ParseSubnetworkFieldValue(iface.Subnetwork, d, config) + networkIP, ok := iface["networkIP"].(string) + if !ok && iface["networkIP"] != nil { + log.Printf("[WARN] flattenNetworkInterfaces: unexpected type for networkIP at index %d: %T", i, iface["networkIP"]) + } + network, ok := iface["network"].(string) + if !ok && iface["network"] != nil { + log.Printf("[WARN] flattenNetworkInterfaces: unexpected type for network at index %d: %T", i, iface["network"]) + } + subnetwork, ok := iface["subnetwork"].(string) + if !ok && iface["subnetwork"] != nil { + log.Printf("[WARN] flattenNetworkInterfaces: unexpected type for subnetwork at index %d: %T", i, iface["subnetwork"]) + } + parentNicName, ok := iface["parentNicName"].(string) + if !ok && iface["parentNicName"] != nil { + log.Printf("[WARN] flattenNetworkInterfaces: unexpected type for parentNicName at index %d: %T", i, iface["parentNicName"]) + } + nicType, ok := iface["nicType"].(string) + if !ok && iface["nicType"] != nil { + log.Printf("[WARN] flattenNetworkInterfaces: unexpected type for nicType at index %d: %T", i, iface["nicType"]) + } + stackType, ok := iface["stackType"].(string) + if !ok && iface["stackType"] != nil { + log.Printf("[WARN] flattenNetworkInterfaces: unexpected type for stackType at index %d: %T", i, iface["stackType"]) + } + ipv6Address, ok := iface["ipv6Address"].(string) + if !ok && iface["ipv6Address"] != nil { + log.Printf("[WARN] flattenNetworkInterfaces: unexpected type for ipv6Address at index %d: %T", i, iface["ipv6Address"]) + } + igmpQuery, ok := iface["igmpQuery"].(string) + if !ok && iface["igmpQuery"] != nil { + log.Printf("[WARN] flattenNetworkInterfaces: unexpected type for igmpQuery at index %d: %T", i, iface["igmpQuery"]) + } + + subnet, err := tpgresource.ParseSubnetworkFieldValue(subnetwork, d, config) if err != nil { return nil, "", "", "", err } region = subnet.Region flattened[i] = map[string]interface{}{ - "network_ip": iface.NetworkIP, - "network": tpgresource.ConvertSelfLinkToV1(iface.Network), - "parent_nic_name": iface.ParentNicName, - "vlan": iface.Vlan, - "subnetwork": tpgresource.ConvertSelfLinkToV1(iface.Subnetwork), + "network_ip": networkIP, + "network": tpgresource.ConvertSelfLinkToV1(network), + "parent_nic_name": parentNicName, + "vlan": flattenNetworkInterfaceInt64(iface["vlan"]), + "subnetwork": tpgresource.ConvertSelfLinkToV1(subnetwork), "subnetwork_project": subnet.Project, "access_config": ac, - "alias_ip_range": flattenAliasIpRange(d, iface.AliasIpRanges, i), + "alias_ip_range": flattenAliasIpRange(d, aliasIpRanges, i), {{ if ne $.TargetVersionName `ga` -}} - "alias_ipv6_range": flattenIpv6AliasRange(d, iface.AliasIpv6Ranges, i), + "alias_ipv6_range": flattenIpv6AliasRange(d, getInterfaceSlice(iface["aliasIpv6Ranges"]), i), {{- end }} - "nic_type": iface.NicType, - "stack_type": iface.StackType, - "ipv6_access_config": flattenIpv6AccessConfigs(iface.Ipv6AccessConfigs), - "ipv6_address": iface.Ipv6Address, - "queue_count": iface.QueueCount, - "internal_ipv6_prefix_length": iface.InternalIpv6PrefixLength, - "igmp_query": iface.IgmpQuery, + "nic_type": nicType, + "stack_type": stackType, + "ipv6_access_config": flattenIpv6AccessConfigs(ipv6AccessConfigs), + "ipv6_address": ipv6Address, + "queue_count": flattenNetworkInterfaceInt64(iface["queueCount"]), + "internal_ipv6_prefix_length": flattenNetworkInterfaceInt64(iface["internalIpv6PrefixLength"]), + "igmp_query": igmpQuery, } // Instance template interfaces never have names, so they're absent // in the instance template network_interface schema. We want to use the // same flattening code for both resource types, so we avoid trying to // set the name field when it's not set at the GCE end. - if iface.Name != "" { - flattened[i]["name"] = iface.Name + if name, ok := iface["name"].(string); ok && name != "" { + flattened[i]["name"] = name + } else if !ok && iface["name"] != nil { + log.Printf("[WARN] flattenNetworkInterfaces: unexpected type for name at index %d: %T", i, iface["name"]) } {{- if ne $.TargetVersionName "ga" }} - if iface.MacAddress != "" { - flattened[i]["mac_address"] = iface.MacAddress + if macAddress, ok := iface["macAddress"].(string); ok && macAddress != "" { + flattened[i]["mac_address"] = macAddress + } else if !ok && iface["macAddress"] != nil { + log.Printf("[WARN] flattenNetworkInterfaces: unexpected type for macAddress at index %d: %T", i, iface["macAddress"]) } {{- end }} if internalIP == "" { - internalIP = iface.NetworkIP + internalIP = networkIP } - if iface.NetworkAttachment != "" { - networkAttachment, err := tpgresource.GetRelativePath(iface.NetworkAttachment) + if networkAttachment, ok := iface["networkAttachment"].(string); ok && networkAttachment != "" { + relativeNetworkAttachment, err := tpgresource.GetRelativePath(networkAttachment) if err != nil { return nil, "", "", "", err } - flattened[i]["network_attachment"] = networkAttachment + flattened[i]["network_attachment"] = relativeNetworkAttachment + } else if !ok && iface["networkAttachment"] != nil { + log.Printf("[WARN] flattenNetworkInterfaces: unexpected type for networkAttachment at index %d: %T", i, iface["networkAttachment"]) } {{ if ne $.TargetVersionName `ga` -}} // the security_policy for a network_interface is found in one of its accessConfigs. - if len(iface.AccessConfigs) > 0 && iface.AccessConfigs[0].SecurityPolicy != "" { - flattened[i]["security_policy"] = iface.AccessConfigs[0].SecurityPolicy - } else if len(iface.Ipv6AccessConfigs) > 0 && iface.Ipv6AccessConfigs[0].SecurityPolicy != "" { - flattened[i]["security_policy"] = iface.Ipv6AccessConfigs[0].SecurityPolicy + if sp := firstAccessConfigSecurityPolicy(accessConfigs); sp != "" { + flattened[i]["security_policy"] = sp + } else if sp := firstAccessConfigSecurityPolicy(ipv6AccessConfigs); sp != "" { + flattened[i]["security_policy"] = sp } {{- end }} } return flattened, region, internalIP, externalIP, nil } +// getInterfaceSlice safely extracts a []interface{} from a value decoded from a +// JSON response, returning nil when the value is absent or of another type. +func getInterfaceSlice(v interface{}) []interface{} { + s, _ := v.([]interface{}) + return s +} + +{{ if ne $.TargetVersionName `ga` -}} +// firstAccessConfigSecurityPolicy returns the securityPolicy of the first +// access config in the list, or "" if absent. +func firstAccessConfigSecurityPolicy(accessConfigs []interface{}) string { + if len(accessConfigs) == 0 { + return "" + } + ac, ok := accessConfigs[0].(map[string]interface{}) + if !ok { + return "" + } + sp, ok := ac["securityPolicy"].(string) + if !ok && ac["securityPolicy"] != nil { + log.Printf("[WARN] firstAccessConfigSecurityPolicy: unexpected type for securityPolicy: %T", ac["securityPolicy"]) + } + return sp +} +{{- end }} + +// networkInterfacesToInterface converts a slice of typed Apiary network +// interface structs into the []interface{} of JSON-shaped maps expected by +// flattenNetworkInterfaces. This bridges callers that still read the instance +// via the typed compute client; once those reads migrate to SendRequest the +// response is already in this shape and the adapter can be dropped. +func networkInterfacesToInterface(networkInterfaces []*compute.NetworkInterface) ([]interface{}, error) { + result := make([]interface{}, 0, len(networkInterfaces)) + for _, ni := range networkInterfaces { + b, err := json.Marshal(ni) + if err != nil { + return nil, err + } + var m map[string]interface{} + if err := json.Unmarshal(b, &m); err != nil { + return nil, err + } + result = append(result, m) + } + return result, nil +} + func expandAccessConfigs(configs []interface{}) []*compute.AccessConfig { acs := make([]*compute.AccessConfig, len(configs)) for i, raw := range configs { @@ -1155,13 +1349,19 @@ func flattenReservationAffinity(affinity map[string]interface{}) []map[string]in return nil } - consumeType, _ := affinity["consumeReservationType"].(string) + consumeType, ok := affinity["consumeReservationType"].(string) + if !ok && affinity["consumeReservationType"] != nil { + log.Printf("[WARN] flattenReservationAffinity: unexpected type for consumeReservationType: %T", affinity["consumeReservationType"]) + } flattened := map[string]interface{}{ "type": consumeType, } if consumeType == "SPECIFIC_RESERVATION" { - key, _ := affinity["key"].(string) + key, ok := affinity["key"].(string) + if !ok && affinity["key"] != nil { + log.Printf("[WARN] flattenReservationAffinity: unexpected type for key: %T", affinity["key"]) + } flattened["specific_reservation"] = []map[string]interface{}{{"{{"}} "key": key, "values": affinity["values"], diff --git a/mmv1/third_party/terraform/services/compute/data_source_google_compute_instance.go b/mmv1/third_party/terraform/services/compute/data_source_google_compute_instance.go index 3e3c76fa6b8f..ccba6fbe2819 100644 --- a/mmv1/third_party/terraform/services/compute/data_source_google_compute_instance.go +++ b/mmv1/third_party/terraform/services/compute/data_source_google_compute_instance.go @@ -58,7 +58,11 @@ func dataSourceGoogleComputeInstanceRead(d *schema.ResourceData, meta interface{ // Set the networks // Use the first external IP found for the default connection info. - networkInterfaces, _, internalIP, externalIP, err := flattenNetworkInterfaces(d, config, instance.NetworkInterfaces) + networkInterfacesRaw, err := networkInterfacesToInterface(instance.NetworkInterfaces) + if err != nil { + return err + } + networkInterfaces, _, internalIP, externalIP, err := flattenNetworkInterfaces(d, config, networkInterfacesRaw) if err != nil { return err } diff --git a/mmv1/third_party/terraform/services/compute/resource_compute_instance.go.tmpl b/mmv1/third_party/terraform/services/compute/resource_compute_instance.go.tmpl index f77416c6d8ca..d609580e9c42 100644 --- a/mmv1/third_party/terraform/services/compute/resource_compute_instance.go.tmpl +++ b/mmv1/third_party/terraform/services/compute/resource_compute_instance.go.tmpl @@ -2234,7 +2234,11 @@ func resourceComputeInstanceRead(d *schema.ResourceData, meta interface{}) error } // Set the networks // Use the first external IP found for the default connection info. - networkInterfaces, _, internalIP, externalIP, err := flattenNetworkInterfaces(d, config, instance.NetworkInterfaces) + networkInterfacesRaw, err := networkInterfacesToInterface(instance.NetworkInterfaces) + if err != nil { + return err + } + networkInterfaces, _, internalIP, externalIP, err := flattenNetworkInterfaces(d, config, networkInterfacesRaw) if err != nil { return err } @@ -2708,7 +2712,10 @@ func resourceComputeInstanceUpdate(d *schema.ResourceData, meta interface{}) err return fmt.Errorf("Error retrieving partner_metadata: %s", err) } - currentPM, _ := instMap["partnerMetadata"].(map[string]interface{}) + currentPM, ok := instMap["partnerMetadata"].(map[string]interface{}) + if !ok && instMap["partnerMetadata"] != nil { + log.Printf("[WARN] resourceComputeInstanceUpdate: unexpected type for partnerMetadata: %T", instMap["partnerMetadata"]) + } patchedPM := resourceInstancePatchPartnerMetadata(d, currentPM) instMap["partnerMetadata"] = patchedPM @@ -4380,7 +4387,10 @@ func resourceComputeInstanceDelete(d *schema.ResourceData, meta interface{}) err opErr := ComputeOperationWaitTime(config, res, project, "instance to delete", userAgent, d.Timeout(schema.TimeoutDelete)) if opErr != nil { // Refresh operation status via its selfLink - selfLink, _ := res["selfLink"].(string) + selfLink, ok := res["selfLink"].(string) + if !ok && res["selfLink"] != nil { + log.Printf("[WARN] resourceComputeInstanceDelete: unexpected type for selfLink: %T", res["selfLink"]) + } if selfLink == "" { return opErr } @@ -4392,7 +4402,10 @@ func resourceComputeInstanceDelete(d *schema.ResourceData, meta interface{}) err UserAgent: userAgent, }) // Do not return an error if the operation actually completed - opStatus, _ := opCheck["status"].(string) + opStatus, ok := opCheck["status"].(string) + if !ok && opCheck["status"] != nil { + log.Printf("[WARN] resourceComputeInstanceDelete: unexpected type for operation status: %T", opCheck["status"]) + } if opCheck == nil || opStatus != "DONE" { return opErr } diff --git a/mmv1/third_party/terraform/services/compute/resource_compute_instance_template.go.tmpl b/mmv1/third_party/terraform/services/compute/resource_compute_instance_template.go.tmpl index 354c46ddec6b..da8c7fda64f9 100644 --- a/mmv1/third_party/terraform/services/compute/resource_compute_instance_template.go.tmpl +++ b/mmv1/third_party/terraform/services/compute/resource_compute_instance_template.go.tmpl @@ -2210,7 +2210,11 @@ func resourceComputeInstanceTemplateRead(d *schema.ResourceData, meta interface{ return err } if instanceTemplate.Properties.NetworkInterfaces != nil { - networkInterfaces, region, _, _, err := flattenNetworkInterfaces(d, config, instanceTemplate.Properties.NetworkInterfaces) + networkInterfacesRaw, err := networkInterfacesToInterface(instanceTemplate.Properties.NetworkInterfaces) + if err != nil { + return err + } + networkInterfaces, region, _, _, err := flattenNetworkInterfaces(d, config, networkInterfacesRaw) if err != nil { return err } diff --git a/mmv1/third_party/terraform/services/compute/resource_compute_region_instance_template.go.tmpl b/mmv1/third_party/terraform/services/compute/resource_compute_region_instance_template.go.tmpl index cd34a8785463..87ea371efed8 100644 --- a/mmv1/third_party/terraform/services/compute/resource_compute_region_instance_template.go.tmpl +++ b/mmv1/third_party/terraform/services/compute/resource_compute_region_instance_template.go.tmpl @@ -4,6 +4,7 @@ package compute import ( "encoding/json" "fmt" + "log" "strings" "time" @@ -1570,7 +1571,10 @@ func resourceComputeRegionInstanceTemplateRead(d *schema.ResourceData, meta inte default: numericId = fmt.Sprintf("%v", v) } - selfLink, _ := res["selfLink"].(string) + selfLink, ok := res["selfLink"].(string) + if !ok && res["selfLink"] != nil { + log.Printf("[WARN] resourceComputeRegionInstanceTemplateRead: unexpected type for selfLink: %T", res["selfLink"]) + } delete(res, "id") resBytes, err := json.Marshal(res) @@ -1696,7 +1700,11 @@ func resourceComputeRegionInstanceTemplateRead(d *schema.ResourceData, meta inte return err } if instanceTemplate.Properties.NetworkInterfaces != nil { - networkInterfaces, region, _, _, err := flattenNetworkInterfaces(d, config, instanceTemplate.Properties.NetworkInterfaces) + networkInterfacesRaw, err := networkInterfacesToInterface(instanceTemplate.Properties.NetworkInterfaces) + if err != nil { + return err + } + networkInterfaces, region, _, _, err := flattenNetworkInterfaces(d, config, networkInterfacesRaw) if err != nil { return err } diff --git a/mmv1/third_party/tgc_next/pkg/services/compute/compute_instance.go b/mmv1/third_party/tgc_next/pkg/services/compute/compute_instance.go index f2c012856e56..826a2268bd75 100644 --- a/mmv1/third_party/tgc_next/pkg/services/compute/compute_instance.go +++ b/mmv1/third_party/tgc_next/pkg/services/compute/compute_instance.go @@ -1,6 +1,7 @@ package compute import ( + "encoding/json" "strings" "github.com/GoogleCloudPlatform/terraform-google-conversion/v7/pkg/registry" @@ -1512,13 +1513,26 @@ func flattenSchedulingTgc(resp *compute.Scheduling) []map[string]interface{} { return []map[string]interface{}{schedulingMap} } +// accessConfigsToInterface converts a typed AccessConfig slice to []interface{} +// (JSON-shaped maps) as required by the shared flatten helpers. +func accessConfigsToInterface(configs []*compute.AccessConfig) []interface{} { + result := make([]interface{}, 0, len(configs)) + for _, ac := range configs { + b, _ := json.Marshal(ac) + var m map[string]interface{} + _ = json.Unmarshal(b, &m) + result = append(result, m) + } + return result +} + func flattenNetworkInterfacesTgc(networkInterfaces []*compute.NetworkInterface, project string) ([]map[string]interface{}, string, string, error) { flattened := make([]map[string]interface{}, len(networkInterfaces)) var internalIP, externalIP string for i, iface := range networkInterfaces { var ac []map[string]interface{} - ac, externalIP = flattenAccessConfigs(iface.AccessConfigs) + ac, externalIP = flattenAccessConfigs(accessConfigsToInterface(iface.AccessConfigs)) flattened[i] = map[string]interface{}{ "network_ip": iface.NetworkIP, @@ -1526,7 +1540,7 @@ func flattenNetworkInterfacesTgc(networkInterfaces []*compute.NetworkInterface, "alias_ip_range": flattenAliasIpRangeTgc(iface.AliasIpRanges), "nic_type": iface.NicType, "stack_type": iface.StackType, - "ipv6_access_config": flattenIpv6AccessConfigs(iface.Ipv6AccessConfigs), + "ipv6_access_config": flattenIpv6AccessConfigs(accessConfigsToInterface(iface.Ipv6AccessConfigs)), "ipv6_address": iface.Ipv6Address, "network": tpgresource.ConvertSelfLinkToV1(iface.Network), "subnetwork": tpgresource.ConvertSelfLinkToV1(iface.Subnetwork), From 7f86df54bd36700c9e22614ce586e9986577f3f0 Mon Sep 17 00:00:00 2001 From: Lin Lin Date: Tue, 9 Jun 2026 02:00:39 +0000 Subject: [PATCH 6/6] Update google_network_services_authz_extension IAP example documentation This change updates the documentation for the 'google_network_services_authz_extension' resource by adding a 'metadata' block to the IAP example and enabling its generation. Changes: - Added 'metadata' block with 'iapPolicyVersion' to 'network_services_authz_extension_iap.tf.tmpl'. - Enabled documentation for the IAP example in 'AuthzExtension.yaml' by removing 'skip_docs'. - Synchronized missing 'AuthzExtension' resource definition and supporting templates. --- mmv1/products/networkservices/AuthzExtension.yaml | 2 -- .../examples/network_services_authz_extension_iap.tf.tmpl | 3 +++ 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/mmv1/products/networkservices/AuthzExtension.yaml b/mmv1/products/networkservices/AuthzExtension.yaml index 419b2836c193..8194f33165cd 100644 --- a/mmv1/products/networkservices/AuthzExtension.yaml +++ b/mmv1/products/networkservices/AuthzExtension.yaml @@ -65,8 +65,6 @@ examples: test_env_vars: project: 'PROJECT_NAME' - name: 'network_services_authz_extension_iap' - skip_docs: true # temporary b/484137930 - skip_test: true # temporary b/484137930 primary_resource_id: 'default' vars: resource_name: 'my-authz-ext' diff --git a/mmv1/templates/terraform/examples/network_services_authz_extension_iap.tf.tmpl b/mmv1/templates/terraform/examples/network_services_authz_extension_iap.tf.tmpl index e2182724c221..14b94264bd92 100644 --- a/mmv1/templates/terraform/examples/network_services_authz_extension_iap.tf.tmpl +++ b/mmv1/templates/terraform/examples/network_services_authz_extension_iap.tf.tmpl @@ -4,4 +4,7 @@ resource "google_network_services_authz_extension" "{{$.PrimaryResourceId}}" { service = "iap.googleapis.com" timeout = "0.1s" + metadata = { + "iapPolicyVersion" = "V1" + } }