diff --git a/.changelog/6412.txt b/.changelog/6412.txt
new file mode 100644
index 00000000000..5096693852b
--- /dev/null
+++ b/.changelog/6412.txt
@@ -0,0 +1,3 @@
+```release-note:enhancement
+container: Added general field `reservation_affinity` to `google_container_node_pool`
+```
diff --git a/google/node_config.go b/google/node_config.go
index bd6cbf2b95d..680db1a16db 100644
--- a/google/node_config.go
+++ b/google/node_config.go
@@ -188,7 +188,39 @@ func schemaNodeConfig() *schema.Schema {
Default: false,
Description: `Whether the nodes are created as preemptible VM instances.`,
},
-
+ "reservation_affinity": {
+ Type: schema.TypeList,
+ Optional: true,
+ MaxItems: 1,
+ Description: `The reservation affinity configuration for the node pool.`,
+ ForceNew: true,
+ Elem: &schema.Resource{
+ Schema: map[string]*schema.Schema{
+ "consume_reservation_type": {
+ Type: schema.TypeString,
+ Required: true,
+ ForceNew: true,
+ Description: `Corresponds to the type of reservation consumption.`,
+ ValidateFunc: validation.StringInSlice([]string{"UNSPECIFIED", "NO_RESERVATION", "ANY_RESERVATION", "SPECIFIC_RESERVATION"}, false),
+ },
+ "key": {
+ Type: schema.TypeString,
+ Optional: true,
+ ForceNew: true,
+ Description: `The label key of a reservation resource.`,
+ },
+ "values": {
+ Type: schema.TypeSet,
+ Description: "The label values of the reservation resource.",
+ ForceNew: true,
+ Optional: true,
+ Elem: &schema.Schema{
+ Type: schema.TypeString,
+ },
+ },
+ },
+ },
+ },
"spot": {
Type: schema.TypeBool,
Optional: true,
@@ -369,6 +401,21 @@ func expandNodeConfig(v interface{}) *container.NodeConfig {
}
}
+ if v, ok := nodeConfig["reservation_affinity"]; ok && len(v.([]interface{})) > 0 {
+ conf := v.([]interface{})[0].(map[string]interface{})
+ valuesSet := conf["values"].(*schema.Set)
+ values := make([]string, valuesSet.Len())
+ for i, value := range valuesSet.List() {
+ values[i] = value.(string)
+ }
+
+ nc.ReservationAffinity = &container.ReservationAffinity{
+ ConsumeReservationType: conf["consume_reservation_type"].(string),
+ Key: conf["key"].(string),
+ Values: values,
+ }
+ }
+
if scopes, ok := nodeConfig["oauth_scopes"]; ok {
scopesSet := scopes.(*schema.Set)
scopes := make([]string, scopesSet.Len())
@@ -496,6 +543,7 @@ func flattenNodeConfig(c *container.NodeConfig) []map[string]interface{} {
"local_ssd_count": c.LocalSsdCount,
"gcfs_config": flattenGcfsConfig(c.GcfsConfig),
"gvnic": flattenGvnic(c.Gvnic),
+ "reservation_affinity": flattenGKEReservationAffinity(c.ReservationAffinity),
"service_account": c.ServiceAccount,
"metadata": c.Metadata,
"image_type": c.ImageType,
@@ -561,6 +609,18 @@ func flattenGvnic(c *container.VirtualNIC) []map[string]interface{} {
return result
}
+func flattenGKEReservationAffinity(c *container.ReservationAffinity) []map[string]interface{} {
+ result := []map[string]interface{}{}
+ if c != nil {
+ result = append(result, map[string]interface{}{
+ "consume_reservation_type": c.ConsumeReservationType,
+ "key": c.Key,
+ "values": c.Values,
+ })
+ }
+ return result
+}
+
func flattenTaints(c []*container.NodeTaint) []map[string]interface{} {
result := []map[string]interface{}{}
for _, taint := range c {
diff --git a/google/resource_container_cluster_test.go b/google/resource_container_cluster_test.go
index a1a43fc182f..8a087270b3e 100755
--- a/google/resource_container_cluster_test.go
+++ b/google/resource_container_cluster_test.go
@@ -888,6 +888,69 @@ func TestAccContainerCluster_withNodeConfigShieldedInstanceConfig(t *testing.T)
})
}
+func TestAccContainerCluster_withNodeConfigReservationAffinity(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_withNodeConfigReservationAffinity(clusterName),
+ Check: resource.ComposeTestCheckFunc(
+ resource.TestCheckResourceAttr("google_container_cluster.with_node_config",
+ "node_config.0.reservation_affinity.#", "1"),
+ resource.TestCheckResourceAttr("google_container_cluster.with_node_config",
+ "node_config.0.reservation_affinity.0.consume_reservation_type", "ANY_RESERVATION"),
+ ),
+ },
+ {
+ ResourceName: "google_container_cluster.with_node_config",
+ ImportState: true,
+ ImportStateVerify: true,
+ },
+ },
+ })
+}
+
+func TestAccContainerCluster_withNodeConfigReservationAffinitySpecific(t *testing.T) {
+ t.Parallel()
+
+ reservationName := fmt.Sprintf("tf-test-reservation-%s", randString(t, 10))
+ 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_withNodeConfigReservationAffinitySpecific(reservationName, clusterName),
+ Check: resource.ComposeTestCheckFunc(
+ resource.TestCheckResourceAttr("google_container_cluster.with_node_config",
+ "node_config.0.reservation_affinity.#", "1"),
+ resource.TestCheckResourceAttr("google_container_cluster.with_node_config",
+ "node_config.0.reservation_affinity.0.consume_reservation_type", "SPECIFIC_RESERVATION"),
+ resource.TestCheckResourceAttr("google_container_cluster.with_node_config",
+ "node_config.0.reservation_affinity.0.key", "compute.googleapis.com/reservation-name"),
+ resource.TestCheckResourceAttr("google_container_cluster.with_node_config",
+ "node_config.0.reservation_affinity.0.values.#", "1"),
+ resource.TestCheckResourceAttr("google_container_cluster.with_node_config",
+ "node_config.0.reservation_affinity.0.values.0", reservationName),
+ ),
+ },
+ {
+ ResourceName: "google_container_cluster.with_node_config",
+ ImportState: true,
+ ImportStateVerify: true,
+ },
+ },
+ })
+}
+
func TestAccContainerCluster_withWorkloadMetadataConfig(t *testing.T) {
t.Parallel()
@@ -3112,6 +3175,116 @@ resource "google_container_cluster" "with_node_config" {
`, clusterName)
}
+func testAccContainerCluster_withNodeConfigReservationAffinity(clusterName string) string {
+ return fmt.Sprintf(`
+resource "google_container_cluster" "with_node_config" {
+ name = "%s"
+ location = "us-central1-f"
+ initial_node_count = 1
+
+ node_config {
+ machine_type = "e2-medium"
+ disk_size_gb = 15
+ disk_type = "pd-ssd"
+ oauth_scopes = [
+ "https://www.googleapis.com/auth/monitoring",
+ "https://www.googleapis.com/auth/compute",
+ "https://www.googleapis.com/auth/devstorage.read_only",
+ "https://www.googleapis.com/auth/logging.write",
+ ]
+ service_account = "default"
+ metadata = {
+ foo = "bar"
+ disable-legacy-endpoints = "true"
+ }
+ labels = {
+ foo = "bar"
+ }
+ tags = ["foo", "bar"]
+ preemptible = true
+
+ // Updatable fields
+ image_type = "COS_CONTAINERD"
+
+ reservation_affinity {
+ consume_reservation_type = "ANY_RESERVATION"
+ }
+ }
+}
+`, clusterName)
+}
+
+func testAccContainerCluster_withNodeConfigReservationAffinitySpecific(reservation, clusterName string) string {
+ return fmt.Sprintf(`
+
+resource "google_project_service" "compute" {
+ service = "compute.googleapis.com"
+ disable_on_destroy = false
+}
+
+resource "google_project_service" "container" {
+ service = "container.googleapis.com"
+ disable_on_destroy = false
+ depends_on = [google_project_service.compute]
+}
+
+
+resource "google_compute_reservation" "gce_reservation" {
+ name = "%s"
+ zone = "us-central1-f"
+
+ specific_reservation {
+ count = 1
+ instance_properties {
+ machine_type = "n1-standard-1"
+ }
+ }
+
+ specific_reservation_required = true
+ depends_on = [google_project_service.compute]
+}
+
+resource "google_container_cluster" "with_node_config" {
+ name = "%s"
+ location = "us-central1-f"
+ initial_node_count = 1
+
+ node_config {
+ machine_type = "n1-standard-1"
+ disk_size_gb = 15
+ disk_type = "pd-ssd"
+ oauth_scopes = [
+ "https://www.googleapis.com/auth/monitoring",
+ "https://www.googleapis.com/auth/compute",
+ "https://www.googleapis.com/auth/devstorage.read_only",
+ "https://www.googleapis.com/auth/logging.write",
+ ]
+ service_account = "default"
+ metadata = {
+ foo = "bar"
+ disable-legacy-endpoints = "true"
+ }
+ labels = {
+ foo = "bar"
+ }
+ tags = ["foo", "bar"]
+
+ // Updatable fields
+ image_type = "COS_CONTAINERD"
+
+ reservation_affinity {
+ consume_reservation_type = "SPECIFIC_RESERVATION"
+ key = "compute.googleapis.com/reservation-name"
+ values = [
+ google_compute_reservation.gce_reservation.name
+ ]
+ }
+ }
+ depends_on = [google_project_service.container]
+}
+`, reservation, clusterName)
+}
+
func testAccContainerCluster_withWorkloadMetadataConfig(clusterName string) string {
return fmt.Sprintf(`
data "google_container_engine_versions" "central1a" {
diff --git a/google/resource_container_node_pool_test.go b/google/resource_container_node_pool_test.go
index 9fd5870691e..c78fef270d2 100644
--- a/google/resource_container_node_pool_test.go
+++ b/google/resource_container_node_pool_test.go
@@ -189,6 +189,71 @@ func TestAccContainerNodePool_withNodeConfig(t *testing.T) {
})
}
+func TestAccContainerNodePool_withReservationAffinity(t *testing.T) {
+ t.Parallel()
+
+ cluster := fmt.Sprintf("tf-test-cluster-%s", randString(t, 10))
+ np := fmt.Sprintf("tf-test-np-%s", randString(t, 10))
+
+ vcrTest(t, resource.TestCase{
+ PreCheck: func() { testAccPreCheck(t) },
+ Providers: testAccProviders,
+ CheckDestroy: testAccCheckContainerClusterDestroyProducer(t),
+ Steps: []resource.TestStep{
+ {
+ Config: testAccContainerNodePool_withReservationAffinity(cluster, np),
+ Check: resource.ComposeTestCheckFunc(
+ resource.TestCheckResourceAttr("google_container_node_pool.with_reservation_affinity",
+ "node_config.0.reservation_affinity.#", "1"),
+ resource.TestCheckResourceAttr("google_container_node_pool.with_reservation_affinity",
+ "node_config.0.reservation_affinity.0.consume_reservation_type", "ANY_RESERVATION"),
+ ),
+ },
+ {
+ ResourceName: "google_container_node_pool.with_reservation_affinity",
+ ImportState: true,
+ ImportStateVerify: true,
+ },
+ },
+ })
+}
+
+func TestAccContainerNodePool_withReservationAffinitySpecific(t *testing.T) {
+ t.Parallel()
+
+ cluster := fmt.Sprintf("tf-test-cluster-%s", randString(t, 10))
+ reservation := fmt.Sprintf("tf-test-reservation-%s", randString(t, 10))
+ np := fmt.Sprintf("tf-test-np-%s", randString(t, 10))
+
+ vcrTest(t, resource.TestCase{
+ PreCheck: func() { testAccPreCheck(t) },
+ Providers: testAccProviders,
+ CheckDestroy: testAccCheckContainerClusterDestroyProducer(t),
+ Steps: []resource.TestStep{
+ {
+ Config: testAccContainerNodePool_withReservationAffinitySpecific(cluster, reservation, np),
+ Check: resource.ComposeTestCheckFunc(
+ resource.TestCheckResourceAttr("google_container_node_pool.with_reservation_affinity",
+ "node_config.0.reservation_affinity.#", "1"),
+ resource.TestCheckResourceAttr("google_container_node_pool.with_reservation_affinity",
+ "node_config.0.reservation_affinity.0.consume_reservation_type", "SPECIFIC_RESERVATION"),
+ resource.TestCheckResourceAttr("google_container_node_pool.with_reservation_affinity",
+ "node_config.0.reservation_affinity.0.key", "compute.googleapis.com/reservation-name"),
+ resource.TestCheckResourceAttr("google_container_node_pool.with_reservation_affinity",
+ "node_config.0.reservation_affinity.0.values.#", "1"),
+ resource.TestCheckResourceAttr("google_container_node_pool.with_reservation_affinity",
+ "node_config.0.reservation_affinity.0.values.0", reservation),
+ ),
+ },
+ {
+ ResourceName: "google_container_node_pool.with_reservation_affinity",
+ ImportState: true,
+ ImportStateVerify: true,
+ },
+ },
+ })
+}
+
func TestAccContainerNodePool_withWorkloadIdentityConfig(t *testing.T) {
t.Parallel()
@@ -1224,6 +1289,88 @@ resource "google_container_node_pool" "np_with_node_config" {
`, cluster, nodePool)
}
+func testAccContainerNodePool_withReservationAffinity(cluster, np string) string {
+ return fmt.Sprintf(`
+data "google_container_engine_versions" "central1a" {
+ location = "us-central1-a"
+}
+
+resource "google_container_cluster" "cluster" {
+ name = "%s"
+ location = "us-central1-a"
+ initial_node_count = 1
+ min_master_version = data.google_container_engine_versions.central1a.latest_master_version
+}
+
+resource "google_container_node_pool" "with_reservation_affinity" {
+ name = "%s"
+ location = "us-central1-a"
+ cluster = google_container_cluster.cluster.name
+ initial_node_count = 1
+ node_config {
+ machine_type = "n1-standard-1"
+ oauth_scopes = [
+ "https://www.googleapis.com/auth/logging.write",
+ "https://www.googleapis.com/auth/monitoring",
+ ]
+ reservation_affinity {
+ consume_reservation_type = "ANY_RESERVATION"
+ }
+ }
+}
+`, cluster, np)
+}
+
+func testAccContainerNodePool_withReservationAffinitySpecific(cluster, reservation, np string) string {
+ return fmt.Sprintf(`
+data "google_container_engine_versions" "central1a" {
+ location = "us-central1-a"
+}
+
+resource "google_container_cluster" "cluster" {
+ name = "%s"
+ location = "us-central1-a"
+ initial_node_count = 1
+ min_master_version = data.google_container_engine_versions.central1a.latest_master_version
+}
+
+resource "google_compute_reservation" "gce_reservation" {
+ name = "%s"
+ zone = "us-central1-a"
+
+ specific_reservation {
+ count = 1
+ instance_properties {
+ machine_type = "n1-standard-1"
+ }
+ }
+
+ specific_reservation_required = true
+}
+
+resource "google_container_node_pool" "with_reservation_affinity" {
+ name = "%s"
+ location = "us-central1-a"
+ cluster = google_container_cluster.cluster.name
+ initial_node_count = 1
+ node_config {
+ machine_type = "n1-standard-1"
+ oauth_scopes = [
+ "https://www.googleapis.com/auth/logging.write",
+ "https://www.googleapis.com/auth/monitoring",
+ ]
+ reservation_affinity {
+ consume_reservation_type = "SPECIFIC_RESERVATION"
+ key = "compute.googleapis.com/reservation-name"
+ values = [
+ google_compute_reservation.gce_reservation.name
+ ]
+ }
+ }
+}
+`, cluster, reservation, np)
+}
+
func testAccContainerNodePool_withWorkloadMetadataConfig(cluster, np string) string {
return fmt.Sprintf(`
data "google_container_engine_versions" "central1a" {
diff --git a/website/docs/r/container_cluster.html.markdown b/website/docs/r/container_cluster.html.markdown
index 7ec84544a48..203854b02e7 100755
--- a/website/docs/r/container_cluster.html.markdown
+++ b/website/docs/r/container_cluster.html.markdown
@@ -747,6 +747,8 @@ gvnic {
are preemptible. See the [official documentation](https://cloud.google.com/container-engine/docs/preemptible-vm)
for more information. Defaults to false.
+* `reservation_affinity` (Optional) The configuration of the desired reservation which instances could take capacity from. Structure is [documented below](#nested_reservation_affinity).
+
* `spot` - (Optional) A boolean that represents whether the underlying node VMs are spot.
See the [official documentation](https://cloud.google.com/kubernetes-engine/docs/concepts/spot-vms)
for more information. Defaults to false.
@@ -927,6 +929,19 @@ recommended that you omit the block entirely if the field is not set to `true`.
* `enabled` (Optional) - Whether the cluster master is accessible globally or
not.
+The `reservation_affinity` block supports:
+
+* `consume_reservation_type` (Required) The type of reservation consumption
+ Accepted values are:
+
+ * `"UNSPECIFIED"`: Default value. This should not be used.
+ * `"NO_RESERVATION"`: Do not consume from any reserved capacity.
+ * `"ANY_RESERVATION"`: Consume any reservation available.
+ * `"SPECIFIC_RESERVATION"`: Must consume from a specific reservation. Must specify key value fields for specifying the reservations.
+* `key` (Optional) The label key of a reservation resource. To target a SPECIFIC_RESERVATION by name, specify "compute.googleapis.com/reservation-name" as the key and specify the name of your reservation as its value.
+* `values` (Optional) The list of label values of reservation resources. For example: the name of the specific reservation when using a key of "compute.googleapis.com/reservation-name"
+
+
The `sandbox_config` block supports:
* `sandbox_type` (Required) Which sandbox to use for pods in the node pool.