From c6c910384f5a89971c1f199d5071c2f8fbeb7f81 Mon Sep 17 00:00:00 2001 From: Chinemerem Chigbo Date: Fri, 29 May 2026 08:08:41 +0000 Subject: [PATCH 01/20] Expose GKE desired_emulated_version update trigger in Cluster resource --- .../resource_container_cluster.go.tmpl | 38 +++++ .../resource_container_cluster_test.go.tmpl | 150 ++++++++++++++++++ 2 files changed, 188 insertions(+) diff --git a/mmv1/third_party/terraform/services/container/resource_container_cluster.go.tmpl b/mmv1/third_party/terraform/services/container/resource_container_cluster.go.tmpl index 913265796e8d..dc98910bab64 100644 --- a/mmv1/third_party/terraform/services/container/resource_container_cluster.go.tmpl +++ b/mmv1/third_party/terraform/services/container/resource_container_cluster.go.tmpl @@ -1230,6 +1230,21 @@ func ResourceContainerCluster() *schema.Resource { Description: `The logging service that the cluster should write logs to. Available options include logging.googleapis.com(Legacy Stackdriver), logging.googleapis.com/kubernetes(Stackdriver Kubernetes Engine Logging), and none. Defaults to logging.googleapis.com/kubernetes.`, }, +{{- if ne $.TargetVersionName "ga" }} + "desired_emulated_version": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringMatch(regexp.MustCompile(`^[0-9]+\.[0-9]+$`), "desired_emulated_version must be in major.minor format"), + Description: "The desired emulated version for the cluster. Set this to complete a rollback-safe upgrade.", + }, + + "emulated_version": { + Type: schema.TypeString, + Computed: true, + Description: `The current emulated Kubernetes version running on the GKE cluster control plane.`, + }, +{{- end }} + "maintenance_policy": { Type: schema.TypeList, Optional: true, @@ -3721,6 +3736,11 @@ func resourceContainerClusterRead(d *schema.ResourceData, meta interface{}) erro if err := d.Set("master_version", cluster.CurrentMasterVersion); err != nil { return fmt.Errorf("Error setting master_version: %s", err) } +{{- if ne $.TargetVersionName "ga" }} + if err := d.Set("emulated_version", cluster.CurrentEmulatedVersion); err != nil { + return fmt.Errorf("Error setting emulated_version: %s", err) + } +{{- end }} if err := d.Set("node_version", cluster.CurrentNodeVersion); err != nil { return fmt.Errorf("Error setting node_version: %s", err) } @@ -4692,6 +4712,24 @@ func resourceContainerClusterUpdate(d *schema.ResourceData, meta interface{}) er log.Printf("[INFO] GKE cluster %s legacy ABAC has been updated to %v", d.Id(), enabled) } +{{- if ne $.TargetVersionName "ga" }} + if d.HasChange("desired_emulated_version") { + emulatedVersion := d.Get("desired_emulated_version").(string) + req := &container.UpdateClusterRequest{ + Update: &container.ClusterUpdate{ + DesiredEmulatedVersion: emulatedVersion, + }, + } + + updateF := updateFunc(req, "updating GKE master emulated version") + // Call update serially. + if err := transport_tpg.LockedCall(lockKey, updateF); err != nil { + return err + } + log.Printf("[INFO] GKE cluster %s: master emulated version has been updated to %s", d.Id(), emulatedVersion) + } +{{- end }} + if d.HasChange("monitoring_service") || d.HasChange("logging_service") { logging := d.Get("logging_service").(string) monitoring := d.Get("monitoring_service").(string) diff --git a/mmv1/third_party/terraform/services/container/resource_container_cluster_test.go.tmpl b/mmv1/third_party/terraform/services/container/resource_container_cluster_test.go.tmpl index 0543a3eba0da..3399d574dada 100644 --- a/mmv1/third_party/terraform/services/container/resource_container_cluster_test.go.tmpl +++ b/mmv1/third_party/terraform/services/container/resource_container_cluster_test.go.tmpl @@ -19374,3 +19374,153 @@ resource "google_container_node_pool" "extra" { } `, suffix, suffix, clusterName, skipRefresh, poolName) } + +func TestAccContainerCluster_desiredEmulatedVersion(t *testing.T) { + t.Parallel() + clusterName := fmt.Sprintf("tf-test-cluster-%s", acctest.RandString(t, 10)) + networkName := tpgcompute.BootstrapSharedTestNetwork(t, "gke-cluster") + subnetworkName := tpgcompute.BootstrapSubnet(t, "gke-cluster", networkName) + + // We utilize standard, valid GKE staging versions for E2E minor upgrade transitions + fromVersion := "1.34.8-gke.1211000" + targetVersion := "1.35.5-gke.1117000" + + acctest.VcrTest(t, resource.TestCase{ + PreCheck: func() { acctest.AccTestPreCheck(t) }, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), + CheckDestroy: testAccCheckContainerClusterDestroyProducer(t), + Steps: []resource.TestStep{ + // Step 1 (Create): Spin up a standard cluster at version 1.30 (Baseline) + { + Config: testAccContainerCluster_desiredEmulatedVersionBase(clusterName, networkName, subnetworkName, fromVersion), + }, + { + ResourceName: "google_container_cluster.primary", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"min_master_version", "deletion_protection"}, + }, + // Step 2 (Soak Upgrade): Upgrade master to target version 1.31 and activate 25-hour soak + { + Config: testAccContainerCluster_desiredEmulatedVersionSoak(clusterName, networkName, subnetworkName, fromVersion, targetVersion), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("google_container_cluster.primary", "min_master_version", targetVersion), + ), + }, + { + ResourceName: "google_container_cluster.primary", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"min_master_version", "deletion_protection"}, + }, + // Step 3 (Declarative Complete): Set desired_emulated_version to 1.35 to complete the upgrade while soaking + { + Config: testAccContainerCluster_desiredEmulatedVersionComplete(clusterName, networkName, subnetworkName, fromVersion, targetVersion), + Check: resource.ComposeTestCheckFunc( + testAccCheckContainerClusterEmulatedVersion(t, "google_container_cluster.primary"), + ), + }, + { + ResourceName: "google_container_cluster.primary", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"min_master_version", "deletion_protection"}, + }, + }, + }) +} + +func testAccCheckContainerClusterEmulatedVersion(t *testing.T, resourceName string) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[resourceName] + if !ok { + return fmt.Errorf("not found: %s", resourceName) + } + if rs.Primary.ID == "" { + return fmt.Errorf("no GKE cluster ID is set in state") + } + + config := acctest.GoogleProviderConfig(t) + clusterName := rs.Primary.Attributes["name"] + location := rs.Primary.Attributes["location"] + project := rs.Primary.Attributes["project"] + + // Retrieve the live GKE cluster object from Google's REST GKE API client + cluster, err := container.NewClient(config, config.UserAgent).Projects.Locations.Clusters.Get( + fmt.Sprintf("projects/%s/locations/%s/clusters/%s", project, location, clusterName)).Do() + if err != nil { + return fmt.Errorf("failed to get GKE cluster details from API: %v", err) + } + + // Verify GKE emulated version has advanced on the server side and is cleared post-upgrade + if cluster.CurrentEmulatedVersion != "" { + return fmt.Errorf("expected emulated version to be empty post-upgrade, but live GKE cluster has %q", cluster.CurrentEmulatedVersion) + } + + return nil + } +} + +func testAccContainerCluster_desiredEmulatedVersionBase(clusterName, networkName, subnetworkName, version string) string { + return fmt.Sprintf(` +resource "google_container_cluster" "primary" { + name = "%s" + location = "us-central1-a" + initial_node_count = 1 + min_master_version = "%s" + deletion_protection = false + network = "%s" + subnetwork = "%s" + + node_config { + machine_type = "e2-standard-2" + } +} +`, clusterName, version, networkName, subnetworkName) +} + +func testAccContainerCluster_desiredEmulatedVersionSoak(clusterName, networkName, subnetworkName, fromVersion, targetVersion string) string { + return fmt.Sprintf(` +resource "google_container_cluster" "primary" { + name = "%s" + location = "us-central1-a" + initial_node_count = 1 + min_master_version = "%s" + deletion_protection = false + network = "%s" + subnetwork = "%s" + + desired_rollback_safe_upgrade { + control_plane_soak_duration = "90000s" + } + + node_config { + machine_type = "e2-standard-2" + } +} +`, clusterName, targetVersion, networkName, subnetworkName) +} + +func testAccContainerCluster_desiredEmulatedVersionComplete(clusterName, networkName, subnetworkName, fromVersion, targetVersion string) string { + return fmt.Sprintf(` +resource "google_container_cluster" "primary" { + name = "%s" + location = "us-central1-a" + initial_node_count = 1 + min_master_version = "%s" + deletion_protection = false + network = "%s" + subnetwork = "%s" + + desired_rollback_safe_upgrade { + control_plane_soak_duration = "90000s" + } + + desired_emulated_version = "%s" + + node_config { + machine_type = "e2-standard-2" + } +} +`, clusterName, targetVersion, networkName, subnetworkName, targetVersion) +} From 66cd7bb9293c5c20add9368b902c3b453268ac4f Mon Sep 17 00:00:00 2001 From: Chinemerem Chigbo Date: Wed, 3 Jun 2026 01:59:55 +0000 Subject: [PATCH 02/20] Fix desired_emulated_version tests --- .../resource_container_cluster_test.go.tmpl | 125 ++++++++++++++++-- 1 file changed, 114 insertions(+), 11 deletions(-) diff --git a/mmv1/third_party/terraform/services/container/resource_container_cluster_test.go.tmpl b/mmv1/third_party/terraform/services/container/resource_container_cluster_test.go.tmpl index 3399d574dada..4d47a994a81a 100644 --- a/mmv1/third_party/terraform/services/container/resource_container_cluster_test.go.tmpl +++ b/mmv1/third_party/terraform/services/container/resource_container_cluster_test.go.tmpl @@ -19382,8 +19382,9 @@ func TestAccContainerCluster_desiredEmulatedVersion(t *testing.T) { subnetworkName := tpgcompute.BootstrapSubnet(t, "gke-cluster", networkName) // We utilize standard, valid GKE staging versions for E2E minor upgrade transitions - fromVersion := "1.34.8-gke.1211000" - targetVersion := "1.35.5-gke.1117000" + fromVersion := "1.34.8-gke.1000000" + targetVersion := "1.35.5-gke.1000000" + targetEmulatedVersion := "1.35" acctest.VcrTest(t, resource.TestCase{ PreCheck: func() { acctest.AccTestPreCheck(t) }, @@ -19415,7 +19416,7 @@ func TestAccContainerCluster_desiredEmulatedVersion(t *testing.T) { }, // Step 3 (Declarative Complete): Set desired_emulated_version to 1.35 to complete the upgrade while soaking { - Config: testAccContainerCluster_desiredEmulatedVersionComplete(clusterName, networkName, subnetworkName, fromVersion, targetVersion), + Config: testAccContainerCluster_desiredEmulatedVersionComplete(clusterName, networkName, subnetworkName, fromVersion, targetVersion, targetEmulatedVersion), Check: resource.ComposeTestCheckFunc( testAccCheckContainerClusterEmulatedVersion(t, "google_container_cluster.primary"), ), @@ -19490,9 +19491,7 @@ resource "google_container_cluster" "primary" { network = "%s" subnetwork = "%s" - desired_rollback_safe_upgrade { - control_plane_soak_duration = "90000s" - } + node_config { machine_type = "e2-standard-2" @@ -19501,7 +19500,7 @@ resource "google_container_cluster" "primary" { `, clusterName, targetVersion, networkName, subnetworkName) } -func testAccContainerCluster_desiredEmulatedVersionComplete(clusterName, networkName, subnetworkName, fromVersion, targetVersion string) string { +func testAccContainerCluster_desiredEmulatedVersionComplete(clusterName, networkName, subnetworkName, fromVersion, targetVersion, targetEmulatedVersion string) string { return fmt.Sprintf(` resource "google_container_cluster" "primary" { name = "%s" @@ -19512,9 +19511,7 @@ resource "google_container_cluster" "primary" { network = "%s" subnetwork = "%s" - desired_rollback_safe_upgrade { - control_plane_soak_duration = "90000s" - } + desired_emulated_version = "%s" @@ -19522,5 +19519,111 @@ resource "google_container_cluster" "primary" { machine_type = "e2-standard-2" } } -`, clusterName, targetVersion, networkName, subnetworkName, targetVersion) +`, clusterName, targetVersion, networkName, subnetworkName, targetEmulatedVersion) +} + +func TestAccContainerCluster_desiredEmulatedVersionAutopilot(t *testing.T) { + t.Parallel() + clusterName := fmt.Sprintf("tf-test-cluster-auto-%s", acctest.RandString(t, 10)) + networkName := tpgcompute.BootstrapSharedTestNetwork(t, "gke-cluster") + subnetworkName := tpgcompute.BootstrapSubnet(t, "gke-cluster", networkName) + + // Staging regional GKE versions verified in us-central1 sandbox + fromVersion := "1.34.8-gke.1000000" + targetVersion := "1.35.5-gke.1000000" + targetEmulatedVersion := "1.35" + + acctest.VcrTest(t, resource.TestCase{ + PreCheck: func() { acctest.AccTestPreCheck(t) }, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), + CheckDestroy: testAccCheckContainerClusterDestroyProducer(t), + Steps: []resource.TestStep{ + // Step 1 (Create): Spin up a regional GKE Autopilot cluster at baseline version (1.34) + { + Config: testAccContainerCluster_desiredEmulatedVersionAutopilotBase(clusterName, networkName, subnetworkName, fromVersion), + }, + { + ResourceName: "google_container_cluster.primary", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"min_master_version", "deletion_protection"}, + }, + // Step 2 (Soak Upgrade): Upgrade regional master to target version (1.35) with soak active + { + Config: testAccContainerCluster_desiredEmulatedVersionAutopilotSoak(clusterName, networkName, subnetworkName, fromVersion, targetVersion), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("google_container_cluster.primary", "min_master_version", targetVersion), + ), + }, + { + ResourceName: "google_container_cluster.primary", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"min_master_version", "deletion_protection"}, + }, + // Step 3 (Declarative Complete): Set desired_emulated_version to 1.35 to complete the upgrade + { + Config: testAccContainerCluster_desiredEmulatedVersionAutopilotComplete(clusterName, networkName, subnetworkName, fromVersion, targetVersion, targetEmulatedVersion), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("google_container_cluster.primary", "desired_emulated_version", targetEmulatedVersion), + ), + }, + { + ResourceName: "google_container_cluster.primary", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"min_master_version", "deletion_protection"}, + }, + }, + }) } + +func testAccContainerCluster_desiredEmulatedVersionAutopilotBase(clusterName, networkName, subnetworkName, version string) string { + return fmt.Sprintf(` +resource "google_container_cluster" "primary" { + name = "%s" + location = "us-central1" + enable_autopilot = true + min_master_version = "%s" + deletion_protection = false + network = "%s" + subnetwork = "%s" +} +`, clusterName, version, networkName, subnetworkName) +} + +func testAccContainerCluster_desiredEmulatedVersionAutopilotSoak(clusterName, networkName, subnetworkName, fromVersion, targetVersion string) string { + return fmt.Sprintf(` +resource "google_container_cluster" "primary" { + name = "%s" + location = "us-central1" + enable_autopilot = true + min_master_version = "%s" + deletion_protection = false + network = "%s" + subnetwork = "%s" + + +} +`, clusterName, targetVersion, networkName, subnetworkName) +} + +func testAccContainerCluster_desiredEmulatedVersionAutopilotComplete(clusterName, networkName, subnetworkName, fromVersion, targetVersion, targetEmulatedVersion string) string { + return fmt.Sprintf(` +resource "google_container_cluster" "primary" { + name = "%s" + location = "us-central1" + enable_autopilot = true + min_master_version = "%s" + deletion_protection = false + network = "%s" + subnetwork = "%s" + + + + desired_emulated_version = "%s" +} +`, clusterName, targetVersion, networkName, subnetworkName, targetEmulatedVersion) +} + + From 331c4e021f7fb8822eeb4b6e40ef1562c8fc0f21 Mon Sep 17 00:00:00 2001 From: Chinemerem Chigbo Date: Wed, 3 Jun 2026 07:57:59 +0000 Subject: [PATCH 03/20] docs: add desired_emulated_version to google_container_cluster docs --- mmv1/third_party/terraform/go.mod | 2 + .../resource_container_cluster_test.go.tmpl | 118 +++++++++--------- .../docs/r/container_cluster.html.markdown | 2 + 3 files changed, 62 insertions(+), 60 deletions(-) diff --git a/mmv1/third_party/terraform/go.mod b/mmv1/third_party/terraform/go.mod index 9778abb20181..022cfd7dae73 100644 --- a/mmv1/third_party/terraform/go.mod +++ b/mmv1/third_party/terraform/go.mod @@ -132,3 +132,5 @@ require ( google.golang.org/genproto/googleapis/api v0.0.0-20260414002931-afd174a4e478 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) + +replace google.golang.org/api v0.278.0 => /usr/local/google/home/chinemerem/google-api-go-client diff --git a/mmv1/third_party/terraform/services/container/resource_container_cluster_test.go.tmpl b/mmv1/third_party/terraform/services/container/resource_container_cluster_test.go.tmpl index 4d47a994a81a..40f431a111ed 100644 --- a/mmv1/third_party/terraform/services/container/resource_container_cluster_test.go.tmpl +++ b/mmv1/third_party/terraform/services/container/resource_container_cluster_test.go.tmpl @@ -19381,19 +19381,14 @@ func TestAccContainerCluster_desiredEmulatedVersion(t *testing.T) { networkName := tpgcompute.BootstrapSharedTestNetwork(t, "gke-cluster") subnetworkName := tpgcompute.BootstrapSubnet(t, "gke-cluster", networkName) - // We utilize standard, valid GKE staging versions for E2E minor upgrade transitions - fromVersion := "1.34.8-gke.1000000" - targetVersion := "1.35.5-gke.1000000" - targetEmulatedVersion := "1.35" - acctest.VcrTest(t, resource.TestCase{ PreCheck: func() { acctest.AccTestPreCheck(t) }, ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), CheckDestroy: testAccCheckContainerClusterDestroyProducer(t), Steps: []resource.TestStep{ - // Step 1 (Create): Spin up a standard cluster at version 1.30 (Baseline) + // Step 1 (Create): Spin up a standard cluster at STABLE default { - Config: testAccContainerCluster_desiredEmulatedVersionBase(clusterName, networkName, subnetworkName, fromVersion), + Config: testAccContainerCluster_desiredEmulatedVersionBase(clusterName, networkName, subnetworkName), }, { ResourceName: "google_container_cluster.primary", @@ -19401,12 +19396,9 @@ func TestAccContainerCluster_desiredEmulatedVersion(t *testing.T) { ImportStateVerify: true, ImportStateVerifyIgnore: []string{"min_master_version", "deletion_protection"}, }, - // Step 2 (Soak Upgrade): Upgrade master to target version 1.31 and activate 25-hour soak + // Step 2 (Soak Upgrade): Upgrade master to target version (RAPID latest) and activate soak { - Config: testAccContainerCluster_desiredEmulatedVersionSoak(clusterName, networkName, subnetworkName, fromVersion, targetVersion), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr("google_container_cluster.primary", "min_master_version", targetVersion), - ), + Config: testAccContainerCluster_desiredEmulatedVersionSoak(clusterName, networkName, subnetworkName), }, { ResourceName: "google_container_cluster.primary", @@ -19414,9 +19406,9 @@ func TestAccContainerCluster_desiredEmulatedVersion(t *testing.T) { ImportStateVerify: true, ImportStateVerifyIgnore: []string{"min_master_version", "deletion_protection"}, }, - // Step 3 (Declarative Complete): Set desired_emulated_version to 1.35 to complete the upgrade while soaking + // Step 3 (Declarative Complete): Set desired_emulated_version to complete the upgrade { - Config: testAccContainerCluster_desiredEmulatedVersionComplete(clusterName, networkName, subnetworkName, fromVersion, targetVersion, targetEmulatedVersion), + Config: testAccContainerCluster_desiredEmulatedVersionComplete(clusterName, networkName, subnetworkName), Check: resource.ComposeTestCheckFunc( testAccCheckContainerClusterEmulatedVersion(t, "google_container_cluster.primary"), ), @@ -19462,13 +19454,17 @@ func testAccCheckContainerClusterEmulatedVersion(t *testing.T, resourceName stri } } -func testAccContainerCluster_desiredEmulatedVersionBase(clusterName, networkName, subnetworkName, version string) string { + +func testAccContainerCluster_desiredEmulatedVersionBase(clusterName, networkName, subnetworkName string) string { return fmt.Sprintf(` +data "google_container_engine_versions" "central1" { + location = "us-central1" +} resource "google_container_cluster" "primary" { name = "%s" - location = "us-central1-a" + location = "us-central1" initial_node_count = 1 - min_master_version = "%s" + min_master_version = data.google_container_engine_versions.central1.release_channel_default_version["STABLE"] deletion_protection = false network = "%s" subnetwork = "%s" @@ -19477,49 +19473,51 @@ resource "google_container_cluster" "primary" { machine_type = "e2-standard-2" } } -`, clusterName, version, networkName, subnetworkName) +`, clusterName, networkName, subnetworkName) } -func testAccContainerCluster_desiredEmulatedVersionSoak(clusterName, networkName, subnetworkName, fromVersion, targetVersion string) string { +func testAccContainerCluster_desiredEmulatedVersionSoak(clusterName, networkName, subnetworkName string) string { return fmt.Sprintf(` +data "google_container_engine_versions" "central1" { + location = "us-central1" +} resource "google_container_cluster" "primary" { name = "%s" - location = "us-central1-a" + location = "us-central1" initial_node_count = 1 - min_master_version = "%s" + min_master_version = data.google_container_engine_versions.central1.release_channel_latest_version["RAPID"] deletion_protection = false network = "%s" subnetwork = "%s" - - node_config { machine_type = "e2-standard-2" } } -`, clusterName, targetVersion, networkName, subnetworkName) +`, clusterName, networkName, subnetworkName) } -func testAccContainerCluster_desiredEmulatedVersionComplete(clusterName, networkName, subnetworkName, fromVersion, targetVersion, targetEmulatedVersion string) string { +func testAccContainerCluster_desiredEmulatedVersionComplete(clusterName, networkName, subnetworkName string) string { return fmt.Sprintf(` +data "google_container_engine_versions" "central1" { + location = "us-central1" +} resource "google_container_cluster" "primary" { name = "%s" - location = "us-central1-a" + location = "us-central1" initial_node_count = 1 - min_master_version = "%s" + min_master_version = data.google_container_engine_versions.central1.release_channel_latest_version["RAPID"] deletion_protection = false network = "%s" subnetwork = "%s" - - - desired_emulated_version = "%s" + desired_emulated_version = regex("^[0-9]+\\.[0-9]+", data.google_container_engine_versions.central1.release_channel_latest_version["RAPID"]) node_config { machine_type = "e2-standard-2" } } -`, clusterName, targetVersion, networkName, subnetworkName, targetEmulatedVersion) +`, clusterName, networkName, subnetworkName) } func TestAccContainerCluster_desiredEmulatedVersionAutopilot(t *testing.T) { @@ -19528,19 +19526,14 @@ func TestAccContainerCluster_desiredEmulatedVersionAutopilot(t *testing.T) { networkName := tpgcompute.BootstrapSharedTestNetwork(t, "gke-cluster") subnetworkName := tpgcompute.BootstrapSubnet(t, "gke-cluster", networkName) - // Staging regional GKE versions verified in us-central1 sandbox - fromVersion := "1.34.8-gke.1000000" - targetVersion := "1.35.5-gke.1000000" - targetEmulatedVersion := "1.35" - acctest.VcrTest(t, resource.TestCase{ PreCheck: func() { acctest.AccTestPreCheck(t) }, ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), CheckDestroy: testAccCheckContainerClusterDestroyProducer(t), Steps: []resource.TestStep{ - // Step 1 (Create): Spin up a regional GKE Autopilot cluster at baseline version (1.34) + // Step 1 (Create): Spin up a regional GKE Autopilot cluster at STABLE default { - Config: testAccContainerCluster_desiredEmulatedVersionAutopilotBase(clusterName, networkName, subnetworkName, fromVersion), + Config: testAccContainerCluster_desiredEmulatedVersionAutopilotBase(clusterName, networkName, subnetworkName), }, { ResourceName: "google_container_cluster.primary", @@ -19548,12 +19541,9 @@ func TestAccContainerCluster_desiredEmulatedVersionAutopilot(t *testing.T) { ImportStateVerify: true, ImportStateVerifyIgnore: []string{"min_master_version", "deletion_protection"}, }, - // Step 2 (Soak Upgrade): Upgrade regional master to target version (1.35) with soak active + // Step 2 (Soak Upgrade): Upgrade regional master to target version (RAPID latest) with soak active { - Config: testAccContainerCluster_desiredEmulatedVersionAutopilotSoak(clusterName, networkName, subnetworkName, fromVersion, targetVersion), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr("google_container_cluster.primary", "min_master_version", targetVersion), - ), + Config: testAccContainerCluster_desiredEmulatedVersionAutopilotSoak(clusterName, networkName, subnetworkName), }, { ResourceName: "google_container_cluster.primary", @@ -19561,11 +19551,11 @@ func TestAccContainerCluster_desiredEmulatedVersionAutopilot(t *testing.T) { ImportStateVerify: true, ImportStateVerifyIgnore: []string{"min_master_version", "deletion_protection"}, }, - // Step 3 (Declarative Complete): Set desired_emulated_version to 1.35 to complete the upgrade + // Step 3 (Declarative Complete): Set desired_emulated_version to complete the upgrade { - Config: testAccContainerCluster_desiredEmulatedVersionAutopilotComplete(clusterName, networkName, subnetworkName, fromVersion, targetVersion, targetEmulatedVersion), + Config: testAccContainerCluster_desiredEmulatedVersionAutopilotComplete(clusterName, networkName, subnetworkName), Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr("google_container_cluster.primary", "desired_emulated_version", targetEmulatedVersion), + testAccCheckContainerClusterEmulatedVersion(t, "google_container_cluster.primary"), ), }, { @@ -19578,52 +19568,60 @@ func TestAccContainerCluster_desiredEmulatedVersionAutopilot(t *testing.T) { }) } -func testAccContainerCluster_desiredEmulatedVersionAutopilotBase(clusterName, networkName, subnetworkName, version string) string { +func testAccContainerCluster_desiredEmulatedVersionAutopilotBase(clusterName, networkName, subnetworkName string) string { return fmt.Sprintf(` +data "google_container_engine_versions" "central1" { + location = "us-central1" +} resource "google_container_cluster" "primary" { name = "%s" location = "us-central1" enable_autopilot = true - min_master_version = "%s" + min_master_version = data.google_container_engine_versions.central1.release_channel_default_version["STABLE"] deletion_protection = false network = "%s" subnetwork = "%s" + ip_allocation_policy {} } -`, clusterName, version, networkName, subnetworkName) +`, clusterName, networkName, subnetworkName) } -func testAccContainerCluster_desiredEmulatedVersionAutopilotSoak(clusterName, networkName, subnetworkName, fromVersion, targetVersion string) string { +func testAccContainerCluster_desiredEmulatedVersionAutopilotSoak(clusterName, networkName, subnetworkName string) string { return fmt.Sprintf(` +data "google_container_engine_versions" "central1" { + location = "us-central1" +} resource "google_container_cluster" "primary" { name = "%s" location = "us-central1" enable_autopilot = true - min_master_version = "%s" + min_master_version = data.google_container_engine_versions.central1.release_channel_latest_version["RAPID"] deletion_protection = false network = "%s" subnetwork = "%s" - - + ip_allocation_policy {} } -`, clusterName, targetVersion, networkName, subnetworkName) +`, clusterName, networkName, subnetworkName) } -func testAccContainerCluster_desiredEmulatedVersionAutopilotComplete(clusterName, networkName, subnetworkName, fromVersion, targetVersion, targetEmulatedVersion string) string { +func testAccContainerCluster_desiredEmulatedVersionAutopilotComplete(clusterName, networkName, subnetworkName string) string { return fmt.Sprintf(` +data "google_container_engine_versions" "central1" { + location = "us-central1" +} resource "google_container_cluster" "primary" { name = "%s" location = "us-central1" enable_autopilot = true - min_master_version = "%s" + min_master_version = data.google_container_engine_versions.central1.release_channel_latest_version["RAPID"] deletion_protection = false network = "%s" subnetwork = "%s" + ip_allocation_policy {} - - - desired_emulated_version = "%s" + desired_emulated_version = regex("^[0-9]+\\.[0-9]+", data.google_container_engine_versions.central1.release_channel_latest_version["RAPID"]) } -`, clusterName, targetVersion, networkName, subnetworkName, targetEmulatedVersion) +`, clusterName, networkName, subnetworkName) } diff --git a/mmv1/third_party/terraform/website/docs/r/container_cluster.html.markdown b/mmv1/third_party/terraform/website/docs/r/container_cluster.html.markdown index 6c260a6253fb..c3c55f9d77dc 100644 --- a/mmv1/third_party/terraform/website/docs/r/container_cluster.html.markdown +++ b/mmv1/third_party/terraform/website/docs/r/container_cluster.html.markdown @@ -254,6 +254,8 @@ Structure is [documented below](#nested_master_auth). to the datasource. A region can have a different set of supported versions than its corresponding zones, and not all zones in a region are guaranteed to support the same version. +* `desired_emulated_version` - (Optional) The desired emulated version for the cluster. + * `monitoring_config` - (Optional) Monitoring configuration for the cluster. Structure is [documented below](#nested_monitoring_config). From 2adca0740fadc8b041bfe1f30be7df8ed696a189 Mon Sep 17 00:00:00 2001 From: Chinemerem Chigbo Date: Fri, 5 Jun 2026 01:31:57 +0000 Subject: [PATCH 04/20] Fix ImportStateVerify for desired_emulated_version --- .../container/resource_container_cluster_test.go.tmpl | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/mmv1/third_party/terraform/services/container/resource_container_cluster_test.go.tmpl b/mmv1/third_party/terraform/services/container/resource_container_cluster_test.go.tmpl index 40f431a111ed..a19deb985ff8 100644 --- a/mmv1/third_party/terraform/services/container/resource_container_cluster_test.go.tmpl +++ b/mmv1/third_party/terraform/services/container/resource_container_cluster_test.go.tmpl @@ -19417,7 +19417,7 @@ func TestAccContainerCluster_desiredEmulatedVersion(t *testing.T) { ResourceName: "google_container_cluster.primary", ImportState: true, ImportStateVerify: true, - ImportStateVerifyIgnore: []string{"min_master_version", "deletion_protection"}, + ImportStateVerifyIgnore: []string{"min_master_version", "deletion_protection", "desired_emulated_version"}, }, }, }) @@ -19539,7 +19539,7 @@ func TestAccContainerCluster_desiredEmulatedVersionAutopilot(t *testing.T) { ResourceName: "google_container_cluster.primary", ImportState: true, ImportStateVerify: true, - ImportStateVerifyIgnore: []string{"min_master_version", "deletion_protection"}, + ImportStateVerifyIgnore: []string{"min_master_version", "deletion_protection", "node_pool.0.node_count"}, }, // Step 2 (Soak Upgrade): Upgrade regional master to target version (RAPID latest) with soak active { @@ -19548,8 +19548,7 @@ func TestAccContainerCluster_desiredEmulatedVersionAutopilot(t *testing.T) { { ResourceName: "google_container_cluster.primary", ImportState: true, - ImportStateVerify: true, - ImportStateVerifyIgnore: []string{"min_master_version", "deletion_protection"}, + ImportStateVerify: false, }, // Step 3 (Declarative Complete): Set desired_emulated_version to complete the upgrade { @@ -19561,8 +19560,7 @@ func TestAccContainerCluster_desiredEmulatedVersionAutopilot(t *testing.T) { { ResourceName: "google_container_cluster.primary", ImportState: true, - ImportStateVerify: true, - ImportStateVerifyIgnore: []string{"min_master_version", "deletion_protection"}, + ImportStateVerify: false, }, }, }) From 37e6b4c58427326e8fb081011705b191e455d177 Mon Sep 17 00:00:00 2001 From: Chinemerem Chigbo Date: Tue, 23 Jun 2026 09:46:25 +0000 Subject: [PATCH 05/20] Add version guards to documentation --- .../website/docs/r/container_cluster.html.markdown | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/mmv1/third_party/terraform/website/docs/r/container_cluster.html.markdown b/mmv1/third_party/terraform/website/docs/r/container_cluster.html.markdown index c3c55f9d77dc..9164270b6038 100644 --- a/mmv1/third_party/terraform/website/docs/r/container_cluster.html.markdown +++ b/mmv1/third_party/terraform/website/docs/r/container_cluster.html.markdown @@ -254,7 +254,9 @@ Structure is [documented below](#nested_master_auth). to the datasource. A region can have a different set of supported versions than its corresponding zones, and not all zones in a region are guaranteed to support the same version. +{{- if ne $.TargetVersionName "ga" }} * `desired_emulated_version` - (Optional) The desired emulated version for the cluster. +{{- end }} * `monitoring_config` - (Optional) Monitoring configuration for the cluster. Structure is [documented below](#nested_monitoring_config). @@ -2060,6 +2062,10 @@ exported: * `enterprise_config.0.cluster_tier` - The effective tier of the cluster. +{{- if ne $.TargetVersionName "ga" }} +* `emulated_version` - The current emulated version of the cluster. +{{- end }} + ## Timeouts This resource provides the following From 4a399da9daa434fd2a9f0b92a6f6c4a7cb34100e Mon Sep 17 00:00:00 2001 From: Chinemerem Chigbo Date: Tue, 23 Jun 2026 17:14:04 +0000 Subject: [PATCH 06/20] Revert local go.mod replace directive --- mmv1/third_party/terraform/go.mod | 2 -- 1 file changed, 2 deletions(-) diff --git a/mmv1/third_party/terraform/go.mod b/mmv1/third_party/terraform/go.mod index 022cfd7dae73..9778abb20181 100644 --- a/mmv1/third_party/terraform/go.mod +++ b/mmv1/third_party/terraform/go.mod @@ -132,5 +132,3 @@ require ( google.golang.org/genproto/googleapis/api v0.0.0-20260414002931-afd174a4e478 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) - -replace google.golang.org/api v0.278.0 => /usr/local/google/home/chinemerem/google-api-go-client From 7fad56641fbb8c41d8abe818639ad2f056e263f5 Mon Sep 17 00:00:00 2001 From: Chinemerem Chigbo Date: Tue, 23 Jun 2026 22:17:57 +0000 Subject: [PATCH 07/20] Address PR feedback: fix autopilot tests, add safety guards, and polish docs --- .../resource_container_cluster.go.tmpl | 22 ++++++++++--------- .../resource_container_cluster_test.go.tmpl | 14 +++++++++++- .../docs/r/container_cluster.html.markdown | 4 ++-- 3 files changed, 27 insertions(+), 13 deletions(-) diff --git a/mmv1/third_party/terraform/services/container/resource_container_cluster.go.tmpl b/mmv1/third_party/terraform/services/container/resource_container_cluster.go.tmpl index dc98910bab64..0b1ed3b11101 100644 --- a/mmv1/third_party/terraform/services/container/resource_container_cluster.go.tmpl +++ b/mmv1/third_party/terraform/services/container/resource_container_cluster.go.tmpl @@ -4715,18 +4715,20 @@ func resourceContainerClusterUpdate(d *schema.ResourceData, meta interface{}) er {{- if ne $.TargetVersionName "ga" }} if d.HasChange("desired_emulated_version") { emulatedVersion := d.Get("desired_emulated_version").(string) - req := &container.UpdateClusterRequest{ - Update: &container.ClusterUpdate{ - DesiredEmulatedVersion: emulatedVersion, - }, - } + if emulatedVersion != "" { + req := &container.UpdateClusterRequest{ + Update: &container.ClusterUpdate{ + DesiredEmulatedVersion: emulatedVersion, + }, + } - updateF := updateFunc(req, "updating GKE master emulated version") - // Call update serially. - if err := transport_tpg.LockedCall(lockKey, updateF); err != nil { - return err + updateF := updateFunc(req, "updating GKE master emulated version") + // Call update serially. + if err := transport_tpg.LockedCall(lockKey, updateF); err != nil { + return err + } + log.Printf("[INFO] GKE cluster %s: master emulated version has been updated to %s", d.Id(), emulatedVersion) } - log.Printf("[INFO] GKE cluster %s: master emulated version has been updated to %s", d.Id(), emulatedVersion) } {{- end }} diff --git a/mmv1/third_party/terraform/services/container/resource_container_cluster_test.go.tmpl b/mmv1/third_party/terraform/services/container/resource_container_cluster_test.go.tmpl index a19deb985ff8..1333fd94cc53 100644 --- a/mmv1/third_party/terraform/services/container/resource_container_cluster_test.go.tmpl +++ b/mmv1/third_party/terraform/services/container/resource_container_cluster_test.go.tmpl @@ -19575,11 +19575,15 @@ resource "google_container_cluster" "primary" { name = "%s" location = "us-central1" enable_autopilot = true - min_master_version = data.google_container_engine_versions.central1.release_channel_default_version["STABLE"] + min_master_version = data.google_container_engine_versions.central1.release_channel_default_version["REGULAR"] deletion_protection = false network = "%s" subnetwork = "%s" ip_allocation_policy {} + + release_channel { + channel = "RAPID" + } } `, clusterName, networkName, subnetworkName) } @@ -19598,6 +19602,10 @@ resource "google_container_cluster" "primary" { network = "%s" subnetwork = "%s" ip_allocation_policy {} + + release_channel { + channel = "RAPID" + } } `, clusterName, networkName, subnetworkName) } @@ -19617,6 +19625,10 @@ resource "google_container_cluster" "primary" { subnetwork = "%s" ip_allocation_policy {} + release_channel { + channel = "RAPID" + } + desired_emulated_version = regex("^[0-9]+\\.[0-9]+", data.google_container_engine_versions.central1.release_channel_latest_version["RAPID"]) } `, clusterName, networkName, subnetworkName) diff --git a/mmv1/third_party/terraform/website/docs/r/container_cluster.html.markdown b/mmv1/third_party/terraform/website/docs/r/container_cluster.html.markdown index 9164270b6038..10bea8897bba 100644 --- a/mmv1/third_party/terraform/website/docs/r/container_cluster.html.markdown +++ b/mmv1/third_party/terraform/website/docs/r/container_cluster.html.markdown @@ -255,7 +255,7 @@ to the datasource. A region can have a different set of supported versions than region are guaranteed to support the same version. {{- if ne $.TargetVersionName "ga" }} -* `desired_emulated_version` - (Optional) The desired emulated version for the cluster. +* `desired_emulated_version` - (Beta) (Optional) The desired emulated version for the cluster. Used to complete a rollback-safe upgrade after a soak period. Must be in major.minor format (e.g., "1.31"). To complete the upgrade declaratively, set this field to the target minor version. Removing this field from your configuration will not trigger completion. {{- end }} * `monitoring_config` - (Optional) Monitoring configuration for the cluster. @@ -2063,7 +2063,7 @@ exported: * `enterprise_config.0.cluster_tier` - The effective tier of the cluster. {{- if ne $.TargetVersionName "ga" }} -* `emulated_version` - The current emulated version of the cluster. +* `emulated_version` - (Beta) The current emulated version of the cluster. {{- end }} ## Timeouts From 37f21453cd0b5bcabe425769902042dd7307dd29 Mon Sep 17 00:00:00 2001 From: Chinemerem Date: Wed, 24 Jun 2026 14:47:22 -0700 Subject: [PATCH 08/20] Update mmv1/third_party/terraform/website/docs/r/container_cluster.html.markdown Co-authored-by: Stephen Lewis (Burrows) --- .../terraform/website/docs/r/container_cluster.html.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mmv1/third_party/terraform/website/docs/r/container_cluster.html.markdown b/mmv1/third_party/terraform/website/docs/r/container_cluster.html.markdown index 10bea8897bba..17bcca10b1eb 100644 --- a/mmv1/third_party/terraform/website/docs/r/container_cluster.html.markdown +++ b/mmv1/third_party/terraform/website/docs/r/container_cluster.html.markdown @@ -255,7 +255,7 @@ to the datasource. A region can have a different set of supported versions than region are guaranteed to support the same version. {{- if ne $.TargetVersionName "ga" }} -* `desired_emulated_version` - (Beta) (Optional) The desired emulated version for the cluster. Used to complete a rollback-safe upgrade after a soak period. Must be in major.minor format (e.g., "1.31"). To complete the upgrade declaratively, set this field to the target minor version. Removing this field from your configuration will not trigger completion. +* `desired_emulated_version` - (Optional, [Beta](../guides/provider_versions.html.markdown)) The desired emulated version for the cluster. Used to complete a rollback-safe upgrade after a soak period. Must be in major.minor format (e.g., "1.31"). To complete the upgrade declaratively, set this field to the target minor version. Removing this field from your configuration will not trigger completion. {{- end }} * `monitoring_config` - (Optional) Monitoring configuration for the cluster. From 1b46bb5cd442bf61aef929c0e82fca259cf460cb Mon Sep 17 00:00:00 2001 From: Chinemerem Date: Wed, 24 Jun 2026 14:47:29 -0700 Subject: [PATCH 09/20] Update mmv1/third_party/terraform/website/docs/r/container_cluster.html.markdown Co-authored-by: Stephen Lewis (Burrows) --- .../terraform/website/docs/r/container_cluster.html.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mmv1/third_party/terraform/website/docs/r/container_cluster.html.markdown b/mmv1/third_party/terraform/website/docs/r/container_cluster.html.markdown index 17bcca10b1eb..56369687fbad 100644 --- a/mmv1/third_party/terraform/website/docs/r/container_cluster.html.markdown +++ b/mmv1/third_party/terraform/website/docs/r/container_cluster.html.markdown @@ -2063,7 +2063,7 @@ exported: * `enterprise_config.0.cluster_tier` - The effective tier of the cluster. {{- if ne $.TargetVersionName "ga" }} -* `emulated_version` - (Beta) The current emulated version of the cluster. +* `emulated_version` - ([Beta](../guides/provider_versions.html.markdown)) The current emulated version of the cluster. {{- end }} ## Timeouts From 7f67f73113c2fb8dd7913cdd01977c2e466720f0 Mon Sep 17 00:00:00 2001 From: Chinemerem Chigbo Date: Thu, 9 Jul 2026 18:07:47 +0000 Subject: [PATCH 10/20] Trigger CI From 30bfb20cf8a1883c7361d225c31a91949b1e256c Mon Sep 17 00:00:00 2001 From: Chinemerem Chigbo Date: Fri, 10 Jul 2026 17:36:43 +0000 Subject: [PATCH 11/20] feat: implement rollback_safe_upgrade block for Two-Step Upgrades --- .../resource_container_cluster.go.tmpl | 29 +++++++++++++++++++ .../resource_container_cluster_test.go.tmpl | 16 ++++++++++ .../docs/r/container_cluster.html.markdown | 6 ++++ 3 files changed, 51 insertions(+) diff --git a/mmv1/third_party/terraform/services/container/resource_container_cluster.go.tmpl b/mmv1/third_party/terraform/services/container/resource_container_cluster.go.tmpl index 0b1ed3b11101..b8162efb25a1 100644 --- a/mmv1/third_party/terraform/services/container/resource_container_cluster.go.tmpl +++ b/mmv1/third_party/terraform/services/container/resource_container_cluster.go.tmpl @@ -1231,6 +1231,21 @@ func ResourceContainerCluster() *schema.Resource { }, {{- if ne $.TargetVersionName "ga" }} + "rollback_safe_upgrade": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Description: `Configuration for rollback-safe (two-step) upgrades.`, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "control_plane_soak_duration": { + Type: schema.TypeString, + Optional: true, + Description: `A user-defined period for the cluster remains in the rollbackable state. E.g., '30d'.`, + }, + }, + }, + }, "desired_emulated_version": { Type: schema.TypeString, Optional: true, @@ -4935,6 +4950,20 @@ func resourceContainerClusterUpdate(d *schema.ResourceData, meta interface{}) er }, } +{{- if ne $.TargetVersionName "ga" }} + if r, ok := d.GetOk("rollback_safe_upgrade"); ok { + rls := r.([]interface{}) + if len(rls) > 0 && rls[0] != nil { + rl := rls[0].(map[string]interface{}) + if soakDuration, ok := rl["control_plane_soak_duration"].(string); ok && soakDuration != "" { + req.Update.DesiredRollbackSafeUpgrade = &container.RollbackSafeUpgrade{ + ControlPlaneSoakDuration: soakDuration, + } + } + } + } +{{- end }} + updateF := updateFunc(req, "updating GKE master version") // Call update serially. if err := transport_tpg.LockedCall(lockKey, updateF); err != nil { diff --git a/mmv1/third_party/terraform/services/container/resource_container_cluster_test.go.tmpl b/mmv1/third_party/terraform/services/container/resource_container_cluster_test.go.tmpl index 1333fd94cc53..8add9e60d2eb 100644 --- a/mmv1/third_party/terraform/services/container/resource_container_cluster_test.go.tmpl +++ b/mmv1/third_party/terraform/services/container/resource_container_cluster_test.go.tmpl @@ -19490,6 +19490,10 @@ resource "google_container_cluster" "primary" { network = "%s" subnetwork = "%s" + rollback_safe_upgrade { + control_plane_soak_duration = "30d" + } + node_config { machine_type = "e2-standard-2" } @@ -19511,6 +19515,10 @@ resource "google_container_cluster" "primary" { network = "%s" subnetwork = "%s" + rollback_safe_upgrade { + control_plane_soak_duration = "30d" + } + desired_emulated_version = regex("^[0-9]+\\.[0-9]+", data.google_container_engine_versions.central1.release_channel_latest_version["RAPID"]) node_config { @@ -19606,6 +19614,10 @@ resource "google_container_cluster" "primary" { release_channel { channel = "RAPID" } + + rollback_safe_upgrade { + control_plane_soak_duration = "30d" + } } `, clusterName, networkName, subnetworkName) } @@ -19629,6 +19641,10 @@ resource "google_container_cluster" "primary" { channel = "RAPID" } + rollback_safe_upgrade { + control_plane_soak_duration = "30d" + } + desired_emulated_version = regex("^[0-9]+\\.[0-9]+", data.google_container_engine_versions.central1.release_channel_latest_version["RAPID"]) } `, clusterName, networkName, subnetworkName) diff --git a/mmv1/third_party/terraform/website/docs/r/container_cluster.html.markdown b/mmv1/third_party/terraform/website/docs/r/container_cluster.html.markdown index 56369687fbad..f18098b89fd2 100644 --- a/mmv1/third_party/terraform/website/docs/r/container_cluster.html.markdown +++ b/mmv1/third_party/terraform/website/docs/r/container_cluster.html.markdown @@ -255,6 +255,8 @@ to the datasource. A region can have a different set of supported versions than region are guaranteed to support the same version. {{- if ne $.TargetVersionName "ga" }} +* `rollback_safe_upgrade` - (Optional, [Beta](../guides/provider_versions.html.markdown)) Configuration for rollback-safe (two-step) upgrades. Structure is [documented below](#nested_rollback_safe_upgrade). + * `desired_emulated_version` - (Optional, [Beta](../guides/provider_versions.html.markdown)) The desired emulated version for the cluster. Used to complete a rollback-safe upgrade after a soak period. Must be in major.minor format (e.g., "1.31"). To complete the upgrade declaratively, set this field to the target minor version. Removing this field from your configuration will not trigger completion. {{- end }} @@ -1000,6 +1002,10 @@ Structure is [documented below](#nested_additional_ip_ranges_config). * `NETWORK_TIER_STANDARD`: Standard network tier. +The `rollback_safe_upgrade` block supports: + +* `control_plane_soak_duration` - (Optional) A user-defined period for the cluster remains in the rollbackable state. E.g., '30d'. + The `master_auth` block supports: * `client_certificate_config` - (Required) Whether client certificate authorization is enabled for this cluster. For example: From c84c6c6d261616037b24533d2621c377e703d11b Mon Sep 17 00:00:00 2001 From: Chinemerem Chigbo Date: Fri, 10 Jul 2026 17:51:31 +0000 Subject: [PATCH 12/20] feat: add rollback_safe_upgrade to GKE cluster and fix meta.yaml --- .../container/resource_container_cluster_meta.yaml.tmpl | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/mmv1/third_party/terraform/services/container/resource_container_cluster_meta.yaml.tmpl b/mmv1/third_party/terraform/services/container/resource_container_cluster_meta.yaml.tmpl index 131567ed1704..2977c48fc2d1 100644 --- a/mmv1/third_party/terraform/services/container/resource_container_cluster_meta.yaml.tmpl +++ b/mmv1/third_party/terraform/services/container/resource_container_cluster_meta.yaml.tmpl @@ -949,3 +949,11 @@ fields: provider_only: true - field: 'ignore_node_count_changes' provider_only: true +{{- if ne $.TargetVersionName "ga" }} + - field: 'desired_emulated_version' + provider_only: true + - field: 'emulated_version' + provider_only: true + - field: 'rollback_safe_upgrade.control_plane_soak_duration' + provider_only: true +{{- end }} From ac6e3d7fbcdb00788a9c21941b42d3e36ec579dd Mon Sep 17 00:00:00 2001 From: Chinemerem Chigbo Date: Tue, 14 Jul 2026 18:53:05 +0000 Subject: [PATCH 13/20] Fix protobuf duration format and graduate fields to GA --- .../container/resource_container_cluster.go.tmpl | 11 +---------- .../resource_container_cluster_meta.yaml.tmpl | 2 -- .../resource_container_cluster_test.go.tmpl | 8 ++++---- .../website/docs/r/container_cluster.html.markdown | 12 ++++-------- 4 files changed, 9 insertions(+), 24 deletions(-) diff --git a/mmv1/third_party/terraform/services/container/resource_container_cluster.go.tmpl b/mmv1/third_party/terraform/services/container/resource_container_cluster.go.tmpl index b8162efb25a1..5f4ae2e51298 100644 --- a/mmv1/third_party/terraform/services/container/resource_container_cluster.go.tmpl +++ b/mmv1/third_party/terraform/services/container/resource_container_cluster.go.tmpl @@ -1229,8 +1229,6 @@ func ResourceContainerCluster() *schema.Resource { ValidateFunc: validation.StringInSlice([]string{"logging.googleapis.com", "logging.googleapis.com/kubernetes", "none"}, false), Description: `The logging service that the cluster should write logs to. Available options include logging.googleapis.com(Legacy Stackdriver), logging.googleapis.com/kubernetes(Stackdriver Kubernetes Engine Logging), and none. Defaults to logging.googleapis.com/kubernetes.`, }, - -{{- if ne $.TargetVersionName "ga" }} "rollback_safe_upgrade": { Type: schema.TypeList, Optional: true, @@ -1241,7 +1239,7 @@ func ResourceContainerCluster() *schema.Resource { "control_plane_soak_duration": { Type: schema.TypeString, Optional: true, - Description: `A user-defined period for the cluster remains in the rollbackable state. E.g., '30d'.`, + Description: `A user-defined period for the cluster remains in the rollbackable state. A duration in seconds with up to nine fractional digits, ending with 's'. Example: "2592000s" for 30 days.`, }, }, }, @@ -1258,7 +1256,6 @@ func ResourceContainerCluster() *schema.Resource { Computed: true, Description: `The current emulated Kubernetes version running on the GKE cluster control plane.`, }, -{{- end }} "maintenance_policy": { Type: schema.TypeList, @@ -3751,11 +3748,9 @@ func resourceContainerClusterRead(d *schema.ResourceData, meta interface{}) erro if err := d.Set("master_version", cluster.CurrentMasterVersion); err != nil { return fmt.Errorf("Error setting master_version: %s", err) } -{{- if ne $.TargetVersionName "ga" }} if err := d.Set("emulated_version", cluster.CurrentEmulatedVersion); err != nil { return fmt.Errorf("Error setting emulated_version: %s", err) } -{{- end }} if err := d.Set("node_version", cluster.CurrentNodeVersion); err != nil { return fmt.Errorf("Error setting node_version: %s", err) } @@ -4727,7 +4722,6 @@ func resourceContainerClusterUpdate(d *schema.ResourceData, meta interface{}) er log.Printf("[INFO] GKE cluster %s legacy ABAC has been updated to %v", d.Id(), enabled) } -{{- if ne $.TargetVersionName "ga" }} if d.HasChange("desired_emulated_version") { emulatedVersion := d.Get("desired_emulated_version").(string) if emulatedVersion != "" { @@ -4745,7 +4739,6 @@ func resourceContainerClusterUpdate(d *schema.ResourceData, meta interface{}) er log.Printf("[INFO] GKE cluster %s: master emulated version has been updated to %s", d.Id(), emulatedVersion) } } -{{- end }} if d.HasChange("monitoring_service") || d.HasChange("logging_service") { logging := d.Get("logging_service").(string) @@ -4950,7 +4943,6 @@ func resourceContainerClusterUpdate(d *schema.ResourceData, meta interface{}) er }, } -{{- if ne $.TargetVersionName "ga" }} if r, ok := d.GetOk("rollback_safe_upgrade"); ok { rls := r.([]interface{}) if len(rls) > 0 && rls[0] != nil { @@ -4962,7 +4954,6 @@ func resourceContainerClusterUpdate(d *schema.ResourceData, meta interface{}) er } } } -{{- end }} updateF := updateFunc(req, "updating GKE master version") // Call update serially. diff --git a/mmv1/third_party/terraform/services/container/resource_container_cluster_meta.yaml.tmpl b/mmv1/third_party/terraform/services/container/resource_container_cluster_meta.yaml.tmpl index 2977c48fc2d1..77db3a4b2ec0 100644 --- a/mmv1/third_party/terraform/services/container/resource_container_cluster_meta.yaml.tmpl +++ b/mmv1/third_party/terraform/services/container/resource_container_cluster_meta.yaml.tmpl @@ -949,11 +949,9 @@ fields: provider_only: true - field: 'ignore_node_count_changes' provider_only: true -{{- if ne $.TargetVersionName "ga" }} - field: 'desired_emulated_version' provider_only: true - field: 'emulated_version' provider_only: true - field: 'rollback_safe_upgrade.control_plane_soak_duration' provider_only: true -{{- end }} diff --git a/mmv1/third_party/terraform/services/container/resource_container_cluster_test.go.tmpl b/mmv1/third_party/terraform/services/container/resource_container_cluster_test.go.tmpl index 8add9e60d2eb..64fb273bf1ff 100644 --- a/mmv1/third_party/terraform/services/container/resource_container_cluster_test.go.tmpl +++ b/mmv1/third_party/terraform/services/container/resource_container_cluster_test.go.tmpl @@ -19491,7 +19491,7 @@ resource "google_container_cluster" "primary" { subnetwork = "%s" rollback_safe_upgrade { - control_plane_soak_duration = "30d" + control_plane_soak_duration = "259200s" } node_config { @@ -19516,7 +19516,7 @@ resource "google_container_cluster" "primary" { subnetwork = "%s" rollback_safe_upgrade { - control_plane_soak_duration = "30d" + control_plane_soak_duration = "259200s" } desired_emulated_version = regex("^[0-9]+\\.[0-9]+", data.google_container_engine_versions.central1.release_channel_latest_version["RAPID"]) @@ -19616,7 +19616,7 @@ resource "google_container_cluster" "primary" { } rollback_safe_upgrade { - control_plane_soak_duration = "30d" + control_plane_soak_duration = "259200s" } } `, clusterName, networkName, subnetworkName) @@ -19642,7 +19642,7 @@ resource "google_container_cluster" "primary" { } rollback_safe_upgrade { - control_plane_soak_duration = "30d" + control_plane_soak_duration = "259200s" } desired_emulated_version = regex("^[0-9]+\\.[0-9]+", data.google_container_engine_versions.central1.release_channel_latest_version["RAPID"]) diff --git a/mmv1/third_party/terraform/website/docs/r/container_cluster.html.markdown b/mmv1/third_party/terraform/website/docs/r/container_cluster.html.markdown index f18098b89fd2..277b9cc2c99f 100644 --- a/mmv1/third_party/terraform/website/docs/r/container_cluster.html.markdown +++ b/mmv1/third_party/terraform/website/docs/r/container_cluster.html.markdown @@ -254,11 +254,9 @@ Structure is [documented below](#nested_master_auth). to the datasource. A region can have a different set of supported versions than its corresponding zones, and not all zones in a region are guaranteed to support the same version. -{{- if ne $.TargetVersionName "ga" }} -* `rollback_safe_upgrade` - (Optional, [Beta](../guides/provider_versions.html.markdown)) Configuration for rollback-safe (two-step) upgrades. Structure is [documented below](#nested_rollback_safe_upgrade). +* `rollback_safe_upgrade` - (Optional) Configuration for rollback-safe (two-step) upgrades. Structure is [documented below](#nested_rollback_safe_upgrade). -* `desired_emulated_version` - (Optional, [Beta](../guides/provider_versions.html.markdown)) The desired emulated version for the cluster. Used to complete a rollback-safe upgrade after a soak period. Must be in major.minor format (e.g., "1.31"). To complete the upgrade declaratively, set this field to the target minor version. Removing this field from your configuration will not trigger completion. -{{- end }} +* `desired_emulated_version` - (Optional) The desired emulated version for the cluster. Used to complete a rollback-safe upgrade after a soak period. Must be in major.minor format (e.g., "1.31"). To complete the upgrade declaratively, set this field to the target minor version. Removing this field from your configuration will not trigger completion. * `monitoring_config` - (Optional) Monitoring configuration for the cluster. Structure is [documented below](#nested_monitoring_config). @@ -1004,7 +1002,7 @@ Structure is [documented below](#nested_additional_ip_ranges_config). The `rollback_safe_upgrade` block supports: -* `control_plane_soak_duration` - (Optional) A user-defined period for the cluster remains in the rollbackable state. E.g., '30d'. +* `control_plane_soak_duration` - (Optional) A user-defined period for the cluster remains in the rollbackable state. A duration in seconds with up to nine fractional digits, ending with 's'. Example: "259200s" for 3 days. The `master_auth` block supports: @@ -2068,9 +2066,7 @@ exported: * `enterprise_config.0.cluster_tier` - The effective tier of the cluster. -{{- if ne $.TargetVersionName "ga" }} -* `emulated_version` - ([Beta](../guides/provider_versions.html.markdown)) The current emulated version of the cluster. -{{- end }} +* `emulated_version` - The current emulated version of the cluster. ## Timeouts From 1209a62821dcc51229def2fe8c6b01e3a8a419a4 Mon Sep 17 00:00:00 2001 From: Chinemerem Chigbo Date: Wed, 15 Jul 2026 22:12:41 +0000 Subject: [PATCH 14/20] fix: resolve empty rollback_safe_upgrade block bug and stabilize VCR tests --- .../resource_container_cluster.go.tmpl | 5 +- .../resource_container_cluster_test.go.tmpl | 157 +++++++++++++++++- 2 files changed, 151 insertions(+), 11 deletions(-) diff --git a/mmv1/third_party/terraform/services/container/resource_container_cluster.go.tmpl b/mmv1/third_party/terraform/services/container/resource_container_cluster.go.tmpl index 5f4ae2e51298..20d87e224f82 100644 --- a/mmv1/third_party/terraform/services/container/resource_container_cluster.go.tmpl +++ b/mmv1/third_party/terraform/services/container/resource_container_cluster.go.tmpl @@ -4946,11 +4946,10 @@ func resourceContainerClusterUpdate(d *schema.ResourceData, meta interface{}) er if r, ok := d.GetOk("rollback_safe_upgrade"); ok { rls := r.([]interface{}) if len(rls) > 0 && rls[0] != nil { + req.Update.DesiredRollbackSafeUpgrade = &container.RollbackSafeUpgrade{} rl := rls[0].(map[string]interface{}) if soakDuration, ok := rl["control_plane_soak_duration"].(string); ok && soakDuration != "" { - req.Update.DesiredRollbackSafeUpgrade = &container.RollbackSafeUpgrade{ - ControlPlaneSoakDuration: soakDuration, - } + req.Update.DesiredRollbackSafeUpgrade.ControlPlaneSoakDuration = soakDuration } } } diff --git a/mmv1/third_party/terraform/services/container/resource_container_cluster_test.go.tmpl b/mmv1/third_party/terraform/services/container/resource_container_cluster_test.go.tmpl index 64fb273bf1ff..211295b8e73d 100644 --- a/mmv1/third_party/terraform/services/container/resource_container_cluster_test.go.tmpl +++ b/mmv1/third_party/terraform/services/container/resource_container_cluster_test.go.tmpl @@ -19460,11 +19460,37 @@ func testAccContainerCluster_desiredEmulatedVersionBase(clusterName, networkName data "google_container_engine_versions" "central1" { location = "us-central1" } + +locals { + valid_minors = distinct([ + for v in data.google_container_engine_versions.central1.valid_master_versions : + regex("^[0-9]+\\.([0-9]+)\\.", v)[0] + ]) + sorted_minors = reverse(sort(local.valid_minors)) + target_minor = local.sorted_minors[0] + base_minor = local.sorted_minors[1] +} + +data "google_container_engine_versions" "base" { + location = "us-central1" + version_prefix = "1.${local.base_minor}." +} + +data "google_container_engine_versions" "target" { + location = "us-central1" + version_prefix = "1.${local.target_minor}." +} + resource "google_container_cluster" "primary" { name = "%s" location = "us-central1" initial_node_count = 1 - min_master_version = data.google_container_engine_versions.central1.release_channel_default_version["STABLE"] + + release_channel { + channel = "RAPID" + } + + min_master_version = data.google_container_engine_versions.base.latest_master_version deletion_protection = false network = "%s" subnetwork = "%s" @@ -19481,11 +19507,37 @@ func testAccContainerCluster_desiredEmulatedVersionSoak(clusterName, networkName data "google_container_engine_versions" "central1" { location = "us-central1" } + +locals { + valid_minors = distinct([ + for v in data.google_container_engine_versions.central1.valid_master_versions : + regex("^[0-9]+\\.([0-9]+)\\.", v)[0] + ]) + sorted_minors = reverse(sort(local.valid_minors)) + target_minor = local.sorted_minors[0] + base_minor = local.sorted_minors[1] +} + +data "google_container_engine_versions" "base" { + location = "us-central1" + version_prefix = "1.${local.base_minor}." +} + +data "google_container_engine_versions" "target" { + location = "us-central1" + version_prefix = "1.${local.target_minor}." +} + resource "google_container_cluster" "primary" { name = "%s" location = "us-central1" initial_node_count = 1 - min_master_version = data.google_container_engine_versions.central1.release_channel_latest_version["RAPID"] + + release_channel { + channel = "RAPID" + } + + min_master_version = data.google_container_engine_versions.target.latest_master_version deletion_protection = false network = "%s" subnetwork = "%s" @@ -19506,11 +19558,37 @@ func testAccContainerCluster_desiredEmulatedVersionComplete(clusterName, network data "google_container_engine_versions" "central1" { location = "us-central1" } + +locals { + valid_minors = distinct([ + for v in data.google_container_engine_versions.central1.valid_master_versions : + regex("^[0-9]+\\.([0-9]+)\\.", v)[0] + ]) + sorted_minors = reverse(sort(local.valid_minors)) + target_minor = local.sorted_minors[0] + base_minor = local.sorted_minors[1] +} + +data "google_container_engine_versions" "base" { + location = "us-central1" + version_prefix = "1.${local.base_minor}." +} + +data "google_container_engine_versions" "target" { + location = "us-central1" + version_prefix = "1.${local.target_minor}." +} + resource "google_container_cluster" "primary" { name = "%s" location = "us-central1" initial_node_count = 1 - min_master_version = data.google_container_engine_versions.central1.release_channel_latest_version["RAPID"] + + release_channel { + channel = "RAPID" + } + + min_master_version = data.google_container_engine_versions.target.latest_master_version deletion_protection = false network = "%s" subnetwork = "%s" @@ -19519,7 +19597,7 @@ resource "google_container_cluster" "primary" { control_plane_soak_duration = "259200s" } - desired_emulated_version = regex("^[0-9]+\\.[0-9]+", data.google_container_engine_versions.central1.release_channel_latest_version["RAPID"]) + desired_emulated_version = regex("^[0-9]+\\.[0-9]+", data.google_container_engine_versions.target.latest_master_version) node_config { machine_type = "e2-standard-2" @@ -19579,11 +19657,32 @@ func testAccContainerCluster_desiredEmulatedVersionAutopilotBase(clusterName, ne data "google_container_engine_versions" "central1" { location = "us-central1" } + +locals { + valid_minors = distinct([ + for v in data.google_container_engine_versions.central1.valid_master_versions : + regex("^[0-9]+\\.([0-9]+)\\.", v)[0] + ]) + sorted_minors = reverse(sort(local.valid_minors)) + target_minor = local.sorted_minors[0] + base_minor = local.sorted_minors[1] +} + +data "google_container_engine_versions" "base" { + location = "us-central1" + version_prefix = "1.${local.base_minor}." +} + +data "google_container_engine_versions" "target" { + location = "us-central1" + version_prefix = "1.${local.target_minor}." +} + resource "google_container_cluster" "primary" { name = "%s" location = "us-central1" enable_autopilot = true - min_master_version = data.google_container_engine_versions.central1.release_channel_default_version["REGULAR"] + min_master_version = data.google_container_engine_versions.base.latest_master_version deletion_protection = false network = "%s" subnetwork = "%s" @@ -19601,11 +19700,32 @@ func testAccContainerCluster_desiredEmulatedVersionAutopilotSoak(clusterName, ne data "google_container_engine_versions" "central1" { location = "us-central1" } + +locals { + valid_minors = distinct([ + for v in data.google_container_engine_versions.central1.valid_master_versions : + regex("^[0-9]+\\.([0-9]+)\\.", v)[0] + ]) + sorted_minors = reverse(sort(local.valid_minors)) + target_minor = local.sorted_minors[0] + base_minor = local.sorted_minors[1] +} + +data "google_container_engine_versions" "base" { + location = "us-central1" + version_prefix = "1.${local.base_minor}." +} + +data "google_container_engine_versions" "target" { + location = "us-central1" + version_prefix = "1.${local.target_minor}." +} + resource "google_container_cluster" "primary" { name = "%s" location = "us-central1" enable_autopilot = true - min_master_version = data.google_container_engine_versions.central1.release_channel_latest_version["RAPID"] + min_master_version = data.google_container_engine_versions.target.latest_master_version deletion_protection = false network = "%s" subnetwork = "%s" @@ -19627,11 +19747,32 @@ func testAccContainerCluster_desiredEmulatedVersionAutopilotComplete(clusterName data "google_container_engine_versions" "central1" { location = "us-central1" } + +locals { + valid_minors = distinct([ + for v in data.google_container_engine_versions.central1.valid_master_versions : + regex("^[0-9]+\\.([0-9]+)\\.", v)[0] + ]) + sorted_minors = reverse(sort(local.valid_minors)) + target_minor = local.sorted_minors[0] + base_minor = local.sorted_minors[1] +} + +data "google_container_engine_versions" "base" { + location = "us-central1" + version_prefix = "1.${local.base_minor}." +} + +data "google_container_engine_versions" "target" { + location = "us-central1" + version_prefix = "1.${local.target_minor}." +} + resource "google_container_cluster" "primary" { name = "%s" location = "us-central1" enable_autopilot = true - min_master_version = data.google_container_engine_versions.central1.release_channel_latest_version["RAPID"] + min_master_version = data.google_container_engine_versions.target.latest_master_version deletion_protection = false network = "%s" subnetwork = "%s" @@ -19645,7 +19786,7 @@ resource "google_container_cluster" "primary" { control_plane_soak_duration = "259200s" } - desired_emulated_version = regex("^[0-9]+\\.[0-9]+", data.google_container_engine_versions.central1.release_channel_latest_version["RAPID"]) + desired_emulated_version = regex("^[0-9]+\\.[0-9]+", data.google_container_engine_versions.target.latest_master_version) } `, clusterName, networkName, subnetworkName) } From f660fb5f9f624f4b926a1b86e7e8df1f414a0489 Mon Sep 17 00:00:00 2001 From: Chinemerem Chigbo Date: Thu, 16 Jul 2026 00:39:03 +0000 Subject: [PATCH 15/20] test: Add missing ImportStateVerifyIgnore for rollback_safe_upgrade --- .../container/resource_container_cluster_test.go.tmpl | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/mmv1/third_party/terraform/services/container/resource_container_cluster_test.go.tmpl b/mmv1/third_party/terraform/services/container/resource_container_cluster_test.go.tmpl index 211295b8e73d..212a37d8e16c 100644 --- a/mmv1/third_party/terraform/services/container/resource_container_cluster_test.go.tmpl +++ b/mmv1/third_party/terraform/services/container/resource_container_cluster_test.go.tmpl @@ -19404,7 +19404,7 @@ func TestAccContainerCluster_desiredEmulatedVersion(t *testing.T) { ResourceName: "google_container_cluster.primary", ImportState: true, ImportStateVerify: true, - ImportStateVerifyIgnore: []string{"min_master_version", "deletion_protection"}, + ImportStateVerifyIgnore: []string{"min_master_version", "deletion_protection", "rollback_safe_upgrade"}, }, // Step 3 (Declarative Complete): Set desired_emulated_version to complete the upgrade { @@ -19417,7 +19417,7 @@ func TestAccContainerCluster_desiredEmulatedVersion(t *testing.T) { ResourceName: "google_container_cluster.primary", ImportState: true, ImportStateVerify: true, - ImportStateVerifyIgnore: []string{"min_master_version", "deletion_protection", "desired_emulated_version"}, + ImportStateVerifyIgnore: []string{"min_master_version", "deletion_protection", "desired_emulated_version", "rollback_safe_upgrade"}, }, }, }) @@ -19634,7 +19634,8 @@ func TestAccContainerCluster_desiredEmulatedVersionAutopilot(t *testing.T) { { ResourceName: "google_container_cluster.primary", ImportState: true, - ImportStateVerify: false, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"min_master_version", "deletion_protection", "node_pool.0.node_count", "rollback_safe_upgrade"}, }, // Step 3 (Declarative Complete): Set desired_emulated_version to complete the upgrade { @@ -19646,7 +19647,8 @@ func TestAccContainerCluster_desiredEmulatedVersionAutopilot(t *testing.T) { { ResourceName: "google_container_cluster.primary", ImportState: true, - ImportStateVerify: false, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"min_master_version", "deletion_protection", "node_pool.0.node_count", "desired_emulated_version", "rollback_safe_upgrade"}, }, }, }) From dc7f99b1ca7cd6773e50168a35e63672fe6e408c Mon Sep 17 00:00:00 2001 From: Chinemerem Chigbo Date: Thu, 16 Jul 2026 16:19:06 +0000 Subject: [PATCH 16/20] fix: Correct CAI export mapping and VCR test sorting logic --- .../resource_container_cluster_meta.yaml.tmpl | 2 +- .../resource_container_cluster_test.go.tmpl | 30 ++++++++----------- 2 files changed, 13 insertions(+), 19 deletions(-) diff --git a/mmv1/third_party/terraform/services/container/resource_container_cluster_meta.yaml.tmpl b/mmv1/third_party/terraform/services/container/resource_container_cluster_meta.yaml.tmpl index 77db3a4b2ec0..38c836e9e141 100644 --- a/mmv1/third_party/terraform/services/container/resource_container_cluster_meta.yaml.tmpl +++ b/mmv1/third_party/terraform/services/container/resource_container_cluster_meta.yaml.tmpl @@ -952,6 +952,6 @@ fields: - field: 'desired_emulated_version' provider_only: true - field: 'emulated_version' - provider_only: true + api_field: 'currentEmulatedVersion' - field: 'rollback_safe_upgrade.control_plane_soak_duration' provider_only: true diff --git a/mmv1/third_party/terraform/services/container/resource_container_cluster_test.go.tmpl b/mmv1/third_party/terraform/services/container/resource_container_cluster_test.go.tmpl index 212a37d8e16c..c2cbe500d8ce 100644 --- a/mmv1/third_party/terraform/services/container/resource_container_cluster_test.go.tmpl +++ b/mmv1/third_party/terraform/services/container/resource_container_cluster_test.go.tmpl @@ -19466,9 +19466,8 @@ locals { for v in data.google_container_engine_versions.central1.valid_master_versions : regex("^[0-9]+\\.([0-9]+)\\.", v)[0] ]) - sorted_minors = reverse(sort(local.valid_minors)) - target_minor = local.sorted_minors[0] - base_minor = local.sorted_minors[1] + target_minor = local.valid_minors[0] + base_minor = local.valid_minors[1] } data "google_container_engine_versions" "base" { @@ -19513,9 +19512,8 @@ locals { for v in data.google_container_engine_versions.central1.valid_master_versions : regex("^[0-9]+\\.([0-9]+)\\.", v)[0] ]) - sorted_minors = reverse(sort(local.valid_minors)) - target_minor = local.sorted_minors[0] - base_minor = local.sorted_minors[1] + target_minor = local.valid_minors[0] + base_minor = local.valid_minors[1] } data "google_container_engine_versions" "base" { @@ -19564,9 +19562,8 @@ locals { for v in data.google_container_engine_versions.central1.valid_master_versions : regex("^[0-9]+\\.([0-9]+)\\.", v)[0] ]) - sorted_minors = reverse(sort(local.valid_minors)) - target_minor = local.sorted_minors[0] - base_minor = local.sorted_minors[1] + target_minor = local.valid_minors[0] + base_minor = local.valid_minors[1] } data "google_container_engine_versions" "base" { @@ -19665,9 +19662,8 @@ locals { for v in data.google_container_engine_versions.central1.valid_master_versions : regex("^[0-9]+\\.([0-9]+)\\.", v)[0] ]) - sorted_minors = reverse(sort(local.valid_minors)) - target_minor = local.sorted_minors[0] - base_minor = local.sorted_minors[1] + target_minor = local.valid_minors[0] + base_minor = local.valid_minors[1] } data "google_container_engine_versions" "base" { @@ -19708,9 +19704,8 @@ locals { for v in data.google_container_engine_versions.central1.valid_master_versions : regex("^[0-9]+\\.([0-9]+)\\.", v)[0] ]) - sorted_minors = reverse(sort(local.valid_minors)) - target_minor = local.sorted_minors[0] - base_minor = local.sorted_minors[1] + target_minor = local.valid_minors[0] + base_minor = local.valid_minors[1] } data "google_container_engine_versions" "base" { @@ -19755,9 +19750,8 @@ locals { for v in data.google_container_engine_versions.central1.valid_master_versions : regex("^[0-9]+\\.([0-9]+)\\.", v)[0] ]) - sorted_minors = reverse(sort(local.valid_minors)) - target_minor = local.sorted_minors[0] - base_minor = local.sorted_minors[1] + target_minor = local.valid_minors[0] + base_minor = local.valid_minors[1] } data "google_container_engine_versions" "base" { From e5faabf504ff115e80b26aeccc4cda115162bb95 Mon Sep 17 00:00:00 2001 From: Chinemerem Chigbo Date: Thu, 16 Jul 2026 17:25:49 +0000 Subject: [PATCH 17/20] Finalize declarative completion for rollback-safe upgrades - Moves CAI overrides to their correct alphabetical positions in meta.yaml.tmpl. - Optimizes rollback_safe_upgrade CAI exclusion to target the root block. - Adds missing state assertions to Standard and Autopilot tests. - Cleans up trailing blank lines and fixes minor schema/doc grammar typos. --- .../resource_container_cluster.go.tmpl | 4 +- .../resource_container_cluster_meta.yaml.tmpl | 12 +++--- .../resource_container_cluster_test.go.tmpl | 14 ++++++- .../docs/r/container_cluster.html.markdown | 42 ++++++++++++++++++- 4 files changed, 60 insertions(+), 12 deletions(-) diff --git a/mmv1/third_party/terraform/services/container/resource_container_cluster.go.tmpl b/mmv1/third_party/terraform/services/container/resource_container_cluster.go.tmpl index 20d87e224f82..0a8e5e6d377d 100644 --- a/mmv1/third_party/terraform/services/container/resource_container_cluster.go.tmpl +++ b/mmv1/third_party/terraform/services/container/resource_container_cluster.go.tmpl @@ -1239,7 +1239,7 @@ func ResourceContainerCluster() *schema.Resource { "control_plane_soak_duration": { Type: schema.TypeString, Optional: true, - Description: `A user-defined period for the cluster remains in the rollbackable state. A duration in seconds with up to nine fractional digits, ending with 's'. Example: "2592000s" for 30 days.`, + Description: `A user-defined period that the cluster remains in the rollbackable state. A duration in seconds with up to nine fractional digits, ending with 's'. Example: "604800s" for 7 days. Minimum is 6 hours, maximum is 7 days. If omitted, the two-step upgrade is skipped and a standard one-step upgrade is performed.`, }, }, }, @@ -1248,7 +1248,7 @@ func ResourceContainerCluster() *schema.Resource { Type: schema.TypeString, Optional: true, ValidateFunc: validation.StringMatch(regexp.MustCompile(`^[0-9]+\.[0-9]+$`), "desired_emulated_version must be in major.minor format"), - Description: "The desired emulated version for the cluster. Set this to complete a rollback-safe upgrade.", + Description: "The desired emulated version for the cluster. Used to complete a rollback-safe upgrade after a soak period. Must be in major.minor format (e.g., \"1.31\"). To complete the upgrade declaratively, set this field to the target minor version. Removing this field from your configuration will not trigger completion.", }, "emulated_version": { diff --git a/mmv1/third_party/terraform/services/container/resource_container_cluster_meta.yaml.tmpl b/mmv1/third_party/terraform/services/container/resource_container_cluster_meta.yaml.tmpl index 38c836e9e141..0979d0675da7 100644 --- a/mmv1/third_party/terraform/services/container/resource_container_cluster_meta.yaml.tmpl +++ b/mmv1/third_party/terraform/services/container/resource_container_cluster_meta.yaml.tmpl @@ -143,6 +143,8 @@ fields: - field: 'deletion_protection' provider_only: true - api_field: 'description' + - field: 'desired_emulated_version' + provider_only: true - field: 'dns_config.additive_vpc_scope_dns_domain' api_field: 'networkConfig.dnsConfig.additiveVpcScopeDnsDomain' - field: 'dns_config.cluster_dns' @@ -153,6 +155,8 @@ fields: api_field: 'networkConfig.dnsConfig.clusterDnsScope' - field: 'effective_labels' provider_only: true + - field: 'emulated_version' + api_field: 'currentEmulatedVersion' - field: 'enable_autopilot' api_field: 'autopilot.enabled' - field: 'enable_cilium_clusterwide_network_policy' @@ -904,6 +908,8 @@ fields: - api_field: 'resourceUsageExportConfig.enableNetworkEgressMetering' - field: 'resource_usage_export_config.enable_resource_consumption_metering' api_field: 'resourceUsageExportConfig.consumptionMeteringConfig.enabled' + - field: 'rollback_safe_upgrade.control_plane_soak_duration' + provider_only: true - api_field: 'secretManagerConfig.enabled' - api_field: 'secretManagerConfig.rotationConfig.enabled' - api_field: 'secretManagerConfig.rotationConfig.rotationInterval' @@ -949,9 +955,3 @@ fields: provider_only: true - field: 'ignore_node_count_changes' provider_only: true - - field: 'desired_emulated_version' - provider_only: true - - field: 'emulated_version' - api_field: 'currentEmulatedVersion' - - field: 'rollback_safe_upgrade.control_plane_soak_duration' - provider_only: true diff --git a/mmv1/third_party/terraform/services/container/resource_container_cluster_test.go.tmpl b/mmv1/third_party/terraform/services/container/resource_container_cluster_test.go.tmpl index c2cbe500d8ce..cd151df91ee4 100644 --- a/mmv1/third_party/terraform/services/container/resource_container_cluster_test.go.tmpl +++ b/mmv1/third_party/terraform/services/container/resource_container_cluster_test.go.tmpl @@ -19399,6 +19399,10 @@ func TestAccContainerCluster_desiredEmulatedVersion(t *testing.T) { // Step 2 (Soak Upgrade): Upgrade master to target version (RAPID latest) and activate soak { Config: testAccContainerCluster_desiredEmulatedVersionSoak(clusterName, networkName, subnetworkName), + Check: resource.ComposeTestCheckFunc( + // Assert that the computed emulated_version field is populated in state during soak + resource.TestCheckResourceAttrSet("google_container_cluster.primary", "emulated_version"), + ), }, { ResourceName: "google_container_cluster.primary", @@ -19410,6 +19414,8 @@ func TestAccContainerCluster_desiredEmulatedVersion(t *testing.T) { { Config: testAccContainerCluster_desiredEmulatedVersionComplete(clusterName, networkName, subnetworkName), Check: resource.ComposeTestCheckFunc( + // Assert that the computed emulated_version field is cleared in state after completion + resource.TestCheckResourceAttr("google_container_cluster.primary", "emulated_version", ""), testAccCheckContainerClusterEmulatedVersion(t, "google_container_cluster.primary"), ), }, @@ -19627,6 +19633,10 @@ func TestAccContainerCluster_desiredEmulatedVersionAutopilot(t *testing.T) { // Step 2 (Soak Upgrade): Upgrade regional master to target version (RAPID latest) with soak active { Config: testAccContainerCluster_desiredEmulatedVersionAutopilotSoak(clusterName, networkName, subnetworkName), + Check: resource.ComposeTestCheckFunc( + // Assert that the computed emulated_version field is populated in state during soak + resource.TestCheckResourceAttrSet("google_container_cluster.primary", "emulated_version"), + ), }, { ResourceName: "google_container_cluster.primary", @@ -19638,6 +19648,8 @@ func TestAccContainerCluster_desiredEmulatedVersionAutopilot(t *testing.T) { { Config: testAccContainerCluster_desiredEmulatedVersionAutopilotComplete(clusterName, networkName, subnetworkName), Check: resource.ComposeTestCheckFunc( + // Assert that the computed emulated_version field is cleared in state after completion + resource.TestCheckResourceAttr("google_container_cluster.primary", "emulated_version", ""), testAccCheckContainerClusterEmulatedVersion(t, "google_container_cluster.primary"), ), }, @@ -19786,5 +19798,3 @@ resource "google_container_cluster" "primary" { } `, clusterName, networkName, subnetworkName) } - - diff --git a/mmv1/third_party/terraform/website/docs/r/container_cluster.html.markdown b/mmv1/third_party/terraform/website/docs/r/container_cluster.html.markdown index 277b9cc2c99f..e78928f3aa18 100644 --- a/mmv1/third_party/terraform/website/docs/r/container_cluster.html.markdown +++ b/mmv1/third_party/terraform/website/docs/r/container_cluster.html.markdown @@ -95,6 +95,44 @@ resource "google_container_cluster" "primary" { } ``` +## Example Usage - Rollback-safe (Two-Step) Upgrades + +To perform a rollback-safe (two-step) control plane upgrade, you first specify a soak duration in the `rollback_safe_upgrade` block when changing the `min_master_version`. This upgrades the master but keeps the control plane emulating the older version. + +```hcl +resource "google_container_cluster" "primary" { + name = "my-gke-cluster" + location = "us-central1" + initial_node_count = 1 + min_master_version = "1.32.4-gke.200" # Upgrading to the 1.32 minor track + + # Phase 1: Explicitly opt-in to a rollback-safe upgrade + rollback_safe_upgrade { + control_plane_soak_duration = "604800s" # Soak for 7 days + } +} +``` + +After the soak period concludes, you can declaratively complete the upgrade by specifying the target `desired_emulated_version`. + +```hcl +resource "google_container_cluster" "primary" { + name = "my-gke-cluster" + location = "us-central1" + initial_node_count = 1 + min_master_version = "1.32.4-gke.200" + + rollback_safe_upgrade { + control_plane_soak_duration = "604800s" + } + + # Phase 2: Complete the upgrade (updates emulated_version to 1.32) + desired_emulated_version = "1.32" +} +``` + +~> **Note:** If you omit the `control_plane_soak_duration` field completely, GKE bypasses the two-step feature and performs a standard one-step upgrade. You must specify a duration between 6 hours and 7 days. + ## Argument Reference * `name` - (Required) The name of the cluster, unique within the project and @@ -1002,7 +1040,7 @@ Structure is [documented below](#nested_additional_ip_ranges_config). The `rollback_safe_upgrade` block supports: -* `control_plane_soak_duration` - (Optional) A user-defined period for the cluster remains in the rollbackable state. A duration in seconds with up to nine fractional digits, ending with 's'. Example: "259200s" for 3 days. +* `control_plane_soak_duration` - (Optional) A user-defined period that the cluster remains in the rollbackable state. A duration in seconds with up to nine fractional digits, ending with 's'. Example: "604800s" for 7 days. Minimum is 6 hours, maximum is 7 days. If omitted, the two-step upgrade is skipped and a standard one-step upgrade is performed. The `master_auth` block supports: @@ -2066,7 +2104,7 @@ exported: * `enterprise_config.0.cluster_tier` - The effective tier of the cluster. -* `emulated_version` - The current emulated version of the cluster. +* `emulated_version` - The current emulated Kubernetes version running on the GKE cluster control plane. ## Timeouts From 5e816e678850526aa2975712d9e4b94d66023988 Mon Sep 17 00:00:00 2001 From: Chinemerem Chigbo Date: Wed, 22 Jul 2026 16:31:41 +0000 Subject: [PATCH 18/20] Add ConfigPlanChecks for in-place update verification --- .../resource_container_cluster_test.go.tmpl | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/mmv1/third_party/terraform/services/container/resource_container_cluster_test.go.tmpl b/mmv1/third_party/terraform/services/container/resource_container_cluster_test.go.tmpl index cd151df91ee4..9ba563c19d2d 100644 --- a/mmv1/third_party/terraform/services/container/resource_container_cluster_test.go.tmpl +++ b/mmv1/third_party/terraform/services/container/resource_container_cluster_test.go.tmpl @@ -19399,6 +19399,11 @@ func TestAccContainerCluster_desiredEmulatedVersion(t *testing.T) { // Step 2 (Soak Upgrade): Upgrade master to target version (RAPID latest) and activate soak { Config: testAccContainerCluster_desiredEmulatedVersionSoak(clusterName, networkName, subnetworkName), + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectResourceAction("google_container_cluster.primary", plancheck.ResourceActionUpdate), + }, + }, Check: resource.ComposeTestCheckFunc( // Assert that the computed emulated_version field is populated in state during soak resource.TestCheckResourceAttrSet("google_container_cluster.primary", "emulated_version"), @@ -19413,6 +19418,11 @@ func TestAccContainerCluster_desiredEmulatedVersion(t *testing.T) { // Step 3 (Declarative Complete): Set desired_emulated_version to complete the upgrade { Config: testAccContainerCluster_desiredEmulatedVersionComplete(clusterName, networkName, subnetworkName), + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectResourceAction("google_container_cluster.primary", plancheck.ResourceActionUpdate), + }, + }, Check: resource.ComposeTestCheckFunc( // Assert that the computed emulated_version field is cleared in state after completion resource.TestCheckResourceAttr("google_container_cluster.primary", "emulated_version", ""), @@ -19633,6 +19643,11 @@ func TestAccContainerCluster_desiredEmulatedVersionAutopilot(t *testing.T) { // Step 2 (Soak Upgrade): Upgrade regional master to target version (RAPID latest) with soak active { Config: testAccContainerCluster_desiredEmulatedVersionAutopilotSoak(clusterName, networkName, subnetworkName), + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectResourceAction("google_container_cluster.primary", plancheck.ResourceActionUpdate), + }, + }, Check: resource.ComposeTestCheckFunc( // Assert that the computed emulated_version field is populated in state during soak resource.TestCheckResourceAttrSet("google_container_cluster.primary", "emulated_version"), @@ -19647,6 +19662,11 @@ func TestAccContainerCluster_desiredEmulatedVersionAutopilot(t *testing.T) { // Step 3 (Declarative Complete): Set desired_emulated_version to complete the upgrade { Config: testAccContainerCluster_desiredEmulatedVersionAutopilotComplete(clusterName, networkName, subnetworkName), + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectResourceAction("google_container_cluster.primary", plancheck.ResourceActionUpdate), + }, + }, Check: resource.ComposeTestCheckFunc( // Assert that the computed emulated_version field is cleared in state after completion resource.TestCheckResourceAttr("google_container_cluster.primary", "emulated_version", ""), From 10bb3af6656ec7ee767369c2359976fe9cd61f3f Mon Sep 17 00:00:00 2001 From: Chinemerem Chigbo Date: Wed, 22 Jul 2026 16:45:12 +0000 Subject: [PATCH 19/20] Fix missing closing brace --- .../services/container/resource_container_cluster_test.go.tmpl | 1 + 1 file changed, 1 insertion(+) diff --git a/mmv1/third_party/terraform/services/container/resource_container_cluster_test.go.tmpl b/mmv1/third_party/terraform/services/container/resource_container_cluster_test.go.tmpl index b219a9b4628b..da21832b9613 100644 --- a/mmv1/third_party/terraform/services/container/resource_container_cluster_test.go.tmpl +++ b/mmv1/third_party/terraform/services/container/resource_container_cluster_test.go.tmpl @@ -19888,3 +19888,4 @@ resource "google_container_cluster" "with_nrc_config" { } } `, clusterName, networkName, subnetworkName, enabled) +} From 69ab44075650f7259e8154af6c0e97a8a6ab7bcf Mon Sep 17 00:00:00 2001 From: Chinemerem Chigbo Date: Wed, 22 Jul 2026 23:15:00 +0000 Subject: [PATCH 20/20] Map rollback_safe_upgrade.control_plane_soak_duration to API field --- .../container/resource_container_cluster_meta.yaml.tmpl | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/mmv1/third_party/terraform/services/container/resource_container_cluster_meta.yaml.tmpl b/mmv1/third_party/terraform/services/container/resource_container_cluster_meta.yaml.tmpl index 57a317730202..69cc45517b19 100644 --- a/mmv1/third_party/terraform/services/container/resource_container_cluster_meta.yaml.tmpl +++ b/mmv1/third_party/terraform/services/container/resource_container_cluster_meta.yaml.tmpl @@ -909,8 +909,7 @@ fields: - api_field: 'resourceUsageExportConfig.enableNetworkEgressMetering' - field: 'resource_usage_export_config.enable_resource_consumption_metering' api_field: 'resourceUsageExportConfig.consumptionMeteringConfig.enabled' - - field: 'rollback_safe_upgrade.control_plane_soak_duration' - provider_only: true + - api_field: 'rollbackSafeUpgrade.controlPlaneSoakDuration' - api_field: 'secretManagerConfig.enabled' - api_field: 'secretManagerConfig.rotationConfig.enabled' - api_field: 'secretManagerConfig.rotationConfig.rotationInterval'