From 7c54260832e353dbd6e0d5340edc81e2c625727f Mon Sep 17 00:00:00 2001 From: The Magician Date: Tue, 13 Dec 2022 14:13:39 -0800 Subject: [PATCH] google_container_cluster: add support for new GKE Gateway API controller (#6875) (#13233) * add support for gateway api flag * fix test by adding proper release channel; add missing functions * add update cluster handling for gateway api config; extend test cases for gateway api config * remove problematic / unrecognized channels from gateway api; these channels are mentioned in the actual code base but are not recognized by the gcloud api Signed-off-by: Modular Magician Signed-off-by: Modular Magician --- .changelog/6875.txt | 3 + google/resource_container_cluster.go | 61 +++++++++++++++++++ google/resource_container_cluster_test.go | 60 ++++++++++++++++++ .../docs/r/container_cluster.html.markdown | 7 +++ 4 files changed, 131 insertions(+) create mode 100644 .changelog/6875.txt diff --git a/.changelog/6875.txt b/.changelog/6875.txt new file mode 100644 index 00000000000..27b207ca44b --- /dev/null +++ b/.changelog/6875.txt @@ -0,0 +1,3 @@ +```release-note:enhancement +container: added `gateway_api_config` block to `google_container_cluster` resource for supporting the gke gateway api controller +``` diff --git a/google/resource_container_cluster.go b/google/resource_container_cluster.go index 5913dbc6904..599377287f2 100644 --- a/google/resource_container_cluster.go +++ b/google/resource_container_cluster.go @@ -1604,6 +1604,22 @@ func resourceContainerCluster() *schema.Resource { }, }, }, + "gateway_api_config": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Description: `Configuration for GKE Gateway API controller.`, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "channel": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice([]string{"CHANNEL_DISABLED", "CHANNEL_STANDARD"}, false), + Description: `The Gateway API release channel to use for Gateway API.`, + }, + }, + }, + }, }, } } @@ -1722,6 +1738,7 @@ func resourceContainerClusterCreate(d *schema.ResourceData, meta interface{}) er PrivateIpv6GoogleAccess: d.Get("private_ipv6_google_access").(string), EnableL4ilbSubsetting: d.Get("enable_l4_ilb_subsetting").(bool), DnsConfig: expandDnsConfig(d.Get("dns_config")), + GatewayApiConfig: expandGatewayApiConfig(d.Get("gateway_api_config")), }, MasterAuth: expandMasterAuth(d.Get("master_auth")), NotificationConfig: expandNotificationConfig(d.Get("notification_config")), @@ -2191,6 +2208,9 @@ func resourceContainerClusterRead(d *schema.ResourceData, meta interface{}) erro if err := d.Set("dns_config", flattenDnsConfig(cluster.NetworkConfig.DnsConfig)); err != nil { return err } + if err := d.Set("gateway_api_config", flattenGatewayApiConfig(cluster.NetworkConfig.GatewayApiConfig)); err != nil { + return err + } if err := d.Set("logging_config", flattenContainerClusterLoggingConfig(cluster.LoggingConfig)); err != nil { return err } @@ -3122,6 +3142,24 @@ func resourceContainerClusterUpdate(d *schema.ResourceData, meta interface{}) er log.Printf("[INFO] GKE cluster %s resource usage export config has been updated", d.Id()) } + if d.HasChange("gateway_api_config") { + if gac, ok := d.GetOk("gateway_api_config"); ok { + req := &container.UpdateClusterRequest{ + Update: &container.ClusterUpdate{ + DesiredGatewayApiConfig: expandGatewayApiConfig(gac), + }, + } + + updateF := updateFunc(req, "updating GKE Gateway API") + // Call update serially. + if err := lockedCall(lockKey, updateF); err != nil { + return err + } + + log.Printf("[INFO] GKE cluster %s Gateway API has been updated", d.Id()) + } + } + if d.HasChange("node_pool_defaults") && d.HasChange("node_pool_defaults.0.node_config_defaults.0.logging_variant") { if v, ok := d.GetOk("node_pool_defaults.0.node_config_defaults.0.logging_variant"); ok { loggingVariant := v.(string) @@ -4005,6 +4043,18 @@ func expandDnsConfig(configured interface{}) *container.DNSConfig { } } +func expandGatewayApiConfig(configured interface{}) *container.GatewayAPIConfig { + l := configured.([]interface{}) + if len(l) == 0 || l[0] == nil { + return nil + } + + config := l[0].(map[string]interface{}) + return &container.GatewayAPIConfig{ + Channel: config["channel"].(string), + } +} + func expandContainerClusterLoggingConfig(configured interface{}) *container.LoggingConfig { l := configured.([]interface{}) if len(l) == 0 { @@ -4652,6 +4702,17 @@ func flattenDnsConfig(c *container.DNSConfig) []map[string]interface{} { } } +func flattenGatewayApiConfig(c *container.GatewayAPIConfig) []map[string]interface{} { + if c == nil { + return nil + } + return []map[string]interface{}{ + { + "channel": c.Channel, + }, + } +} + func flattenContainerClusterLoggingConfig(c *container.LoggingConfig) []map[string]interface{} { if c == nil { return nil diff --git a/google/resource_container_cluster_test.go b/google/resource_container_cluster_test.go index 4b3ab79a1ce..3660c317557 100644 --- a/google/resource_container_cluster_test.go +++ b/google/resource_container_cluster_test.go @@ -2893,6 +2893,52 @@ func TestAccContainerCluster_withDNSConfig(t *testing.T) { }) } +func TestAccContainerCluster_withGatewayApiConfig(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_withGatewayApiConfig(clusterName, "CHANNEL_DISABLED"), + }, + { + ResourceName: "google_container_cluster.primary", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"min_master_version"}, + }, + { + Config: testAccContainerCluster_withGatewayApiConfig(clusterName, "CHANNEL_STANDARD"), + }, + { + ResourceName: "google_container_cluster.primary", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"min_master_version"}, + }, + }, + }) +} + +func TestAccContainerCluster_withInvalidGatewayApiConfigChannel(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_withGatewayApiConfig(clusterName, "CANARY"), + ExpectError: regexp.MustCompile(`expected gateway_api_config\.0\.channel to be one of \[CHANNEL_DISABLED CHANNEL_STANDARD\], got CANARY`), + }, + }, + }) +} + func testAccContainerCluster_masterAuthorizedNetworksDisabled(t *testing.T, resource_name string) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[resource_name] @@ -5924,6 +5970,20 @@ resource "google_container_cluster" "with_dns_config" { `, clusterName, clusterDns, clusterDnsDomain, clusterDnsScope) } +func testAccContainerCluster_withGatewayApiConfig(clusterName string, gatewayApiChannel string) string { + return fmt.Sprintf(` +resource "google_container_cluster" "primary" { + name = "%s" + location = "us-central1-f" + initial_node_count = 1 + min_master_version = "1.24" + gateway_api_config { + channel = "%s" + } +} +`, clusterName, gatewayApiChannel) +} + func testAccContainerCluster_withLoggingConfigEnabled(name string) string { return fmt.Sprintf(` resource "google_container_cluster" "primary" { diff --git a/website/docs/r/container_cluster.html.markdown b/website/docs/r/container_cluster.html.markdown index 5b99834346b..467e113e44e 100644 --- a/website/docs/r/container_cluster.html.markdown +++ b/website/docs/r/container_cluster.html.markdown @@ -355,6 +355,9 @@ subnetwork in which the cluster's instances are launched. * `dns_config` - (Optional) Configuration for [Using Cloud DNS for GKE](https://cloud.google.com/kubernetes-engine/docs/how-to/cloud-dns). Structure is [documented below](#nested_dns_config). +* `gateway_api_config` - (Optional) + Configuration for [GKE Gateway API controller](https://cloud.google.com/kubernetes-engine/docs/concepts/gateway-api). Structure is [documented below](#nested_gateway_api_config). + The `default_snat_status` block supports * `disabled` - (Required) Whether the cluster disables default in-node sNAT rules. In-node sNAT rules will be disabled when defaultSnatStatus is disabled.When disabled is set to false, default IP masquerade rules will be applied to the nodes to prevent sNAT on cluster internal traffic @@ -1132,6 +1135,10 @@ and all pods running on the nodes. Specified as a map from the key, such as * `cluster_dns_domain` - (Optional) The suffix used for all cluster service records. +The `gateway_api_config` block supports: + +* `channel` - (Required) Which Gateway Api channel should be used. `CHANNEL_DISABLED` or `CHANNEL_STANDARD`. + ## Attributes Reference In addition to the arguments listed above, the following computed attributes are