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 be86462333c0..500e87c18805 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 @@ -1205,6 +1205,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, @@ -3584,6 +3599,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) } @@ -4546,6 +4566,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 189b2b5ef654..0a2830c46d20 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 @@ -18210,3 +18210,153 @@ func testAccContainerCluster_custom_subnet(clusterName string, networkName strin `, clusterName, networkName, sri[0].SubnetName, additionalIpRangesStr, firstSubnet, firstSubnet) } + +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) +}