diff --git a/autogen/main/README.md b/autogen/main/README.md index 82341a7269..fa153d2290 100644 --- a/autogen/main/README.md +++ b/autogen/main/README.md @@ -44,6 +44,11 @@ The implications of this are that: - You will indeed need sufficient IP space (and compute capacity) to create both node pools {% endif %} + +## Kubernetes version lookup + +When `kubernetes_version` is set to `"latest"`, the module resolves the version via the `google_container_engine_versions` data source. For **regional** clusters only a region-level lookup is performed. For **zonal** clusters a zone-level lookup is used. This allows the module to work in environments where the Container API accepts only region-level `location` (e.g. some sovereign or partner clouds). + ## Compatibility This module is meant for use with Terraform 1.3+ and tested using Terraform 1.10+. diff --git a/autogen/main/cluster.tf.tmpl b/autogen/main/cluster.tf.tmpl index 1f77921b1a..985876500b 100644 --- a/autogen/main/cluster.tf.tmpl +++ b/autogen/main/cluster.tf.tmpl @@ -31,8 +31,8 @@ resource "google_container_cluster" "primary" { project = var.project_id resource_labels = var.cluster_resource_labels - location = local.location - node_locations = local.node_locations + location = local.cluster_location_input + node_locations = local.cluster_node_locations cluster_ipv4_cidr = var.cluster_ipv4_cidr network = "projects/${local.network_project_id}/global/networks/${var.network}" deletion_protection = var.deletion_protection diff --git a/autogen/main/main.tf.tmpl b/autogen/main/main.tf.tmpl index e2e2e014d0..e755b6ff24 100644 --- a/autogen/main/main.tf.tmpl +++ b/autogen/main/main.tf.tmpl @@ -48,9 +48,14 @@ locals { region = var.regional ? var.region : join("-", slice(split("-", var.zones[0]), 0, 2)) // for regional cluster - use var.zones if provided, use available otherwise, for zonal cluster use var.zones with first element extracted node_locations = var.regional ? coalescelist(compact(var.zones), try(sort(random_shuffle.available_zones[0].result),[])) : slice(var.zones, 1, length(var.zones)) - // Kubernetes version + // when cluster_location_override is set, use it for the cluster resource; when override equals first zone (regional), use remaining zones for node_locations so the cluster is created as zonal + cluster_location_input = coalesce(var.cluster_location_override, local.location) + use_zone_location = var.cluster_location_override != null && var.regional && length(var.zones) > 0 && var.cluster_location_override == var.zones[0] + // when omit_node_locations_for_regional is true, use var.zones so config matches API-populated node_locations and avoids drift on subsequent apply + cluster_node_locations = var.omit_node_locations_for_regional && var.regional ? var.zones : (local.use_zone_location ? slice(var.zones, 1, length(var.zones)) : local.node_locations) + // Kubernetes version: for regional clusters only region-level lookup is used; zone-level lookup is skipped to support environments where the API accepts only region-level location. master_version_regional = var.kubernetes_version != "latest" ? var.kubernetes_version : data.google_container_engine_versions.region.latest_master_version - master_version_zonal = var.kubernetes_version != "latest" ? var.kubernetes_version : data.google_container_engine_versions.zone.latest_master_version + master_version_zonal = var.kubernetes_version != "latest" ? var.kubernetes_version : (var.regional ? local.master_version_regional : data.google_container_engine_versions.zone[0].latest_master_version) master_version = var.regional ? local.master_version_regional : local.master_version_zonal {% if autopilot_cluster != true %} // Build a map of maps of node pools from a list of objects @@ -255,11 +260,10 @@ data "google_container_engine_versions" "region" { project = var.project_id } +// Only created for zonal clusters; regional clusters use region-level version lookup only (avoids zone-level API calls in environments that support only region-level location). data "google_container_engine_versions" "zone" { - // Work around to prevent a lack of zone declaration from causing regional cluster creation from erroring out due to error - // - // data.google_container_engine_versions.zone: Cannot determine zone: set in this resource, or set provider-level zone. - // + count = var.regional ? 0 : 1 + location = local.zone_count == 0 ? data.google_compute_zones.available[0].names[0] : var.zones[0] project = var.project_id } diff --git a/autogen/main/variables.tf.tmpl b/autogen/main/variables.tf.tmpl index d58e58cc3a..825f01efc9 100644 --- a/autogen/main/variables.tf.tmpl +++ b/autogen/main/variables.tf.tmpl @@ -50,6 +50,18 @@ variable "zones" { default = [] } +variable "cluster_location_override" { + type = string + description = "Optional override for the cluster's location attribute. When set, this value is used as the cluster location instead of the default (region for regional, first zone for zonal). Use when the API rejects the default location (e.g. backends that validate location as a zone). When set to the first zone while regional=true, node_locations are set to the remaining zones so the cluster is created as zonal." + default = null +} + +variable "omit_node_locations_for_regional" { + type = bool + description = "When true and regional=true, node_locations is set from var.zones so Terraform config matches the API-populated zones and avoids drift on subsequent apply. Use when the API only accepts region-level location at create and populates zones automatically; set var.zones to the region's zones so desired state matches the cluster after create." + default = false +} + variable "network" { type = string description = "The VPC network to host the cluster in (required)" diff --git a/main.tf b/main.tf index 8c0b8d707b..4ce97a0767 100644 --- a/main.tf +++ b/main.tf @@ -44,9 +44,9 @@ locals { region = var.regional ? var.region : join("-", slice(split("-", var.zones[0]), 0, 2)) // for regional cluster - use var.zones if provided, use available otherwise, for zonal cluster use var.zones with first element extracted node_locations = var.regional ? coalescelist(compact(var.zones), try(sort(random_shuffle.available_zones[0].result), [])) : slice(var.zones, 1, length(var.zones)) - // Kubernetes version + // Kubernetes version: for regional clusters only region-level lookup is used; zone-level lookup is skipped to support environments where the API accepts only region-level location. master_version_regional = var.kubernetes_version != "latest" ? var.kubernetes_version : data.google_container_engine_versions.region.latest_master_version - master_version_zonal = var.kubernetes_version != "latest" ? var.kubernetes_version : data.google_container_engine_versions.zone.latest_master_version + master_version_zonal = var.kubernetes_version != "latest" ? var.kubernetes_version : (var.regional ? local.master_version_regional : data.google_container_engine_versions.zone[0].latest_master_version) master_version = var.regional ? local.master_version_regional : local.master_version_zonal // Build a map of maps of node pools from a list of objects node_pools = { for np in var.node_pools : np.name => np } @@ -186,11 +186,10 @@ data "google_container_engine_versions" "region" { project = var.project_id } +// Only created for zonal clusters; regional clusters use region-level version lookup only (avoids zone-level API calls in environments that support only region-level location). data "google_container_engine_versions" "zone" { - // Work around to prevent a lack of zone declaration from causing regional cluster creation from erroring out due to error - // - // data.google_container_engine_versions.zone: Cannot determine zone: set in this resource, or set provider-level zone. - // + count = var.regional ? 0 : 1 + location = local.zone_count == 0 ? data.google_compute_zones.available[0].names[0] : var.zones[0] project = var.project_id } diff --git a/modules/beta-autopilot-private-cluster/README.md b/modules/beta-autopilot-private-cluster/README.md index 6d3cb7287a..9f19c7900c 100644 --- a/modules/beta-autopilot-private-cluster/README.md +++ b/modules/beta-autopilot-private-cluster/README.md @@ -14,6 +14,16 @@ Sub modules are provided for creating private clusters, beta private clusters, a For details on configuring private clusters with this module, check the [troubleshooting guide](https://github.com/terraform-google-modules/terraform-google-kubernetes-engine/blob/master/docs/private_clusters.md). +## Kubernetes version lookup + +When `kubernetes_version` is set to `"latest"`, the module resolves the version via the `google_container_engine_versions` data source. For **regional** clusters only a region-level lookup is performed. For **zonal** clusters a zone-level lookup is used. This allows the module to work in environments where the Container API accepts only region-level `location` (e.g. some sovereign or partner clouds). + +## Cluster location override + +When the API rejects the default cluster location (e.g. backends that validate `location` as a zone and reject a region), set `cluster_location_override` to the value the API accepts. For regional clusters, setting it to the first zone (e.g. `var.zones[0]`) creates the cluster as zonal from the API’s perspective; `node_locations` is then set to the remaining zones so nodes can still span multiple zones. + +When the API accepts the region as `location` but rejects explicit zone names at create (e.g. "Specified location is not a valid zone"), set `omit_node_locations_for_regional = true`. The module then creates the cluster with `node_locations = []` so the API accepts the request; the API populates zones automatically. A dedicated cluster resource variant with `lifecycle { ignore_changes = [node_locations] }` is used so subsequent applies do not drift and destroy/recreate works (create again sends `[]`, no update is attempted on node_locations). + ## Compatibility This module is meant for use with Terraform 1.3+ and tested using Terraform 1.10+. diff --git a/modules/beta-autopilot-private-cluster/cluster.tf b/modules/beta-autopilot-private-cluster/cluster.tf index b2210a1157..1b5f47580f 100644 --- a/modules/beta-autopilot-private-cluster/cluster.tf +++ b/modules/beta-autopilot-private-cluster/cluster.tf @@ -19,7 +19,17 @@ /****************************************** Create Container Cluster *****************************************/ +// +// Why two cluster resources (primary and primary_omit_nodelocs)? +// In some backends the API accepts regional location but rejects explicit zone names at create; +// we must send node_locations=[] so create succeeds, then the API populates zones. To avoid drift +// on later apply we must not update node_locations (lifecycle ignore_changes). Terraform requires +// ignore_changes to be a static list—it cannot be conditional. So we use two resources with +// opposite count: only one is created. primary = normal path with explicit node_locations; +// primary_omit_nodelocs = node_locations=[] and lifecycle { ignore_changes = [node_locations] }. +// resource "google_container_cluster" "primary" { + count = (var.omit_node_locations_for_regional && var.regional) ? 0 : 1 provider = google-beta name = var.name @@ -27,8 +37,8 @@ resource "google_container_cluster" "primary" { project = var.project_id resource_labels = var.cluster_resource_labels - location = local.location - node_locations = local.node_locations + location = local.cluster_location_input + node_locations = local.cluster_node_locations cluster_ipv4_cidr = var.cluster_ipv4_cidr network = "projects/${local.network_project_id}/global/networks/${var.network}" deletion_protection = var.deletion_protection @@ -377,7 +387,474 @@ resource "google_container_cluster" "primary" { condition = var.ip_range_services == null && var.kubernetes_version != "latest" ? tonumber(split(".", var.kubernetes_version)[0]) >= 1 && tonumber(split(".", var.kubernetes_version)[1]) >= 27 : true error_message = "Setting ip_range_services is required for this GKE version. Please set ip_range_services or use kubernetes_version 1.27 or later." } + } + + timeouts { + create = lookup(var.timeouts, "create", "45m") + update = lookup(var.timeouts, "update", "45m") + delete = lookup(var.timeouts, "delete", "45m") + } + + dynamic "resource_usage_export_config" { + for_each = var.resource_usage_export_dataset_id != "" ? [{ + enable_network_egress_metering = var.enable_network_egress_export + enable_resource_consumption_metering = var.enable_resource_consumption_export + dataset_id = var.resource_usage_export_dataset_id + }] : [] + + content { + enable_network_egress_metering = resource_usage_export_config.value.enable_network_egress_metering + enable_resource_consumption_metering = resource_usage_export_config.value.enable_resource_consumption_metering + bigquery_destination { + dataset_id = resource_usage_export_config.value.dataset_id + } + } + } + + dynamic "private_cluster_config" { + for_each = var.enable_private_nodes ? [{ + enable_private_nodes = var.enable_private_nodes, + enable_private_endpoint = var.enable_private_endpoint + master_ipv4_cidr_block = var.master_ipv4_cidr_block + private_endpoint_subnetwork = var.private_endpoint_subnetwork + }] : [] + + content { + enable_private_endpoint = private_cluster_config.value.enable_private_endpoint + enable_private_nodes = private_cluster_config.value.enable_private_nodes + master_ipv4_cidr_block = var.private_endpoint_subnetwork == null ? private_cluster_config.value.master_ipv4_cidr_block : null + private_endpoint_subnetwork = private_cluster_config.value.private_endpoint_subnetwork + dynamic "master_global_access_config" { + for_each = var.master_global_access_enabled ? [var.master_global_access_enabled] : [] + content { + enabled = master_global_access_config.value + } + } + } + } + + dynamic "control_plane_endpoints_config" { + for_each = var.dns_allow_external_traffic != null || var.ip_endpoints_enabled != null ? [1] : [] + content { + dynamic "dns_endpoint_config" { + for_each = var.dns_allow_external_traffic != null ? [1] : [] + content { + allow_external_traffic = var.dns_allow_external_traffic + } + } + dynamic "ip_endpoints_config" { + for_each = var.ip_endpoints_enabled != null ? [1] : [] + content { + enabled = var.ip_endpoints_enabled + } + } + } + } + + + dynamic "database_encryption" { + for_each = var.database_encryption + + content { + key_name = database_encryption.value.key_name + state = database_encryption.value.state + } + } + + dynamic "workload_identity_config" { + for_each = local.cluster_workload_identity_config + + content { + workload_pool = workload_identity_config.value.workload_pool + } + } + + + dynamic "authenticator_groups_config" { + for_each = local.cluster_authenticator_security_group + content { + security_group = authenticator_groups_config.value.security_group + } + } + + notification_config { + pubsub { + enabled = var.notification_config_topic != "" ? true : false + topic = var.notification_config_topic + dynamic "filter" { + for_each = length(var.notification_filter_event_type) > 0 ? [1] : [] + content { + event_type = var.notification_filter_event_type + } + } + } + } + + depends_on = [google_project_iam_member.service_agent] +} + +resource "google_container_cluster" "primary_omit_nodelocs" { + count = (var.omit_node_locations_for_regional && var.regional) ? 1 : 0 + provider = google-beta + // When omit_node_locations_for_regional is true: create with node_locations=[] so the API accepts the request; lifecycle ignore_changes on node_locations avoids drift and allows destroy/recreate. + + name = var.name + description = var.description + project = var.project_id + resource_labels = var.cluster_resource_labels + + location = local.cluster_location_input + node_locations = [] + cluster_ipv4_cidr = var.cluster_ipv4_cidr + network = "projects/${local.network_project_id}/global/networks/${var.network}" + deletion_protection = var.deletion_protection + + dynamic "enable_k8s_beta_apis" { + for_each = length(var.enable_k8s_beta_apis) > 0 ? [1] : [] + content { + enabled_apis = var.enable_k8s_beta_apis + } + } + + + dynamic "release_channel" { + for_each = local.release_channel + + content { + channel = release_channel.value.channel + } + } + + dynamic "gateway_api_config" { + for_each = local.gateway_api_config + + content { + channel = gateway_api_config.value.channel + } + } + + dynamic "gke_auto_upgrade_config" { + for_each = var.gke_auto_upgrade_config_patch_mode != null ? [1] : [] + + content { + patch_mode = var.gke_auto_upgrade_config_patch_mode + } + } + + dynamic "cost_management_config" { + for_each = var.enable_cost_allocation ? [1] : [] + content { + enabled = var.enable_cost_allocation + } + } + + dynamic "confidential_nodes" { + for_each = local.confidential_node_config + content { + enabled = confidential_nodes.value.enabled + } + } + + subnetwork = "projects/${local.network_project_id}/regions/${local.region}/subnetworks/${var.subnetwork}" + + default_snat_status { + disabled = var.disable_default_snat + } + + min_master_version = var.release_channel == null || var.release_channel == "UNSPECIFIED" ? local.master_version : var.kubernetes_version == "latest" ? null : var.kubernetes_version + + dynamic "logging_config" { + for_each = length(var.logging_enabled_components) > 0 ? [1] : [] + + content { + enable_components = var.logging_enabled_components + } + } + + dynamic "monitoring_config" { + for_each = length(var.monitoring_enabled_components) > 0 ? [1] : [] + content { + enable_components = var.monitoring_enabled_components + } + } + + cluster_autoscaling { + default_compute_class_enabled = var.default_compute_class_enabled + dynamic "auto_provisioning_defaults" { + for_each = (var.create_service_account || var.service_account != "" || var.boot_disk_kms_key != null) ? [1] : [] + + content { + service_account = local.service_account + boot_disk_kms_key = var.boot_disk_kms_key + } + } + } + vertical_pod_autoscaling { + enabled = var.enable_vertical_pod_autoscaling + } + + dynamic "binary_authorization" { + for_each = var.enable_binary_authorization ? [var.enable_binary_authorization] : [] + content { + evaluation_mode = "PROJECT_SINGLETON_POLICY_ENFORCE" + } + } + + enable_l4_ilb_subsetting = var.enable_l4_ilb_subsetting + + disable_l4_lb_firewall_reconciliation = var.disable_l4_lb_firewall_reconciliation + + enable_multi_networking = var.enable_multi_networking + + enable_cilium_clusterwide_network_policy = var.enable_cilium_clusterwide_network_policy + + in_transit_encryption_config = var.in_transit_encryption_config + + dynamic "anonymous_authentication_config" { + for_each = var.anonymous_authentication_config_mode != null ? [1] : [] + content { + mode = var.anonymous_authentication_config_mode + } + } + + dynamic "network_performance_config" { + for_each = var.total_egress_bandwidth_tier != null ? [1] : [] + content { + total_egress_bandwidth_tier = var.total_egress_bandwidth_tier + } + } + + dynamic "rbac_binding_config" { + for_each = var.rbac_binding_config.enable_insecure_binding_system_unauthenticated != null || var.rbac_binding_config.enable_insecure_binding_system_authenticated != null ? [var.rbac_binding_config] : [] + content { + enable_insecure_binding_system_unauthenticated = rbac_binding_config.value["enable_insecure_binding_system_unauthenticated"] + enable_insecure_binding_system_authenticated = rbac_binding_config.value["enable_insecure_binding_system_authenticated"] + } + } + + dynamic "secret_manager_config" { + for_each = var.enable_secret_manager_addon ? [var.enable_secret_manager_addon] : [] + content { + enabled = secret_manager_config.value + } + } + + dynamic "pod_autoscaling" { + for_each = length(var.hpa_profile) > 0 ? [1] : [] + content { + hpa_profile = var.hpa_profile + } + } + + dynamic "enterprise_config" { + for_each = var.enterprise_config != null ? [1] : [] + content { + desired_tier = var.enterprise_config + } + } + + enable_fqdn_network_policy = var.enable_fqdn_network_policy + enable_autopilot = true + dynamic "master_authorized_networks_config" { + for_each = var.enable_private_endpoint || var.gcp_public_cidrs_access_enabled != null || length(var.master_authorized_networks) > 0 ? [true] : [] + content { + gcp_public_cidrs_access_enabled = var.gcp_public_cidrs_access_enabled + dynamic "cidr_blocks" { + for_each = var.master_authorized_networks + content { + cidr_block = lookup(cidr_blocks.value, "cidr_block", "") + display_name = lookup(cidr_blocks.value, "display_name", "") + } + } + } + } + dynamic "node_pool_auto_config" { + for_each = length(var.network_tags) > 0 || length(var.resource_manager_tags) > 0 || var.add_cluster_firewall_rules || var.add_master_webhook_firewall_rules || var.add_shadow_firewall_rules || var.insecure_kubelet_readonly_port_enabled != null || var.node_pools_cgroup_mode != null ? [1] : [] + content { + dynamic "network_tags" { + for_each = length(var.network_tags) > 0 || var.add_cluster_firewall_rules || var.add_master_webhook_firewall_rules || var.add_shadow_firewall_rules ? [1] : [] + content { + tags = var.add_cluster_firewall_rules || var.add_master_webhook_firewall_rules || var.add_shadow_firewall_rules ? concat(var.network_tags, [local.cluster_network_tag]) : length(var.network_tags) > 0 ? var.network_tags : null + } + } + + resource_manager_tags = length(var.resource_manager_tags) > 0 ? var.resource_manager_tags : null + + dynamic "node_kubelet_config" { + for_each = var.insecure_kubelet_readonly_port_enabled != null ? [1] : [] + content { + insecure_kubelet_readonly_port_enabled = upper(tostring(var.insecure_kubelet_readonly_port_enabled)) + } + } + dynamic "linux_node_config" { + for_each = var.node_pools_cgroup_mode != null ? [1] : [] + content { + cgroup_mode = var.node_pools_cgroup_mode + } + } + } + } + + + master_auth { + client_certificate_config { + issue_client_certificate = var.issue_client_certificate + } + } + + dynamic "service_external_ips_config" { + for_each = var.service_external_ips ? [1] : [] + content { + enabled = var.service_external_ips + } + } + + addons_config { + http_load_balancing { + disabled = !var.http_load_balancing + } + + horizontal_pod_autoscaling { + disabled = !var.horizontal_pod_autoscaling + } + + gcp_filestore_csi_driver_config { + enabled = var.filestore_csi_driver + } + + dynamic "lustre_csi_driver_config" { + for_each = var.lustre_csi_driver == null ? [] : ["lustre_csi_driver_config"] + content { + enabled = var.lustre_csi_driver + enable_legacy_lustre_port = var.enable_legacy_lustre_port + } + } + + + dynamic "gke_backup_agent_config" { + for_each = local.gke_backup_agent_config + + content { + enabled = gke_backup_agent_config.value.enabled + } + } + + dynamic "stateful_ha_config" { + for_each = local.stateful_ha_config + + content { + enabled = stateful_ha_config.value.enabled + } + } + + dynamic "ray_operator_config" { + for_each = local.ray_operator_config + + content { + + enabled = ray_operator_config.value.enabled + + ray_cluster_logging_config { + enabled = ray_operator_config.value.logging_enabled + } + ray_cluster_monitoring_config { + enabled = ray_operator_config.value.monitoring_enabled + } + } + } + + + } + + allow_net_admin = var.allow_net_admin + + networking_mode = "VPC_NATIVE" + + protect_config { + workload_config { + audit_mode = var.workload_config_audit_mode + } + workload_vulnerability_mode = var.workload_vulnerability_mode + } + + security_posture_config { + mode = var.security_posture_mode + vulnerability_mode = var.security_posture_vulnerability_mode + } + + dynamic "fleet" { + for_each = var.fleet_project != null ? [1] : [] + content { + project = var.fleet_project + } + } + + ip_allocation_policy { + cluster_secondary_range_name = var.ip_range_pods + services_secondary_range_name = var.ip_range_services + dynamic "additional_pod_ranges_config" { + for_each = length(var.additional_ip_range_pods) != 0 ? [1] : [] + content { + pod_range_names = var.additional_ip_range_pods + } + } + dynamic "additional_ip_ranges_config" { + for_each = var.additional_ip_ranges_config + content { + subnetwork = additional_ip_ranges_config.value.subnetwork + pod_ipv4_range_names = additional_ip_ranges_config.value.pod_ipv4_range_names + } + } + dynamic "network_tier_config" { + for_each = var.network_tier_config != null ? [1] : [] + content { + network_tier = var.network_tier_config + } + } + stack_type = var.stack_type + } + + maintenance_policy { + dynamic "recurring_window" { + for_each = local.cluster_maintenance_window_is_recurring + content { + start_time = var.maintenance_start_time + end_time = var.maintenance_end_time + recurrence = var.maintenance_recurrence + } + } + + dynamic "daily_maintenance_window" { + for_each = local.cluster_maintenance_window_is_daily + content { + start_time = var.maintenance_start_time + } + } + + dynamic "maintenance_exclusion" { + for_each = var.maintenance_exclusions + content { + exclusion_name = maintenance_exclusion.value.name + start_time = maintenance_exclusion.value.start_time + end_time = maintenance_exclusion.value.end_time + + dynamic "exclusion_options" { + for_each = maintenance_exclusion.value.exclusion_scope == null ? [] : [maintenance_exclusion.value.exclusion_scope] + content { + scope = exclusion_options.value + } + } + } + } + } + + lifecycle { + precondition { + condition = var.ip_range_services == null && var.kubernetes_version != "latest" ? tonumber(split(".", var.kubernetes_version)[0]) >= 1 && tonumber(split(".", var.kubernetes_version)[1]) >= 27 : true + error_message = "Setting ip_range_services is required for this GKE version. Please set ip_range_services or use kubernetes_version 1.27 or later." + } + ignore_changes = [node_locations] } timeouts { diff --git a/modules/beta-autopilot-private-cluster/firewall.tf b/modules/beta-autopilot-private-cluster/firewall.tf index 3908a63364..f67032bf50 100644 --- a/modules/beta-autopilot-private-cluster/firewall.tf +++ b/modules/beta-autopilot-private-cluster/firewall.tf @@ -72,7 +72,7 @@ resource "google_compute_firewall" "tpu_egress" { direction = "EGRESS" target_tags = [local.cluster_network_tag] - destination_ranges = [google_container_cluster.primary.tpu_ipv4_cidr_block] + destination_ranges = [local.cluster_self.tpu_ipv4_cidr_block] # Allow all possible protocols allow { protocol = "tcp" } diff --git a/modules/beta-autopilot-private-cluster/main.tf b/modules/beta-autopilot-private-cluster/main.tf index bdd7167857..3f09b84246 100644 --- a/modules/beta-autopilot-private-cluster/main.tf +++ b/modules/beta-autopilot-private-cluster/main.tf @@ -36,20 +36,24 @@ resource "random_shuffle" "available_zones" { } locals { - // ID of the cluster - cluster_id = google_container_cluster.primary.id + cluster_id = local.cluster_self.id - // location - location = var.regional ? var.region : var.zones[0] - region = var.regional ? var.region : join("-", slice(split("-", var.zones[0]), 0, 2)) - // for regional cluster - use var.zones if provided, use available otherwise, for zonal cluster use var.zones with first element extracted + location = var.regional ? var.region : var.zones[0] + region = var.regional ? var.region : join("-", slice(split("-", var.zones[0]), 0, 2)) node_locations = var.regional ? coalescelist(compact(var.zones), try(sort(random_shuffle.available_zones[0].result), [])) : slice(var.zones, 1, length(var.zones)) - // Kubernetes version + + cluster_location_input = coalesce(var.cluster_location_override, local.location) + use_zone_location = var.cluster_location_override != null && var.regional && length(var.zones) > 0 && var.cluster_location_override == var.zones[0] + + cluster_node_locations = var.omit_node_locations_for_regional && var.regional ? [] : (local.use_zone_location ? slice(var.zones, 1, length(var.zones)) : local.node_locations) + + cluster_self = length(google_container_cluster.primary) > 0 ? google_container_cluster.primary[0] : google_container_cluster.primary_omit_nodelocs[0] + master_version_regional = var.kubernetes_version != "latest" ? var.kubernetes_version : data.google_container_engine_versions.region.latest_master_version - master_version_zonal = var.kubernetes_version != "latest" ? var.kubernetes_version : data.google_container_engine_versions.zone.latest_master_version + master_version_zonal = var.kubernetes_version != "latest" ? var.kubernetes_version : (var.regional ? local.master_version_regional : data.google_container_engine_versions.zone[0].latest_master_version) master_version = var.regional ? local.master_version_regional : local.master_version_zonal - fleet_membership = var.fleet_project != null ? google_container_cluster.primary.fleet[0].membership : null + fleet_membership = var.fleet_project != null ? local.cluster_self.fleet[0].membership : null release_channel = var.release_channel != null ? [{ channel : var.release_channel }] : [] gateway_api_config = var.gateway_api_channel != null ? [{ channel : var.gateway_api_channel }] : [] @@ -73,43 +77,38 @@ locals { }] - cluster_output_regional_zones = google_container_cluster.primary.node_locations - cluster_output_zones = local.cluster_output_regional_zones - - cluster_endpoint = (var.enable_private_nodes && length(google_container_cluster.primary.private_cluster_config) > 0) ? (var.enable_private_endpoint || var.deploy_using_private_endpoint ? google_container_cluster.primary.private_cluster_config[0].private_endpoint : google_container_cluster.primary.private_cluster_config[0].public_endpoint) : google_container_cluster.primary.endpoint - cluster_peering_name = (var.enable_private_nodes && length(google_container_cluster.primary.private_cluster_config) > 0) ? google_container_cluster.primary.private_cluster_config[0].peering_name : null - cluster_endpoint_for_nodes = (var.enable_private_nodes && length(google_container_cluster.primary.private_cluster_config) > 0) ? ( + cluster_endpoint = (var.enable_private_nodes && length(local.cluster_self.private_cluster_config) > 0) ? (var.enable_private_endpoint || var.deploy_using_private_endpoint ? local.cluster_self.private_cluster_config[0].private_endpoint : local.cluster_self.private_cluster_config[0].public_endpoint) : local.cluster_self.endpoint + cluster_peering_name = (var.enable_private_nodes && length(local.cluster_self.private_cluster_config) > 0) ? local.cluster_self.private_cluster_config[0].peering_name : null + cluster_endpoint_for_nodes = (var.enable_private_nodes && length(local.cluster_self.private_cluster_config) > 0) ? ( var.private_endpoint_subnetwork != null ? data.google_compute_subnetwork.private_endpoint_subnetwork[0].ip_cidr_range : var.master_ipv4_cidr_block != null ? - google_container_cluster.primary.private_cluster_config[0].master_ipv4_cidr_block : + local.cluster_self.private_cluster_config[0].master_ipv4_cidr_block : local.cluster_subnet_cidr ) : local.cluster_subnet_cidr - cluster_output_master_auth = concat(google_container_cluster.primary[*].master_auth, []) - cluster_output_master_version = google_container_cluster.primary.master_version - cluster_output_min_master_version = google_container_cluster.primary.min_master_version - cluster_output_logging_service = google_container_cluster.primary.logging_service - cluster_output_monitoring_service = google_container_cluster.primary.monitoring_service - cluster_output_http_load_balancing_enabled = coalescelist(lookup(coalescelist(google_container_cluster.primary.addons_config, [{}])[0], "http_load_balancing", [{}]), [{ disabled = false }])[0].disabled - cluster_output_horizontal_pod_autoscaling_enabled = coalescelist(lookup(coalescelist(google_container_cluster.primary.addons_config, [{}])[0], "horizontal_pod_autoscaling", [{}]), [{ disabled = false }])[0].disabled - cluster_output_vertical_pod_autoscaling_enabled = google_container_cluster.primary.vertical_pod_autoscaling != null && length(google_container_cluster.primary.vertical_pod_autoscaling) == 1 ? google_container_cluster.primary.vertical_pod_autoscaling[0].enabled : false - cluster_output_intranode_visbility_enabled = google_container_cluster.primary.enable_intranode_visibility - cluster_output_identity_service_enabled = google_container_cluster.primary.identity_service_config != null && length(google_container_cluster.primary.identity_service_config) == 1 ? google_container_cluster.primary.identity_service_config[0].enabled : false - cluster_output_secret_manager_addon_enabled = google_container_cluster.primary.secret_manager_config != null && length(google_container_cluster.primary.secret_manager_config) == 1 ? google_container_cluster.primary.secret_manager_config[0].enabled : false + cluster_output_master_auth = concat([local.cluster_self.master_auth], []) + cluster_output_master_version = local.cluster_self.master_version + cluster_output_min_master_version = local.cluster_self.min_master_version + cluster_output_logging_service = local.cluster_self.logging_service + cluster_output_monitoring_service = local.cluster_self.monitoring_service + cluster_output_http_load_balancing_enabled = coalescelist(lookup(coalescelist(local.cluster_self.addons_config, [{}])[0], "http_load_balancing", [{}]), [{ disabled = false }])[0].disabled + cluster_output_horizontal_pod_autoscaling_enabled = coalescelist(lookup(coalescelist(local.cluster_self.addons_config, [{}])[0], "horizontal_pod_autoscaling", [{}]), [{ disabled = false }])[0].disabled + cluster_output_vertical_pod_autoscaling_enabled = local.cluster_self.vertical_pod_autoscaling != null && length(local.cluster_self.vertical_pod_autoscaling) == 1 ? local.cluster_self.vertical_pod_autoscaling[0].enabled : false + cluster_output_intranode_visbility_enabled = local.cluster_self.enable_intranode_visibility + cluster_output_identity_service_enabled = local.cluster_self.identity_service_config != null && length(local.cluster_self.identity_service_config) == 1 ? local.cluster_self.identity_service_config[0].enabled : false + cluster_output_secret_manager_addon_enabled = local.cluster_self.secret_manager_config != null && length(local.cluster_self.secret_manager_config) == 1 ? local.cluster_self.secret_manager_config[0].enabled : false # BETA features - cluster_output_pod_security_policy_enabled = google_container_cluster.primary.pod_security_policy_config != null && length(google_container_cluster.primary.pod_security_policy_config) == 1 ? google_container_cluster.primary.pod_security_policy_config[0].enabled : false + cluster_output_pod_security_policy_enabled = local.cluster_self.pod_security_policy_config != null && length(local.cluster_self.pod_security_policy_config) == 1 ? local.cluster_self.pod_security_policy_config[0].enabled : false # /BETA features - cluster_master_auth_list_layer1 = local.cluster_output_master_auth - cluster_master_auth_list_layer2 = local.cluster_master_auth_list_layer1[0] - cluster_master_auth_map = local.cluster_master_auth_list_layer2[0] + cluster_master_auth_map = local.cluster_output_master_auth[0][0] - cluster_location = google_container_cluster.primary.location + cluster_location = local.cluster_self.location cluster_region = var.regional ? var.region : join("-", slice(split("-", local.cluster_location), 0, 2)) - cluster_zones = sort(local.cluster_output_zones) + cluster_zones = sort(local.cluster_self.node_locations) // cluster ID is in the form project/location/name cluster_name_computed = element(split("/", local.cluster_id), length(split("/", local.cluster_id)) - 1) @@ -149,11 +148,10 @@ data "google_container_engine_versions" "region" { project = var.project_id } +// Only created for zonal clusters; regional clusters use region-level version lookup only (avoids zone-level API calls in environments that support only region-level location). data "google_container_engine_versions" "zone" { - // Work around to prevent a lack of zone declaration from causing regional cluster creation from erroring out due to error - // - // data.google_container_engine_versions.zone: Cannot determine zone: set in this resource, or set provider-level zone. - // + count = var.regional ? 0 : 1 + location = local.zone_count == 0 ? data.google_compute_zones.available[0].names[0] : var.zones[0] project = var.project_id } diff --git a/modules/beta-autopilot-private-cluster/outputs.tf b/modules/beta-autopilot-private-cluster/outputs.tf index ab49302f80..170e7e592f 100644 --- a/modules/beta-autopilot-private-cluster/outputs.tf +++ b/modules/beta-autopilot-private-cluster/outputs.tf @@ -32,6 +32,7 @@ output "name" { * to be up. */ google_container_cluster.primary, + google_container_cluster.primary_omit_nodelocs, ] } @@ -67,12 +68,13 @@ output "endpoint" { * to be up. */ google_container_cluster.primary, + google_container_cluster.primary_omit_nodelocs, ] } output "endpoint_dns" { description = "Cluster endpoint DNS" - value = google_container_cluster.primary.control_plane_endpoints_config[0].dns_endpoint_config[0].endpoint + value = local.cluster_self.control_plane_endpoints_config[0].dns_endpoint_config[0].endpoint depends_on = [ /* Nominally, the endpoint is populated as soon as it is known to Terraform. * However, the cluster may not be in a usable state yet. Therefore any @@ -81,6 +83,7 @@ output "endpoint_dns" { * to be up. */ google_container_cluster.primary, + google_container_cluster.primary_omit_nodelocs, ] } @@ -101,7 +104,7 @@ output "monitoring_service" { output "master_authorized_networks_config" { description = "Networks from which access to master is permitted" - value = google_container_cluster.primary.master_authorized_networks_config + value = local.cluster_self.master_authorized_networks_config } output "master_version" { @@ -152,20 +155,21 @@ output "identity_namespace" { description = "Workload Identity pool" value = length(local.cluster_workload_identity_config) > 0 ? local.cluster_workload_identity_config[0].workload_pool : null depends_on = [ - google_container_cluster.primary + google_container_cluster.primary, + google_container_cluster.primary_omit_nodelocs, ] } output "tpu_ipv4_cidr_block" { description = "The IP range in CIDR notation used for the TPUs" - value = var.enable_tpu ? google_container_cluster.primary.tpu_ipv4_cidr_block : null + value = var.enable_tpu ? local.cluster_self.tpu_ipv4_cidr_block : null } output "master_ipv4_cidr_block" { description = "The IP range in CIDR notation used for the hosted master network" - value = var.enable_private_nodes ? google_container_cluster.primary.private_cluster_config[0].master_ipv4_cidr_block : null + value = var.enable_private_nodes ? local.cluster_self.private_cluster_config[0].master_ipv4_cidr_block : null } output "peering_name" { diff --git a/modules/beta-autopilot-private-cluster/variables.tf b/modules/beta-autopilot-private-cluster/variables.tf index ab5f586141..9ce380d66d 100644 --- a/modules/beta-autopilot-private-cluster/variables.tf +++ b/modules/beta-autopilot-private-cluster/variables.tf @@ -50,6 +50,18 @@ variable "zones" { default = [] } +variable "cluster_location_override" { + type = string + description = "Optional override for the cluster's location attribute. When set, this value is used as the cluster location instead of the default (region for regional, first zone for zonal). Use when the API rejects the default location (e.g. backends that validate location as a zone). When set to the first zone while regional=true, node_locations are set to the remaining zones so the cluster is created as zonal." + default = null +} + +variable "omit_node_locations_for_regional" { + type = bool + description = "When true and regional=true, the cluster is created with node_locations=[] so the API accepts the request (e.g. when it rejects explicit zones at create). The API then populates zones automatically; lifecycle ignore_changes on node_locations avoids drift on subsequent apply. Use for backends that only accept region-level location at create." + default = false +} + variable "network" { type = string description = "The VPC network to host the cluster in (required)" diff --git a/modules/beta-autopilot-public-cluster/main.tf b/modules/beta-autopilot-public-cluster/main.tf index 7ae5bf9f72..1308df1a8a 100644 --- a/modules/beta-autopilot-public-cluster/main.tf +++ b/modules/beta-autopilot-public-cluster/main.tf @@ -44,9 +44,9 @@ locals { region = var.regional ? var.region : join("-", slice(split("-", var.zones[0]), 0, 2)) // for regional cluster - use var.zones if provided, use available otherwise, for zonal cluster use var.zones with first element extracted node_locations = var.regional ? coalescelist(compact(var.zones), try(sort(random_shuffle.available_zones[0].result), [])) : slice(var.zones, 1, length(var.zones)) - // Kubernetes version + // Kubernetes version: for regional clusters only region-level lookup is used; zone-level lookup is skipped to support environments where the API accepts only region-level location. master_version_regional = var.kubernetes_version != "latest" ? var.kubernetes_version : data.google_container_engine_versions.region.latest_master_version - master_version_zonal = var.kubernetes_version != "latest" ? var.kubernetes_version : data.google_container_engine_versions.zone.latest_master_version + master_version_zonal = var.kubernetes_version != "latest" ? var.kubernetes_version : (var.regional ? local.master_version_regional : data.google_container_engine_versions.zone[0].latest_master_version) master_version = var.regional ? local.master_version_regional : local.master_version_zonal fleet_membership = var.fleet_project != null ? google_container_cluster.primary.fleet[0].membership : null @@ -142,11 +142,10 @@ data "google_container_engine_versions" "region" { project = var.project_id } +// Only created for zonal clusters; regional clusters use region-level version lookup only (avoids zone-level API calls in environments that support only region-level location). data "google_container_engine_versions" "zone" { - // Work around to prevent a lack of zone declaration from causing regional cluster creation from erroring out due to error - // - // data.google_container_engine_versions.zone: Cannot determine zone: set in this resource, or set provider-level zone. - // + count = var.regional ? 0 : 1 + location = local.zone_count == 0 ? data.google_compute_zones.available[0].names[0] : var.zones[0] project = var.project_id } diff --git a/modules/beta-private-cluster-update-variant/main.tf b/modules/beta-private-cluster-update-variant/main.tf index 72f1d8fc17..3e7ef4d206 100644 --- a/modules/beta-private-cluster-update-variant/main.tf +++ b/modules/beta-private-cluster-update-variant/main.tf @@ -44,9 +44,9 @@ locals { region = var.regional ? var.region : join("-", slice(split("-", var.zones[0]), 0, 2)) // for regional cluster - use var.zones if provided, use available otherwise, for zonal cluster use var.zones with first element extracted node_locations = var.regional ? coalescelist(compact(var.zones), try(sort(random_shuffle.available_zones[0].result), [])) : slice(var.zones, 1, length(var.zones)) - // Kubernetes version + // Kubernetes version: for regional clusters only region-level lookup is used; zone-level lookup is skipped to support environments where the API accepts only region-level location. master_version_regional = var.kubernetes_version != "latest" ? var.kubernetes_version : data.google_container_engine_versions.region.latest_master_version - master_version_zonal = var.kubernetes_version != "latest" ? var.kubernetes_version : data.google_container_engine_versions.zone.latest_master_version + master_version_zonal = var.kubernetes_version != "latest" ? var.kubernetes_version : (var.regional ? local.master_version_regional : data.google_container_engine_versions.zone[0].latest_master_version) master_version = var.regional ? local.master_version_regional : local.master_version_zonal // Build a map of maps of node pools from a list of objects node_pools = { for np in var.node_pools : np.name => np } @@ -214,11 +214,10 @@ data "google_container_engine_versions" "region" { project = var.project_id } +// Only created for zonal clusters; regional clusters use region-level version lookup only (avoids zone-level API calls in environments that support only region-level location). data "google_container_engine_versions" "zone" { - // Work around to prevent a lack of zone declaration from causing regional cluster creation from erroring out due to error - // - // data.google_container_engine_versions.zone: Cannot determine zone: set in this resource, or set provider-level zone. - // + count = var.regional ? 0 : 1 + location = local.zone_count == 0 ? data.google_compute_zones.available[0].names[0] : var.zones[0] project = var.project_id } diff --git a/modules/beta-private-cluster/main.tf b/modules/beta-private-cluster/main.tf index 72f1d8fc17..3e7ef4d206 100644 --- a/modules/beta-private-cluster/main.tf +++ b/modules/beta-private-cluster/main.tf @@ -44,9 +44,9 @@ locals { region = var.regional ? var.region : join("-", slice(split("-", var.zones[0]), 0, 2)) // for regional cluster - use var.zones if provided, use available otherwise, for zonal cluster use var.zones with first element extracted node_locations = var.regional ? coalescelist(compact(var.zones), try(sort(random_shuffle.available_zones[0].result), [])) : slice(var.zones, 1, length(var.zones)) - // Kubernetes version + // Kubernetes version: for regional clusters only region-level lookup is used; zone-level lookup is skipped to support environments where the API accepts only region-level location. master_version_regional = var.kubernetes_version != "latest" ? var.kubernetes_version : data.google_container_engine_versions.region.latest_master_version - master_version_zonal = var.kubernetes_version != "latest" ? var.kubernetes_version : data.google_container_engine_versions.zone.latest_master_version + master_version_zonal = var.kubernetes_version != "latest" ? var.kubernetes_version : (var.regional ? local.master_version_regional : data.google_container_engine_versions.zone[0].latest_master_version) master_version = var.regional ? local.master_version_regional : local.master_version_zonal // Build a map of maps of node pools from a list of objects node_pools = { for np in var.node_pools : np.name => np } @@ -214,11 +214,10 @@ data "google_container_engine_versions" "region" { project = var.project_id } +// Only created for zonal clusters; regional clusters use region-level version lookup only (avoids zone-level API calls in environments that support only region-level location). data "google_container_engine_versions" "zone" { - // Work around to prevent a lack of zone declaration from causing regional cluster creation from erroring out due to error - // - // data.google_container_engine_versions.zone: Cannot determine zone: set in this resource, or set provider-level zone. - // + count = var.regional ? 0 : 1 + location = local.zone_count == 0 ? data.google_compute_zones.available[0].names[0] : var.zones[0] project = var.project_id } diff --git a/modules/beta-public-cluster-update-variant/main.tf b/modules/beta-public-cluster-update-variant/main.tf index a871fe370a..b624ba4317 100644 --- a/modules/beta-public-cluster-update-variant/main.tf +++ b/modules/beta-public-cluster-update-variant/main.tf @@ -44,9 +44,9 @@ locals { region = var.regional ? var.region : join("-", slice(split("-", var.zones[0]), 0, 2)) // for regional cluster - use var.zones if provided, use available otherwise, for zonal cluster use var.zones with first element extracted node_locations = var.regional ? coalescelist(compact(var.zones), try(sort(random_shuffle.available_zones[0].result), [])) : slice(var.zones, 1, length(var.zones)) - // Kubernetes version + // Kubernetes version: for regional clusters only region-level lookup is used; zone-level lookup is skipped to support environments where the API accepts only region-level location. master_version_regional = var.kubernetes_version != "latest" ? var.kubernetes_version : data.google_container_engine_versions.region.latest_master_version - master_version_zonal = var.kubernetes_version != "latest" ? var.kubernetes_version : data.google_container_engine_versions.zone.latest_master_version + master_version_zonal = var.kubernetes_version != "latest" ? var.kubernetes_version : (var.regional ? local.master_version_regional : data.google_container_engine_versions.zone[0].latest_master_version) master_version = var.regional ? local.master_version_regional : local.master_version_zonal // Build a map of maps of node pools from a list of objects node_pools = { for np in var.node_pools : np.name => np } @@ -207,11 +207,10 @@ data "google_container_engine_versions" "region" { project = var.project_id } +// Only created for zonal clusters; regional clusters use region-level version lookup only (avoids zone-level API calls in environments that support only region-level location). data "google_container_engine_versions" "zone" { - // Work around to prevent a lack of zone declaration from causing regional cluster creation from erroring out due to error - // - // data.google_container_engine_versions.zone: Cannot determine zone: set in this resource, or set provider-level zone. - // + count = var.regional ? 0 : 1 + location = local.zone_count == 0 ? data.google_compute_zones.available[0].names[0] : var.zones[0] project = var.project_id } diff --git a/modules/beta-public-cluster/main.tf b/modules/beta-public-cluster/main.tf index a871fe370a..b624ba4317 100644 --- a/modules/beta-public-cluster/main.tf +++ b/modules/beta-public-cluster/main.tf @@ -44,9 +44,9 @@ locals { region = var.regional ? var.region : join("-", slice(split("-", var.zones[0]), 0, 2)) // for regional cluster - use var.zones if provided, use available otherwise, for zonal cluster use var.zones with first element extracted node_locations = var.regional ? coalescelist(compact(var.zones), try(sort(random_shuffle.available_zones[0].result), [])) : slice(var.zones, 1, length(var.zones)) - // Kubernetes version + // Kubernetes version: for regional clusters only region-level lookup is used; zone-level lookup is skipped to support environments where the API accepts only region-level location. master_version_regional = var.kubernetes_version != "latest" ? var.kubernetes_version : data.google_container_engine_versions.region.latest_master_version - master_version_zonal = var.kubernetes_version != "latest" ? var.kubernetes_version : data.google_container_engine_versions.zone.latest_master_version + master_version_zonal = var.kubernetes_version != "latest" ? var.kubernetes_version : (var.regional ? local.master_version_regional : data.google_container_engine_versions.zone[0].latest_master_version) master_version = var.regional ? local.master_version_regional : local.master_version_zonal // Build a map of maps of node pools from a list of objects node_pools = { for np in var.node_pools : np.name => np } @@ -207,11 +207,10 @@ data "google_container_engine_versions" "region" { project = var.project_id } +// Only created for zonal clusters; regional clusters use region-level version lookup only (avoids zone-level API calls in environments that support only region-level location). data "google_container_engine_versions" "zone" { - // Work around to prevent a lack of zone declaration from causing regional cluster creation from erroring out due to error - // - // data.google_container_engine_versions.zone: Cannot determine zone: set in this resource, or set provider-level zone. - // + count = var.regional ? 0 : 1 + location = local.zone_count == 0 ? data.google_compute_zones.available[0].names[0] : var.zones[0] project = var.project_id } diff --git a/modules/private-cluster-update-variant/main.tf b/modules/private-cluster-update-variant/main.tf index 78bc2c61c9..59e75e1287 100644 --- a/modules/private-cluster-update-variant/main.tf +++ b/modules/private-cluster-update-variant/main.tf @@ -44,9 +44,9 @@ locals { region = var.regional ? var.region : join("-", slice(split("-", var.zones[0]), 0, 2)) // for regional cluster - use var.zones if provided, use available otherwise, for zonal cluster use var.zones with first element extracted node_locations = var.regional ? coalescelist(compact(var.zones), try(sort(random_shuffle.available_zones[0].result), [])) : slice(var.zones, 1, length(var.zones)) - // Kubernetes version + // Kubernetes version: for regional clusters only region-level lookup is used; zone-level lookup is skipped to support environments where the API accepts only region-level location. master_version_regional = var.kubernetes_version != "latest" ? var.kubernetes_version : data.google_container_engine_versions.region.latest_master_version - master_version_zonal = var.kubernetes_version != "latest" ? var.kubernetes_version : data.google_container_engine_versions.zone.latest_master_version + master_version_zonal = var.kubernetes_version != "latest" ? var.kubernetes_version : (var.regional ? local.master_version_regional : data.google_container_engine_versions.zone[0].latest_master_version) master_version = var.regional ? local.master_version_regional : local.master_version_zonal // Build a map of maps of node pools from a list of objects node_pools = { for np in var.node_pools : np.name => np } @@ -193,11 +193,10 @@ data "google_container_engine_versions" "region" { project = var.project_id } +// Only created for zonal clusters; regional clusters use region-level version lookup only (avoids zone-level API calls in environments that support only region-level location). data "google_container_engine_versions" "zone" { - // Work around to prevent a lack of zone declaration from causing regional cluster creation from erroring out due to error - // - // data.google_container_engine_versions.zone: Cannot determine zone: set in this resource, or set provider-level zone. - // + count = var.regional ? 0 : 1 + location = local.zone_count == 0 ? data.google_compute_zones.available[0].names[0] : var.zones[0] project = var.project_id } diff --git a/modules/private-cluster/main.tf b/modules/private-cluster/main.tf index 78bc2c61c9..59e75e1287 100644 --- a/modules/private-cluster/main.tf +++ b/modules/private-cluster/main.tf @@ -44,9 +44,9 @@ locals { region = var.regional ? var.region : join("-", slice(split("-", var.zones[0]), 0, 2)) // for regional cluster - use var.zones if provided, use available otherwise, for zonal cluster use var.zones with first element extracted node_locations = var.regional ? coalescelist(compact(var.zones), try(sort(random_shuffle.available_zones[0].result), [])) : slice(var.zones, 1, length(var.zones)) - // Kubernetes version + // Kubernetes version: for regional clusters only region-level lookup is used; zone-level lookup is skipped to support environments where the API accepts only region-level location. master_version_regional = var.kubernetes_version != "latest" ? var.kubernetes_version : data.google_container_engine_versions.region.latest_master_version - master_version_zonal = var.kubernetes_version != "latest" ? var.kubernetes_version : data.google_container_engine_versions.zone.latest_master_version + master_version_zonal = var.kubernetes_version != "latest" ? var.kubernetes_version : (var.regional ? local.master_version_regional : data.google_container_engine_versions.zone[0].latest_master_version) master_version = var.regional ? local.master_version_regional : local.master_version_zonal // Build a map of maps of node pools from a list of objects node_pools = { for np in var.node_pools : np.name => np } @@ -193,11 +193,10 @@ data "google_container_engine_versions" "region" { project = var.project_id } +// Only created for zonal clusters; regional clusters use region-level version lookup only (avoids zone-level API calls in environments that support only region-level location). data "google_container_engine_versions" "zone" { - // Work around to prevent a lack of zone declaration from causing regional cluster creation from erroring out due to error - // - // data.google_container_engine_versions.zone: Cannot determine zone: set in this resource, or set provider-level zone. - // + count = var.regional ? 0 : 1 + location = local.zone_count == 0 ? data.google_compute_zones.available[0].names[0] : var.zones[0] project = var.project_id }