Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions mmv1/third_party/terraform/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -132,3 +132,5 @@ require (
google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9 // 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
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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)
}
Expand Down Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18210,3 +18210,252 @@ 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)

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 STABLE default
{
Config: testAccContainerCluster_desiredEmulatedVersionBase(clusterName, networkName, subnetworkName),
},
{
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 (RAPID latest) and activate soak
{
Config: testAccContainerCluster_desiredEmulatedVersionSoak(clusterName, networkName, subnetworkName),
},
{
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 complete the upgrade
{
Config: testAccContainerCluster_desiredEmulatedVersionComplete(clusterName, networkName, subnetworkName),
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", "desired_emulated_version"},
},
},
})
}

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 string) string {
return fmt.Sprintf(`
data "google_container_engine_versions" "central1" {
location = "us-central1"
}
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"]
deletion_protection = false
network = "%s"
subnetwork = "%s"

node_config {
machine_type = "e2-standard-2"
}
}
`, clusterName, networkName, subnetworkName)
}

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"
initial_node_count = 1
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, networkName, subnetworkName)
}

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"
initial_node_count = 1
min_master_version = data.google_container_engine_versions.central1.release_channel_latest_version["RAPID"]
deletion_protection = false
network = "%s"
subnetwork = "%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, networkName, subnetworkName)
}

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)

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 STABLE default
{
Config: testAccContainerCluster_desiredEmulatedVersionAutopilotBase(clusterName, networkName, subnetworkName),
},
{
ResourceName: "google_container_cluster.primary",
ImportState: true,
ImportStateVerify: true,
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
{
Config: testAccContainerCluster_desiredEmulatedVersionAutopilotSoak(clusterName, networkName, subnetworkName),
},
{
ResourceName: "google_container_cluster.primary",
ImportState: true,
ImportStateVerify: false,
},
// Step 3 (Declarative Complete): Set desired_emulated_version to complete the upgrade
{
Config: testAccContainerCluster_desiredEmulatedVersionAutopilotComplete(clusterName, networkName, subnetworkName),
Check: resource.ComposeTestCheckFunc(
testAccCheckContainerClusterEmulatedVersion(t, "google_container_cluster.primary"),
),
},
{
ResourceName: "google_container_cluster.primary",
ImportState: true,
ImportStateVerify: false,
},
},
})
}

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 = data.google_container_engine_versions.central1.release_channel_default_version["STABLE"]
deletion_protection = false
network = "%s"
subnetwork = "%s"
ip_allocation_policy {}
}
`, clusterName, networkName, subnetworkName)
}

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 = data.google_container_engine_versions.central1.release_channel_latest_version["RAPID"]
deletion_protection = false
network = "%s"
subnetwork = "%s"
ip_allocation_policy {}
}
`, clusterName, networkName, subnetworkName)
}

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 = data.google_container_engine_versions.central1.release_channel_latest_version["RAPID"]
deletion_protection = false
network = "%s"
subnetwork = "%s"
ip_allocation_policy {}

desired_emulated_version = regex("^[0-9]+\\.[0-9]+", data.google_container_engine_versions.central1.release_channel_latest_version["RAPID"])
}
`, clusterName, networkName, subnetworkName)
}


Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,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).

Expand Down