diff --git a/.changelog/6815.txt b/.changelog/6815.txt new file mode 100644 index 00000000000..4ab8407742c --- /dev/null +++ b/.changelog/6815.txt @@ -0,0 +1,6 @@ +```release-note:enhancement +container: added `auto_provisioning_defaults.cluster_autoscaling.upgrade_settings` in `google_container_cluster` +``` +```release-note:enhancement +container: promoted `min_cpu_platform` in `google_container_cluster` to GA +``` diff --git a/google/resource_container_cluster.go b/google/resource_container_cluster.go index f7a63a873a8..d249c737f82 100644 --- a/google/resource_container_cluster.go +++ b/google/resource_container_cluster.go @@ -171,6 +171,7 @@ func resourceContainerCluster() *schema.Resource { containerClusterAutopilotCustomizeDiff, containerClusterNodeVersionRemoveDefaultCustomizeDiff, containerClusterNetworkPolicyEmptyCustomizeDiff, + containerClusterSurgeSettingsCustomizeDiff, ), Timeouts: &schema.ResourceTimeout{ @@ -458,6 +459,12 @@ func resourceContainerCluster() *schema.Resource { DiffSuppressFunc: suppressDiffForAutopilot, ValidateFunc: validation.StringInSlice([]string{"COS_CONTAINERD", "COS", "UBUNTU_CONTAINERD", "UBUNTU"}, false), }, + "min_cpu_platform": { + Type: schema.TypeString, + Optional: true, + DiffSuppressFunc: emptyOrDefaultStringSuppress("automatic"), + Description: `Minimum CPU platform to be used by this instance. The instance may be scheduled on the specified or newer CPU platform. Applicable values are the friendly names of CPU platforms, such as Intel Haswell.`, + }, "boot_disk_kms_key": { Type: schema.TypeString, Optional: true, @@ -536,6 +543,93 @@ func resourceContainerCluster() *schema.Resource { }, }, }, + "upgrade_settings": { + Type: schema.TypeList, + Optional: true, + Description: `Specifies the upgrade settings for NAP created node pools`, + Computed: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "max_surge": { + Type: schema.TypeInt, + Optional: true, + Description: `The maximum number of nodes that can be created beyond the current size of the node pool during the upgrade process.`, + }, + "max_unavailable": { + Type: schema.TypeInt, + Optional: true, + Description: `The maximum number of nodes that can be simultaneously unavailable during the upgrade process.`, + }, + "strategy": { + Type: schema.TypeString, + Optional: true, + Computed: true, + Description: `Update strategy of the node pool.`, + ValidateFunc: validation.StringInSlice([]string{"NODE_POOL_UPDATE_STRATEGY_UNSPECIFIED", "BLUE_GREEN", "SURGE"}, false), + }, + "blue_green_settings": { + Type: schema.TypeList, + Optional: true, + Computed: true, + MaxItems: 1, + Description: `Settings for blue-green upgrade strategy.`, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "node_pool_soak_duration": { + Type: schema.TypeString, + Optional: true, + Computed: true, + Description: `Time needed after draining entire blue pool. After this period, blue pool will be cleaned up. + + A duration in seconds with up to nine fractional digits, ending with 's'. Example: "3.5s".`, + }, + "standard_rollout_policy": { + Type: schema.TypeList, + Optional: true, + Computed: true, + MaxItems: 1, + Description: `Standard policy for the blue-green upgrade.`, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "batch_percentage": { + Type: schema.TypeFloat, + Optional: true, + Computed: true, + ValidateFunc: validation.FloatBetween(0.0, 1.0), + ExactlyOneOf: []string{ + "cluster_autoscaling.0.auto_provisioning_defaults.0.upgrade_settings.0.blue_green_settings.0.standard_rollout_policy.0.batch_percentage", + "cluster_autoscaling.0.auto_provisioning_defaults.0.upgrade_settings.0.blue_green_settings.0.standard_rollout_policy.0.batch_node_count", + }, + Description: `Percentage of the bool pool nodes to drain in a batch. The range of this field should be (0.0, 1.0].`, + }, + "batch_node_count": { + Type: schema.TypeInt, + Optional: true, + Computed: true, + ExactlyOneOf: []string{ + "cluster_autoscaling.0.auto_provisioning_defaults.0.upgrade_settings.0.blue_green_settings.0.standard_rollout_policy.0.batch_percentage", + "cluster_autoscaling.0.auto_provisioning_defaults.0.upgrade_settings.0.blue_green_settings.0.standard_rollout_policy.0.batch_node_count", + }, + Description: `Number of blue nodes to drain in a batch.`, + }, + "batch_soak_duration": { + Type: schema.TypeString, + Optional: true, + Default: "0s", + Description: `Soak time after each batch gets drained. + + A duration in seconds with up to nine fractional digits, ending with 's'. Example: "3.5s".`, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, }, }, }, @@ -3448,13 +3542,14 @@ func expandAutoProvisioningDefaults(configured interface{}, d *schema.ResourceDa config := l[0].(map[string]interface{}) npd := &container.AutoprovisioningNodePoolDefaults{ - OauthScopes: convertStringArr(config["oauth_scopes"].([]interface{})), - ServiceAccount: config["service_account"].(string), - DiskSizeGb: int64(config["disk_size"].(int)), - DiskType: config["disk_type"].(string), - ImageType: config["image_type"].(string), - BootDiskKmsKey: config["boot_disk_kms_key"].(string), - Management: expandManagement(config["management"]), + OauthScopes: convertStringArr(config["oauth_scopes"].([]interface{})), + ServiceAccount: config["service_account"].(string), + DiskSizeGb: int64(config["disk_size"].(int)), + DiskType: config["disk_type"].(string), + ImageType: config["image_type"].(string), + BootDiskKmsKey: config["boot_disk_kms_key"].(string), + Management: expandManagement(config["management"]), + UpgradeSettings: expandUpgradeSettings(config["upgrade_settings"]), } if v, ok := config["shielded_instance_config"]; ok && len(v.([]interface{})) > 0 { @@ -3465,9 +3560,64 @@ func expandAutoProvisioningDefaults(configured interface{}, d *schema.ResourceDa } } + cpu := config["min_cpu_platform"].(string) + // the only way to unset the field is to pass "automatic" as its value + if cpu == "" { + cpu = "automatic" + } + npd.MinCpuPlatform = cpu + return npd } +func expandUpgradeSettings(configured interface{}) *container.UpgradeSettings { + l, ok := configured.([]interface{}) + if !ok || l == nil || len(l) == 0 || l[0] == nil { + return &container.UpgradeSettings{} + } + config := l[0].(map[string]interface{}) + + upgradeSettings := &container.UpgradeSettings{ + MaxSurge: int64(config["max_surge"].(int)), + MaxUnavailable: int64(config["max_unavailable"].(int)), + Strategy: config["strategy"].(string), + BlueGreenSettings: expandBlueGreenSettings(config["blue_green_settings"]), + } + + return upgradeSettings +} + +func expandBlueGreenSettings(configured interface{}) *container.BlueGreenSettings { + l, ok := configured.([]interface{}) + if !ok || l == nil || len(l) == 0 || l[0] == nil { + return &container.BlueGreenSettings{} + } + config := l[0].(map[string]interface{}) + + blueGreenSettings := &container.BlueGreenSettings{ + NodePoolSoakDuration: config["node_pool_soak_duration"].(string), + StandardRolloutPolicy: expandStandardRolloutPolicy(config["standard_rollout_policy"]), + } + + return blueGreenSettings +} + +func expandStandardRolloutPolicy(configured interface{}) *container.StandardRolloutPolicy { + l, ok := configured.([]interface{}) + if !ok || l == nil || len(l) == 0 || l[0] == nil { + return &container.StandardRolloutPolicy{} + } + + config := l[0].(map[string]interface{}) + standardRolloutPolicy := &container.StandardRolloutPolicy{ + BatchPercentage: config["batch_percentage"].(float64), + BatchNodeCount: int64(config["batch_node_count"].(int)), + BatchSoakDuration: config["batch_soak_duration"].(string), + } + + return standardRolloutPolicy +} + func expandManagement(configured interface{}) *container.NodeManagement { l, ok := configured.([]interface{}) if !ok || l == nil || len(l) == 0 || l[0] == nil { @@ -4304,9 +4454,49 @@ func flattenAutoProvisioningDefaults(a *container.AutoprovisioningNodePoolDefaul r["disk_size"] = a.DiskSizeGb r["disk_type"] = a.DiskType r["image_type"] = a.ImageType + r["min_cpu_platform"] = a.MinCpuPlatform r["boot_disk_kms_key"] = a.BootDiskKmsKey r["shielded_instance_config"] = flattenShieldedInstanceConfig(a.ShieldedInstanceConfig) r["management"] = flattenManagement(a.Management) + r["upgrade_settings"] = flattenUpgradeSettings(a.UpgradeSettings) + + return []map[string]interface{}{r} +} + +func flattenUpgradeSettings(a *container.UpgradeSettings) []map[string]interface{} { + if a == nil { + return nil + } + r := make(map[string]interface{}) + r["max_surge"] = a.MaxSurge + r["max_unavailable"] = a.MaxUnavailable + r["strategy"] = a.Strategy + r["blue_green_settings"] = flattenBlueGreenSettings(a.BlueGreenSettings) + + return []map[string]interface{}{r} +} + +func flattenBlueGreenSettings(a *container.BlueGreenSettings) []map[string]interface{} { + if a == nil { + return nil + } + + r := make(map[string]interface{}) + r["node_pool_soak_duration"] = a.NodePoolSoakDuration + r["standard_rollout_policy"] = flattenStandardRolloutPolicy(a.StandardRolloutPolicy) + + return []map[string]interface{}{r} +} + +func flattenStandardRolloutPolicy(a *container.StandardRolloutPolicy) []map[string]interface{} { + if a == nil { + return nil + } + + r := make(map[string]interface{}) + r["batch_percentage"] = a.BatchPercentage + r["batch_node_count"] = a.BatchNodeCount + r["batch_soak_duration"] = a.BatchSoakDuration return []map[string]interface{}{r} } @@ -4667,3 +4857,20 @@ func BinaryAuthorizationDiffSuppress(k, old, new string, r *schema.ResourceData) return false } + +func containerClusterSurgeSettingsCustomizeDiff(_ context.Context, d *schema.ResourceDiff, meta interface{}) error { + if v, ok := d.GetOk("cluster_autoscaling.0.auto_provisioning_defaults.0.upgrade_settings.0.strategy"); ok { + if v != "SURGE" { + if _, maxSurgeIsPresent := d.GetOk("cluster_autoscaling.0.auto_provisioning_defaults.0.upgrade_settings.0.max_surge"); maxSurgeIsPresent { + return fmt.Errorf("Surge upgrade settings max_surge/max_unavailable can only be used when strategy is set to SURGE") + } + } + if v != "SURGE" { + if _, maxSurgeIsPresent := d.GetOk("cluster_autoscaling.0.auto_provisioning_defaults.0.upgrade_settings.0.max_unavailable"); maxSurgeIsPresent { + return fmt.Errorf("Surge upgrade settings max_surge/max_unavailable can only be used when strategy is set to SURGE") + } + } + } + + return nil +} diff --git a/google/resource_container_cluster_test.go b/google/resource_container_cluster_test.go index ce3f7a9e831..4a979d57f22 100644 --- a/google/resource_container_cluster_test.go +++ b/google/resource_container_cluster_test.go @@ -1986,6 +1986,7 @@ func TestAccContainerCluster_nodeAutoprovisioningDefaults(t *testing.T) { t.Parallel() clusterName := fmt.Sprintf("tf-test-cluster-%s", randString(t, 10)) + includeMinCpuPlatform := true vcrTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -2010,6 +2011,58 @@ func TestAccContainerCluster_nodeAutoprovisioningDefaults(t *testing.T) { PlanOnly: true, ExpectNonEmptyPlan: false, }, + { + Config: testAccContainerCluster_autoprovisioningDefaultsMinCpuPlatform(clusterName, includeMinCpuPlatform), + }, + { + ResourceName: "google_container_cluster.with_autoprovisioning", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"min_master_version"}, + }, + { + Config: testAccContainerCluster_autoprovisioningDefaultsMinCpuPlatform(clusterName, !includeMinCpuPlatform), + }, + { + ResourceName: "google_container_cluster.with_autoprovisioning", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"min_master_version"}, + }, + }, + }) +} + +func TestAccContainerCluster_autoprovisioningDefaultsUpgradeSettings(t *testing.T) { + t.Parallel() + + clusterName := fmt.Sprintf("tf-test-cluster-%s", randString(t, 10)) + + vcrTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckContainerClusterDestroyProducer(t), + Steps: []resource.TestStep{ + { + Config: testAccContainerCluster_autoprovisioningDefaultsUpgradeSettings(clusterName, 2, 1, "SURGE"), + }, + { + ResourceName: "google_container_cluster.with_autoprovisioning_upgrade_settings", + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccContainerCluster_autoprovisioningDefaultsUpgradeSettings(clusterName, 2, 1, "BLUE_GREEN"), + ExpectError: regexp.MustCompile(`Surge upgrade settings max_surge/max_unavailable can only be used when strategy is set to SURGE`), + }, + { + Config: testAccContainerCluster_autoprovisioningDefaultsUpgradeSettingsWithBlueGreenStrategy(clusterName, "3.500s", "BLUE_GREEN"), + }, + { + ResourceName: "google_container_cluster.with_autoprovisioning_upgrade_settings", + ImportState: true, + ImportStateVerify: true, + }, }, }) } @@ -4502,6 +4555,126 @@ resource "google_container_cluster" "with_autoprovisioning" { return config } +func testAccContainerCluster_autoprovisioningDefaultsMinCpuPlatform(cluster string, includeMinCpuPlatform bool) string { + minCpuPlatformCfg := "" + if includeMinCpuPlatform { + minCpuPlatformCfg = `min_cpu_platform = "Intel Haswell"` + } + + return fmt.Sprintf(` +data "google_container_engine_versions" "central1a" { + location = "us-central1-a" +} + +resource "google_container_cluster" "with_autoprovisioning" { + name = "%s" + location = "us-central1-a" + initial_node_count = 1 + + min_master_version = data.google_container_engine_versions.central1a.latest_master_version + + cluster_autoscaling { + enabled = true + + resource_limits { + resource_type = "cpu" + maximum = 2 + } + resource_limits { + resource_type = "memory" + maximum = 2048 + } + + auto_provisioning_defaults { + %s + } + } +}`, cluster, minCpuPlatformCfg) +} + +func testAccContainerCluster_autoprovisioningDefaultsUpgradeSettings(clusterName string, maxSurge, maxUnavailable int, strategy string) string { + blueGreenSettings := "" + if strategy == "BLUE_GREEN" { + blueGreenSettings = ` + blue_green_settings { + node_pool_soak_duration = "3.500s" + standard_rollout_policy { + batch_percentage = 0.5 + batch_soak_duration = "3.500s" + } + } + ` + } + + return fmt.Sprintf(` + resource "google_container_cluster" "with_autoprovisioning_upgrade_settings" { + name = "%s" + location = "us-central1-f" + initial_node_count = 1 + + cluster_autoscaling { + enabled = true + + resource_limits { + resource_type = "cpu" + maximum = 2 + } + + resource_limits { + resource_type = "memory" + maximum = 2048 + } + + auto_provisioning_defaults { + upgrade_settings { + max_surge = %d + max_unavailable = %d + strategy = "%s" + %s + } + } + } + } + `, clusterName, maxSurge, maxUnavailable, strategy, blueGreenSettings) +} + +func testAccContainerCluster_autoprovisioningDefaultsUpgradeSettingsWithBlueGreenStrategy(clusterName string, duration, strategy string) string { + return fmt.Sprintf(` + resource "google_container_cluster" "with_autoprovisioning_upgrade_settings" { + name = "%s" + location = "us-central1-f" + initial_node_count = 1 + + cluster_autoscaling { + enabled = true + + resource_limits { + resource_type = "cpu" + maximum = 2 + } + + resource_limits { + resource_type = "memory" + maximum = 2048 + } + + auto_provisioning_defaults { + upgrade_settings { + strategy = "%s" + blue_green_settings { + node_pool_soak_duration = "%s" + standard_rollout_policy { + batch_percentage = 0.5 + batch_soak_duration = "%s" + } + } + } + } + } + } + `, clusterName, strategy, duration, duration) +} + func testAccContainerCluster_autoprovisioningDefaultsDiskSizeGb(cluster string, includeDiskSizeGb bool) string { DiskSizeGbCfg := "" if includeDiskSizeGb { diff --git a/website/docs/r/container_cluster.html.markdown b/website/docs/r/container_cluster.html.markdown index 1cc428c3ddd..82774661248 100644 --- a/website/docs/r/container_cluster.html.markdown +++ b/website/docs/r/container_cluster.html.markdown @@ -527,6 +527,32 @@ as "Intel Haswell" or "Intel Sandy Bridge". This block also contains several computed attributes, documented below. +* `upgrade_settings` - (Optional) Specifies the upgrade settings for NAP created node pools. Structure is [documented below](#nested_upgrade_settings). + +The `upgrade_settings` block supports: + +* `strategy` - (Optional) Strategy used for node pool update. Strategy can only be one of BLUE_GREEN or SURGE. The default is value is SURGE. + +* `max_surge` - (Optional) The maximum number of nodes that can be created beyond the current size of the node pool during the upgrade process. To be used when strategy is set to SURGE. Default is 0. + +* `max_unavailable` - (Optional) The maximum number of nodes that can be simultaneously unavailable during the upgrade process. To be used when strategy is set to SURGE. Default is 0. + +* `blue_green_settings` - (Optional) Settings for blue-green upgrade strategy. To be specified when strategy is set to BLUE_GREEN. Structure is [documented below](#nested_blue_green_settings). + +The `blue_green_settings` block supports: + +* `node_pool_soak_duration` - (Optional) Time needed after draining entire blue pool. After this period, blue pool will be cleaned up. A duration in seconds with up to nine fractional digits, ending with 's'. Example: "3.5s". + +* `standard_rollout_policy`: (Optional) Standard policy for the blue-green upgrade. To be specified when strategy is set to BLUE_GREEN. Structure is [documented below](#nested_standard_rollout_policy). + +The `standard_rollout_policy` block supports: + +* `batch_percentage`: (Optional) Percentage of the bool pool nodes to drain in a batch. The range of this field should be (0.0, 1.0). Only one of the batch_percentage or batch_node_count can be specified. + +* `batch_node_count` - (Optional) Number of blue nodes to drain in a batch. Only one of the batch_percentage or batch_node_count can be specified. + +* `batch_soak_duration` - (Optional) Soak time after each batch gets drained. A duration in seconds with up to nine fractional digits, ending with 's'. Example: "3.5s".`. + The `authenticator_groups_config` block supports: * `security_group` - (Required) The name of the RBAC security group for use with Google security groups in Kubernetes RBAC. Group name must be in format `gke-security-groups@yourdomain.com`.