diff --git a/google/resource_container_cluster.go b/google/resource_container_cluster.go index f841178d..31856d25 100644 --- a/google/resource_container_cluster.go +++ b/google/resource_container_cluster.go @@ -42,6 +42,10 @@ var ( }, }, } + + ipAllocationSubnetFields = []string{"ip_allocation_policy.0.create_subnetwork", "ip_allocation_policy.0.subnetwork_name"} + ipAllocationCidrBlockFields = []string{"ip_allocation_policy.0.cluster_ipv4_cidr_block", "ip_allocation_policy.0.services_ipv4_cidr_block"} + ipAllocationRangeFields = []string{"ip_allocation_policy.0.cluster_secondary_range_name", "ip_allocation_policy.0.services_secondary_range_name"} ) func resourceContainerCluster() *schema.Resource { @@ -433,15 +437,52 @@ func resourceContainerCluster() *schema.Resource { MaxItems: 1, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ + // GKE creates subnetwork automatically + "create_subnetwork": { + Type: schema.TypeBool, + Optional: true, + ForceNew: true, + ConflictsWith: append(ipAllocationCidrBlockFields, ipAllocationRangeFields...), + }, + "subnetwork_name": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + ConflictsWith: append(ipAllocationCidrBlockFields, ipAllocationRangeFields...), + }, + + // GKE creates/deletes secondary ranges in VPC + "cluster_ipv4_cidr_block": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + ConflictsWith: append(ipAllocationSubnetFields, ipAllocationRangeFields...), + DiffSuppressFunc: cidrOrSizeDiffSuppress, + }, + "services_ipv4_cidr_block": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + ConflictsWith: append(ipAllocationSubnetFields, ipAllocationRangeFields...), + DiffSuppressFunc: cidrOrSizeDiffSuppress, + }, + + // User manages secondary ranges manually "cluster_secondary_range_name": { - Type: schema.TypeString, - Optional: true, - ForceNew: true, + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + ConflictsWith: append(ipAllocationSubnetFields, ipAllocationCidrBlockFields...), }, "services_secondary_range_name": { - Type: schema.TypeString, - Optional: true, - ForceNew: true, + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + ConflictsWith: append(ipAllocationSubnetFields, ipAllocationCidrBlockFields...), }, }, }, @@ -475,6 +516,11 @@ func resourceContainerCluster() *schema.Resource { } } +func cidrOrSizeDiffSuppress(k, old, new string, d *schema.ResourceData) bool { + // If the user specified a size and the API returned a full cidr block, suppress. + return strings.HasPrefix(new, "/") && strings.HasSuffix(old, new) +} + func resourceContainerClusterCreate(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) @@ -1409,24 +1455,24 @@ func expandClusterAddonsConfig(configured interface{}) *containerBeta.AddonsConf } func expandIPAllocationPolicy(configured interface{}) (*containerBeta.IPAllocationPolicy, error) { - ap := &containerBeta.IPAllocationPolicy{} l := configured.([]interface{}) - if len(l) > 0 { - if config, ok := l[0].(map[string]interface{}); ok { - ap.UseIpAliases = true - if v, ok := config["cluster_secondary_range_name"]; ok { - ap.ClusterSecondaryRangeName = v.(string) - } - - if v, ok := config["services_secondary_range_name"]; ok { - ap.ServicesSecondaryRangeName = v.(string) - } - } else { - return nil, fmt.Errorf("clusters using IP aliases must specify secondary ranges") - } + if len(l) == 0 { + return &containerBeta.IPAllocationPolicy{}, nil } + config := l[0].(map[string]interface{}) - return ap, nil + return &containerBeta.IPAllocationPolicy{ + UseIpAliases: true, + + CreateSubnetwork: config["create_subnetwork"].(bool), + SubnetworkName: config["subnetwork_name"].(string), + + ClusterIpv4CidrBlock: config["cluster_ipv4_cidr_block"].(string), + ServicesIpv4CidrBlock: config["services_ipv4_cidr_block"].(string), + + ClusterSecondaryRangeName: config["cluster_secondary_range_name"].(string), + ServicesSecondaryRangeName: config["services_secondary_range_name"].(string), + }, nil } func expandMaintenancePolicy(configured interface{}) *containerBeta.MaintenancePolicy { @@ -1583,6 +1629,12 @@ func flattenIPAllocationPolicy(c *containerBeta.IPAllocationPolicy) []map[string } return []map[string]interface{}{ { + "create_subnetwork": c.CreateSubnetwork, + "subnetwork_name": c.SubnetworkName, + + "cluster_ipv4_cidr_block": c.ClusterIpv4CidrBlock, + "services_ipv4_cidr_block": c.ServicesIpv4CidrBlock, + "cluster_secondary_range_name": c.ClusterSecondaryRangeName, "services_secondary_range_name": c.ServicesSecondaryRangeName, }, diff --git a/google/resource_container_cluster_test.go b/google/resource_container_cluster_test.go index 3566fb9f..2cc4615d 100644 --- a/google/resource_container_cluster_test.go +++ b/google/resource_container_cluster_test.go @@ -1093,7 +1093,7 @@ func TestAccContainerCluster_withMaintenanceWindow(t *testing.T) { }) } -func TestAccContainerCluster_withIPAllocationPolicy(t *testing.T) { +func TestAccContainerCluster_withIPAllocationPolicy_existingSecondaryRanges(t *testing.T) { t.Parallel() cluster := fmt.Sprintf("cluster-test-%s", acctest.RandString(10)) @@ -1103,23 +1103,7 @@ func TestAccContainerCluster_withIPAllocationPolicy(t *testing.T) { CheckDestroy: testAccCheckContainerClusterDestroy, Steps: []resource.TestStep{ { - Config: testAccContainerCluster_withIPAllocationPolicy( - cluster, - map[string]string{ - "pods": "10.1.0.0/16", - "services": "10.2.0.0/20", - }, - map[string]string{ - "cluster_secondary_range_name": "pods", - "services_secondary_range_name": "services", - }, - ), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr("google_container_cluster.with_ip_allocation_policy", - "ip_allocation_policy.0.cluster_secondary_range_name", "pods"), - resource.TestCheckResourceAttr("google_container_cluster.with_ip_allocation_policy", - "ip_allocation_policy.0.services_secondary_range_name", "services"), - ), + Config: testAccContainerCluster_withIPAllocationPolicy_existingSecondaryRanges(cluster), }, { ResourceName: "google_container_cluster.with_ip_allocation_policy", @@ -1127,29 +1111,71 @@ func TestAccContainerCluster_withIPAllocationPolicy(t *testing.T) { ImportState: true, ImportStateVerify: true, }, + }, + }) +} + +func TestAccContainerCluster_withIPAllocationPolicy_specificIPRanges(t *testing.T) { + t.Parallel() + + cluster := fmt.Sprintf("cluster-test-%s", acctest.RandString(10)) + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckContainerClusterDestroy, + Steps: []resource.TestStep{ { - Config: testAccContainerCluster_withIPAllocationPolicy( - cluster, - map[string]string{ - "pods": "10.1.0.0/16", - "services": "10.2.0.0/20", - }, - map[string]string{}, - ), - ExpectError: regexp.MustCompile("clusters using IP aliases must specify secondary ranges"), + Config: testAccContainerCluster_withIPAllocationPolicy_specificIPRanges(cluster), }, { - Config: testAccContainerCluster_withIPAllocationPolicy( - cluster, - map[string]string{ - "pods": "10.1.0.0/16", - }, - map[string]string{ - "cluster_secondary_range_name": "pods", - "services_secondary_range_name": "services", - }, - ), - ExpectError: regexp.MustCompile("secondary range \"services\" does not exist in network"), + ResourceName: "google_container_cluster.with_ip_allocation_policy", + ImportStateIdPrefix: "us-central1-a/", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccContainerCluster_withIPAllocationPolicy_specificSizes(t *testing.T) { + t.Parallel() + + cluster := fmt.Sprintf("cluster-test-%s", acctest.RandString(10)) + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckContainerClusterDestroy, + Steps: []resource.TestStep{ + { + Config: testAccContainerCluster_withIPAllocationPolicy_specificSizes(cluster), + }, + { + ResourceName: "google_container_cluster.with_ip_allocation_policy", + ImportStateIdPrefix: "us-central1-a/", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccContainerCluster_withIPAllocationPolicy_createSubnetwork(t *testing.T) { + t.Parallel() + + cluster := fmt.Sprintf("cluster-test-%s", acctest.RandString(10)) + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckContainerClusterDestroy, + Steps: []resource.TestStep{ + { + Config: testAccContainerCluster_withIPAllocationPolicy_createSubnetwork(cluster), + }, + { + ResourceName: "google_container_cluster.with_ip_allocation_policy", + ImportStateIdPrefix: "us-central1-a/", + ImportState: true, + ImportStateVerify: true, }, }, }) @@ -2233,23 +2259,7 @@ resource "google_container_cluster" "with_maintenance_window" { }`, clusterName, maintenancePolicy) } -func testAccContainerCluster_withIPAllocationPolicy(cluster string, ranges, policy map[string]string) string { - - var secondaryRanges bytes.Buffer - for rangeName, cidr := range ranges { - secondaryRanges.WriteString(fmt.Sprintf(` - secondary_ip_range { - range_name = "%s" - ip_cidr_range = "%s" - }`, rangeName, cidr)) - } - - var ipAllocationPolicy bytes.Buffer - for key, value := range policy { - ipAllocationPolicy.WriteString(fmt.Sprintf(` - %s = "%s"`, key, value)) - } - +func testAccContainerCluster_withIPAllocationPolicy_existingSecondaryRanges(cluster string) string { return fmt.Sprintf(` resource "google_compute_network" "container_network" { name = "container-net-%s" @@ -2262,7 +2272,14 @@ resource "google_compute_subnetwork" "container_subnetwork" { ip_cidr_range = "10.0.0.0/24" region = "us-central1" - %s + secondary_ip_range { + range_name = "pods" + ip_cidr_range = "10.1.0.0/16" + } + secondary_ip_range { + range_name = "services" + ip_cidr_range = "10.2.0.0/20" + } } resource "google_container_cluster" "with_ip_allocation_policy" { @@ -2274,9 +2291,66 @@ resource "google_container_cluster" "with_ip_allocation_policy" { initial_node_count = 1 ip_allocation_policy { - %s + cluster_secondary_range_name = "pods" + services_secondary_range_name = "services" } -}`, acctest.RandString(10), secondaryRanges.String(), cluster, ipAllocationPolicy.String()) +}`, cluster, cluster) +} + +func testAccContainerCluster_withIPAllocationPolicy_specificIPRanges(cluster string) string { + return fmt.Sprintf(` +resource "google_container_cluster" "with_ip_allocation_policy" { + name = "%s" + zone = "us-central1-a" + + initial_node_count = 1 + ip_allocation_policy { + cluster_ipv4_cidr_block = "10.90.0.0/19" + services_ipv4_cidr_block = "10.40.0.0/19" + } +}`, cluster) +} + +func testAccContainerCluster_withIPAllocationPolicy_specificSizes(cluster string) string { + return fmt.Sprintf(` +resource "google_compute_network" "container_network" { + name = "container-net-%s" + auto_create_subnetworks = false +} + +resource "google_compute_subnetwork" "container_subnetwork" { + name = "${google_compute_network.container_network.name}" + network = "${google_compute_network.container_network.name}" + ip_cidr_range = "10.0.0.0/24" + region = "us-central1" +} + +resource "google_container_cluster" "with_ip_allocation_policy" { + name = "%s" + zone = "us-central1-a" + + network = "${google_compute_network.container_network.name}" + subnetwork = "${google_compute_subnetwork.container_subnetwork.name}" + + initial_node_count = 1 + ip_allocation_policy { + cluster_ipv4_cidr_block = "/16" + services_ipv4_cidr_block = "/22" + } +}`, cluster, cluster) +} + +func testAccContainerCluster_withIPAllocationPolicy_createSubnetwork(cluster string) string { + return fmt.Sprintf(` +resource "google_container_cluster" "with_ip_allocation_policy" { + name = "%s" + zone = "us-central1-a" + + initial_node_count = 1 + ip_allocation_policy { + create_subnetwork = true + } +}`, cluster) } func testAccContainerCluster_withPodSecurityPolicy(clusterName string, enabled bool) string { diff --git a/website/docs/r/container_cluster.html.markdown b/website/docs/r/container_cluster.html.markdown index e0c11093..a8742b57 100644 --- a/website/docs/r/container_cluster.html.markdown +++ b/website/docs/r/container_cluster.html.markdown @@ -238,6 +238,23 @@ The `ip_allocation_policy` block supports: ClusterIPs. This must be an existing secondary range associated with the cluster subnetwork. +* `cluster_ipv4_cidr_block` - (Optional) The IP address range for the cluster pod IPs. + Set to blank to have a range chosen with the default size. Set to /netmask (e.g. /14) + to have a range chosen with a specific netmask. Set to a CIDR notation (e.g. 10.96.0.0/14) + from the RFC-1918 private networks (e.g. 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16) to + pick a specific range to use. + +* `services_ipv4_cidr_block` - (Optional) The IP address range of the services IPs in this cluster. + Set to blank to have a range chosen with the default size. Set to /netmask (e.g. /14) + to have a range chosen with a specific netmask. Set to a CIDR notation (e.g. 10.96.0.0/14) + from the RFC-1918 private networks (e.g. 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16) to + pick a specific range to use. + +* `create_subnetwork`- (Optional) Whether a new subnetwork will be created automatically for the cluster. + +* `subnetwork_name` - (Optional) A custom subnetwork name to be used if create_subnetwork is true. + If this field is empty, then an automatic name will be chosen for the new subnetwork. + The `master_auth` block supports: * `password` - (Required) The password to use for HTTP basic authentication when accessing