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: 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/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/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" + } } 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 { 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/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) } 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),