From a7cbdacae13f77a894a7b37215d7aaadd23a6cc6 Mon Sep 17 00:00:00 2001 From: Igor Nadenenko Date: Wed, 17 Jun 2026 15:17:44 +0200 Subject: [PATCH 01/30] Migrate PT. 5 of compute_instance_helpers.go.tmpl to new API --- .../compute/compute_instance_helpers.go.tmpl | 439 ++++++------------ .../data_source_google_compute_instance.go | 6 +- .../compute/resource_compute_instance.go.tmpl | 87 +++- ...ompute_instance_from_machine_image.go.tmpl | 62 +-- ...resource_compute_instance_template.go.tmpl | 136 +++++- ...e_compute_region_instance_template.go.tmpl | 12 +- 6 files changed, 388 insertions(+), 354 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 72034159cf5f..0cf8c7003b8e 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,6 @@ package compute import ( - "encoding/json" "fmt" "log" "reflect" @@ -12,13 +11,6 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" - "google.golang.org/api/googleapi" - -{{ if eq $.TargetVersionName `ga` }} - "google.golang.org/api/compute/v1" -{{- else }} - compute "google.golang.org/api/compute/v0.beta" -{{- end }} ) func instanceSchedulingNodeAffinitiesElemSchema() *schema.Resource { @@ -136,19 +128,17 @@ func flattenIpv6AliasRange(d *schema.ResourceData, ranges []interface{}, i int) } {{- end }} -func expandScheduling(v interface{}) (*compute.Scheduling, error) { +func expandScheduling(v interface{}) (map[string]interface{}, error) { if v == nil { - // We can't set default values for lists. - return &compute.Scheduling{ - AutomaticRestart: googleapi.Bool(true), + return map[string]interface{}{ + "automaticRestart": true, }, nil } ls := v.([]interface{}) if len(ls) == 0 { - // We can't set default values for lists - return &compute.Scheduling{ - AutomaticRestart: googleapi.Bool(true), + return map[string]interface{}{ + "automaticRestart": true, }, nil } @@ -157,64 +147,55 @@ func expandScheduling(v interface{}) (*compute.Scheduling, error) { } original := ls[0].(map[string]interface{}) - scheduling := &compute.Scheduling{ - ForceSendFields: make([]string, 0, 4), - } + result := map[string]interface{}{} if v, ok := original["automatic_restart"]; ok { - scheduling.AutomaticRestart = googleapi.Bool(v.(bool)) - scheduling.ForceSendFields = append(scheduling.ForceSendFields, "AutomaticRestart") + result["automaticRestart"] = v.(bool) } if v, ok := original["preemptible"]; ok { - scheduling.Preemptible = v.(bool) - scheduling.ForceSendFields = append(scheduling.ForceSendFields, "Preemptible") + result["preemptible"] = v.(bool) } if v, ok := original["on_host_maintenance"]; ok { - scheduling.OnHostMaintenance = v.(string) - scheduling.ForceSendFields = append(scheduling.ForceSendFields, "OnHostMaintenance") + result["onHostMaintenance"] = v.(string) } if v, ok := original["node_affinities"]; ok && v != nil { naSet := v.(*schema.Set).List() - scheduling.NodeAffinities = make([]*compute.SchedulingNodeAffinity, len(ls)) - scheduling.ForceSendFields = append(scheduling.ForceSendFields, "NodeAffinities") + nodeAffinities := make([]interface{}, 0, len(naSet)) for _, nodeAffRaw := range naSet { if nodeAffRaw == nil { continue } nodeAff := nodeAffRaw.(map[string]interface{}) - transformed := &compute.SchedulingNodeAffinity{ - Key: nodeAff["key"].(string), - Operator: nodeAff["operator"].(string), - Values: tpgresource.ConvertStringArr(nodeAff["values"].(*schema.Set).List()), - } - scheduling.NodeAffinities = append(scheduling.NodeAffinities, transformed) + nodeAffinities = append(nodeAffinities, map[string]interface{}{ + "key": nodeAff["key"].(string), + "operator": nodeAff["operator"].(string), + "values": tpgresource.ConvertStringArr(nodeAff["values"].(*schema.Set).List()), + }) } + result["nodeAffinities"] = nodeAffinities } - if v, ok := original["min_node_cpus"]; ok { - scheduling.MinNodeCpus = int64(v.(int)) + if v, ok := original["min_node_cpus"]; ok && v.(int) != 0 { + result["minNodeCpus"] = int64(v.(int)) } if v, ok := original["provisioning_model"]; ok { - scheduling.ProvisioningModel = v.(string) - scheduling.ForceSendFields = append(scheduling.ForceSendFields, "ProvisioningModel") + result["provisioningModel"] = v.(string) } if v, ok := original["instance_termination_action"]; ok { - scheduling.InstanceTerminationAction = v.(string) - scheduling.ForceSendFields = append(scheduling.ForceSendFields, "InstanceTerminationAction") + result["instanceTerminationAction"] = v.(string) } - if v, ok := original["availability_domain"]; ok && v != nil { - scheduling.AvailabilityDomain = int64(v.(int)) + if v, ok := original["availability_domain"]; ok && v != nil && v.(int) != 0 { + result["availabilityDomain"] = int64(v.(int)) } if v, ok := original["max_run_duration"]; ok { transformedMaxRunDuration, err := expandComputeMaxRunDuration(v) if err != nil { return nil, err } - scheduling.MaxRunDuration = transformedMaxRunDuration - scheduling.ForceSendFields = append(scheduling.ForceSendFields, "MaxRunDuration") + result["maxRunDuration"] = transformedMaxRunDuration } if v, ok := original["on_instance_stop_action"]; ok { @@ -222,23 +203,21 @@ func expandScheduling(v interface{}) (*compute.Scheduling, error) { if err != nil { return nil, err } - scheduling.OnInstanceStopAction = transformedOnInstanceStopAction - scheduling.ForceSendFields = append(scheduling.ForceSendFields, "OnInstanceStopAction") + result["onInstanceStopAction"] = transformedOnInstanceStopAction } {{- if ne $.TargetVersionName "ga" }} if v, ok := original["host_error_timeout_seconds"]; ok { - scheduling.HostErrorTimeoutSeconds = int64(v.(int)) //host_error_timeout_seconds doesn't get removed correctly due to an API bug on instances.SetScheduling. - //We need to set it to NullFields as a workaround because nil is rounded to 0 + //We need to send null as a workaround because nil is rounded to 0 if v == 0 || v == nil { - scheduling.NullFields = append(scheduling.NullFields, "HostErrorTimeoutSeconds") + result["hostErrorTimeoutSeconds"] = nil } else { - scheduling.ForceSendFields = append(scheduling.ForceSendFields, "HostErrorTimeoutSeconds") + result["hostErrorTimeoutSeconds"] = int64(v.(int)) } } - if v, ok := original["maintenance_interval"]; ok { - scheduling.MaintenanceInterval = v.(string) + if v, ok := original["maintenance_interval"]; ok && v.(string) != "" { + result["maintenanceInterval"] = v.(string) } if v, ok := original["graceful_shutdown"]; ok { @@ -246,13 +225,11 @@ func expandScheduling(v interface{}) (*compute.Scheduling, error) { if err != nil { return nil, err } - scheduling.GracefulShutdown = transformedGracefulShutdown - scheduling.ForceSendFields = append(scheduling.ForceSendFields, "GracefulShutdown") + result["gracefulShutdown"] = transformedGracefulShutdown } if v, ok := original["skip_guest_os_shutdown"]; ok { - scheduling.SkipGuestOsShutdown = v.(bool) - scheduling.ForceSendFields = append(scheduling.ForceSendFields, "SkipGuestOsShutdown") + result["skipGuestOsShutdown"] = v.(bool) } if v, ok := original["preemption_notice_duration"]; ok { @@ -260,8 +237,7 @@ func expandScheduling(v interface{}) (*compute.Scheduling, error) { if err != nil { return nil, err } - scheduling.PreemptionNoticeDuration = transformedPreemptionNoticeDuration - scheduling.ForceSendFields = append(scheduling.ForceSendFields, "PreemptionNoticeDuration") + result["preemptionNoticeDuration"] = transformedPreemptionNoticeDuration } {{- end }} if v, ok := original["local_ssd_recovery_timeout"]; ok { @@ -269,39 +245,38 @@ func expandScheduling(v interface{}) (*compute.Scheduling, error) { if err != nil { return nil, err } - scheduling.LocalSsdRecoveryTimeout = transformedLocalSsdRecoveryTimeout - scheduling.ForceSendFields = append(scheduling.ForceSendFields, "LocalSsdRecoveryTimeout") + result["localSsdRecoveryTimeout"] = transformedLocalSsdRecoveryTimeout } - if v, ok := original["termination_time"]; ok { - scheduling.TerminationTime = v.(string) + if v, ok := original["termination_time"]; ok && v.(string) != "" { + result["terminationTime"] = v.(string) } - return scheduling, nil + return result, nil } -func expandComputeMaxRunDuration(v interface{}) (*compute.Duration, error) { +func expandComputeMaxRunDuration(v interface{}) (map[string]interface{}, error) { l := v.([]interface{}) - duration := compute.Duration{} if len(l) == 0 || l[0] == nil { return nil, nil } raw := l[0] original := raw.(map[string]interface{}) + result := map[string]interface{}{} transformedNanos, err := expandComputeMaxRunDurationNanos(original["nanos"]) if err != nil { return nil, err } else if val := reflect.ValueOf(transformedNanos); val.IsValid() && !tpgresource.IsEmptyValue(val) { - duration.Nanos = int64(transformedNanos.(int)) + result["nanos"] = int64(transformedNanos.(int)) } transformedSeconds, err := expandComputeMaxRunDurationSeconds(original["seconds"]) if err != nil { return nil, err } else if val := reflect.ValueOf(transformedSeconds); val.IsValid() && !tpgresource.IsEmptyValue(val) { - duration.Seconds = int64(transformedSeconds.(int)) + result["seconds"] = int64(transformedSeconds.(int)) } - return &duration, nil + return result, nil } func expandComputeMaxRunDurationNanos(v interface{}) (interface{}, error) { @@ -312,47 +287,45 @@ func expandComputeMaxRunDurationSeconds(v interface{}) (interface{}, error) { return v, nil } -func expandComputeOnInstanceStopAction(v interface{}) (*compute.SchedulingOnInstanceStopAction, error){ +func expandComputeOnInstanceStopAction(v interface{}) (map[string]interface{}, error) { l := v.([]interface{}) - onInstanceStopAction := compute.SchedulingOnInstanceStopAction{} if len(l) == 0 || l[0] == nil { return nil, nil } raw := l[0] original := raw.(map[string]interface{}) - + if d, ok := original["discard_local_ssd"]; ok { - onInstanceStopAction.DiscardLocalSsd = d.(bool) - } else { - return nil, nil + return map[string]interface{}{ + "discardLocalSsd": d.(bool), + }, nil } - - return &onInstanceStopAction, nil + return nil, nil } -func expandComputeLocalSsdRecoveryTimeout(v interface{}) (*compute.Duration, error) { +func expandComputeLocalSsdRecoveryTimeout(v interface{}) (map[string]interface{}, error) { l := v.([]interface{}) - duration := compute.Duration{} if len(l) == 0 || l[0] == nil { return nil, nil } raw := l[0] original := raw.(map[string]interface{}) + result := map[string]interface{}{} transformedNanos, err := expandComputeLocalSsdRecoveryTimeoutNanos(original["nanos"]) if err != nil { return nil, err } else if val := reflect.ValueOf(transformedNanos); val.IsValid() && !tpgresource.IsEmptyValue(val) { - duration.Nanos = int64(transformedNanos.(int)) + result["nanos"] = int64(transformedNanos.(int)) } transformedSeconds, err := expandComputeLocalSsdRecoveryTimeoutSeconds(original["seconds"]) if err != nil { return nil, err } else if val := reflect.ValueOf(transformedSeconds); val.IsValid() && !tpgresource.IsEmptyValue(val) { - duration.Seconds = int64(transformedSeconds.(int)) + result["seconds"] = int64(transformedSeconds.(int)) } - return &duration, nil + return result, nil } func expandComputeLocalSsdRecoveryTimeoutNanos(v interface{}) (interface{}, error) { @@ -364,191 +337,209 @@ func expandComputeLocalSsdRecoveryTimeoutSeconds(v interface{}) (interface{}, er } {{- if ne $.TargetVersionName "ga" }} -func expandGracefulShutdown(v interface{}) (*compute.SchedulingGracefulShutdown, error) { +func expandGracefulShutdown(v interface{}) (map[string]interface{}, error) { l := v.([]interface{}) - gracefulShutdown := compute.SchedulingGracefulShutdown{} if len(l) == 0 || l[0] == nil { return nil, nil } raw := l[0] original := raw.(map[string]interface{}) + result := map[string]interface{}{ + "enabled": original["enabled"].(bool), + } + originalMaxDuration := original["max_duration"].([]interface{}) maxDuration, err := expandGracefulShutdownMaxDuration(originalMaxDuration) if err != nil { return nil, err } if maxDuration != nil { - gracefulShutdown.MaxDuration = maxDuration + result["maxDuration"] = maxDuration } - gracefulShutdown.Enabled = original["enabled"].(bool) - gracefulShutdown.ForceSendFields = append(gracefulShutdown.ForceSendFields, "Enabled") - return &gracefulShutdown, nil + return result, nil } -func expandGracefulShutdownMaxDuration(v interface{}) (*compute.Duration, error) { +func expandGracefulShutdownMaxDuration(v interface{}) (map[string]interface{}, error) { l := v.([]interface{}) - duration := compute.Duration{} if len(l) == 0 || l[0] == nil { return nil, nil } raw := l[0] maxDurationMap := raw.(map[string]interface{}) - transformedNanos := maxDurationMap["nanos"] - transformedSeconds := maxDurationMap["seconds"] + result := map[string]interface{}{} - if val := reflect.ValueOf(transformedNanos); val.IsValid() && !tpgresource.IsEmptyValue(val) { - duration.Nanos = int64(transformedNanos.(int)) - } - if val := reflect.ValueOf(transformedSeconds); val.IsValid() && !tpgresource.IsEmptyValue(val) { - duration.Seconds = int64(transformedSeconds.(int)) + if transformedNanos := maxDurationMap["nanos"]; reflect.ValueOf(transformedNanos).IsValid() && !tpgresource.IsEmptyValue(reflect.ValueOf(transformedNanos)) { + result["nanos"] = int64(transformedNanos.(int)) } + // Seconds was in ForceSendFields — always include even when zero + result["seconds"] = int64(maxDurationMap["seconds"].(int)) - duration.ForceSendFields = append(duration.ForceSendFields, "Seconds") - - return &duration, nil + return result, nil } {{ end }} -func flattenScheduling(resp *compute.Scheduling) []map[string]interface{} { +func flattenScheduling(resp map[string]interface{}) []map[string]interface{} { schedulingMap := map[string]interface{}{ - "on_host_maintenance": resp.OnHostMaintenance, - "preemptible": resp.Preemptible, - "min_node_cpus": resp.MinNodeCpus, - "provisioning_model": resp.ProvisioningModel, - "instance_termination_action": resp.InstanceTerminationAction, - "availability_domain": resp.AvailabilityDomain, - "termination_time": resp.TerminationTime, + "on_host_maintenance": resp["onHostMaintenance"], + "preemptible": resp["preemptible"], + "min_node_cpus": getInt(resp["minNodeCpus"]), + "provisioning_model": resp["provisioningModel"], + "instance_termination_action": resp["instanceTerminationAction"], + "availability_domain": getInt(resp["availabilityDomain"]), + "termination_time": resp["terminationTime"], } - if resp.AutomaticRestart != nil { - schedulingMap["automatic_restart"] = *resp.AutomaticRestart + if ar, ok := resp["automaticRestart"].(bool); ok { + schedulingMap["automatic_restart"] = ar } - if resp.MaxRunDuration != nil { - schedulingMap["max_run_duration"] = flattenComputeMaxRunDuration(resp.MaxRunDuration) + if maxRun, ok := resp["maxRunDuration"].(map[string]interface{}); ok { + schedulingMap["max_run_duration"] = flattenComputeMaxRunDuration(maxRun) } - if resp.OnInstanceStopAction != nil { - schedulingMap["on_instance_stop_action"] = flattenOnInstanceStopAction(resp.OnInstanceStopAction) + if ois, ok := resp["onInstanceStopAction"].(map[string]interface{}); ok { + schedulingMap["on_instance_stop_action"] = flattenOnInstanceStopAction(ois) } {{ if ne $.TargetVersionName `ga` -}} - schedulingMap["skip_guest_os_shutdown"] = resp.SkipGuestOsShutdown + schedulingMap["skip_guest_os_shutdown"] = resp["skipGuestOsShutdown"] - if resp.HostErrorTimeoutSeconds != 0 { - schedulingMap["host_error_timeout_seconds"] = resp.HostErrorTimeoutSeconds + if h := getInt(resp["hostErrorTimeoutSeconds"]); h != 0 { + schedulingMap["host_error_timeout_seconds"] = h } - if resp.MaintenanceInterval != "" { - schedulingMap["maintenance_interval"] = resp.MaintenanceInterval + if mi, ok := resp["maintenanceInterval"].(string); ok && mi != "" { + schedulingMap["maintenance_interval"] = mi } - if resp.GracefulShutdown != nil { - schedulingMap["graceful_shutdown"] = flattenGracefulShutdown(resp.GracefulShutdown) + if gs, ok := resp["gracefulShutdown"].(map[string]interface{}); ok { + schedulingMap["graceful_shutdown"] = flattenGracefulShutdown(gs) } - if resp.PreemptionNoticeDuration != nil { - schedulingMap["preemption_notice_duration"] = flattenComputePreemptionNoticeDuration(resp.PreemptionNoticeDuration) + if pnd, ok := resp["preemptionNoticeDuration"].(map[string]interface{}); ok { + schedulingMap["preemption_notice_duration"] = flattenComputePreemptionNoticeDuration(pnd) } {{- end }} - if resp.LocalSsdRecoveryTimeout != nil { - schedulingMap["local_ssd_recovery_timeout"] = flattenComputeLocalSsdRecoveryTimeout(resp.LocalSsdRecoveryTimeout) + if lsrt, ok := resp["localSsdRecoveryTimeout"].(map[string]interface{}); ok { + schedulingMap["local_ssd_recovery_timeout"] = flattenComputeLocalSsdRecoveryTimeout(lsrt) } nodeAffinities := schema.NewSet(schema.HashResource(instanceSchedulingNodeAffinitiesElemSchema()), nil) - for _, na := range resp.NodeAffinities { - nodeAffinities.Add(map[string]interface{}{ - "key": na.Key, - "operator": na.Operator, - "values": schema.NewSet(schema.HashString, tpgresource.ConvertStringArrToInterface(na.Values)), - }) + if nodeAffRaw, ok := resp["nodeAffinities"].([]interface{}); ok { + for _, raw := range nodeAffRaw { + na, ok := raw.(map[string]interface{}) + if !ok { + continue + } + valuesRaw, _ := na["values"].([]interface{}) + values := make([]string, 0, len(valuesRaw)) + for _, v := range valuesRaw { + if s, ok := v.(string); ok { + values = append(values, s) + } + } + nodeAffinities.Add(map[string]interface{}{ + "key": na["key"], + "operator": na["operator"], + "values": schema.NewSet(schema.HashString, tpgresource.ConvertStringArrToInterface(values)), + }) + } } schedulingMap["node_affinities"] = nodeAffinities return []map[string]interface{}{schedulingMap} } -func flattenComputeMaxRunDuration(v *compute.Duration) []interface{} { +func flattenComputeMaxRunDuration(v map[string]interface{}) []interface{} { if v == nil { return nil } transformed := make(map[string]interface{}) - transformed["nanos"] = v.Nanos - transformed["seconds"] = v.Seconds + transformed["nanos"] = getInt(v["nanos"]) + transformed["seconds"] = getInt(v["seconds"]) return []interface{}{transformed} } -func flattenOnInstanceStopAction(v *compute.SchedulingOnInstanceStopAction) []interface{} { +func flattenOnInstanceStopAction(v map[string]interface{}) []interface{} { if v == nil { return nil } transformed := make(map[string]interface{}) - transformed["discard_local_ssd"] = v.DiscardLocalSsd + transformed["discard_local_ssd"] = v["discardLocalSsd"] return []interface{}{transformed} } -func flattenComputeLocalSsdRecoveryTimeout(v *compute.Duration) []interface{} { +func flattenComputeLocalSsdRecoveryTimeout(v map[string]interface{}) []interface{} { if v == nil { return nil } transformed := make(map[string]interface{}) - transformed["nanos"] = v.Nanos - transformed["seconds"] = v.Seconds + transformed["nanos"] = getInt(v["nanos"]) + transformed["seconds"] = getInt(v["seconds"]) return []interface{}{transformed} } {{ if ne $.TargetVersionName `ga` -}} -func flattenGracefulShutdown(v *compute.SchedulingGracefulShutdown) []interface{} { +func flattenGracefulShutdown(v map[string]interface{}) []interface{} { if v == nil { return nil } transformed := make(map[string]interface{}) - transformed["enabled"] = v.Enabled - transformed["max_duration"] = flattenGracefulShutdownMaxDuration(v.MaxDuration) + transformed["enabled"] = v["enabled"] + if md, ok := v["maxDuration"].(map[string]interface{}); ok { + transformed["max_duration"] = flattenGracefulShutdownMaxDuration(md) + } return []interface{}{transformed} } -func flattenGracefulShutdownMaxDuration(v *compute.Duration) []interface{} { +func flattenGracefulShutdownMaxDuration(v map[string]interface{}) []interface{} { if v == nil { return nil } transformed := make(map[string]interface{}) - transformed["nanos"] = v.Nanos - transformed["seconds"] = v.Seconds + transformed["nanos"] = getInt(v["nanos"]) + transformed["seconds"] = getInt(v["seconds"]) return []interface{}{transformed} } -func expandComputePreemptionNoticeDuration(v interface{}) (*compute.Duration, error) { +func expandComputePreemptionNoticeDuration(v interface{}) (map[string]interface{}, error) { l := v.([]interface{}) - duration := compute.Duration{} if len(l) == 0 || l[0] == nil { return nil, nil } raw := l[0] original := raw.(map[string]interface{}) + result := map[string]interface{}{} if transformedNanos, ok := original["nanos"]; ok && transformedNanos != nil { - duration.Nanos = int64(transformedNanos.(int)) + if n := int64(transformedNanos.(int)); n != 0 { + result["nanos"] = n + } } if transformedSeconds, ok := original["seconds"]; ok && transformedSeconds != nil { - duration.Seconds = int64(transformedSeconds.(int)) + if s := int64(transformedSeconds.(int)); s != 0 { + result["seconds"] = s + } } - return &duration, nil + if len(result) == 0 { + return nil, nil + } + return result, nil } -func flattenComputePreemptionNoticeDuration(v *compute.Duration) []interface{} { +func flattenComputePreemptionNoticeDuration(v map[string]interface{}) []interface{} { if v == nil { return nil } transformed := make(map[string]interface{}) - transformed["nanos"] = v.Nanos - transformed["seconds"] = v.Seconds + transformed["nanos"] = getInt(v["nanos"]) + transformed["seconds"] = getInt(v["seconds"]) return []interface{}{transformed} } {{- end }} @@ -812,27 +803,6 @@ func firstAccessConfigSecurityPolicy(accessConfigs []interface{}) string { } {{- 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{}) []interface{} { acs := make([]interface{}, len(configs)) for i, raw := range configs { @@ -988,61 +958,6 @@ func expandNetworkInterfaces(d tpgresource.TerraformResourceData, config *transp return ifaces, nil } -// convertViaJSON marshals a map-based representation and unmarshals it into the -// typed Apiary struct (slice). Used by the typed adapters below so the shared -// helpers file does not depend on tpgresource.Convert, which is not available in -// the tgc/tgc_next tpgresource packages. -func convertViaJSON(in, out interface{}) error { - bytes, err := json.Marshal(in) - if err != nil { - return err - } - return json.Unmarshal(bytes, out) -} - -// expandNetworkInterfacesTyped adapts the map-based expandNetworkInterfaces -// output to the typed []*compute.NetworkInterface still required by callers that -// build Apiary request structs directly or read typed fields. -func expandNetworkInterfacesTyped(d tpgresource.TerraformResourceData, config *transport_tpg.Config) ([]*compute.NetworkInterface, error) { - expanded, err := expandNetworkInterfaces(d, config) - if err != nil { - return nil, err - } - ifaces := make([]*compute.NetworkInterface, 0, len(expanded)) - if err := convertViaJSON(expanded, &ifaces); err != nil { - return nil, fmt.Errorf("Error converting network interfaces: %s", err) - } - return ifaces, nil -} - -// expandAccessConfigsTyped / expandIpv6AccessConfigsTyped / expandAliasIpRangesTyped -// adapt the map-based expanders back to the typed Apiary slices still required by -// callers that build compute structs field-by-field. -func expandAccessConfigsTyped(configs []interface{}) ([]*compute.AccessConfig, error) { - return accessConfigsToTyped(expandAccessConfigs(configs)) -} - -func expandIpv6AccessConfigsTyped(configs []interface{}) ([]*compute.AccessConfig, error) { - return accessConfigsToTyped(expandIpv6AccessConfigs(configs)) -} - -func accessConfigsToTyped(expanded []interface{}) ([]*compute.AccessConfig, error) { - acs := make([]*compute.AccessConfig, 0, len(expanded)) - if err := convertViaJSON(expanded, &acs); err != nil { - return nil, fmt.Errorf("Error converting access configs: %s", err) - } - return acs, nil -} - -func expandAliasIpRangesTyped(ranges []interface{}) ([]*compute.AliasIpRange, error) { - expanded := expandAliasIpRanges(ranges) - out := make([]*compute.AliasIpRange, 0, len(expanded)) - if err := convertViaJSON(expanded, &out); err != nil { - return nil, fmt.Errorf("Error converting alias ip ranges: %s", err) - } - return out, nil -} - func flattenServiceAccounts(serviceAccounts []interface{}) []map[string]interface{} { result := make([]map[string]interface{}, len(serviceAccounts)) for i, raw := range serviceAccounts { @@ -1077,35 +992,6 @@ func expandServiceAccounts(configs []interface{}) []interface{} { return accounts } -// expandServiceAccountsTyped adapts the map-based expandServiceAccounts output -// to the typed []*compute.ServiceAccount still required by callers that build -// Apiary request structs directly. -func expandServiceAccountsTyped(configs []interface{}) []*compute.ServiceAccount { - expanded := expandServiceAccounts(configs) - accounts := make([]*compute.ServiceAccount, len(expanded)) - for i, raw := range expanded { - data := raw.(map[string]interface{}) - accounts[i] = &compute.ServiceAccount{ - Email: data["email"].(string), - Scopes: data["scopes"].([]string), - } - } - return accounts -} - -// serviceAccountsToInterface adapts typed []*compute.ServiceAccount decoded from -// an Apiary response into the []interface{} form accepted by flattenServiceAccounts. -func serviceAccountsToInterface(serviceAccounts []*compute.ServiceAccount) []interface{} { - result := make([]interface{}, len(serviceAccounts)) - for i, sa := range serviceAccounts { - result[i] = map[string]interface{}{ - "email": sa.Email, - "scopes": tpgresource.ConvertStringArrToInterface(sa.Scopes), - } - } - return result -} - func flattenGuestAccelerators(accelerators []interface{}) []map[string]interface{} { acceleratorsSchema := make([]map[string]interface{}, 0, len(accelerators)) for _, raw := range accelerators { @@ -1121,20 +1007,6 @@ func flattenGuestAccelerators(accelerators []interface{}) []map[string]interface return acceleratorsSchema } -// guestAcceleratorsToInterface adapts typed []*compute.AcceleratorConfig decoded -// from an Apiary response into the []interface{} form accepted by -// flattenGuestAccelerators. -func guestAcceleratorsToInterface(accelerators []*compute.AcceleratorConfig) []interface{} { - result := make([]interface{}, len(accelerators)) - for i, a := range accelerators { - result[i] = map[string]interface{}{ - "acceleratorCount": a.AcceleratorCount, - "acceleratorType": a.AcceleratorType, - } - } - return result -} - func resourceInstanceTags(d tpgresource.TerraformResourceData) map[string]interface{} { v := d.Get("tags") if v == nil { @@ -1232,35 +1104,6 @@ func flattenAdvancedMachineFeatures(v map[string]interface{}) []map[string]inter return []map[string]interface{}{result} } -func expandAdvancedMachineFeaturesTyped(d tpgresource.TerraformResourceData) *compute.AdvancedMachineFeatures { - if _, ok := d.GetOk("advanced_machine_features"); !ok { - return nil - } - - prefix := "advanced_machine_features.0" - return &compute.AdvancedMachineFeatures{ - EnableNestedVirtualization: d.Get(prefix + ".enable_nested_virtualization").(bool), - ThreadsPerCore: int64(d.Get(prefix + ".threads_per_core").(int)), - TurboMode: d.Get(prefix + ".turbo_mode").(string), - VisibleCoreCount: int64(d.Get(prefix + ".visible_core_count").(int)), - PerformanceMonitoringUnit: d.Get(prefix + ".performance_monitoring_unit").(string), - EnableUefiNetworking: d.Get(prefix + ".enable_uefi_networking").(bool), - } -} - -func flattenAdvancedMachineFeaturesTyped(AdvancedMachineFeatures *compute.AdvancedMachineFeatures) []map[string]interface{} { - if AdvancedMachineFeatures == nil { - return nil - } - return []map[string]interface{}{{"{{"}} - "enable_nested_virtualization": AdvancedMachineFeatures.EnableNestedVirtualization, - "threads_per_core": AdvancedMachineFeatures.ThreadsPerCore, - "turbo_mode": AdvancedMachineFeatures.TurboMode, - "visible_core_count": AdvancedMachineFeatures.VisibleCoreCount, - "performance_monitoring_unit": AdvancedMachineFeatures.PerformanceMonitoringUnit, - "enable_uefi_networking": AdvancedMachineFeatures.EnableUefiNetworking, - {{"}}"}} -} func flattenShieldedVmConfig(shieldedVmConfig map[string]interface{}) []map[string]bool { if shieldedVmConfig == nil { 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 e79e94444cad..f84f5821e84b 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 @@ -152,7 +152,11 @@ func dataSourceGoogleComputeInstanceRead(d *schema.ResourceData, meta interface{ return err } - err = d.Set("scheduling", flattenScheduling(instance.Scheduling)) + schedulingMap, err := tpgresource.ConvertToMap(instance.Scheduling) + if err != nil { + return fmt.Errorf("Error converting scheduling: %s", err) + } + err = d.Set("scheduling", flattenScheduling(schedulingMap)) 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 5ee46c5a202f..0c351506a4d9 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 @@ -1862,12 +1862,7 @@ func expandComputeInstance(project string, d *schema.ResourceData, config *trans disks = append(disks, disk) } - scheduling, err := expandScheduling(d.Get("scheduling")) - if err != nil { - return nil, fmt.Errorf("Error creating scheduling: %s", err) - } - - params, err := expandParamsTyped(d) + params, err := expandParams(d) if err != nil { return nil, fmt.Errorf("Error creating params: %s", err) } @@ -1957,11 +1952,9 @@ func expandComputeInstance(project string, d *schema.ResourceData, config *trans ServiceAccounts: expandServiceAccountsTyped(d.Get("service_account").([]interface{})), GuestAccelerators: accels, MinCpuPlatform: d.Get("min_cpu_platform").(string), - Scheduling: scheduling, DeletionProtection: d.Get("deletion_protection").(bool), Hostname: d.Get("hostname").(string), ForceSendFields: []string{"CanIpForward", "DeletionProtection"}, - AdvancedMachineFeatures: expandAdvancedMachineFeaturesTyped(d), ResourcePolicies: tpgresource.ConvertStringArr(d.Get("resource_policies").([]interface{})), ReservationAffinity: reservationAffinity, KeyRevocationActionType: d.Get("key_revocation_action_type").(string), @@ -2128,6 +2121,36 @@ func resourceComputeInstanceCreate(d *schema.ResourceData, meta interface{}) err if err != nil { return fmt.Errorf("Error converting instance: %s", err) } + metadataMap, err := resourceInstanceMetadata(d) + if err != nil { + return fmt.Errorf("Error creating metadata: %s", err) + } + if len(metadataMap) > 0 { + instanceBody["metadata"] = metadataMap + } + {{ if ne $.TargetVersionName `ga` -}} + partnerMetadataMap, err := resourceInstancePartnerMetadata(d) + if err != nil { + return fmt.Errorf("Error creating partner metadata: %s", err) + } + partnerMetadataConverted, err := convertPartnerMetadataToCompute(partnerMetadataMap) + if err != nil { + return fmt.Errorf("Error converting partner metadata: %s", err) + } + if len(partnerMetadataConverted) > 0 { + instanceBody["partnerMetadata"] = partnerMetadataConverted + } + {{- end }} + schedulingBody, err := expandScheduling(d.Get("scheduling")) + if err != nil { + return fmt.Errorf("Error creating scheduling: %s", err) + } + if schedulingBody != nil { + instanceBody["scheduling"] = schedulingBody + } + if amf := expandAdvancedMachineFeatures(d); amf != nil { + instanceBody["advancedMachineFeatures"] = amf + } insertUrl, err := tpgresource.ReplaceVars(d, config, "{{"{{"}}ComputeBasePath{{"}}"}}projects/{{"{{"}}project{{"}}"}}/zones/{{"{{"}}zone{{"}}"}}/instances") if err != nil { return fmt.Errorf("Error generating URL: %s", err) @@ -2447,9 +2470,35 @@ func populateComputeInstanceResourceData(d *schema.ResourceData, instance *compu return fmt.Errorf("Error setting scratch_disk: %s", err) } - if err := d.Set("scheduling", flattenScheduling(instance.Scheduling)); err != nil { +{{ if eq $.TargetVersionName `ga` -}} + schedulingMap, err := tpgresource.ConvertToMap(instance.Scheduling) + if err != nil { + return fmt.Errorf("Error converting scheduling: %s", err) + } + if err := d.Set("scheduling", flattenScheduling(schedulingMap)); err != nil { + return fmt.Errorf("Error setting scheduling: %s", err) + } +{{ else -}} + // Workaroud: API doesn't update the scheduling.graceful_shutdown.max_duration.nanos field. + // To avoid diff, we need to set the value from the state not from API response. + schedulingMap, err := tpgresource.ConvertToMap(instance.Scheduling) + if err != nil { + return fmt.Errorf("Error converting scheduling: %s", err) + } + scheduling := flattenScheduling(schedulingMap) + if nanos, ok := d.GetOk("scheduling.0.graceful_shutdown.0.max_duration.0.nanos"); ok { + graceful_shutdown := scheduling[0]["graceful_shutdown"].([]interface{})[0].(map[string]interface{}) + max_duration := graceful_shutdown["max_duration"].([]interface{})[0].(map[string]interface{}) + max_duration["nanos"] = int64(nanos.(int)) + + graceful_shutdown["max_duration"] = []interface{}{max_duration} + scheduling[0]["graceful_shutdown"] = []interface{}{graceful_shutdown} + } + if err := d.Set("scheduling", scheduling); err != nil { return fmt.Errorf("Error setting scheduling: %s", err) } +{{- end }} + if err := d.Set("guest_accelerator", flattenGuestAccelerators(guestAcceleratorsToInterface(instance.GuestAccelerators))); err != nil { return fmt.Errorf("Error setting guest_accelerator: %s", err) } @@ -2516,7 +2565,11 @@ func populateComputeInstanceResourceData(d *schema.ResourceData, instance *compu return fmt.Errorf("Error setting confidential_instance_config: %s", err) } } - if err := d.Set("advanced_machine_features", flattenAdvancedMachineFeaturesTyped(instance.AdvancedMachineFeatures)); err != nil { + amfMap, err := tpgresource.ConvertToMap(instance.AdvancedMachineFeatures) + if err != nil { + return fmt.Errorf("Error converting advanced_machine_features: %s", err) + } + if err := d.Set("advanced_machine_features", flattenAdvancedMachineFeatures(amfMap)); err != nil { return fmt.Errorf("Error setting advanced_machine_features: %s", err) } if d.Get("desired_status") != "" { @@ -2928,15 +2981,10 @@ func resourceComputeInstanceUpdate(d *schema.ResourceData, meta interface{}) err bootRequiredSchedulingChange := schedulingHasChangeRequiringReboot(d) bootNotRequiredSchedulingChange := schedulingHasChangeWithoutReboot(d) if bootNotRequiredSchedulingChange { - scheduling, err := expandScheduling(d.Get("scheduling")) + schedulingBody, err := expandScheduling(d.Get("scheduling")) if err != nil { return fmt.Errorf("Error creating request data to update scheduling: %s", err) } - - schedulingBody, err := tpgresource.ConvertToMap(scheduling) - if err != nil { - return fmt.Errorf("Error converting scheduling: %s", err) - } url, err := tpgresource.ReplaceVars(d, config, "{{"{{"}}ComputeBasePath{{"}}"}}projects/{{"{{"}}project{{"}}"}}/zones/{{"{{"}}zone{{"}}"}}/instances/{{"{{"}}name{{"}}"}}/setScheduling") if err != nil { return fmt.Errorf("Error generating URL: %s", err) @@ -3965,15 +4013,10 @@ func resourceComputeInstanceUpdate(d *schema.ResourceData, meta interface{}) err } if bootRequiredSchedulingChange { - scheduling, err := expandScheduling(d.Get("scheduling")) + schedulingBody, err := expandScheduling(d.Get("scheduling")) if err != nil { return fmt.Errorf("Error creating request data to update scheduling: %s", err) } - - schedulingBody, err := tpgresource.ConvertToMap(scheduling) - if err != nil { - return fmt.Errorf("Error converting scheduling: %s", err) - } url, err := tpgresource.ReplaceVars(d, config, "{{"{{"}}ComputeBasePath{{"}}"}}projects/{{"{{"}}project{{"}}"}}/zones/{{"{{"}}zone{{"}}"}}/instances/{{"{{"}}name{{"}}"}}/setScheduling") if err != nil { return fmt.Errorf("Error generating URL: %s", err) diff --git a/mmv1/third_party/terraform/services/compute/resource_compute_instance_from_machine_image.go.tmpl b/mmv1/third_party/terraform/services/compute/resource_compute_instance_from_machine_image.go.tmpl index b3dea620113c..5981fa4770ef 100644 --- a/mmv1/third_party/terraform/services/compute/resource_compute_instance_from_machine_image.go.tmpl +++ b/mmv1/third_party/terraform/services/compute/resource_compute_instance_from_machine_image.go.tmpl @@ -215,38 +215,48 @@ func resourceComputeInstanceFromMachineImageCreate(d *schema.ResourceData, meta } instanceBody["disks"] = disks - // when we make the original call to expandComputeInstance expandScheduling is called, which sets default values. - // However, we want the values to be read from the machine image instead. - if _, hasSchedule := d.GetOk("scheduling"); !hasSchedule { - if srcProps, ok := miRes["sourceInstanceProperties"].(map[string]interface{}); ok { - if schedulingRaw, ok := srcProps["scheduling"].(map[string]interface{}); ok { - // Drop zero-value fields so the marshaled body matches the - // behavior of the typed compute.Scheduling JSON tags (omitempty). - filtered := map[string]interface{}{} - for k, v := range schedulingRaw { - switch val := v.(type) { - case bool: - if val { - filtered[k] = v - } - case string: - if val != "" { - filtered[k] = v - } - case float64: - if val != 0 { - filtered[k] = v - } - case nil: - // skip - default: + // expandComputeInstance no longer sets scheduling in the typed struct, so we handle + // it explicitly here. When the user has scheduling configured, use it; otherwise + // inherit scheduling from the machine image. + if _, hasSchedule := d.GetOk("scheduling"); hasSchedule { + schedulingBody, err := expandScheduling(d.Get("scheduling")) + if err != nil { + return fmt.Errorf("Error expanding scheduling: %s", err) + } + if schedulingBody != nil { + instanceBody["scheduling"] = schedulingBody + } + } else if srcProps, ok := miRes["sourceInstanceProperties"].(map[string]interface{}); ok { + if schedulingRaw, ok := srcProps["scheduling"].(map[string]interface{}); ok { + // Drop zero-value fields so the marshaled body matches the + // behavior of the typed compute.Scheduling JSON tags (omitempty). + filtered := map[string]interface{}{} + for k, v := range schedulingRaw { + switch val := v.(type) { + case bool: + if val { + filtered[k] = v + } + case string: + if val != "" { + filtered[k] = v + } + case float64: + if val != 0 { filtered[k] = v } + case nil: + // skip + default: + filtered[k] = v } - instanceBody["scheduling"] = filtered } + instanceBody["scheduling"] = filtered } } + if amf := expandAdvancedMachineFeatures(d); amf != nil { + instanceBody["advancedMachineFeatures"] = amf + } log.Printf("[INFO] Requesting instance creation") insertURL := fmt.Sprintf("%sprojects/%s/zones/%s/instances", transport_tpg.BaseUrl(Product, config), project, z) 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 eb72d9af676d..477fdd2d6ee0 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 @@ -2281,7 +2281,11 @@ func resourceComputeInstanceTemplateRead(d *schema.ResourceData, meta interface{ } } if instanceTemplate.Properties.Scheduling != nil { - scheduling := flattenScheduling(instanceTemplate.Properties.Scheduling) + schedulingMap, err := tpgresource.ConvertToMap(instanceTemplate.Properties.Scheduling) + if err != nil { + return fmt.Errorf("Error converting scheduling: %s", err) + } + scheduling := flattenScheduling(schedulingMap) {{ if ne $.TargetVersionName `ga` }} // Workaroud: API doesn't update the scheduling.graceful_shutdown.max_duration.nanos field. // To avoid diff, we need to set the value from the state not from API response. @@ -2338,7 +2342,11 @@ func resourceComputeInstanceTemplateRead(d *schema.ResourceData, meta interface{ } } if instanceTemplate.Properties.AdvancedMachineFeatures != nil { - if err = d.Set("advanced_machine_features", flattenAdvancedMachineFeaturesTyped(instanceTemplate.Properties.AdvancedMachineFeatures)); err != nil { + amfMap, err := tpgresource.ConvertToMap(instanceTemplate.Properties.AdvancedMachineFeatures) + if err != nil { + return fmt.Errorf("Error converting advanced_machine_features: %s", err) + } + if err = d.Set("advanced_machine_features", flattenAdvancedMachineFeatures(amfMap)); err != nil { return fmt.Errorf("Error setting advanced_machine_features: %s", err) } } @@ -2429,10 +2437,128 @@ func expandResourceComputeInstanceTemplateScheduling(d *schema.ResourceData, met } // Make sure we have an appropriate value for OnHostMaintenance if Preemptible - if expanded.Preemptible && expanded.OnHostMaintenance == "" { - expanded.OnHostMaintenance = "TERMINATE" + preemptible, _ := expanded["preemptible"].(bool) + onHostMaintenance, _ := expanded["onHostMaintenance"].(string) + if preemptible && onHostMaintenance == "" { + expanded["onHostMaintenance"] = "TERMINATE" + } + + schedulingTyped := &compute.Scheduling{} + if err := tpgresource.Convert(expanded, schedulingTyped); err != nil { + return nil, fmt.Errorf("Error converting scheduling: %s", err) + } + return schedulingTyped, nil +} + +// networkInterfacesToInterface converts a slice of typed Apiary network +// interface structs into the []interface{} of JSON-shaped maps expected by +// flattenNetworkInterfaces. +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 convertViaJSON(in, out interface{}) error { + bytes, err := json.Marshal(in) + if err != nil { + return err + } + return json.Unmarshal(bytes, out) +} + +func expandNetworkInterfacesTyped(d tpgresource.TerraformResourceData, config *transport_tpg.Config) ([]*compute.NetworkInterface, error) { + expanded, err := expandNetworkInterfaces(d, config) + if err != nil { + return nil, err + } + ifaces := make([]*compute.NetworkInterface, 0, len(expanded)) + if err := convertViaJSON(expanded, &ifaces); err != nil { + return nil, fmt.Errorf("Error converting network interfaces: %s", err) + } + return ifaces, nil +} + +func expandAccessConfigsTyped(configs []interface{}) ([]*compute.AccessConfig, error) { + return accessConfigsToTyped(expandAccessConfigs(configs)) +} + +func expandIpv6AccessConfigsTyped(configs []interface{}) ([]*compute.AccessConfig, error) { + return accessConfigsToTyped(expandIpv6AccessConfigs(configs)) +} + +func accessConfigsToTyped(expanded []interface{}) ([]*compute.AccessConfig, error) { + acs := make([]*compute.AccessConfig, 0, len(expanded)) + if err := convertViaJSON(expanded, &acs); err != nil { + return nil, fmt.Errorf("Error converting access configs: %s", err) + } + return acs, nil +} + +func expandAliasIpRangesTyped(ranges []interface{}) ([]*compute.AliasIpRange, error) { + expanded := expandAliasIpRanges(ranges) + out := make([]*compute.AliasIpRange, 0, len(expanded)) + if err := convertViaJSON(expanded, &out); err != nil { + return nil, fmt.Errorf("Error converting alias ip ranges: %s", err) + } + return out, nil +} + +func expandServiceAccountsTyped(configs []interface{}) []*compute.ServiceAccount { + expanded := expandServiceAccounts(configs) + accounts := make([]*compute.ServiceAccount, len(expanded)) + for i, raw := range expanded { + data := raw.(map[string]interface{}) + accounts[i] = &compute.ServiceAccount{ + Email: data["email"].(string), + Scopes: data["scopes"].([]string), + } + } + return accounts +} + +func serviceAccountsToInterface(serviceAccounts []*compute.ServiceAccount) []interface{} { + result := make([]interface{}, len(serviceAccounts)) + for i, sa := range serviceAccounts { + result[i] = map[string]interface{}{ + "email": sa.Email, + "scopes": tpgresource.ConvertStringArrToInterface(sa.Scopes), + } + } + return result +} + +func guestAcceleratorsToInterface(accelerators []*compute.AcceleratorConfig) []interface{} { + result := make([]interface{}, len(accelerators)) + for i, a := range accelerators { + result[i] = map[string]interface{}{ + "acceleratorCount": a.AcceleratorCount, + "acceleratorType": a.AcceleratorType, + } + } + return result +} + +func expandAdvancedMachineFeaturesTyped(d tpgresource.TerraformResourceData) *compute.AdvancedMachineFeatures { + amfMap := expandAdvancedMachineFeatures(d) + if amfMap == nil { + return nil + } + typed := &compute.AdvancedMachineFeatures{} + if err := tpgresource.Convert(amfMap, typed); err != nil { + return nil } - return expanded, nil + return typed } func resourceComputeInstanceTemplateImportState(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { 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 af711ae36b06..d2cf28f24413 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 @@ -1699,7 +1699,11 @@ func resourceComputeRegionInstanceTemplateRead(d *schema.ResourceData, meta inte } } if instanceTemplate.Properties.Scheduling != nil { - scheduling := flattenScheduling(instanceTemplate.Properties.Scheduling) + schedulingMap, err := tpgresource.ConvertToMap(instanceTemplate.Properties.Scheduling) + if err != nil { + return fmt.Errorf("Error converting scheduling: %s", err) + } + scheduling := flattenScheduling(schedulingMap) {{ if ne $.TargetVersionName `ga` }} // Workaroud: API doesn't update the scheduling.graceful_shutdown.max_duration.nanos field. // To avoid diff, we need to set the value from the state not from API response. @@ -1756,7 +1760,11 @@ func resourceComputeRegionInstanceTemplateRead(d *schema.ResourceData, meta inte } } if instanceTemplate.Properties.AdvancedMachineFeatures != nil { - if err = d.Set("advanced_machine_features", flattenAdvancedMachineFeaturesTyped(instanceTemplate.Properties.AdvancedMachineFeatures)); err != nil { + amfMap, err := tpgresource.ConvertToMap(instanceTemplate.Properties.AdvancedMachineFeatures) + if err != nil { + return fmt.Errorf("Error converting advanced_machine_features: %s", err) + } + if err = d.Set("advanced_machine_features", flattenAdvancedMachineFeatures(amfMap)); err != nil { return fmt.Errorf("Error setting advanced_machine_features: %s", err) } } From 1171d0eb4d81283439534f51792f7f81ca72f579 Mon Sep 17 00:00:00 2001 From: Igor Nadenenko Date: Wed, 17 Jun 2026 15:42:36 +0200 Subject: [PATCH 02/30] fix build --- .../compute/compute_instance_helpers.go.tmpl | 16 +++++++ ...pute_region_instance_group_manager.go.tmpl | 15 ------ .../services/compute/compute_instance.go.tmpl | 48 +++++++++++++++++-- 3 files changed, 61 insertions(+), 18 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 0cf8c7003b8e..9b994196e6a2 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 @@ -784,6 +784,22 @@ func getInterfaceSlice(v interface{}) []interface{} { return s } +func getInt(v interface{}) int64 { + switch t := v.(type) { + case int: + return int64(t) + case int64: + return t + case float64: + return int64(t) + case string: + i, _ := strconv.ParseInt(t, 10, 64) + return i + default: + return 0 + } +} + {{ if ne $.TargetVersionName `ga` -}} // firstAccessConfigSecurityPolicy returns the securityPolicy of the first // access config in the list, or "" if absent. diff --git a/mmv1/third_party/terraform/services/compute/resource_compute_region_instance_group_manager.go.tmpl b/mmv1/third_party/terraform/services/compute/resource_compute_region_instance_group_manager.go.tmpl index 2ebbf974c825..5d3b5ba9e10d 100644 --- a/mmv1/third_party/terraform/services/compute/resource_compute_region_instance_group_manager.go.tmpl +++ b/mmv1/third_party/terraform/services/compute/resource_compute_region_instance_group_manager.go.tmpl @@ -2148,21 +2148,6 @@ func flattenDistributionPolicy(distributionPolicyRaw interface{}) []string { return zones } -func getInt(v interface{}) int64 { - switch t := v.(type) { - case int: - return int64(t) - case int64: - return t - case float64: - return int64(t) - case string: - i, _ := strconv.ParseInt(t, 10, 64) - return i - default: - return 0 - } -} func flattenRegionNamedPorts(raw interface{}) []map[string]interface{} { result := make([]map[string]interface{}, 0) diff --git a/mmv1/third_party/tgc/services/compute/compute_instance.go.tmpl b/mmv1/third_party/tgc/services/compute/compute_instance.go.tmpl index 563361506d82..db6d3912b1dd 100644 --- a/mmv1/third_party/tgc/services/compute/compute_instance.go.tmpl +++ b/mmv1/third_party/tgc/services/compute/compute_instance.go.tmpl @@ -134,10 +134,14 @@ func expandComputeInstance(project string, d tpgresource.TerraformResourceData, return nil, fmt.Errorf("Error creating metadata: %s", err) } - networkInterfaces, err := expandNetworkInterfacesTyped(d, config) + networkInterfacesRaw, err := expandNetworkInterfaces(d, config) if err != nil { return nil, fmt.Errorf("Error creating network interfaces: %s", err) } + networkInterfaces, err := expandNetworkInterfacesTyped(networkInterfacesRaw) + if err != nil { + return nil, fmt.Errorf("Error converting network interfaces: %s", err) + } accels, err := expandInstanceGuestAccelerators(d, config) if err != nil { @@ -165,14 +169,19 @@ func expandComputeInstance(project string, d tpgresource.TerraformResourceData, NetworkInterfaces: networkInterfaces, Tags: tags, Labels: tpgresource.ExpandLabels(d), - ServiceAccounts: expandServiceAccountsTyped(d.Get("service_account").([]interface{})), + ServiceAccounts: expandServiceAccountsForTGC(d.Get("service_account").([]interface{})), GuestAccelerators: accels, MinCpuPlatform: d.Get("min_cpu_platform").(string), Scheduling: scheduling, DeletionProtection: d.Get("deletion_protection").(bool), Hostname: d.Get("hostname").(string), ForceSendFields: []string{"CanIpForward", "DeletionProtection"}, - AdvancedMachineFeatures: expandAdvancedMachineFeaturesTyped(d), + AdvancedMachineFeatures: expandAdvancedMachineFeaturesForTGC(d), + } + if metadataMap != nil { + if err := tpgresource.Convert(metadataMap, &instance.Metadata); err != nil { + return nil, fmt.Errorf("Error converting metadata: %s", err) + } } if sicMap := expandShieldedVmConfigs(d); sicMap != nil { instance.ShieldedInstanceConfig = &compute.ShieldedInstanceConfig{ @@ -192,6 +201,39 @@ func expandComputeInstance(project string, d tpgresource.TerraformResourceData, return instance, nil } +func expandNetworkInterfacesTyped(expanded []interface{}) ([]*compute.NetworkInterface, error) { + ifaces := make([]*compute.NetworkInterface, 0, len(expanded)) + if err := tpgresource.Convert(expanded, &ifaces); err != nil { + return nil, fmt.Errorf("Error converting network interfaces: %s", err) + } + return ifaces, nil +} + +func expandServiceAccountsForTGC(configs []interface{}) []*compute.ServiceAccount { + expanded := expandServiceAccounts(configs) + accounts := make([]*compute.ServiceAccount, len(expanded)) + for i, raw := range expanded { + data := raw.(map[string]interface{}) + accounts[i] = &compute.ServiceAccount{ + Email: data["email"].(string), + Scopes: data["scopes"].([]string), + } + } + return accounts +} + +func expandAdvancedMachineFeaturesForTGC(d tpgresource.TerraformResourceData) *compute.AdvancedMachineFeatures { + amfMap := expandAdvancedMachineFeatures(d) + if amfMap == nil { + return nil + } + typed := &compute.AdvancedMachineFeatures{} + if err := tpgresource.Convert(amfMap, typed); err != nil { + return nil + } + return typed +} + func expandAttachedDisk(diskConfig map[string]interface{}, d tpgresource.TerraformResourceData, meta interface{}) (*compute.AttachedDisk, error) { config := meta.(*transport_tpg.Config) From 0806c167ece189e14dc75464eefac81eab7789eb Mon Sep 17 00:00:00 2001 From: Igor Nadenenko Date: Wed, 17 Jun 2026 16:08:59 +0200 Subject: [PATCH 03/30] guard strconv --- .../resource_compute_region_instance_group_manager.go.tmpl | 2 ++ 1 file changed, 2 insertions(+) diff --git a/mmv1/third_party/terraform/services/compute/resource_compute_region_instance_group_manager.go.tmpl b/mmv1/third_party/terraform/services/compute/resource_compute_region_instance_group_manager.go.tmpl index 5d3b5ba9e10d..8b8435c46c19 100644 --- a/mmv1/third_party/terraform/services/compute/resource_compute_region_instance_group_manager.go.tmpl +++ b/mmv1/third_party/terraform/services/compute/resource_compute_region_instance_group_manager.go.tmpl @@ -3,7 +3,9 @@ package compute import ( "fmt" "log" +{{- if ne $.TargetVersionName "ga" }} "strconv" +{{- end }} "strings" "time" From 513b066b2c4181f12cce80321e7f59beb70e147e Mon Sep 17 00:00:00 2001 From: Igor Nadenenko Date: Wed, 17 Jun 2026 16:20:15 +0200 Subject: [PATCH 04/30] fix build --- .../compute/compute_instance_tfplan2cai.go | 68 +++++++++++++++---- 1 file changed, 56 insertions(+), 12 deletions(-) diff --git a/mmv1/third_party/tgc_next/pkg/services/compute/compute_instance_tfplan2cai.go b/mmv1/third_party/tgc_next/pkg/services/compute/compute_instance_tfplan2cai.go index 0d2bb6a214c5..11929863cdb5 100644 --- a/mmv1/third_party/tgc_next/pkg/services/compute/compute_instance_tfplan2cai.go +++ b/mmv1/third_party/tgc_next/pkg/services/compute/compute_instance_tfplan2cai.go @@ -197,7 +197,7 @@ func expandComputeInstance(project string, d tpgresource.TerraformResourceData, Scheduling: scheduling, DeletionProtection: d.Get("deletion_protection").(bool), Hostname: d.Get("hostname").(string), - AdvancedMachineFeatures: expandAdvancedMachineFeaturesTgcNext(d), + AdvancedMachineFeatures: expandAdvancedMachineFeaturesTypedTgcNext(d), ResourcePolicies: tpgresource.ConvertStringArr(d.Get("resource_policies").([]interface{})), ReservationAffinity: reservationAffinity, KeyRevocationActionType: d.Get("key_revocation_action_type").(string), @@ -579,21 +579,33 @@ func expandSchedulingTgc(v interface{}) (*compute.Scheduling, error) { scheduling.AvailabilityDomain = int64(v.(int)) } if v, ok := original["max_run_duration"]; ok { - transformedMaxRunDuration, err := expandComputeMaxRunDuration(v) + maxRunDurationMap, err := expandComputeMaxRunDuration(v) if err != nil { return nil, err } - scheduling.MaxRunDuration = transformedMaxRunDuration - scheduling.ForceSendFields = append(scheduling.ForceSendFields, "MaxRunDuration") + if maxRunDurationMap != nil { + typed := &compute.Duration{} + if err := tpgresource.Convert(maxRunDurationMap, typed); err != nil { + return nil, fmt.Errorf("Error converting max_run_duration: %s", err) + } + scheduling.MaxRunDuration = typed + scheduling.ForceSendFields = append(scheduling.ForceSendFields, "MaxRunDuration") + } } if v, ok := original["on_instance_stop_action"]; ok { - transformedOnInstanceStopAction, err := expandComputeOnInstanceStopAction(v) + onInstanceStopActionMap, err := expandComputeOnInstanceStopAction(v) if err != nil { return nil, err } - scheduling.OnInstanceStopAction = transformedOnInstanceStopAction - scheduling.ForceSendFields = append(scheduling.ForceSendFields, "OnInstanceStopAction") + if onInstanceStopActionMap != nil { + typed := &compute.SchedulingOnInstanceStopAction{} + if err := tpgresource.Convert(onInstanceStopActionMap, typed); err != nil { + return nil, fmt.Errorf("Error converting on_instance_stop_action: %s", err) + } + scheduling.OnInstanceStopAction = typed + scheduling.ForceSendFields = append(scheduling.ForceSendFields, "OnInstanceStopAction") + } } if v, ok := original["local_ssd_recovery_timeout"]; ok { @@ -657,10 +669,42 @@ func expandComputeLocalSsdRecoveryTimeoutTgc(v interface{}) (*compute.Duration, return duration, nil } -func expandAdvancedMachineFeaturesTgcNext(d tpgresource.TerraformResourceData) *compute.AdvancedMachineFeatures { - features := expandAdvancedMachineFeaturesTyped(d) - if features != nil && features.PerformanceMonitoringUnit == "" { - features.PerformanceMonitoringUnit = "STANDARD" + +func expandAccessConfigsTyped(configs []interface{}) ([]*compute.AccessConfig, error) { + expanded := expandAccessConfigs(configs) + acs := make([]*compute.AccessConfig, 0, len(expanded)) + if err := tpgresource.Convert(expanded, &acs); err != nil { + return nil, fmt.Errorf("Error converting access configs: %s", err) + } + return acs, nil +} + +func expandAliasIpRangesTyped(ranges []interface{}) ([]*compute.AliasIpRange, error) { + expanded := expandAliasIpRanges(ranges) + out := make([]*compute.AliasIpRange, 0, len(expanded)) + if err := tpgresource.Convert(expanded, &out); err != nil { + return nil, fmt.Errorf("Error converting alias ip ranges: %s", err) + } + return out, nil +} + +func expandIpv6AccessConfigsTyped(configs []interface{}) ([]*compute.AccessConfig, error) { + expanded := expandIpv6AccessConfigs(configs) + acs := make([]*compute.AccessConfig, 0, len(expanded)) + if err := tpgresource.Convert(expanded, &acs); err != nil { + return nil, fmt.Errorf("Error converting ipv6 access configs: %s", err) + } + return acs, nil +} + +func expandAdvancedMachineFeaturesTypedTgcNext(d tpgresource.TerraformResourceData) *compute.AdvancedMachineFeatures { + amfMap := expandAdvancedMachineFeatures(d) + if amfMap == nil { + return nil + } + typed := &compute.AdvancedMachineFeatures{} + if err := tpgresource.Convert(amfMap, typed); err != nil { + return nil } - return features + return typed } From 22537fb976bcfd981e454afa81de5f17bdcdb537 Mon Sep 17 00:00:00 2001 From: Igor Nadenenko Date: Wed, 17 Jun 2026 16:29:58 +0200 Subject: [PATCH 05/30] fix convertion --- .../compute/compute_instance_tfplan2cai.go | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/mmv1/third_party/tgc_next/pkg/services/compute/compute_instance_tfplan2cai.go b/mmv1/third_party/tgc_next/pkg/services/compute/compute_instance_tfplan2cai.go index 11929863cdb5..8777718bdd85 100644 --- a/mmv1/third_party/tgc_next/pkg/services/compute/compute_instance_tfplan2cai.go +++ b/mmv1/third_party/tgc_next/pkg/services/compute/compute_instance_tfplan2cai.go @@ -585,7 +585,7 @@ func expandSchedulingTgc(v interface{}) (*compute.Scheduling, error) { } if maxRunDurationMap != nil { typed := &compute.Duration{} - if err := tpgresource.Convert(maxRunDurationMap, typed); err != nil { + if err := convertViaJSONTgcNext(maxRunDurationMap, typed); err != nil { return nil, fmt.Errorf("Error converting max_run_duration: %s", err) } scheduling.MaxRunDuration = typed @@ -600,7 +600,7 @@ func expandSchedulingTgc(v interface{}) (*compute.Scheduling, error) { } if onInstanceStopActionMap != nil { typed := &compute.SchedulingOnInstanceStopAction{} - if err := tpgresource.Convert(onInstanceStopActionMap, typed); err != nil { + if err := convertViaJSONTgcNext(onInstanceStopActionMap, typed); err != nil { return nil, fmt.Errorf("Error converting on_instance_stop_action: %s", err) } scheduling.OnInstanceStopAction = typed @@ -673,7 +673,7 @@ func expandComputeLocalSsdRecoveryTimeoutTgc(v interface{}) (*compute.Duration, func expandAccessConfigsTyped(configs []interface{}) ([]*compute.AccessConfig, error) { expanded := expandAccessConfigs(configs) acs := make([]*compute.AccessConfig, 0, len(expanded)) - if err := tpgresource.Convert(expanded, &acs); err != nil { + if err := convertViaJSONTgcNext(expanded, &acs); err != nil { return nil, fmt.Errorf("Error converting access configs: %s", err) } return acs, nil @@ -682,7 +682,7 @@ func expandAccessConfigsTyped(configs []interface{}) ([]*compute.AccessConfig, e func expandAliasIpRangesTyped(ranges []interface{}) ([]*compute.AliasIpRange, error) { expanded := expandAliasIpRanges(ranges) out := make([]*compute.AliasIpRange, 0, len(expanded)) - if err := tpgresource.Convert(expanded, &out); err != nil { + if err := convertViaJSONTgcNext(expanded, &out); err != nil { return nil, fmt.Errorf("Error converting alias ip ranges: %s", err) } return out, nil @@ -691,7 +691,7 @@ func expandAliasIpRangesTyped(ranges []interface{}) ([]*compute.AliasIpRange, er func expandIpv6AccessConfigsTyped(configs []interface{}) ([]*compute.AccessConfig, error) { expanded := expandIpv6AccessConfigs(configs) acs := make([]*compute.AccessConfig, 0, len(expanded)) - if err := tpgresource.Convert(expanded, &acs); err != nil { + if err := convertViaJSONTgcNext(expanded, &acs); err != nil { return nil, fmt.Errorf("Error converting ipv6 access configs: %s", err) } return acs, nil @@ -703,8 +703,16 @@ func expandAdvancedMachineFeaturesTypedTgcNext(d tpgresource.TerraformResourceDa return nil } typed := &compute.AdvancedMachineFeatures{} - if err := tpgresource.Convert(amfMap, typed); err != nil { + if err := convertViaJSONTgcNext(amfMap, typed); err != nil { return nil } return typed } + +func convertViaJSONTgcNext(in, out interface{}) error { + b, err := json.Marshal(in) + if err != nil { + return err + } + return json.Unmarshal(b, out) +} From 2f5e29d87be4fde6d106a69f17ce4d469f8e823e Mon Sep 17 00:00:00 2001 From: Igor Nadenenko Date: Wed, 17 Jun 2026 16:46:44 +0200 Subject: [PATCH 06/30] fix tests panic --- .../services/compute/compute_instance.go.tmpl | 21 +++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/mmv1/third_party/tgc/services/compute/compute_instance.go.tmpl b/mmv1/third_party/tgc/services/compute/compute_instance.go.tmpl index db6d3912b1dd..5e1a7c4949ad 100644 --- a/mmv1/third_party/tgc/services/compute/compute_instance.go.tmpl +++ b/mmv1/third_party/tgc/services/compute/compute_instance.go.tmpl @@ -9,6 +9,7 @@ package compute import ( + "encoding/json" "errors" "fmt" "strings" @@ -151,8 +152,12 @@ func expandComputeInstance(project string, d tpgresource.TerraformResourceData, tagsMap := resourceInstanceTags(d) var tags *compute.Tags if tagsMap != nil { + b, err := json.Marshal(tagsMap) + if err != nil { + return nil, fmt.Errorf("Error converting tags: %s", err) + } tags = &compute.Tags{} - if err := tpgresource.Convert(tagsMap, tags); err != nil { + if err := json.Unmarshal(b, tags); err != nil { return nil, fmt.Errorf("Error converting tags: %s", err) } } @@ -202,8 +207,12 @@ func expandComputeInstance(project string, d tpgresource.TerraformResourceData, } func expandNetworkInterfacesTyped(expanded []interface{}) ([]*compute.NetworkInterface, error) { - ifaces := make([]*compute.NetworkInterface, 0, len(expanded)) - if err := tpgresource.Convert(expanded, &ifaces); err != nil { + b, err := json.Marshal(expanded) + if err != nil { + return nil, fmt.Errorf("Error converting network interfaces: %s", err) + } + var ifaces []*compute.NetworkInterface + if err := json.Unmarshal(b, &ifaces); err != nil { return nil, fmt.Errorf("Error converting network interfaces: %s", err) } return ifaces, nil @@ -227,8 +236,12 @@ func expandAdvancedMachineFeaturesForTGC(d tpgresource.TerraformResourceData) *c if amfMap == nil { return nil } + b, err := json.Marshal(amfMap) + if err != nil { + return nil + } typed := &compute.AdvancedMachineFeatures{} - if err := tpgresource.Convert(amfMap, typed); err != nil { + if err := json.Unmarshal(b, typed); err != nil { return nil } return typed From b53d75f27ee5d3b65c2e141200935d9d405e6457 Mon Sep 17 00:00:00 2001 From: Igor Nadenenko Date: Wed, 17 Jun 2026 16:59:59 +0200 Subject: [PATCH 07/30] update changes to resource_compute_instance --- .../services/compute/resource_compute_instance.go.tmpl | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) 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 0c351506a4d9..50a66acfb22e 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 @@ -1883,6 +1883,7 @@ func expandComputeInstance(project string, d *schema.ResourceData, config *trans } {{- end }} + networkInterfaces, err := expandNetworkInterfacesTyped(d, config) if err != nil { return nil, fmt.Errorf("Error creating network interfaces: %s", err) @@ -1947,7 +1948,6 @@ func expandComputeInstance(project string, d *schema.ResourceData, config *trans NetworkInterfaces: networkInterfaces, NetworkPerformanceConfig: networkPerformanceConfig, Tags: tags, - Params: params, Labels: tpgresource.ExpandEffectiveLabels(d), ServiceAccounts: expandServiceAccountsTyped(d.Get("service_account").([]interface{})), GuestAccelerators: accels, @@ -2151,6 +2151,11 @@ func resourceComputeInstanceCreate(d *schema.ResourceData, meta interface{}) err if amf := expandAdvancedMachineFeatures(d); amf != nil { instanceBody["advancedMachineFeatures"] = amf } + if paramsBody, err := expandParams(d); err != nil { + return fmt.Errorf("Error expanding params: %s", err) + } else if len(paramsBody) > 0 { + instanceBody["params"] = paramsBody + } insertUrl, err := tpgresource.ReplaceVars(d, config, "{{"{{"}}ComputeBasePath{{"}}"}}projects/{{"{{"}}project{{"}}"}}/zones/{{"{{"}}zone{{"}}"}}/instances") if err != nil { return fmt.Errorf("Error generating URL: %s", err) From 66ef5aabced1cb200dfd8ef33537feb08b1241eb Mon Sep 17 00:00:00 2001 From: Igor Nadenenko Date: Thu, 18 Jun 2026 15:39:44 +0200 Subject: [PATCH 08/30] other PRs merge --- .../compute/resource_compute_instance.go.tmpl | 36 ++++++++----------- ...e_compute_region_instance_template.go.tmpl | 2 +- 2 files changed, 15 insertions(+), 23 deletions(-) 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 50a66acfb22e..e1a775ab300e 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 @@ -1862,28 +1862,6 @@ func expandComputeInstance(project string, d *schema.ResourceData, config *trans disks = append(disks, disk) } - params, err := expandParams(d) - if err != nil { - return nil, fmt.Errorf("Error creating params: %s", err) - } - - metadata, err := resourceInstanceMetadataTyped(d) - if err != nil { - return nil, fmt.Errorf("Error creating metadata: %s", err) - } - - {{ if ne $.TargetVersionName `ga` -}} - partnerMetadataMap, err := resourceInstancePartnerMetadata(d) - if err != nil { - return nil, fmt.Errorf("Error creating partner metadata: %s", err) - } - PartnerMetadata, err := convertPartnerMetadataToComputeTyped(partnerMetadataMap) - if err != nil { - return nil, fmt.Errorf("Error converting partner metadata: %s", err) - } - {{- end }} - - networkInterfaces, err := expandNetworkInterfacesTyped(d, config) if err != nil { return nil, fmt.Errorf("Error creating network interfaces: %s", err) @@ -2156,6 +2134,20 @@ func resourceComputeInstanceCreate(d *schema.ResourceData, meta interface{}) err } else if len(paramsBody) > 0 { instanceBody["params"] = paramsBody } + if metadataBody, err := resourceInstanceMetadata(d); err != nil { + return fmt.Errorf("Error expanding metadata: %s", err) + } else if len(metadataBody) > 0 { + instanceBody["metadata"] = metadataBody + } + {{- if ne $.TargetVersionName "ga" }} + if partnerMetadataRaw, err := resourceInstancePartnerMetadata(d); err != nil { + return fmt.Errorf("Error expanding partner metadata: %s", err) + } else if partnerMetadataBody, err := convertPartnerMetadataToCompute(partnerMetadataRaw); err != nil { + return fmt.Errorf("Error converting partner metadata: %s", err) + } else if len(partnerMetadataBody) > 0 { + instanceBody["partnerMetadata"] = partnerMetadataBody + } + {{- end }} insertUrl, err := tpgresource.ReplaceVars(d, config, "{{"{{"}}ComputeBasePath{{"}}"}}projects/{{"{{"}}project{{"}}"}}/zones/{{"{{"}}zone{{"}}"}}/instances") if err != nil { return fmt.Errorf("Error generating URL: %s", 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 d2cf28f24413..652e5c100b52 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 @@ -1399,7 +1399,7 @@ func resourceComputeRegionInstanceTemplateCreate(d *schema.ResourceData, meta in if v := d.Get("key_revocation_action_type").(string); v != "" { instanceProperties["keyRevocationActionType"] = v } - if metadata != nil { + if len(metadata) > 0 { instanceProperties["metadata"] = metadata } if networkPerformanceConfig != nil { From e95b27c39980848581ba10e21deb4c0c70ae5cc7 Mon Sep 17 00:00:00 2001 From: Igor Nadenenko Date: Thu, 18 Jun 2026 18:35:21 +0200 Subject: [PATCH 09/30] fix imports --- .../tgc/services/compute/compute_instance.go.tmpl | 8 ++++++++ .../pkg/services/compute/compute_instance_tfplan2cai.go | 6 +++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/mmv1/third_party/tgc/services/compute/compute_instance.go.tmpl b/mmv1/third_party/tgc/services/compute/compute_instance.go.tmpl index 5e1a7c4949ad..a8a2e1755bc5 100644 --- a/mmv1/third_party/tgc/services/compute/compute_instance.go.tmpl +++ b/mmv1/third_party/tgc/services/compute/compute_instance.go.tmpl @@ -426,3 +426,11 @@ func expandScratchDisks(d tpgresource.TerraformResourceData, config *transport_t return scratchDisks, nil } + +func convertViaJSON(in, out interface{}) error { + b, err := json.Marshal(in) + if err != nil { + return err + } + return json.Unmarshal(b, out) +} diff --git a/mmv1/third_party/tgc_next/pkg/services/compute/compute_instance_tfplan2cai.go b/mmv1/third_party/tgc_next/pkg/services/compute/compute_instance_tfplan2cai.go index 8777718bdd85..25cf95436227 100644 --- a/mmv1/third_party/tgc_next/pkg/services/compute/compute_instance_tfplan2cai.go +++ b/mmv1/third_party/tgc_next/pkg/services/compute/compute_instance_tfplan2cai.go @@ -709,10 +709,14 @@ func expandAdvancedMachineFeaturesTypedTgcNext(d tpgresource.TerraformResourceDa return typed } -func convertViaJSONTgcNext(in, out interface{}) error { +func convertViaJSON(in, out interface{}) error { b, err := json.Marshal(in) if err != nil { return err } return json.Unmarshal(b, out) } + +func convertViaJSONTgcNext(in, out interface{}) error { + return convertViaJSON(in, out) +} From b6083cc6114018a839759265b0d04a0a04836189 Mon Sep 17 00:00:00 2001 From: Igor Nadenenko Date: Thu, 18 Jun 2026 21:20:27 +0200 Subject: [PATCH 10/30] vcr fixes --- ..._compute_instance_from_machine_image.go.tmpl | 17 +++++++++++++++++ ...rce_compute_region_instance_template.go.tmpl | 14 ++++++++------ 2 files changed, 25 insertions(+), 6 deletions(-) diff --git a/mmv1/third_party/terraform/services/compute/resource_compute_instance_from_machine_image.go.tmpl b/mmv1/third_party/terraform/services/compute/resource_compute_instance_from_machine_image.go.tmpl index 5981fa4770ef..fb37d34a394e 100644 --- a/mmv1/third_party/terraform/services/compute/resource_compute_instance_from_machine_image.go.tmpl +++ b/mmv1/third_party/terraform/services/compute/resource_compute_instance_from_machine_image.go.tmpl @@ -224,6 +224,13 @@ func resourceComputeInstanceFromMachineImageCreate(d *schema.ResourceData, meta return fmt.Errorf("Error expanding scheduling: %s", err) } if schedulingBody != nil { + // Strip nil Duration fields so the source machine image values are inherited + // instead of being overridden with null. + for _, k := range []string{"localSsdRecoveryTimeout", "maxRunDuration", "gracefulShutdown", "preemptionNoticeDuration"} { + if v, ok := schedulingBody[k]; ok && v == nil { + delete(schedulingBody, k) + } + } instanceBody["scheduling"] = schedulingBody } } else if srcProps, ok := miRes["sourceInstanceProperties"].(map[string]interface{}); ok { @@ -258,6 +265,16 @@ func resourceComputeInstanceFromMachineImageCreate(d *schema.ResourceData, meta instanceBody["advancedMachineFeatures"] = amf } + if partnerMetadataRaw, err := resourceInstancePartnerMetadata(d); err != nil { + return fmt.Errorf("Error expanding partner_metadata: %s", err) + } else if partnerMetadataBody, err := convertPartnerMetadataToCompute(partnerMetadataRaw); err != nil { + return fmt.Errorf("Error converting partner_metadata: %s", err) + } else if len(partnerMetadataBody) > 0 { + instanceBody["partnerMetadata"] = partnerMetadataBody + } else { + delete(instanceBody, "partnerMetadata") + } + log.Printf("[INFO] Requesting instance creation") insertURL := fmt.Sprintf("%sprojects/%s/zones/%s/instances", transport_tpg.BaseUrl(Product, config), project, z) res, err := transport_tpg.SendRequest(transport_tpg.SendRequestOptions{ 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 652e5c100b52..2354e6bf957f 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 @@ -1557,6 +1557,12 @@ func resourceComputeRegionInstanceTemplateRead(d *schema.ResourceData, meta inte } delete(res, "id") + var schedulingMapFromResponse map[string]interface{} + if propsMap, ok := res["properties"].(map[string]interface{}); ok { + schedulingMapFromResponse, _ = propsMap["scheduling"].(map[string]interface{}) + delete(propsMap, "scheduling") + } + resBytes, err := json.Marshal(res) if err != nil { return fmt.Errorf("Error marshaling instance template response: %s", err) @@ -1698,12 +1704,8 @@ func resourceComputeRegionInstanceTemplateRead(d *schema.ResourceData, meta inte } } } - if instanceTemplate.Properties.Scheduling != nil { - schedulingMap, err := tpgresource.ConvertToMap(instanceTemplate.Properties.Scheduling) - if err != nil { - return fmt.Errorf("Error converting scheduling: %s", err) - } - scheduling := flattenScheduling(schedulingMap) + if schedulingMapFromResponse != nil { + scheduling := flattenScheduling(schedulingMapFromResponse) {{ if ne $.TargetVersionName `ga` }} // Workaroud: API doesn't update the scheduling.graceful_shutdown.max_duration.nanos field. // To avoid diff, we need to set the value from the state not from API response. From 5e2fa2ac706a7508258338f6aac6e7609711692c Mon Sep 17 00:00:00 2001 From: Igor Nadenenko Date: Thu, 18 Jun 2026 22:57:48 +0200 Subject: [PATCH 11/30] more test fixes --- ...ompute_instance_from_machine_image.go.tmpl | 17 +++++++++++++--- ...rce_compute_instance_from_template.go.tmpl | 20 +++++++++++++++++-- 2 files changed, 32 insertions(+), 5 deletions(-) diff --git a/mmv1/third_party/terraform/services/compute/resource_compute_instance_from_machine_image.go.tmpl b/mmv1/third_party/terraform/services/compute/resource_compute_instance_from_machine_image.go.tmpl index fb37d34a394e..2e0d17e868bc 100644 --- a/mmv1/third_party/terraform/services/compute/resource_compute_instance_from_machine_image.go.tmpl +++ b/mmv1/third_party/terraform/services/compute/resource_compute_instance_from_machine_image.go.tmpl @@ -225,10 +225,13 @@ func resourceComputeInstanceFromMachineImageCreate(d *schema.ResourceData, meta } if schedulingBody != nil { // Strip nil Duration fields so the source machine image values are inherited - // instead of being overridden with null. + // instead of being overridden with null. Use json.Marshal to detect typed nils + // (map[string]interface{}(nil)) that are not equal to untyped nil. for _, k := range []string{"localSsdRecoveryTimeout", "maxRunDuration", "gracefulShutdown", "preemptionNoticeDuration"} { - if v, ok := schedulingBody[k]; ok && v == nil { - delete(schedulingBody, k) + if v, ok := schedulingBody[k]; ok { + if b, err := json.Marshal(v); err == nil && string(b) == "null" { + delete(schedulingBody, k) + } } } instanceBody["scheduling"] = schedulingBody @@ -265,6 +268,14 @@ func resourceComputeInstanceFromMachineImageCreate(d *schema.ResourceData, meta instanceBody["advancedMachineFeatures"] = amf } + if metadataBody, err := resourceInstanceMetadata(d); err != nil { + return fmt.Errorf("Error expanding metadata: %s", err) + } else if len(metadataBody) > 0 { + instanceBody["metadata"] = metadataBody + } else { + delete(instanceBody, "metadata") + } + if partnerMetadataRaw, err := resourceInstancePartnerMetadata(d); err != nil { return fmt.Errorf("Error expanding partner_metadata: %s", err) } else if partnerMetadataBody, err := convertPartnerMetadataToCompute(partnerMetadataRaw); err != nil { diff --git a/mmv1/third_party/terraform/services/compute/resource_compute_instance_from_template.go.tmpl b/mmv1/third_party/terraform/services/compute/resource_compute_instance_from_template.go.tmpl index 372fc755f427..226e5457525c 100644 --- a/mmv1/third_party/terraform/services/compute/resource_compute_instance_from_template.go.tmpl +++ b/mmv1/third_party/terraform/services/compute/resource_compute_instance_from_template.go.tmpl @@ -181,10 +181,26 @@ func resourceComputeInstanceFromTemplateCreate(d *schema.ResourceData, meta inte } } - // when we make the original call to expandComputeInstance expandScheduling is called, which sets default values. - // However, we want the values to be read from the template instead. + // expandComputeInstance no longer sets Scheduling; handle it here so the user's + // scheduling overrides are applied. When no scheduling block is set, inherit from + // the instance template. When set, convert the map-based result from expandScheduling + // into the typed struct required by the API client. if _, hasSchedule := d.GetOk("scheduling"); !hasSchedule { instance.Scheduling = it.Properties.Scheduling + } else { + schedulingMap, err := expandScheduling(d.Get("scheduling")) + if err != nil { + return fmt.Errorf("Error expanding scheduling: %s", err) + } + schedulingBytes, err := json.Marshal(schedulingMap) + if err != nil { + return fmt.Errorf("Error marshaling scheduling: %s", err) + } + schedulingTyped := &compute.Scheduling{} + if err := json.Unmarshal(schedulingBytes, schedulingTyped); err != nil { + return fmt.Errorf("Error setting scheduling: %s", err) + } + instance.Scheduling = schedulingTyped } // Force send all top-level fields that have been set in case they're overridden to zero values. From c6ac4439887780c90a1ac1307320d2c2fd0b25f0 Mon Sep 17 00:00:00 2001 From: Igor Nadenenko Date: Fri, 19 Jun 2026 14:03:20 +0200 Subject: [PATCH 12/30] tests fix --- ...rce_compute_instance_from_template.go.tmpl | 28 +++++++++++++++++++ ...resource_compute_instance_template.go.tmpl | 15 ++++++++++ 2 files changed, 43 insertions(+) diff --git a/mmv1/third_party/terraform/services/compute/resource_compute_instance_from_template.go.tmpl b/mmv1/third_party/terraform/services/compute/resource_compute_instance_from_template.go.tmpl index 226e5457525c..25880ddd5a4a 100644 --- a/mmv1/third_party/terraform/services/compute/resource_compute_instance_from_template.go.tmpl +++ b/mmv1/third_party/terraform/services/compute/resource_compute_instance_from_template.go.tmpl @@ -192,6 +192,20 @@ func resourceComputeInstanceFromTemplateCreate(d *schema.ResourceData, meta inte if err != nil { return fmt.Errorf("Error expanding scheduling: %s", err) } + for _, key := range []string{"localSsdRecoveryTimeout", "maxRunDuration", "preemptionNoticeDuration"} { + if dur, ok := schedulingMap[key].(map[string]interface{}); ok { + if sec, ok := dur["seconds"]; ok { + dur["seconds"] = fmt.Sprintf("%d", getInt(sec)) + } + } + } + if gs, ok := schedulingMap["gracefulShutdown"].(map[string]interface{}); ok { + if md, ok := gs["maxDuration"].(map[string]interface{}); ok { + if sec, ok := md["seconds"]; ok { + md["seconds"] = fmt.Sprintf("%d", getInt(sec)) + } + } + } schedulingBytes, err := json.Marshal(schedulingMap) if err != nil { return fmt.Errorf("Error marshaling scheduling: %s", err) @@ -203,6 +217,20 @@ func resourceComputeInstanceFromTemplateCreate(d *schema.ResourceData, meta inte instance.Scheduling = schedulingTyped } + {{- if ne $.TargetVersionName "ga" }} + if pm := d.Get("partner_metadata").(map[string]interface{}); len(pm) > 0 { + pmRaw, err := resourceInstancePartnerMetadata(d) + if err != nil { + return fmt.Errorf("Error expanding partner_metadata: %s", err) + } + partnerMetadataTyped, err := convertPartnerMetadataToComputeTyped(pmRaw) + if err != nil { + return fmt.Errorf("Error converting partner_metadata: %s", err) + } + instance.PartnerMetadata = partnerMetadataTyped + } + {{- end }} + // Force send all top-level fields that have been set in case they're overridden to zero values. // Initialize ForceSendFields to empty so we don't get things that the instance resource // always force-sends. 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 477fdd2d6ee0..0a3be017d28e 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 @@ -2443,6 +2443,21 @@ func expandResourceComputeInstanceTemplateScheduling(d *schema.ResourceData, met expanded["onHostMaintenance"] = "TERMINATE" } + for _, key := range []string{"localSsdRecoveryTimeout", "maxRunDuration", "preemptionNoticeDuration"} { + if dur, ok := expanded[key].(map[string]interface{}); ok { + if sec, ok := dur["seconds"]; ok { + dur["seconds"] = strconv.FormatInt(getInt(sec), 10) + } + } + } + if gs, ok := expanded["gracefulShutdown"].(map[string]interface{}); ok { + if md, ok := gs["maxDuration"].(map[string]interface{}); ok { + if sec, ok := md["seconds"]; ok { + md["seconds"] = strconv.FormatInt(getInt(sec), 10) + } + } + } + schedulingTyped := &compute.Scheduling{} if err := tpgresource.Convert(expanded, schedulingTyped); err != nil { return nil, fmt.Errorf("Error converting scheduling: %s", err) From 206bfe6d0846acc752cc9444897f619647c5abfc Mon Sep 17 00:00:00 2001 From: Igor Nadenenko Date: Thu, 25 Jun 2026 17:54:40 +0200 Subject: [PATCH 13/30] rebase --- .../compute/compute_instance_helpers.go.tmpl | 17 ----------------- 1 file changed, 17 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 9b994196e6a2..649528e66901 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 @@ -374,7 +374,6 @@ func expandGracefulShutdownMaxDuration(v interface{}) (map[string]interface{}, e if transformedNanos := maxDurationMap["nanos"]; reflect.ValueOf(transformedNanos).IsValid() && !tpgresource.IsEmptyValue(reflect.ValueOf(transformedNanos)) { result["nanos"] = int64(transformedNanos.(int)) } - // Seconds was in ForceSendFields — always include even when zero result["seconds"] = int64(maxDurationMap["seconds"].(int)) return result, nil @@ -784,22 +783,6 @@ func getInterfaceSlice(v interface{}) []interface{} { return s } -func getInt(v interface{}) int64 { - switch t := v.(type) { - case int: - return int64(t) - case int64: - return t - case float64: - return int64(t) - case string: - i, _ := strconv.ParseInt(t, 10, 64) - return i - default: - return 0 - } -} - {{ if ne $.TargetVersionName `ga` -}} // firstAccessConfigSecurityPolicy returns the securityPolicy of the first // access config in the list, or "" if absent. From a75e1679e532abd69c6350df9323225d74dcc981 Mon Sep 17 00:00:00 2001 From: nsamartsev Date: Mon, 25 May 2026 16:29:22 +0200 Subject: [PATCH 14/30] Migrate `expandAdvancedMachineFeatures`, `flattenAdvancedMachineFeatures` functions --- .../resource_compute_instance_template.go.tmpl | 12 ------------ 1 file changed, 12 deletions(-) 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 0a3be017d28e..50867e820405 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 @@ -2564,18 +2564,6 @@ func guestAcceleratorsToInterface(accelerators []*compute.AcceleratorConfig) []i return result } -func expandAdvancedMachineFeaturesTyped(d tpgresource.TerraformResourceData) *compute.AdvancedMachineFeatures { - amfMap := expandAdvancedMachineFeatures(d) - if amfMap == nil { - return nil - } - typed := &compute.AdvancedMachineFeatures{} - if err := tpgresource.Convert(amfMap, typed); err != nil { - return nil - } - return typed -} - func resourceComputeInstanceTemplateImportState(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { config := meta.(*transport_tpg.Config) if err := tpgresource.ParseImportId([]string{"projects/(?P[^/]+)/global/instanceTemplates/(?P[^/]+)", "(?P[^/]+)/(?P[^/]+)", "(?P[^/]+)"}, d, config); err != nil { From 3db2d11368393b27e465f80ea47d63770603b73a Mon Sep 17 00:00:00 2001 From: Igor Nadenenko Date: Thu, 25 Jun 2026 18:21:01 +0200 Subject: [PATCH 15/30] fetch other branch changes --- .../compute/compute_instance_helpers.go.tmpl | 17 ++++++++++++++++- .../resource_compute_instance_template.go.tmpl | 12 ++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) 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 649528e66901..58fe8c4fd8c5 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 @@ -783,6 +783,22 @@ func getInterfaceSlice(v interface{}) []interface{} { return s } +func getInt(v interface{}) int64 { + switch t := v.(type) { + case int: + return int64(t) + case int64: + return t + case float64: + return int64(t) + case string: + i, _ := strconv.ParseInt(t, 10, 64) + return i + default: + return 0 + } +} + {{ if ne $.TargetVersionName `ga` -}} // firstAccessConfigSecurityPolicy returns the securityPolicy of the first // access config in the list, or "" if absent. @@ -1103,7 +1119,6 @@ func flattenAdvancedMachineFeatures(v map[string]interface{}) []map[string]inter return []map[string]interface{}{result} } - func flattenShieldedVmConfig(shieldedVmConfig map[string]interface{}) []map[string]bool { if shieldedVmConfig == nil { return nil 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 50867e820405..0a3be017d28e 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 @@ -2564,6 +2564,18 @@ func guestAcceleratorsToInterface(accelerators []*compute.AcceleratorConfig) []i return result } +func expandAdvancedMachineFeaturesTyped(d tpgresource.TerraformResourceData) *compute.AdvancedMachineFeatures { + amfMap := expandAdvancedMachineFeatures(d) + if amfMap == nil { + return nil + } + typed := &compute.AdvancedMachineFeatures{} + if err := tpgresource.Convert(amfMap, typed); err != nil { + return nil + } + return typed +} + func resourceComputeInstanceTemplateImportState(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { config := meta.(*transport_tpg.Config) if err := tpgresource.ParseImportId([]string{"projects/(?P[^/]+)/global/instanceTemplates/(?P[^/]+)", "(?P[^/]+)/(?P[^/]+)", "(?P[^/]+)"}, d, config); err != nil { From 9b01220434e7ab55805e6a7dc34c4171669cee7e Mon Sep 17 00:00:00 2001 From: Igor Nadenenko Date: Thu, 25 Jun 2026 18:57:30 +0200 Subject: [PATCH 16/30] fix --- .../services/compute/resource_compute_instance.go.tmpl | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) 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 e1a775ab300e..3d7f15cd504a 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 @@ -2261,7 +2261,11 @@ func resourceComputeInstanceRead(d *schema.ResourceData, meta interface{}) error {{ if ne $.TargetVersionName `ga` -}} // Workaround: restore nanos from state since the API doesn't persist it (see comment above). if hadNanos { - scheduling := flattenScheduling(instance.Scheduling) + schedulingMap, err := tpgresource.ConvertToMap(instance.Scheduling) + if err != nil { + return fmt.Errorf("Error converting scheduling for nanos workaround: %s", err) + } + scheduling := flattenScheduling(schedulingMap) graceful_shutdown := scheduling[0]["graceful_shutdown"].([]interface{})[0].(map[string]interface{}) max_duration := graceful_shutdown["max_duration"].([]interface{})[0].(map[string]interface{}) max_duration["nanos"] = int64(savedNanos.(int)) From 0b2a751fa734127d153ecddbb2d0ba0fb6f5c7db Mon Sep 17 00:00:00 2001 From: Igor Nadenenko Date: Mon, 29 Jun 2026 19:06:21 +0200 Subject: [PATCH 17/30] fmt --- .../tgc_next/pkg/services/compute/compute_instance_tfplan2cai.go | 1 - 1 file changed, 1 deletion(-) diff --git a/mmv1/third_party/tgc_next/pkg/services/compute/compute_instance_tfplan2cai.go b/mmv1/third_party/tgc_next/pkg/services/compute/compute_instance_tfplan2cai.go index 25cf95436227..a4f450151429 100644 --- a/mmv1/third_party/tgc_next/pkg/services/compute/compute_instance_tfplan2cai.go +++ b/mmv1/third_party/tgc_next/pkg/services/compute/compute_instance_tfplan2cai.go @@ -669,7 +669,6 @@ func expandComputeLocalSsdRecoveryTimeoutTgc(v interface{}) (*compute.Duration, return duration, nil } - func expandAccessConfigsTyped(configs []interface{}) ([]*compute.AccessConfig, error) { expanded := expandAccessConfigs(configs) acs := make([]*compute.AccessConfig, 0, len(expanded)) From 1926776ad71ac48a1f8bf5dc5aa957afe2ad2147 Mon Sep 17 00:00:00 2001 From: Igor Nadenenko Date: Mon, 29 Jun 2026 13:45:07 +0200 Subject: [PATCH 18/30] Migrate metadata files to new API --- .../data_source_google_compute_instance.go | 8 +- .../services/compute/metadata.go.tmpl | 112 ++++++------------ .../compute/resource_compute_instance.go.tmpl | 67 +++++++---- ...resource_compute_instance_template.go.tmpl | 36 ++++-- .../resource_compute_project_metadata.go.tmpl | 69 +++-------- ...urce_compute_project_metadata_item.go.tmpl | 37 +++--- ...urce_compute_project_metadata_item_test.go | 8 +- ...e_compute_region_instance_template.go.tmpl | 32 ++--- 8 files changed, 175 insertions(+), 194 deletions(-) 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 f84f5821e84b..cb92f3c77ceb 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 @@ -1,6 +1,7 @@ package compute import ( + "encoding/json" "fmt" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" @@ -41,7 +42,12 @@ func dataSourceGoogleComputeInstanceRead(d *schema.ResourceData, meta interface{ return transport_tpg.HandleDataSourceNotFoundError(err, d, fmt.Sprintf("Instance %s", name), id) } - md := flattenMetadataBeta(instance.Metadata) + var metadataMap map[string]interface{} + if instance.Metadata != nil { + metaBytes, _ := json.Marshal(instance.Metadata) + _ = json.Unmarshal(metaBytes, &metadataMap) + } + md := flattenMetadataBeta(metadataMap) if err = d.Set("metadata", md); err != nil { return fmt.Errorf("error setting metadata: %s", err) } diff --git a/mmv1/third_party/terraform/services/compute/metadata.go.tmpl b/mmv1/third_party/terraform/services/compute/metadata.go.tmpl index 13553cef706e..d76e63bd8d07 100644 --- a/mmv1/third_party/terraform/services/compute/metadata.go.tmpl +++ b/mmv1/third_party/terraform/services/compute/metadata.go.tmpl @@ -11,12 +11,6 @@ import ( {{- end }} "sort" -{{ if eq $.TargetVersionName `ga` }} - "google.golang.org/api/compute/v1" -{{- else }} - compute "google.golang.org/api/compute/v0.beta" -{{- end }} - "github.com/hashicorp/terraform-provider-google/google/tpgresource" transport_tpg "github.com/hashicorp/terraform-provider-google/google/transport" ) @@ -50,61 +44,61 @@ func BetaMetadataUpdate(oldMDMap map[string]interface{}, newMDMap map[string]int MetadataUpdate(oldMDMap, newMDMap, serverMD) } -func expandComputeMetadata(m map[string]interface{}) []*compute.MetadataItems { - metadata := make([]*compute.MetadataItems, 0, len(m)) +func expandComputeMetadata(m map[string]interface{}) []interface{} { + metadata := make([]interface{}, 0, len(m)) var keys []string for key := range m { keys = append(keys, key) } sort.Strings(keys) - // Append new metadata to existing metadata for _, key := range keys { v := m[key].(string) - metadata = append(metadata, &compute.MetadataItems{ - Key: key, - Value: &v, + metadata = append(metadata, map[string]interface{}{ + "key": key, + "value": v, }) } - return metadata } -func flattenMetadataBeta(metadata *compute.Metadata) map[string]string { +func flattenMetadataBeta(metadata map[string]interface{}) map[string]string { metadataMap := make(map[string]string) if metadata == nil { return metadataMap } - for _, item := range metadata.Items { - if item == nil { + items, _ := metadata["items"].([]interface{}) + for _, raw := range items { + item, ok := raw.(map[string]interface{}) + if !ok || item == nil { continue } - if item.Value == nil { - metadataMap[item.Key] = "" - } else { - metadataMap[item.Key] = *item.Value + key, _ := item["key"].(string) + if key == "" { + continue } + val, _ := item["value"].(string) + metadataMap[key] = val } return metadataMap } -// This function differs from flattenMetadataBeta only in that it takes -// compute.metadata rather than compute.metadata as an argument. It should -// be removed in favour of flattenMetadataBeta if/when all resources using it get -// beta support. -func FlattenMetadata(metadata *compute.Metadata) map[string]interface{} { +func FlattenMetadata(metadata map[string]interface{}) map[string]interface{} { metadataMap := make(map[string]interface{}) if metadata == nil { return metadataMap } - for _, item := range metadata.Items { - if item == nil { + items, _ := metadata["items"].([]interface{}) + for _, raw := range items { + item, ok := raw.(map[string]interface{}) + if !ok || item == nil { continue } - if item.Value == nil { - metadataMap[item.Key] = "" - } else { - metadataMap[item.Key] = *item.Value + key, _ := item["key"].(string) + if key == "" { + continue } + val, _ := item["value"].(string) + metadataMap[key] = val } return metadataMap } @@ -112,9 +106,7 @@ func FlattenMetadata(metadata *compute.Metadata) map[string]interface{} { // resourceInstanceMetadata builds the instance metadata as a JSON-shaped // map[string]interface{} mirroring the compute.Metadata API type (camelCase // keys: "items" -> [{"key", "value"}], "fingerprint"). Empty values are omitted -// to mirror the Apiary struct's omitempty tags, so the marshaled request body is -// byte-identical whether the map is sent directly or round-tripped through the -// typed struct via resourceInstanceMetadataTyped. +// to mirror the Apiary struct's omitempty tags. func resourceInstanceMetadata(d tpgresource.TerraformResourceData) (map[string]interface{}, error) { m := map[string]interface{}{} mdMap := d.Get("metadata").(map[string]interface{}) @@ -154,20 +146,6 @@ func resourceInstanceMetadata(d tpgresource.TerraformResourceData) (map[string]i return m, nil } -// resourceInstanceMetadataTyped adapts the map-based resourceInstanceMetadata -// output to the typed *compute.Metadata still required by callers that build -// Apiary request structs directly. -func resourceInstanceMetadataTyped(d tpgresource.TerraformResourceData) (*compute.Metadata, error) { - mdMap, err := resourceInstanceMetadata(d) - if err != nil { - return nil, err - } - m := &compute.Metadata{} - if err := convertViaJSON(mdMap, m); err != nil { - return nil, err - } - return m, nil -} {{- if ne $.TargetVersionName "ga" }} func resourceInstancePartnerMetadata(d tpgresource.TerraformResourceData) (map[string]interface{}, error) { @@ -210,9 +188,7 @@ func flattenPartnerMetadata(partnerMetadata map[string]interface{}) (map[string] // convertPartnerMetadataToCompute builds the partner metadata as a JSON-shaped // map[string]interface{} mirroring the compute.StructuredEntries API type (each -// value is the parsed entries object, or an empty object for nil). The typed -// adapter convertPartnerMetadataToComputeTyped round-trips this through -// map[string]compute.StructuredEntries for callers that build Apiary structs. +// value is the parsed entries object, or an empty object for nil). func convertPartnerMetadataToCompute(pm map[string]interface{}) (map[string]interface{}, error) { result := make(map[string]interface{}) for key, value := range pm { @@ -225,40 +201,20 @@ func convertPartnerMetadataToCompute(pm map[string]interface{}) (map[string]inte return result, nil } -// convertPartnerMetadataToComputeTyped adapts the map-based -// convertPartnerMetadataToCompute output to the typed -// map[string]compute.StructuredEntries still required by callers that build -// Apiary request structs directly. -func convertPartnerMetadataToComputeTyped(pm map[string]interface{}) (map[string]compute.StructuredEntries, error) { - pmMap, err := convertPartnerMetadataToCompute(pm) - if err != nil { - return nil, err - } - result := make(map[string]compute.StructuredEntries) - if err := convertViaJSON(pmMap, &result); err != nil { - return nil, err - } - return result, nil -} - -func convertPartnerMetadataFromCompute(pm map[string]compute.StructuredEntries) map[string]interface{} { +func convertPartnerMetadataFromCompute(pm map[string]interface{}) map[string]interface{} { result := make(map[string]interface{}) - for key, value := range pm { - if value.Entries == nil { + for key, raw := range pm { + value, ok := raw.(map[string]interface{}) + if !ok || value == nil { result[key] = nil continue } - seBytes, err := json.Marshal(value) - if err != nil { + entries, hasEntries := value["entries"] + if !hasEntries || entries == nil { result[key] = nil continue } - var m map[string]interface{} - if err := json.Unmarshal(seBytes, &m); err != nil { - result[key] = nil - continue - } - result[key] = m + result[key] = value } return result } 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 3d7f15cd504a..716865966003 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 @@ -1862,6 +1862,36 @@ func expandComputeInstance(project string, d *schema.ResourceData, config *trans disks = append(disks, disk) } + metadataMap, err := resourceInstanceMetadata(d) + if err != nil { + return nil, fmt.Errorf("Error creating metadata: %s", err) + } + var metadata *compute.Metadata + if metadataMap != nil { + metadata = &compute.Metadata{} + if err := tpgresource.Convert(metadataMap, metadata); err != nil { + return nil, fmt.Errorf("Error converting metadata: %s", err) + } + } + + {{ if ne $.TargetVersionName `ga` -}} + partnerMetadataMap, err := resourceInstancePartnerMetadata(d) + if err != nil { + return nil, fmt.Errorf("Error creating partner metadata: %s", err) + } + partnerMetadataConverted, err := convertPartnerMetadataToCompute(partnerMetadataMap) + if err != nil { + return nil, fmt.Errorf("Error converting partner metadata: %s", err) + } + var partnerMetadata map[string]compute.StructuredEntries + if len(partnerMetadataConverted) > 0 { + if err := tpgresource.Convert(partnerMetadataConverted, &partnerMetadata); err != nil { + return nil, fmt.Errorf("Error converting partner metadata: %s", err) + } + } + {{- end }} + + networkInterfaces, err := expandNetworkInterfacesTyped(d, config) if err != nil { return nil, fmt.Errorf("Error creating network interfaces: %s", err) @@ -1920,7 +1950,7 @@ func expandComputeInstance(project string, d *schema.ResourceData, config *trans MachineType: machineTypeUrl, Metadata: metadata, {{- if ne $.TargetVersionName "ga" }} - PartnerMetadata: PartnerMetadata, + PartnerMetadata: partnerMetadata, {{- end }} Name: d.Get("name").(string), NetworkInterfaces: networkInterfaces, @@ -2099,26 +2129,6 @@ func resourceComputeInstanceCreate(d *schema.ResourceData, meta interface{}) err if err != nil { return fmt.Errorf("Error converting instance: %s", err) } - metadataMap, err := resourceInstanceMetadata(d) - if err != nil { - return fmt.Errorf("Error creating metadata: %s", err) - } - if len(metadataMap) > 0 { - instanceBody["metadata"] = metadataMap - } - {{ if ne $.TargetVersionName `ga` -}} - partnerMetadataMap, err := resourceInstancePartnerMetadata(d) - if err != nil { - return fmt.Errorf("Error creating partner metadata: %s", err) - } - partnerMetadataConverted, err := convertPartnerMetadataToCompute(partnerMetadataMap) - if err != nil { - return fmt.Errorf("Error converting partner metadata: %s", err) - } - if len(partnerMetadataConverted) > 0 { - instanceBody["partnerMetadata"] = partnerMetadataConverted - } - {{- end }} schedulingBody, err := expandScheduling(d.Get("scheduling")) if err != nil { return fmt.Errorf("Error creating scheduling: %s", err) @@ -2288,7 +2298,14 @@ func resourceComputeInstanceRead(d *schema.ResourceData, meta interface{}) error } func populateComputeInstanceResourceData(d *schema.ResourceData, instance *compute.Instance, project, zone string, config *transport_tpg.Config) error { - if err := d.Set("metadata", flattenMetadataBeta(instance.Metadata)); err != nil { + var metadataMap map[string]interface{} + if instance.Metadata != nil { + metadataMap, err = tpgresource.ConvertToMap(instance.Metadata) + if err != nil { + return fmt.Errorf("Error converting metadata: %s", err) + } + } + if err := d.Set("metadata", flattenMetadataBeta(metadataMap)); err != nil { return fmt.Errorf("Error setting metadata: %s", err) } @@ -2606,7 +2623,11 @@ func populateComputeInstanceResourceData(d *schema.ResourceData, instance *compu {{- if ne $.TargetVersionName `ga` }} if instance.PartnerMetadata != nil { - partnerMetadata, err := flattenPartnerMetadata(convertPartnerMetadataFromCompute(instance.PartnerMetadata)) + var partnerMetadataMap map[string]interface{} + if err := tpgresource.Convert(instance.PartnerMetadata, &partnerMetadataMap); err != nil { + return fmt.Errorf("Error converting partner metadata: %s", err) + } + partnerMetadata, err := flattenPartnerMetadata(convertPartnerMetadataFromCompute(partnerMetadataMap)) if err != nil { return fmt.Errorf("Error parsing partner metadata: %s", err) } 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 0a3be017d28e..0e8780c226ee 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 @@ -1670,19 +1670,32 @@ func resourceComputeInstanceTemplateCreate(d *schema.ResourceData, meta interfac return err } - metadata, err := resourceInstanceMetadataTyped(d) + metadataMap, err := resourceInstanceMetadata(d) if err != nil { return err } + var metadata *compute.Metadata + if metadataMap != nil { + metadata = &compute.Metadata{} + if err := tpgresource.Convert(metadataMap, metadata); err != nil { + return fmt.Errorf("Error converting metadata: %s", err) + } + } {{- if ne $.TargetVersionName "ga" }} partnerMetadataMap, err := resourceInstancePartnerMetadata(d) if err != nil { return err } - PartnerMetadata, err := convertPartnerMetadataToComputeTyped(partnerMetadataMap) + partnerMetadataConverted, err := convertPartnerMetadataToCompute(partnerMetadataMap) if err != nil { return err } + var partnerMetadata map[string]compute.StructuredEntries + if len(partnerMetadataConverted) > 0 { + if err := tpgresource.Convert(partnerMetadataConverted, &partnerMetadata); err != nil { + return fmt.Errorf("Error converting partner metadata: %s", err) + } + } {{- end }} networks, err := expandNetworkInterfacesTyped(d, config) @@ -1736,7 +1749,7 @@ func resourceComputeInstanceTemplateCreate(d *schema.ResourceData, meta interfac Disks: disks, Metadata: metadata, {{- if ne $.TargetVersionName "ga" }} - PartnerMetadata: PartnerMetadata, + PartnerMetadata: partnerMetadata, {{- end }} NetworkInterfaces: networks, NetworkPerformanceConfig: networkPerformanceConfig, @@ -2152,9 +2165,14 @@ func resourceComputeInstanceTemplateRead(d *schema.ResourceData, meta interface{ return fmt.Errorf("Error setting metadata_fingerprint: %s", err) } - md := instanceTemplate.Properties.Metadata - - _md := flattenMetadataBeta(md) + metadata := instanceTemplate.Properties.Metadata + var metadataMap map[string]interface{} + if metadata != nil { + if metadataMap, err = tpgresource.ConvertToMap(metadata); err != nil { + return fmt.Errorf("Error converting metadata: %s", err) + } + } + _md := flattenMetadataBeta(metadataMap) if script, scriptExists := d.GetOk("metadata_startup_script"); scriptExists { if err = d.Set("metadata_startup_script", script); err != nil { @@ -2175,7 +2193,11 @@ func resourceComputeInstanceTemplateRead(d *schema.ResourceData, meta interface{ {{ if ne $.TargetVersionName `ga` -}} if instanceTemplate.Properties.PartnerMetadata != nil { - partnerMetadata, err := flattenPartnerMetadata(convertPartnerMetadataFromCompute(instanceTemplate.Properties.PartnerMetadata)) + var partnerMetadataMap map[string]interface{} + if err := tpgresource.Convert(instanceTemplate.Properties.PartnerMetadata, &partnerMetadataMap); err != nil { + return fmt.Errorf("Error converting partner metadata: %s", err) + } + partnerMetadata, err := flattenPartnerMetadata(convertPartnerMetadataFromCompute(partnerMetadataMap)) if err != nil { return fmt.Errorf("Error parsing partner metadata: %s", err) } diff --git a/mmv1/third_party/terraform/services/compute/resource_compute_project_metadata.go.tmpl b/mmv1/third_party/terraform/services/compute/resource_compute_project_metadata.go.tmpl index 0f1fd7cbe951..22b390b8eff0 100644 --- a/mmv1/third_party/terraform/services/compute/resource_compute_project_metadata.go.tmpl +++ b/mmv1/third_party/terraform/services/compute/resource_compute_project_metadata.go.tmpl @@ -1,7 +1,6 @@ package compute import ( - "encoding/json" "fmt" "log" "time" @@ -12,12 +11,6 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/customdiff" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - -{{ if eq $.TargetVersionName `ga` }} - "google.golang.org/api/compute/v1" -{{- else }} - compute "google.golang.org/api/compute/v0.beta" -{{- end }} ) func ResourceComputeProjectMetadata() *schema.Resource { @@ -82,11 +75,7 @@ func resourceComputeProjectMetadataCreateOrUpdate(d *schema.ResourceData, meta i return err } - md := &compute.Metadata{ - Items: expandComputeMetadata(d.Get("metadata").(map[string]interface{})), - } - - if err = resourceComputeProjectMetadataSet(d, projectID, userAgent, config, md, d.Timeout(schema.TimeoutCreate)); err != nil { + if err = resourceComputeProjectMetadataSet(d, projectID, userAgent, config, d.Get("metadata").(map[string]interface{}), d.Timeout(schema.TimeoutCreate)); err != nil { return fmt.Errorf("SetCommonInstanceMetadata failed: %s", err) } @@ -118,7 +107,11 @@ func resourceComputeProjectMetadataRead(d *schema.ResourceData, meta interface{} return transport_tpg.HandleNotFoundError(err, d, fmt.Sprintf("Project metadata for project %q", projectId)) } - err = d.Set("metadata", FlattenMetadata(project.CommonInstanceMetadata)) + var commonInstanceMetadata map[string]interface{} + if v, ok := project["commonInstanceMetadata"].(map[string]interface{}); ok { + commonInstanceMetadata = v + } + err = d.Set("metadata", FlattenMetadata(commonInstanceMetadata)) if err != nil { return fmt.Errorf("Error setting metadata: %s", err) } @@ -154,15 +147,14 @@ func resourceComputeProjectMetadataDelete(d *schema.ResourceData, meta interface return err } - md := &compute.Metadata{} - if err = resourceComputeProjectMetadataSet(d, projectID, userAgent, config, md, d.Timeout(schema.TimeoutDelete)); err != nil { + if err = resourceComputeProjectMetadataSet(d, projectID, userAgent, config, nil, d.Timeout(schema.TimeoutDelete)); err != nil { return fmt.Errorf("SetCommonInstanceMetadata failed: %s", err) } return resourceComputeProjectMetadataRead(d, meta) } -func resourceComputeProjectMetadataSet(d *schema.ResourceData, projectID, userAgent string, config *transport_tpg.Config, md *compute.Metadata, timeout time.Duration) error { +func resourceComputeProjectMetadataSet(d *schema.ResourceData, projectID, userAgent string, config *transport_tpg.Config, mdItems map[string]interface{}, timeout time.Duration) error { createMD := func() error { log.Printf("[DEBUG] Loading project service: %s", projectID) project, err := getComputeProject(d, config, projectID, userAgent) @@ -170,13 +162,14 @@ func resourceComputeProjectMetadataSet(d *schema.ResourceData, projectID, userAg return fmt.Errorf("Error loading project '%s': %s", projectID, err) } - if project.CommonInstanceMetadata != nil { - md.Fingerprint = project.CommonInstanceMetadata.Fingerprint + body := map[string]interface{}{} + if mdItems != nil { + body["items"] = expandComputeMetadata(mdItems) } - - body, err := computeMetadataToMap(md) - if err != nil { - return fmt.Errorf("Error encoding metadata: %s", err) + if commonInstanceMetadata, ok := project["commonInstanceMetadata"].(map[string]interface{}); ok { + if fp, ok := commonInstanceMetadata["fingerprint"].(string); ok && fp != "" { + body["fingerprint"] = fp + } } url, err := tpgresource.ReplaceVars(d, config, "{{"{{"}}ComputeBasePath{{"}}"}}") @@ -198,51 +191,27 @@ func resourceComputeProjectMetadataSet(d *schema.ResourceData, projectID, userAg } log.Printf("[DEBUG] SetCommonMetadata: %v", res["selfLink"]) - return ComputeOperationWaitTime(config, res, project.Name, "SetCommonMetadata", userAgent, timeout) + projectName, _ := project["name"].(string) + return ComputeOperationWaitTime(config, res, projectName, "SetCommonMetadata", userAgent, timeout) } err := transport_tpg.MetadataRetryWrapper(createMD) return err } -func getComputeProject(d *schema.ResourceData, config *transport_tpg.Config, projectID, userAgent string) (*compute.Project, error) { +func getComputeProject(d *schema.ResourceData, config *transport_tpg.Config, projectID, userAgent string) (map[string]interface{}, error) { url, err := tpgresource.ReplaceVars(d, config, "{{"{{"}}ComputeBasePath{{"}}"}}") if err != nil { return nil, err } url = fmt.Sprintf("%sprojects/%s", url, projectID) - res, err := transport_tpg.SendRequest(transport_tpg.SendRequestOptions{ + return transport_tpg.SendRequest(transport_tpg.SendRequestOptions{ Config: config, Method: "GET", Project: projectID, RawURL: url, UserAgent: userAgent, }) - if err != nil { - return nil, err - } - - resBytes, err := json.Marshal(res) - if err != nil { - return nil, err - } - project := &compute.Project{} - if err := json.Unmarshal(resBytes, project); err != nil { - return nil, err - } - return project, nil -} - -func computeMetadataToMap(md *compute.Metadata) (map[string]interface{}, error) { - mdBytes, err := json.Marshal(md) - if err != nil { - return nil, err - } - body := map[string]interface{}{} - if err := json.Unmarshal(mdBytes, &body); err != nil { - return nil, err - } - return body, nil } func init() { diff --git a/mmv1/third_party/terraform/services/compute/resource_compute_project_metadata_item.go.tmpl b/mmv1/third_party/terraform/services/compute/resource_compute_project_metadata_item.go.tmpl index b0d1772ce0c6..ecdcf9c18cdb 100644 --- a/mmv1/third_party/terraform/services/compute/resource_compute_project_metadata_item.go.tmpl +++ b/mmv1/third_party/terraform/services/compute/resource_compute_project_metadata_item.go.tmpl @@ -11,12 +11,6 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/customdiff" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - -{{ if eq $.TargetVersionName `ga` }} - "google.golang.org/api/compute/v1" -{{- else }} - compute "google.golang.org/api/compute/v0.beta" -{{- end }} ) type metadataPresentBehavior bool @@ -116,7 +110,11 @@ func resourceComputeProjectMetadataItemRead(d *schema.ResourceData, meta interfa return fmt.Errorf("Error loading project '%s': %s", projectID, err) } - md := FlattenMetadata(project.CommonInstanceMetadata) + var commonInstanceMetadata map[string]interface{} + if v, ok := project["commonInstanceMetadata"].(map[string]interface{}); ok { + commonInstanceMetadata = v + } + md := FlattenMetadata(commonInstanceMetadata) val, ok := md[d.Id()] if !ok { // Resource no longer exists @@ -231,7 +229,11 @@ func updateComputeCommonInstanceMetadata(d *schema.ResourceData, config *transpo return fmt.Errorf("Error loading project '%s': %s", projectID, err) } - md := FlattenMetadata(project.CommonInstanceMetadata) + var commonInstanceMetadata map[string]interface{} + if v, ok := project["commonInstanceMetadata"].(map[string]interface{}); ok { + commonInstanceMetadata = v + } + md := FlattenMetadata(commonInstanceMetadata) val, ok := md[key] @@ -256,17 +258,13 @@ func updateComputeCommonInstanceMetadata(d *schema.ResourceData, config *transpo md[key] = *afterVal } - var fingerprint string - if project.CommonInstanceMetadata != nil { - fingerprint = project.CommonInstanceMetadata.Fingerprint + body := map[string]interface{}{ + "items": expandComputeMetadata(md), } - - body, err := computeMetadataToMap(&compute.Metadata{ - Fingerprint: fingerprint, - Items: expandComputeMetadata(md), - }) - if err != nil { - return fmt.Errorf("Error encoding metadata: %s", err) + if commonInstanceMetadata != nil { + if fp, ok := commonInstanceMetadata["fingerprint"].(string); ok && fp != "" { + body["fingerprint"] = fp + } } url, err := tpgresource.ReplaceVars(d, config, "{{"{{"}}ComputeBasePath{{"}}"}}") @@ -289,7 +287,8 @@ func updateComputeCommonInstanceMetadata(d *schema.ResourceData, config *transpo log.Printf("[DEBUG] SetCommonInstanceMetadata: %v", res["selfLink"]) - return ComputeOperationWaitTime(config, res, project.Name, "SetCommonInstanceMetadata", userAgent, timeout) + projectName, _ := project["name"].(string) + return ComputeOperationWaitTime(config, res, projectName, "SetCommonInstanceMetadata", userAgent, timeout) } return transport_tpg.MetadataRetryWrapper(updateMD) diff --git a/mmv1/third_party/terraform/services/compute/resource_compute_project_metadata_item_test.go b/mmv1/third_party/terraform/services/compute/resource_compute_project_metadata_item_test.go index 910cba683687..8fd67a3cc832 100644 --- a/mmv1/third_party/terraform/services/compute/resource_compute_project_metadata_item_test.go +++ b/mmv1/third_party/terraform/services/compute/resource_compute_project_metadata_item_test.go @@ -1,6 +1,7 @@ package compute_test import ( + "encoding/json" "fmt" "regexp" "testing" @@ -160,7 +161,12 @@ func testAccCheckProjectMetadataItemDestroyProducer(t *testing.T) func(s *terraf return err } - metadata := tpgcompute.FlattenMetadata(project.CommonInstanceMetadata) + var commonInstanceMetadataMap map[string]interface{} + if project.CommonInstanceMetadata != nil { + metaBytes, _ := json.Marshal(project.CommonInstanceMetadata) + _ = json.Unmarshal(metaBytes, &commonInstanceMetadataMap) + } + metadata := tpgcompute.FlattenMetadata(commonInstanceMetadataMap) for _, rs := range s.RootModule().Resources { if rs.Type != "google_compute_project_metadata_item" { 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 2354e6bf957f..a7844d812663 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 @@ -1343,7 +1343,7 @@ func resourceComputeRegionInstanceTemplateCreate(d *schema.ResourceData, meta in if err != nil { return err } - PartnerMetadata, err := convertPartnerMetadataToComputeTyped(partnerMetadataMap) + partnerMetadata, err := convertPartnerMetadataToCompute(partnerMetadataMap) if err != nil { return err } @@ -1421,16 +1421,8 @@ func resourceComputeRegionInstanceTemplateCreate(d *schema.ResourceData, meta in instanceProperties["reservationAffinity"] = reservationAffinity } {{- if ne $.TargetVersionName "ga" }} - if len(PartnerMetadata) > 0 { - pmJSON, err := json.Marshal(PartnerMetadata) - if err != nil { - return fmt.Errorf("Error marshaling partner metadata: %s", err) - } - var pmIface interface{} - if err := json.Unmarshal(pmJSON, &pmIface); err != nil { - return fmt.Errorf("Error unmarshaling partner metadata: %s", err) - } - instanceProperties["partnerMetadata"] = pmIface + if len(partnerMetadata) > 0 { + instanceProperties["partnerMetadata"] = partnerMetadata } if dd := expandDisplayDevice(d); dd != nil { ddMap, err := tpgresource.ConvertToMap(dd) @@ -1579,9 +1571,15 @@ func resourceComputeRegionInstanceTemplateRead(d *schema.ResourceData, meta inte return fmt.Errorf("Error setting metadata_fingerprint: %s", err) } - md := instanceTemplate.Properties.Metadata - - _md := flattenMetadataBeta(md) + metadata := instanceTemplate.Properties.Metadata + var metadataMap map[string]interface{} + if metadata != nil { + var convErr error + if metadataMap, convErr = tpgresource.ConvertToMap(metadata); convErr != nil { + return fmt.Errorf("Error converting metadata: %s", convErr) + } + } + _md := flattenMetadataBeta(metadataMap) if script, scriptExists := d.GetOk("metadata_startup_script"); scriptExists { if err = d.Set("metadata_startup_script", script); err != nil { @@ -1602,7 +1600,11 @@ func resourceComputeRegionInstanceTemplateRead(d *schema.ResourceData, meta inte {{ if ne $.TargetVersionName `ga` -}} if instanceTemplate.Properties.PartnerMetadata != nil { - partnerMetadata, err := flattenPartnerMetadata(convertPartnerMetadataFromCompute(instanceTemplate.Properties.PartnerMetadata)) + var partnerMetadataMap map[string]interface{} + if err := tpgresource.Convert(instanceTemplate.Properties.PartnerMetadata, &partnerMetadataMap); err != nil { + return fmt.Errorf("Error converting partner metadata: %s", err) + } + partnerMetadata, err := flattenPartnerMetadata(convertPartnerMetadataFromCompute(partnerMetadataMap)) if err != nil { return fmt.Errorf("Error parsing partner metadata: %s", err) } From ea30bc57fabda317ff86612c7ac2549ab015c8dd Mon Sep 17 00:00:00 2001 From: Igor Nadenenko Date: Mon, 29 Jun 2026 13:55:18 +0200 Subject: [PATCH 19/30] remove remaining compute --- .../compute/resource_compute_instance.go.tmpl | 32 ------------------- ...resource_compute_instance_template.go.tmpl | 29 +++++++---------- 2 files changed, 12 insertions(+), 49 deletions(-) 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 716865966003..56683e1c1ddc 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 @@ -1862,34 +1862,6 @@ func expandComputeInstance(project string, d *schema.ResourceData, config *trans disks = append(disks, disk) } - metadataMap, err := resourceInstanceMetadata(d) - if err != nil { - return nil, fmt.Errorf("Error creating metadata: %s", err) - } - var metadata *compute.Metadata - if metadataMap != nil { - metadata = &compute.Metadata{} - if err := tpgresource.Convert(metadataMap, metadata); err != nil { - return nil, fmt.Errorf("Error converting metadata: %s", err) - } - } - - {{ if ne $.TargetVersionName `ga` -}} - partnerMetadataMap, err := resourceInstancePartnerMetadata(d) - if err != nil { - return nil, fmt.Errorf("Error creating partner metadata: %s", err) - } - partnerMetadataConverted, err := convertPartnerMetadataToCompute(partnerMetadataMap) - if err != nil { - return nil, fmt.Errorf("Error converting partner metadata: %s", err) - } - var partnerMetadata map[string]compute.StructuredEntries - if len(partnerMetadataConverted) > 0 { - if err := tpgresource.Convert(partnerMetadataConverted, &partnerMetadata); err != nil { - return nil, fmt.Errorf("Error converting partner metadata: %s", err) - } - } - {{- end }} networkInterfaces, err := expandNetworkInterfacesTyped(d, config) @@ -1948,10 +1920,6 @@ func expandComputeInstance(project string, d *schema.ResourceData, config *trans Description: d.Get("description").(string), Disks: disks, MachineType: machineTypeUrl, - Metadata: metadata, - {{- if ne $.TargetVersionName "ga" }} - PartnerMetadata: partnerMetadata, - {{- end }} Name: d.Get("name").(string), NetworkInterfaces: networkInterfaces, NetworkPerformanceConfig: networkPerformanceConfig, 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 0e8780c226ee..8f93f12fd31c 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 @@ -1674,13 +1674,6 @@ func resourceComputeInstanceTemplateCreate(d *schema.ResourceData, meta interfac if err != nil { return err } - var metadata *compute.Metadata - if metadataMap != nil { - metadata = &compute.Metadata{} - if err := tpgresource.Convert(metadataMap, metadata); err != nil { - return fmt.Errorf("Error converting metadata: %s", err) - } - } {{- if ne $.TargetVersionName "ga" }} partnerMetadataMap, err := resourceInstancePartnerMetadata(d) if err != nil { @@ -1690,12 +1683,6 @@ func resourceComputeInstanceTemplateCreate(d *schema.ResourceData, meta interfac if err != nil { return err } - var partnerMetadata map[string]compute.StructuredEntries - if len(partnerMetadataConverted) > 0 { - if err := tpgresource.Convert(partnerMetadataConverted, &partnerMetadata); err != nil { - return fmt.Errorf("Error converting partner metadata: %s", err) - } - } {{- end }} networks, err := expandNetworkInterfacesTyped(d, config) @@ -1747,10 +1734,6 @@ func resourceComputeInstanceTemplateCreate(d *schema.ResourceData, meta interfac MachineType: d.Get("machine_type").(string), MinCpuPlatform: d.Get("min_cpu_platform").(string), Disks: disks, - Metadata: metadata, - {{- if ne $.TargetVersionName "ga" }} - PartnerMetadata: partnerMetadata, - {{- end }} NetworkInterfaces: networks, NetworkPerformanceConfig: networkPerformanceConfig, Scheduling: scheduling, @@ -1822,6 +1805,18 @@ func resourceComputeInstanceTemplateCreate(d *schema.ResourceData, meta interfac if err := json.Unmarshal(itBytes, &itBody); err != nil { return fmt.Errorf("Error unmarshaling instance template: %s", err) } + if len(metadataMap) > 0 { + if props, ok := itBody["properties"].(map[string]interface{}); ok { + props["metadata"] = metadataMap + } + } + {{- if ne $.TargetVersionName "ga" }} + if len(partnerMetadataConverted) > 0 { + if props, ok := itBody["properties"].(map[string]interface{}); ok { + props["partnerMetadata"] = partnerMetadataConverted + } + } + {{- end }} url, err := tpgresource.ReplaceVars(d, config, "{{"{{"}}ComputeBasePath{{"}}"}}projects/{{"{{"}}project{{"}}"}}/global/instanceTemplates") if err != nil { From 58ccd563e2627f47b5e2f0599f254b652eee15e0 Mon Sep 17 00:00:00 2001 From: Igor Nadenenko Date: Mon, 29 Jun 2026 14:05:18 +0200 Subject: [PATCH 20/30] err undefined fix --- .../terraform/services/compute/resource_compute_instance.go.tmpl | 1 + 1 file changed, 1 insertion(+) 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 56683e1c1ddc..5802700b35e2 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 @@ -2268,6 +2268,7 @@ func resourceComputeInstanceRead(d *schema.ResourceData, meta interface{}) error func populateComputeInstanceResourceData(d *schema.ResourceData, instance *compute.Instance, project, zone string, config *transport_tpg.Config) error { var metadataMap map[string]interface{} if instance.Metadata != nil { + var err error metadataMap, err = tpgresource.ConvertToMap(instance.Metadata) if err != nil { return fmt.Errorf("Error converting metadata: %s", err) From a2dd03f317586694d6ac84b21083065040e9edf4 Mon Sep 17 00:00:00 2001 From: Igor Nadenenko Date: Mon, 29 Jun 2026 14:17:20 +0200 Subject: [PATCH 21/30] tfplan2cai fix --- .../tgc/services/compute/compute_instance.go.tmpl | 8 ++++++-- .../pkg/services/compute/compute_instance_tfplan2cai.go | 8 ++++++-- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/mmv1/third_party/tgc/services/compute/compute_instance.go.tmpl b/mmv1/third_party/tgc/services/compute/compute_instance.go.tmpl index a8a2e1755bc5..56bac5f244cb 100644 --- a/mmv1/third_party/tgc/services/compute/compute_instance.go.tmpl +++ b/mmv1/third_party/tgc/services/compute/compute_instance.go.tmpl @@ -130,7 +130,7 @@ func expandComputeInstance(project string, d tpgresource.TerraformResourceData, } } - metadata, err := resourceInstanceMetadataTyped(d) + metadataMap, err := resourceInstanceMetadata(d) if err != nil { return nil, fmt.Errorf("Error creating metadata: %s", err) } @@ -168,7 +168,6 @@ func expandComputeInstance(project string, d tpgresource.TerraformResourceData, Description: d.Get("description").(string), Disks: disks, MachineType: machineTypeUrl, - Metadata: metadata, Name: d.Get("name").(string), Zone: d.Get("zone").(string), NetworkInterfaces: networkInterfaces, @@ -188,6 +187,11 @@ func expandComputeInstance(project string, d tpgresource.TerraformResourceData, return nil, fmt.Errorf("Error converting metadata: %s", err) } } + if metadataMap != nil { + if err := tpgresource.Convert(metadataMap, &instance.Metadata); err != nil { + return nil, fmt.Errorf("Error converting metadata: %s", err) + } + } if sicMap := expandShieldedVmConfigs(d); sicMap != nil { instance.ShieldedInstanceConfig = &compute.ShieldedInstanceConfig{ EnableSecureBoot: sicMap["enableSecureBoot"].(bool), diff --git a/mmv1/third_party/tgc_next/pkg/services/compute/compute_instance_tfplan2cai.go b/mmv1/third_party/tgc_next/pkg/services/compute/compute_instance_tfplan2cai.go index a4f450151429..06f270650137 100644 --- a/mmv1/third_party/tgc_next/pkg/services/compute/compute_instance_tfplan2cai.go +++ b/mmv1/third_party/tgc_next/pkg/services/compute/compute_instance_tfplan2cai.go @@ -121,7 +121,7 @@ func expandComputeInstance(project string, d tpgresource.TerraformResourceData, return nil, fmt.Errorf("Error creating params: %s", err) } - metadata, err := resourceInstanceMetadataTyped(d) + metadataMap, err := resourceInstanceMetadata(d) if err != nil { return nil, fmt.Errorf("Error creating metadata: %s", err) } @@ -183,7 +183,6 @@ func expandComputeInstance(project string, d tpgresource.TerraformResourceData, Description: d.Get("description").(string), Disks: disks, MachineType: machineTypeUrl, - Metadata: metadata, Name: d.Get("name").(string), Zone: d.Get("zone").(string), NetworkInterfaces: networkInterfaces, @@ -203,6 +202,11 @@ func expandComputeInstance(project string, d tpgresource.TerraformResourceData, KeyRevocationActionType: d.Get("key_revocation_action_type").(string), InstanceEncryptionKey: instanceEncryptionKey, } + if metadataMap != nil { + if err := tpgresource.Convert(metadataMap, &instance.Metadata); err != nil { + return nil, fmt.Errorf("Error converting metadata: %s", err) + } + } if cic := expandConfidentialInstanceConfig(d); cic != nil { instance.ConfidentialInstanceConfig = &compute.ConfidentialInstanceConfig{ EnableConfidentialCompute: cic["enableConfidentialCompute"].(bool), From 353418c5eeac897965d46bae30d9543689c35741 Mon Sep 17 00:00:00 2001 From: Igor Nadenenko Date: Mon, 29 Jun 2026 14:24:33 +0200 Subject: [PATCH 22/30] fix --- .../pkg/services/compute/compute_instance_tfplan2cai.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/mmv1/third_party/tgc_next/pkg/services/compute/compute_instance_tfplan2cai.go b/mmv1/third_party/tgc_next/pkg/services/compute/compute_instance_tfplan2cai.go index 06f270650137..536b90ff6162 100644 --- a/mmv1/third_party/tgc_next/pkg/services/compute/compute_instance_tfplan2cai.go +++ b/mmv1/third_party/tgc_next/pkg/services/compute/compute_instance_tfplan2cai.go @@ -1,6 +1,7 @@ package compute import ( + "encoding/json" "errors" "fmt" "strings" @@ -203,7 +204,11 @@ func expandComputeInstance(project string, d tpgresource.TerraformResourceData, InstanceEncryptionKey: instanceEncryptionKey, } if metadataMap != nil { - if err := tpgresource.Convert(metadataMap, &instance.Metadata); err != nil { + metadataBytes, err := json.Marshal(metadataMap) + if err != nil { + return nil, fmt.Errorf("Error encoding metadata: %s", err) + } + if err := json.Unmarshal(metadataBytes, &instance.Metadata); err != nil { return nil, fmt.Errorf("Error converting metadata: %s", err) } } From 53acea97c28728b823545ed559ee3a58d7f07a0a Mon Sep 17 00:00:00 2001 From: Igor Nadenenko Date: Mon, 29 Jun 2026 15:30:59 +0200 Subject: [PATCH 23/30] fix panic in tests --- .../services/compute/resource_compute_instance.go.tmpl | 7 ++++++- .../compute/resource_compute_instance_template.go.tmpl | 6 +++++- .../resource_compute_region_instance_template.go.tmpl | 6 +++++- 3 files changed, 16 insertions(+), 3 deletions(-) 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 5802700b35e2..20cedce08339 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 @@ -4,6 +4,7 @@ import ( "context" "crypto/sha256" "encoding/base64" + "encoding/json" "errors" "fmt" "log" @@ -2593,7 +2594,11 @@ func populateComputeInstanceResourceData(d *schema.ResourceData, instance *compu {{- if ne $.TargetVersionName `ga` }} if instance.PartnerMetadata != nil { var partnerMetadataMap map[string]interface{} - if err := tpgresource.Convert(instance.PartnerMetadata, &partnerMetadataMap); err != nil { + partnerMetadataBytes, err := json.Marshal(instance.PartnerMetadata) + if err != nil { + return fmt.Errorf("Error marshaling partner metadata: %s", err) + } + if err = json.Unmarshal(partnerMetadataBytes, &partnerMetadataMap); err != nil { return fmt.Errorf("Error converting partner metadata: %s", err) } partnerMetadata, err := flattenPartnerMetadata(convertPartnerMetadataFromCompute(partnerMetadataMap)) 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 8f93f12fd31c..578fd1990274 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 @@ -2189,7 +2189,11 @@ func resourceComputeInstanceTemplateRead(d *schema.ResourceData, meta interface{ {{ if ne $.TargetVersionName `ga` -}} if instanceTemplate.Properties.PartnerMetadata != nil { var partnerMetadataMap map[string]interface{} - if err := tpgresource.Convert(instanceTemplate.Properties.PartnerMetadata, &partnerMetadataMap); err != nil { + partnerMetadataBytes, err := json.Marshal(instanceTemplate.Properties.PartnerMetadata) + if err != nil { + return fmt.Errorf("Error marshaling partner metadata: %s", err) + } + if err = json.Unmarshal(partnerMetadataBytes, &partnerMetadataMap); err != nil { return fmt.Errorf("Error converting partner metadata: %s", err) } partnerMetadata, err := flattenPartnerMetadata(convertPartnerMetadataFromCompute(partnerMetadataMap)) 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 a7844d812663..526cc39b56ab 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 @@ -1601,7 +1601,11 @@ func resourceComputeRegionInstanceTemplateRead(d *schema.ResourceData, meta inte {{ if ne $.TargetVersionName `ga` -}} if instanceTemplate.Properties.PartnerMetadata != nil { var partnerMetadataMap map[string]interface{} - if err := tpgresource.Convert(instanceTemplate.Properties.PartnerMetadata, &partnerMetadataMap); err != nil { + partnerMetadataBytes, err := json.Marshal(instanceTemplate.Properties.PartnerMetadata) + if err != nil { + return fmt.Errorf("Error marshaling partner metadata: %s", err) + } + if err = json.Unmarshal(partnerMetadataBytes, &partnerMetadataMap); err != nil { return fmt.Errorf("Error converting partner metadata: %s", err) } partnerMetadata, err := flattenPartnerMetadata(convertPartnerMetadataFromCompute(partnerMetadataMap)) From 5185d83feaea0edae60990816473d9da777b523b Mon Sep 17 00:00:00 2001 From: Igor Nadenenko Date: Mon, 29 Jun 2026 15:52:46 +0200 Subject: [PATCH 24/30] add condition for json import --- .../services/compute/resource_compute_instance.go.tmpl | 2 ++ 1 file changed, 2 insertions(+) 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 20cedce08339..e75070a2026d 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 @@ -4,7 +4,9 @@ import ( "context" "crypto/sha256" "encoding/base64" + {{- if ne $.TargetVersionName "ga" }} "encoding/json" + {{- end }} "errors" "fmt" "log" From 6c411d4bd5d4acdade60a81c78b4ba6b99c1efc4 Mon Sep 17 00:00:00 2001 From: Igor Nadenenko Date: Mon, 29 Jun 2026 18:31:20 +0200 Subject: [PATCH 25/30] vcr tests fix --- ...ompute_instance_from_machine_image.go.tmpl | 19 +++++++++++ ...rce_compute_instance_from_template.go.tmpl | 33 +++++++++++++++++++ 2 files changed, 52 insertions(+) diff --git a/mmv1/third_party/terraform/services/compute/resource_compute_instance_from_machine_image.go.tmpl b/mmv1/third_party/terraform/services/compute/resource_compute_instance_from_machine_image.go.tmpl index 2e0d17e868bc..d84f7dc69c69 100644 --- a/mmv1/third_party/terraform/services/compute/resource_compute_instance_from_machine_image.go.tmpl +++ b/mmv1/third_party/terraform/services/compute/resource_compute_instance_from_machine_image.go.tmpl @@ -170,6 +170,25 @@ func resourceComputeInstanceFromMachineImageCreate(d *schema.ResourceData, meta return fmt.Errorf("Error processing instance body: %s", err) } + metadataMap, err := resourceInstanceMetadata(d) + if err != nil { + return fmt.Errorf("Error creating metadata: %s", err) + } + if len(metadataMap) > 0 { + instanceBody["metadata"] = metadataMap + } + partnerMetadataMap, err := resourceInstancePartnerMetadata(d) + if err != nil { + return fmt.Errorf("Error creating partner metadata: %s", err) + } + if len(partnerMetadataMap) > 0 { + partnerMetadataConverted, err := convertPartnerMetadataToCompute(partnerMetadataMap) + if err != nil { + return fmt.Errorf("Error converting partner metadata: %s", err) + } + instanceBody["partnerMetadata"] = partnerMetadataConverted + } + sa := d.Get("service_account").([]interface{}) if len(sa) == 0 { // ServiceAccount is required when the image is from a different project diff --git a/mmv1/third_party/terraform/services/compute/resource_compute_instance_from_template.go.tmpl b/mmv1/third_party/terraform/services/compute/resource_compute_instance_from_template.go.tmpl index 25880ddd5a4a..3244ef5e33bd 100644 --- a/mmv1/third_party/terraform/services/compute/resource_compute_instance_from_template.go.tmpl +++ b/mmv1/third_party/terraform/services/compute/resource_compute_instance_from_template.go.tmpl @@ -120,6 +120,39 @@ func resourceComputeInstanceFromTemplateCreate(d *schema.ResourceData, meta inte return err } + metadataMap, err := resourceInstanceMetadata(d) + if err != nil { + return fmt.Errorf("Error creating metadata: %s", err) + } + if len(metadataMap) > 0 { + metadataBytes, err := json.Marshal(metadataMap) + if err != nil { + return fmt.Errorf("Error marshaling metadata: %s", err) + } + if err := json.Unmarshal(metadataBytes, &instance.Metadata); err != nil { + return fmt.Errorf("Error setting metadata: %s", err) + } + } +{{- if ne $.TargetVersionName "ga" }} + partnerMetadataMap, err := resourceInstancePartnerMetadata(d) + if err != nil { + return fmt.Errorf("Error creating partner metadata: %s", err) + } + if len(partnerMetadataMap) > 0 { + partnerMetadataConverted, err := convertPartnerMetadataToCompute(partnerMetadataMap) + if err != nil { + return fmt.Errorf("Error converting partner metadata: %s", err) + } + partnerMetadataBytes, err := json.Marshal(partnerMetadataConverted) + if err != nil { + return fmt.Errorf("Error marshaling partner metadata: %s", err) + } + if err := json.Unmarshal(partnerMetadataBytes, &instance.PartnerMetadata); err != nil { + return fmt.Errorf("Error setting partner metadata: %s", err) + } + } +{{- end }} + sourceInstanceTemplate:= ConvertToUniqueIdWhenPresent(d.Get("source_instance_template").(string)) tpl, err := tpgresource.ParseInstanceTemplateFieldValue(sourceInstanceTemplate, d, config) if err != nil { From 14f3ec4710a12808b546ef2c5eff12d2e817a26d Mon Sep 17 00:00:00 2001 From: Igor Nadenenko Date: Mon, 29 Jun 2026 19:30:26 +0200 Subject: [PATCH 26/30] fix --- .../resource_compute_instance_from_template.go.tmpl | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/mmv1/third_party/terraform/services/compute/resource_compute_instance_from_template.go.tmpl b/mmv1/third_party/terraform/services/compute/resource_compute_instance_from_template.go.tmpl index 3244ef5e33bd..dac863ead702 100644 --- a/mmv1/third_party/terraform/services/compute/resource_compute_instance_from_template.go.tmpl +++ b/mmv1/third_party/terraform/services/compute/resource_compute_instance_from_template.go.tmpl @@ -256,11 +256,19 @@ func resourceComputeInstanceFromTemplateCreate(d *schema.ResourceData, meta inte if err != nil { return fmt.Errorf("Error expanding partner_metadata: %s", err) } - partnerMetadataTyped, err := convertPartnerMetadataToComputeTyped(pmRaw) + partnerMetadataConverted, err := convertPartnerMetadataToCompute(pmRaw) if err != nil { return fmt.Errorf("Error converting partner_metadata: %s", err) } - instance.PartnerMetadata = partnerMetadataTyped + if len(partnerMetadataConverted) > 0 { + partnerMetadataBytes, err := json.Marshal(partnerMetadataConverted) + if err != nil { + return fmt.Errorf("Error marshaling partner_metadata: %s", err) + } + if err := json.Unmarshal(partnerMetadataBytes, &instance.PartnerMetadata); err != nil { + return fmt.Errorf("Error setting partner_metadata: %s", err) + } + } } {{- end }} From 3c1d26c73631806039230ed8d27ee28cdcda68ce Mon Sep 17 00:00:00 2001 From: Igor Nadenenko Date: Mon, 29 Jun 2026 19:40:23 +0200 Subject: [PATCH 27/30] fix --- .../pkg/services/compute/compute_instance_tfplan2cai.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/mmv1/third_party/tgc_next/pkg/services/compute/compute_instance_tfplan2cai.go b/mmv1/third_party/tgc_next/pkg/services/compute/compute_instance_tfplan2cai.go index 536b90ff6162..b755dcafe25a 100644 --- a/mmv1/third_party/tgc_next/pkg/services/compute/compute_instance_tfplan2cai.go +++ b/mmv1/third_party/tgc_next/pkg/services/compute/compute_instance_tfplan2cai.go @@ -191,7 +191,6 @@ func expandComputeInstance(project string, d tpgresource.TerraformResourceData, Tags: tags, Params: params, Labels: tpgresource.ExpandLabels(d), - ServiceAccounts: expandServiceAccountsTyped(d.Get("service_account").([]interface{})), GuestAccelerators: accels, MinCpuPlatform: d.Get("min_cpu_platform").(string), Scheduling: scheduling, @@ -203,6 +202,11 @@ func expandComputeInstance(project string, d tpgresource.TerraformResourceData, KeyRevocationActionType: d.Get("key_revocation_action_type").(string), InstanceEncryptionKey: instanceEncryptionKey, } + if serviceAccount := expandServiceAccounts(d.Get("service_account").([]interface{})); len(serviceAccount) > 0 { + if err := convertViaJSONTgcNext(serviceAccount, &instance.ServiceAccounts); err != nil { + return nil, fmt.Errorf("Error converting service_accounts: %s", err) + } + } if metadataMap != nil { metadataBytes, err := json.Marshal(metadataMap) if err != nil { From efb1b7f25e1f224a1bd2e0b37993f67a9f7fa655 Mon Sep 17 00:00:00 2001 From: Igor Nadenenko Date: Tue, 30 Jun 2026 14:45:21 +0200 Subject: [PATCH 28/30] VCR tests + json.Marshal fixes --- .../compute/compute_instance_helpers.go.tmpl | 26 ++++++----- .../data_source_google_compute_instance.go | 6 +-- .../compute/resource_compute_instance.go.tmpl | 11 +---- ...ompute_instance_from_machine_image.go.tmpl | 8 +--- ...resource_compute_instance_template.go.tmpl | 46 +++++++++++-------- ...e_compute_region_instance_template.go.tmpl | 6 +-- 6 files changed, 49 insertions(+), 54 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 58fe8c4fd8c5..50965b207401 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 @@ -163,19 +163,23 @@ func expandScheduling(v interface{}) (map[string]interface{}, error) { if v, ok := original["node_affinities"]; ok && v != nil { naSet := v.(*schema.Set).List() - nodeAffinities := make([]interface{}, 0, len(naSet)) - for _, nodeAffRaw := range naSet { - if nodeAffRaw == nil { - continue + if len(naSet) == 0 { + result["nodeAffinities"] = []interface{}{nil} + } else { + nodeAffinities := make([]interface{}, 0, len(naSet)) + for _, nodeAffRaw := range naSet { + if nodeAffRaw == nil { + continue + } + nodeAff := nodeAffRaw.(map[string]interface{}) + nodeAffinities = append(nodeAffinities, map[string]interface{}{ + "key": nodeAff["key"].(string), + "operator": nodeAff["operator"].(string), + "values": tpgresource.ConvertStringArr(nodeAff["values"].(*schema.Set).List()), + }) } - nodeAff := nodeAffRaw.(map[string]interface{}) - nodeAffinities = append(nodeAffinities, map[string]interface{}{ - "key": nodeAff["key"].(string), - "operator": nodeAff["operator"].(string), - "values": tpgresource.ConvertStringArr(nodeAff["values"].(*schema.Set).List()), - }) + result["nodeAffinities"] = nodeAffinities } - result["nodeAffinities"] = nodeAffinities } if v, ok := original["min_node_cpus"]; ok && v.(int) != 0 { 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 cb92f3c77ceb..edfa76a7f3bd 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 @@ -1,7 +1,6 @@ package compute import ( - "encoding/json" "fmt" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" @@ -44,8 +43,9 @@ func dataSourceGoogleComputeInstanceRead(d *schema.ResourceData, meta interface{ var metadataMap map[string]interface{} if instance.Metadata != nil { - metaBytes, _ := json.Marshal(instance.Metadata) - _ = json.Unmarshal(metaBytes, &metadataMap) + if metadataMap, err = tpgresource.ConvertToMap(instance.Metadata); err != nil { + return fmt.Errorf("error converting metadata: %s", err) + } } md := flattenMetadataBeta(metadataMap) if err = d.Set("metadata", md); err != nil { 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 e75070a2026d..b5c9221d00f4 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 @@ -4,9 +4,6 @@ import ( "context" "crypto/sha256" "encoding/base64" - {{- if ne $.TargetVersionName "ga" }} - "encoding/json" - {{- end }} "errors" "fmt" "log" @@ -2117,7 +2114,7 @@ func resourceComputeInstanceCreate(d *schema.ResourceData, meta interface{}) err } if metadataBody, err := resourceInstanceMetadata(d); err != nil { return fmt.Errorf("Error expanding metadata: %s", err) - } else if len(metadataBody) > 0 { + } else { instanceBody["metadata"] = metadataBody } {{- if ne $.TargetVersionName "ga" }} @@ -2595,12 +2592,8 @@ func populateComputeInstanceResourceData(d *schema.ResourceData, instance *compu {{- if ne $.TargetVersionName `ga` }} if instance.PartnerMetadata != nil { - var partnerMetadataMap map[string]interface{} - partnerMetadataBytes, err := json.Marshal(instance.PartnerMetadata) + partnerMetadataMap, err := tpgresource.ConvertToMap(instance.PartnerMetadata) if err != nil { - return fmt.Errorf("Error marshaling partner metadata: %s", err) - } - if err = json.Unmarshal(partnerMetadataBytes, &partnerMetadataMap); err != nil { return fmt.Errorf("Error converting partner metadata: %s", err) } partnerMetadata, err := flattenPartnerMetadata(convertPartnerMetadataFromCompute(partnerMetadataMap)) diff --git a/mmv1/third_party/terraform/services/compute/resource_compute_instance_from_machine_image.go.tmpl b/mmv1/third_party/terraform/services/compute/resource_compute_instance_from_machine_image.go.tmpl index d84f7dc69c69..bd911b708f83 100644 --- a/mmv1/third_party/terraform/services/compute/resource_compute_instance_from_machine_image.go.tmpl +++ b/mmv1/third_party/terraform/services/compute/resource_compute_instance_from_machine_image.go.tmpl @@ -161,13 +161,9 @@ func resourceComputeInstanceFromMachineImageCreate(d *schema.ResourceData, meta } } - instanceBytes, err := json.Marshal(instance) + instanceBody, err := tpgresource.ConvertToMap(instance) if err != nil { - return fmt.Errorf("Error marshaling instance: %s", err) - } - instanceBody := map[string]interface{}{} - if err = json.Unmarshal(instanceBytes, &instanceBody); err != nil { - return fmt.Errorf("Error processing instance body: %s", err) + return fmt.Errorf("Error converting instance: %s", err) } metadataMap, err := resourceInstanceMetadata(d) 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 578fd1990274..aaa153dfcfcc 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 @@ -1797,18 +1797,12 @@ func resourceComputeInstanceTemplateCreate(d *schema.ResourceData, meta interfac Name: itName, } - itBytes, err := json.Marshal(instanceTemplate) + itBody, err := tpgresource.ConvertToMap(instanceTemplate) if err != nil { - return fmt.Errorf("Error marshaling instance template: %s", err) + return fmt.Errorf("Error converting instance template: %s", err) } - var itBody map[string]interface{} - if err := json.Unmarshal(itBytes, &itBody); err != nil { - return fmt.Errorf("Error unmarshaling instance template: %s", err) - } - if len(metadataMap) > 0 { - if props, ok := itBody["properties"].(map[string]interface{}); ok { - props["metadata"] = metadataMap - } + if props, ok := itBody["properties"].(map[string]interface{}); ok { + props["metadata"] = metadataMap } {{- if ne $.TargetVersionName "ga" }} if len(partnerMetadataConverted) > 0 { @@ -2188,12 +2182,8 @@ func resourceComputeInstanceTemplateRead(d *schema.ResourceData, meta interface{ {{ if ne $.TargetVersionName `ga` -}} if instanceTemplate.Properties.PartnerMetadata != nil { - var partnerMetadataMap map[string]interface{} - partnerMetadataBytes, err := json.Marshal(instanceTemplate.Properties.PartnerMetadata) + partnerMetadataMap, err := tpgresource.ConvertToMap(instanceTemplate.Properties.PartnerMetadata) if err != nil { - return fmt.Errorf("Error marshaling partner metadata: %s", err) - } - if err = json.Unmarshal(partnerMetadataBytes, &partnerMetadataMap); err != nil { return fmt.Errorf("Error converting partner metadata: %s", err) } partnerMetadata, err := flattenPartnerMetadata(convertPartnerMetadataFromCompute(partnerMetadataMap)) @@ -2483,6 +2473,26 @@ func expandResourceComputeInstanceTemplateScheduling(d *schema.ResourceData, met if err := tpgresource.Convert(expanded, schedulingTyped); err != nil { return nil, fmt.Errorf("Error converting scheduling: %s", err) } + for _, pair := range [][2]string{ + {"preemptible", "Preemptible"}, + {"onHostMaintenance", "OnHostMaintenance"}, + {"provisioningModel", "ProvisioningModel"}, + {"instanceTerminationAction", "InstanceTerminationAction"}, + {"skipGuestOsShutdown", "SkipGuestOsShutdown"}, + } { + if _, ok := expanded[pair[0]]; ok { + schedulingTyped.ForceSendFields = append(schedulingTyped.ForceSendFields, pair[1]) + } + } +{{- if ne $.TargetVersionName "ga" }} + if v, ok := expanded["hostErrorTimeoutSeconds"]; ok { + if v == nil { + schedulingTyped.NullFields = append(schedulingTyped.NullFields, "HostErrorTimeoutSeconds") + } else { + schedulingTyped.ForceSendFields = append(schedulingTyped.ForceSendFields, "HostErrorTimeoutSeconds") + } + } +{{- end }} return schedulingTyped, nil } @@ -2492,14 +2502,10 @@ func expandResourceComputeInstanceTemplateScheduling(d *schema.ResourceData, met func networkInterfacesToInterface(networkInterfaces []*compute.NetworkInterface) ([]interface{}, error) { result := make([]interface{}, 0, len(networkInterfaces)) for _, ni := range networkInterfaces { - b, err := json.Marshal(ni) + m, err := tpgresource.ConvertToMap(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 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 526cc39b56ab..fe995c75bfc7 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 @@ -1600,12 +1600,8 @@ func resourceComputeRegionInstanceTemplateRead(d *schema.ResourceData, meta inte {{ if ne $.TargetVersionName `ga` -}} if instanceTemplate.Properties.PartnerMetadata != nil { - var partnerMetadataMap map[string]interface{} - partnerMetadataBytes, err := json.Marshal(instanceTemplate.Properties.PartnerMetadata) + partnerMetadataMap, err := tpgresource.ConvertToMap(instanceTemplate.Properties.PartnerMetadata) if err != nil { - return fmt.Errorf("Error marshaling partner metadata: %s", err) - } - if err = json.Unmarshal(partnerMetadataBytes, &partnerMetadataMap); err != nil { return fmt.Errorf("Error converting partner metadata: %s", err) } partnerMetadata, err := flattenPartnerMetadata(convertPartnerMetadataFromCompute(partnerMetadataMap)) From 68c569f6d8e9c699b3c00d292a4e63f3fcaee881 Mon Sep 17 00:00:00 2001 From: Igor Nadenenko Date: Tue, 30 Jun 2026 16:07:07 +0200 Subject: [PATCH 29/30] remove excess len clause --- .../services/compute/resource_compute_instance.go.tmpl | 2 +- .../compute/resource_compute_region_instance_template.go.tmpl | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) 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 b5c9221d00f4..1df8cba5b06e 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 @@ -2109,7 +2109,7 @@ func resourceComputeInstanceCreate(d *schema.ResourceData, meta interface{}) err } if paramsBody, err := expandParams(d); err != nil { return fmt.Errorf("Error expanding params: %s", err) - } else if len(paramsBody) > 0 { + } else { instanceBody["params"] = paramsBody } if metadataBody, err := resourceInstanceMetadata(d); err != nil { 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 fe995c75bfc7..3f32a2e24cae 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 @@ -1399,9 +1399,7 @@ func resourceComputeRegionInstanceTemplateCreate(d *schema.ResourceData, meta in if v := d.Get("key_revocation_action_type").(string); v != "" { instanceProperties["keyRevocationActionType"] = v } - if len(metadata) > 0 { - instanceProperties["metadata"] = metadata - } + instanceProperties["metadata"] = metadata if networkPerformanceConfig != nil { instanceProperties["networkPerformanceConfig"] = networkPerformanceConfig } From b7cb180c7d26d6fa6a991e9c0c6fbd24467cf0d8 Mon Sep 17 00:00:00 2001 From: Igor Nadenenko Date: Tue, 30 Jun 2026 18:18:52 +0200 Subject: [PATCH 30/30] fix instance from machine image --- .../resource_compute_instance_from_machine_image.go.tmpl | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/mmv1/third_party/terraform/services/compute/resource_compute_instance_from_machine_image.go.tmpl b/mmv1/third_party/terraform/services/compute/resource_compute_instance_from_machine_image.go.tmpl index bd911b708f83..98d7bc3f58fa 100644 --- a/mmv1/third_party/terraform/services/compute/resource_compute_instance_from_machine_image.go.tmpl +++ b/mmv1/third_party/terraform/services/compute/resource_compute_instance_from_machine_image.go.tmpl @@ -285,10 +285,8 @@ func resourceComputeInstanceFromMachineImageCreate(d *schema.ResourceData, meta if metadataBody, err := resourceInstanceMetadata(d); err != nil { return fmt.Errorf("Error expanding metadata: %s", err) - } else if len(metadataBody) > 0 { - instanceBody["metadata"] = metadataBody } else { - delete(instanceBody, "metadata") + instanceBody["metadata"] = metadataBody } if partnerMetadataRaw, err := resourceInstancePartnerMetadata(d); err != nil {