diff --git a/.changelog/8622.txt b/.changelog/8622.txt new file mode 100644 index 00000000000..31328a282e7 --- /dev/null +++ b/.changelog/8622.txt @@ -0,0 +1,3 @@ +```release-note:enhancement +container: added `additional_pod_ranges_config` field to `google_container_cluster` resource +``` diff --git a/google/services/container/resource_container_cluster.go b/google/services/container/resource_container_cluster.go index 20360c16e9a..86f4df9284e 100644 --- a/google/services/container/resource_container_cluster.go +++ b/google/services/container/resource_container_cluster.go @@ -1369,6 +1369,23 @@ func ResourceContainerCluster() *schema.Resource { }, }, }, + "additional_pod_ranges_config": { + Type: schema.TypeList, + MaxItems: 1, + Optional: true, + Description: `AdditionalPodRangesConfig is the configuration for additional pod secondary ranges supporting the ClusterUpdate message.`, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "pod_range_names": { + Type: schema.TypeSet, + MinItems: 1, + Required: true, + Elem: &schema.Schema{Type: schema.TypeString}, + Description: `Name for pod secondary ipv4 range which has the actual range defined ahead.`, + }, + }, + }, + }, }, }, }, @@ -2149,6 +2166,38 @@ func resourceContainerClusterCreate(d *schema.ResourceData, meta interface{}) er } } + if names, ok := d.GetOk("ip_allocation_policy.0.additional_pod_ranges_config.0.pod_range_names"); ok { + name := containerClusterFullName(project, location, clusterName) + additionalPodRangesConfig := &container.AdditionalPodRangesConfig{ + PodRangeNames: tpgresource.ConvertStringSet(names.(*schema.Set)), + } + + req := &container.UpdateClusterRequest{ + Update: &container.ClusterUpdate{ + AdditionalPodRangesConfig: additionalPodRangesConfig, + }, + } + + err = transport_tpg.Retry(transport_tpg.RetryOptions{ + RetryFunc: func() error { + clusterUpdateCall := config.NewContainerClient(userAgent).Projects.Locations.Clusters.Update(name, req) + if config.UserProjectOverride { + clusterUpdateCall.Header().Add("X-Goog-User-Project", project) + } + op, err = clusterUpdateCall.Do() + return err + }, + }) + if err != nil { + return errwrap.Wrapf("Error updating AdditionalPodRangesConfig: {{err}}", err) + } + + err = ContainerOperationWait(config, op, project, location, "updating AdditionalPodRangesConfig", userAgent, d.Timeout(schema.TimeoutCreate)) + if err != nil { + return errwrap.Wrapf("Error while waiting to update AdditionalPodRangesConfig: {{err}}", err) + } + } + if err := resourceContainerClusterRead(d, meta); err != nil { return err } @@ -3038,6 +3087,51 @@ func resourceContainerClusterUpdate(d *schema.ResourceData, meta interface{}) er } + if d.HasChange("ip_allocation_policy.0.additional_pod_ranges_config") { + o, n := d.GetChange("ip_allocation_policy.0.additional_pod_ranges_config.0.pod_range_names") + old_names := o.(*schema.Set) + new_names := n.(*schema.Set) + + // Filter unchanged names. + removed_names := old_names.Difference(new_names) + added_names := new_names.Difference(old_names) + + var additional_config *container.AdditionalPodRangesConfig + var removed_config *container.AdditionalPodRangesConfig + if added_names.Len() > 0 { + var names []string + for _, name := range added_names.List() { + names = append(names, name.(string)) + } + additional_config = &container.AdditionalPodRangesConfig{ + PodRangeNames: names, + } + } + if removed_names.Len() > 0 { + var names []string + for _, name := range removed_names.List() { + names = append(names, name.(string)) + } + removed_config = &container.AdditionalPodRangesConfig{ + PodRangeNames: names, + } + } + req := &container.UpdateClusterRequest{ + Update: &container.ClusterUpdate{ + AdditionalPodRangesConfig: additional_config, + RemovedAdditionalPodRangesConfig: removed_config, + }, + } + + updateF := updateFunc(req, "updating AdditionalPodRangesConfig") + // Call update serially. + if err := transport_tpg.LockedCall(lockKey, updateF); err != nil { + return err + } + + log.Printf("[INFO] GKE cluster %s's AdditionalPodRangesConfig has been updated", d.Id()) + } + if n, ok := d.GetOk("node_pool.#"); ok { for i := 0; i < n.(int); i++ { nodePoolInfo, err := extractNodePoolInformationFromCluster(d, config, clusterName) @@ -4108,6 +4202,25 @@ func flattenSecurityPostureConfig(spc *container.SecurityPostureConfig) []map[st return []map[string]interface{}{result} } +func flattenAdditionalPodRangesConfig(ipAllocationPolicy *container.IPAllocationPolicy) []map[string]interface{} { + if ipAllocationPolicy == nil { + return nil + } + result := make(map[string]interface{}) + + if aprc := ipAllocationPolicy.AdditionalPodRangesConfig; aprc != nil { + if len(aprc.PodRangeNames) > 0 { + result["pod_range_names"] = aprc.PodRangeNames + } else { + return nil + } + } else { + return nil + } + + return []map[string]interface{}{result} +} + func expandNotificationConfig(configured interface{}) *container.NotificationConfig { l := configured.([]interface{}) if len(l) == 0 || l[0] == nil { @@ -4878,6 +4991,7 @@ func flattenIPAllocationPolicy(c *container.Cluster, d *schema.ResourceData, con "services_secondary_range_name": p.ServicesSecondaryRangeName, "stack_type": p.StackType, "pod_cidr_overprovision_config": flattenPodCidrOverprovisionConfig(p.PodCidrOverprovisionConfig), + "additional_pod_ranges_config": flattenAdditionalPodRangesConfig(c.IpAllocationPolicy), }, }, nil } diff --git a/google/services/container/resource_container_cluster_test.go b/google/services/container/resource_container_cluster_test.go index 6c448c3c78b..f218b90db76 100644 --- a/google/services/container/resource_container_cluster_test.go +++ b/google/services/container/resource_container_cluster_test.go @@ -3267,6 +3267,80 @@ func TestAccContainerCluster_autopilot_net_admin(t *testing.T) { }) } +func TestAccContainerCluster_additional_pod_ranges_config_on_create(t *testing.T) { + t.Parallel() + + clusterName := fmt.Sprintf("tf-test-cluster-%s", acctest.RandString(t, 10)) + acctest.VcrTest(t, resource.TestCase{ + PreCheck: func() { acctest.AccTestPreCheck(t) }, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), + CheckDestroy: testAccCheckContainerClusterDestroyProducer(t), + Steps: []resource.TestStep{ + { + Config: testAccContainerCluster_additional_pod_ranges_config(clusterName, 1), + }, + { + ResourceName: "google_container_cluster.primary", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccContainerCluster_additional_pod_ranges_config_on_update(t *testing.T) { + t.Parallel() + + clusterName := fmt.Sprintf("tf-test-cluster-%s", acctest.RandString(t, 10)) + acctest.VcrTest(t, resource.TestCase{ + PreCheck: func() { acctest.AccTestPreCheck(t) }, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), + CheckDestroy: testAccCheckContainerClusterDestroyProducer(t), + Steps: []resource.TestStep{ + { + Config: testAccContainerCluster_additional_pod_ranges_config(clusterName, 0), + }, + { + ResourceName: "google_container_cluster.primary", + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccContainerCluster_additional_pod_ranges_config(clusterName, 2), + }, + { + ResourceName: "google_container_cluster.primary", + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccContainerCluster_additional_pod_ranges_config(clusterName, 0), + }, + { + ResourceName: "google_container_cluster.primary", + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccContainerCluster_additional_pod_ranges_config(clusterName, 1), + }, + { + ResourceName: "google_container_cluster.primary", + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccContainerCluster_additional_pod_ranges_config(clusterName, 0), + }, + { + ResourceName: "google_container_cluster.primary", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + func testAccContainerCluster_masterAuthorizedNetworksDisabled(t *testing.T, resource_name string) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[resource_name] @@ -6858,3 +6932,83 @@ resource "google_container_cluster" "cluster" { } }`, policyName, cluster, np) } + +func testAccContainerCluster_additional_pod_ranges_config(name string, nameCount int) string { + var podRangeNamesStr string + names := []string{"\"gke-autopilot-pods-add\",", "\"gke-autopilot-pods-add-2\""} + for i := 0; i < nameCount; i++ { + podRangeNamesStr += names[i] + } + var aprc string + if len(podRangeNamesStr) > 0 { + aprc = fmt.Sprintf(` + additional_pod_ranges_config { + pod_range_names = [%s] + } + `, podRangeNamesStr) + } + + return fmt.Sprintf(` + resource "google_compute_network" "main" { + name = "%s" + auto_create_subnetworks = false + } + resource "google_compute_subnetwork" "main" { + ip_cidr_range = "10.10.0.0/16" + name = "%s" + network = google_compute_network.main.self_link + region = "us-central1" + + secondary_ip_range { + range_name = "gke-autopilot-services" + ip_cidr_range = "10.11.0.0/20" + } + + secondary_ip_range { + range_name = "gke-autopilot-pods" + ip_cidr_range = "10.12.0.0/16" + } + + secondary_ip_range { + range_name = "gke-autopilot-pods-add" + ip_cidr_range = "10.100.0.0/16" + } + secondary_ip_range { + range_name = "gke-autopilot-pods-add-2" + ip_cidr_range = "100.0.0.0/16" + } + } + resource "google_container_cluster" "primary" { + name = "%s" + location = "us-central1" + + enable_autopilot = true + + release_channel { + channel = "REGULAR" + } + + network = google_compute_network.main.name + subnetwork = google_compute_subnetwork.main.name + + private_cluster_config { + enable_private_endpoint = false + enable_private_nodes = true + master_ipv4_cidr_block = "172.16.0.0/28" + } + + # supresses permadiff + dns_config { + cluster_dns = "CLOUD_DNS" + cluster_dns_domain = "cluster.local" + cluster_dns_scope = "CLUSTER_SCOPE" + } + + ip_allocation_policy { + cluster_secondary_range_name = "gke-autopilot-pods" + services_secondary_range_name = "gke-autopilot-services" + %s + } + } + `, name, name, name, aprc) +} diff --git a/website/docs/r/container_cluster.html.markdown b/website/docs/r/container_cluster.html.markdown index d7fd8ca8d93..72888a221da 100644 --- a/website/docs/r/container_cluster.html.markdown +++ b/website/docs/r/container_cluster.html.markdown @@ -723,6 +723,16 @@ pick a specific range to use. Default value is `IPV4`. Possible values are `IPV4` and `IPV4_IPV6`. +* `additional_pod_ranges_config` - (Optional) The configuration for additional pod secondary ranges at +the cluster level. Used for Autopilot clusters and Standard clusters with which control of the +secondary Pod IP address assignment to node pools isn't needed. Structure is [documented below](#nested_additional_pod_ranges_config). + + +The `additional_pod_ranges_config` block supports: + +* `pod_range_names` - (Required) The names of the Pod ranges to add to the cluster. + + The `master_auth` block supports: * `client_certificate_config` - (Required) Whether client certificate authorization is enabled for this cluster. For example: