diff --git a/google/compute_instance_helpers.go b/google/compute_instance_helpers.go index a1b8e0e3..b42b0035 100644 --- a/google/compute_instance_helpers.go +++ b/google/compute_instance_helpers.go @@ -4,9 +4,36 @@ import ( "fmt" "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform/helper/validation" computeBeta "google.golang.org/api/compute/v0.beta" + "google.golang.org/api/googleapi" ) +func instanceSchedulingNodeAffinitiesElemSchema() *schema.Resource { + return &schema.Resource{ + Schema: map[string]*schema.Schema{ + "key": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "operator": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.StringInSlice([]string{"IN", "NOT"}, false), + }, + "values": { + Type: schema.TypeSet, + Required: true, + ForceNew: true, + Elem: &schema.Schema{Type: schema.TypeString}, + Set: schema.HashString, + }, + }, + } +} + func expandAliasIpRanges(ranges []interface{}) []*computeBeta.AliasIpRange { ipRanges := make([]*computeBeta.AliasIpRange, 0, len(ranges)) for _, raw := range ranges { @@ -30,17 +57,89 @@ func flattenAliasIpRange(ranges []*computeBeta.AliasIpRange) []map[string]interf return rangesSchema } -func flattenScheduling(scheduling *computeBeta.Scheduling) []map[string]interface{} { - result := make([]map[string]interface{}, 0, 1) +func expandScheduling(v interface{}) (*computeBeta.Scheduling, error) { + if v == nil { + // We can't set default values for lists. + return &computeBeta.Scheduling{ + AutomaticRestart: googleapi.Bool(true), + }, nil + } + + ls := v.([]interface{}) + if len(ls) == 0 { + // We can't set default values for lists + return &computeBeta.Scheduling{ + AutomaticRestart: googleapi.Bool(true), + }, nil + } + + if len(ls) > 1 || ls[0] == nil { + return nil, fmt.Errorf("expected exactly one scheduling block") + } + + original := ls[0].(map[string]interface{}) + scheduling := &computeBeta.Scheduling{ + ForceSendFields: make([]string, 0, 4), + } + + if v, ok := original["automatic_restart"]; ok { + scheduling.AutomaticRestart = googleapi.Bool(v.(bool)) + scheduling.ForceSendFields = append(scheduling.ForceSendFields, "AutomaticRestart") + } + + if v, ok := original["preemptible"]; ok { + scheduling.Preemptible = v.(bool) + scheduling.ForceSendFields = append(scheduling.ForceSendFields, "Preemptible") + + } + + if v, ok := original["on_host_maintenance"]; ok { + scheduling.OnHostMaintenance = v.(string) + scheduling.ForceSendFields = append(scheduling.ForceSendFields, "OnHostMaintenance") + } + + if v, ok := original["node_affinities"]; ok && v != nil { + naSet := v.(*schema.Set).List() + scheduling.NodeAffinities = make([]*computeBeta.SchedulingNodeAffinity, len(ls)) + scheduling.ForceSendFields = append(scheduling.ForceSendFields, "NodeAffinities") + for _, nodeAffRaw := range naSet { + if nodeAffRaw == nil { + continue + } + nodeAff := nodeAffRaw.(map[string]interface{}) + tranformed := &computeBeta.SchedulingNodeAffinity{ + Key: nodeAff["key"].(string), + Operator: nodeAff["operator"].(string), + Values: convertStringArr(nodeAff["values"].(*schema.Set).List()), + } + scheduling.NodeAffinities = append(scheduling.NodeAffinities, tranformed) + } + } + + return scheduling, nil +} + +func flattenScheduling(resp *computeBeta.Scheduling) []map[string]interface{} { schedulingMap := map[string]interface{}{ - "on_host_maintenance": scheduling.OnHostMaintenance, - "preemptible": scheduling.Preemptible, + "on_host_maintenance": resp.OnHostMaintenance, + "preemptible": resp.Preemptible, } - if scheduling.AutomaticRestart != nil { - schedulingMap["automatic_restart"] = *scheduling.AutomaticRestart + + if resp.AutomaticRestart != nil { + schedulingMap["automatic_restart"] = *resp.AutomaticRestart } - result = append(result, schedulingMap) - return result + + nodeAffinities := schema.NewSet(schema.HashResource(instanceSchedulingNodeAffinitiesElemSchema()), nil) + for _, na := range resp.NodeAffinities { + nodeAffinities.Add(map[string]interface{}{ + "key": na.Key, + "operator": na.Operator, + "values": schema.NewSet(schema.HashString, convertStringArrToInterface(na.Values)), + }) + } + schedulingMap["node_affinities"] = nodeAffinities + + return []map[string]interface{}{schedulingMap} } func flattenAccessConfigs(accessConfigs []*computeBeta.AccessConfig) ([]map[string]interface{}, string) { diff --git a/google/resource_compute_instance.go b/google/resource_compute_instance.go index 980e36a9..37c3705f 100644 --- a/google/resource_compute_instance.go +++ b/google/resource_compute_instance.go @@ -16,7 +16,6 @@ import ( "github.com/mitchellh/hashstructure" computeBeta "google.golang.org/api/compute/v0.beta" "google.golang.org/api/compute/v1" - "google.golang.org/api/googleapi" ) func resourceComputeInstance() *schema.Resource { @@ -441,6 +440,14 @@ func resourceComputeInstance() *schema.Resource { Default: false, ForceNew: true, }, + + "node_affinities": { + Type: schema.TypeSet, + Optional: true, + ForceNew: true, + Elem: instanceSchedulingNodeAffinitiesElemSchema(), + DiffSuppressFunc: emptyOrDefaultStringSuppress(""), + }, }, }, }, @@ -625,22 +632,9 @@ func expandComputeInstance(project string, zone *compute.Zone, d *schema.Resourc disks = append(disks, disk) } - sch := d.Get("scheduling").([]interface{}) - var scheduling *computeBeta.Scheduling - if len(sch) == 0 { - // TF doesn't do anything about defaults inside of nested objects, so if - // scheduling hasn't been set, then send it with its default values. - scheduling = &computeBeta.Scheduling{ - AutomaticRestart: googleapi.Bool(true), - } - } else { - prefix := "scheduling.0" - scheduling = &computeBeta.Scheduling{ - AutomaticRestart: googleapi.Bool(d.Get(prefix + ".automatic_restart").(bool)), - Preemptible: d.Get(prefix + ".preemptible").(bool), - OnHostMaintenance: d.Get(prefix + ".on_host_maintenance").(string), - ForceSendFields: []string{"AutomaticRestart", "Preemptible"}, - } + scheduling, err := expandScheduling(d.Get("scheduling")) + if err != nil { + return nil, fmt.Errorf("Error creating scheduling: %s", err) } metadata, err := resourceInstanceMetadata(d) @@ -1005,22 +999,20 @@ func resourceComputeInstanceUpdate(d *schema.ResourceData, meta interface{}) err } if d.HasChange("scheduling") { - prefix := "scheduling.0" - scheduling := &compute.Scheduling{ - AutomaticRestart: googleapi.Bool(d.Get(prefix + ".automatic_restart").(bool)), - Preemptible: d.Get(prefix + ".preemptible").(bool), - OnHostMaintenance: d.Get(prefix + ".on_host_maintenance").(string), - ForceSendFields: []string{"AutomaticRestart", "Preemptible"}, + scheduling, err := expandScheduling(d.Get("scheduling")) + if err != nil { + return fmt.Errorf("Error creating request data to update scheduling: %s", err) } - op, err := config.clientCompute.Instances.SetScheduling(project, - zone, d.Id(), scheduling).Do() - + op, err := config.clientComputeBeta.Instances.SetScheduling( + project, zone, d.Id(), scheduling).Do() if err != nil { return fmt.Errorf("Error updating scheduling policy: %s", err) } - opErr := computeOperationWaitTime(config.clientCompute, op, project, "scheduling policy update", int(d.Timeout(schema.TimeoutUpdate).Minutes())) + opErr := computeBetaOperationWaitTime( + config.clientCompute, op, project, "scheduling policy update", + int(d.Timeout(schema.TimeoutUpdate).Minutes())) if opErr != nil { return opErr } diff --git a/google/resource_compute_instance_template.go b/google/resource_compute_instance_template.go index d316005c..1bc81f00 100644 --- a/google/resource_compute_instance_template.go +++ b/google/resource_compute_instance_template.go @@ -8,7 +8,6 @@ import ( "github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/helper/validation" computeBeta "google.golang.org/api/compute/v0.beta" - "google.golang.org/api/googleapi" ) func resourceComputeInstanceTemplate() *schema.Resource { @@ -320,6 +319,7 @@ func resourceComputeInstanceTemplate() *schema.Resource { Optional: true, Computed: true, ForceNew: true, + MaxItems: 1, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "preemptible": { @@ -342,6 +342,14 @@ func resourceComputeInstanceTemplate() *schema.Resource { Computed: true, ForceNew: true, }, + + "node_affinities": { + Type: schema.TypeSet, + Optional: true, + ForceNew: true, + Elem: instanceSchedulingNodeAffinitiesElemSchema(), + DiffSuppressFunc: emptyOrDefaultStringSuppress(""), + }, }, }, }, @@ -604,70 +612,40 @@ func resourceComputeInstanceTemplateCreate(d *schema.ResourceData, meta interfac return err } - instanceProperties := &computeBeta.InstanceProperties{ - CanIpForward: d.Get("can_ip_forward").(bool), - Description: d.Get("instance_description").(string), - MachineType: d.Get("machine_type").(string), - MinCpuPlatform: d.Get("min_cpu_platform").(string), - } - disks, err := buildDisks(d, config) if err != nil { return err } - instanceProperties.Disks = disks metadata, err := resourceInstanceMetadata(d) if err != nil { return err } - instanceProperties.Metadata = metadata + networks, err := expandNetworkInterfaces(d, config) if err != nil { return err } - instanceProperties.NetworkInterfaces = networks - instanceProperties.Scheduling = &computeBeta.Scheduling{} - instanceProperties.Scheduling.OnHostMaintenance = "MIGRATE" - - forceSendFieldsScheduling := make([]string, 0, 3) - var hasSendMaintenance bool - hasSendMaintenance = false - if v, ok := d.GetOk("scheduling"); ok { - _schedulings := v.([]interface{}) - if len(_schedulings) > 1 { - return fmt.Errorf("Error, at most one `scheduling` block can be defined") - } - _scheduling := _schedulings[0].(map[string]interface{}) - - // "automatic_restart" has a default value and is always safe to dereference - automaticRestart := _scheduling["automatic_restart"].(bool) - instanceProperties.Scheduling.AutomaticRestart = googleapi.Bool(automaticRestart) - forceSendFieldsScheduling = append(forceSendFieldsScheduling, "AutomaticRestart") - - if vp, okp := _scheduling["on_host_maintenance"]; okp { - instanceProperties.Scheduling.OnHostMaintenance = vp.(string) - forceSendFieldsScheduling = append(forceSendFieldsScheduling, "OnHostMaintenance") - hasSendMaintenance = true - } - - if vp, okp := _scheduling["preemptible"]; okp { - instanceProperties.Scheduling.Preemptible = vp.(bool) - forceSendFieldsScheduling = append(forceSendFieldsScheduling, "Preemptible") - if vp.(bool) && !hasSendMaintenance { - instanceProperties.Scheduling.OnHostMaintenance = "TERMINATE" - forceSendFieldsScheduling = append(forceSendFieldsScheduling, "OnHostMaintenance") - } - } + scheduling, err := expandResourceComputeInstanceTemplateScheduling(d, config) + if err != nil { + return err } - instanceProperties.Scheduling.ForceSendFields = forceSendFieldsScheduling - instanceProperties.ServiceAccounts = expandServiceAccounts(d.Get("service_account").([]interface{})) + instanceProperties := &computeBeta.InstanceProperties{ + CanIpForward: d.Get("can_ip_forward").(bool), + Description: d.Get("instance_description").(string), + GuestAccelerators: expandInstanceTemplateGuestAccelerators(d, config), + MachineType: d.Get("machine_type").(string), + MinCpuPlatform: d.Get("min_cpu_platform").(string), + Disks: disks, + Metadata: metadata, + NetworkInterfaces: networks, + Scheduling: scheduling, + ServiceAccounts: expandServiceAccounts(d.Get("service_account").([]interface{})), + Tags: resourceInstanceTags(d), + } - instanceProperties.GuestAccelerators = expandInstanceTemplateGuestAccelerators(d, config) - - instanceProperties.Tags = resourceInstanceTags(d) if _, ok := d.GetOk("labels"); ok { instanceProperties.Labels = expandLabels(d) } @@ -888,3 +866,27 @@ func resourceComputeInstanceTemplateDelete(d *schema.ResourceData, meta interfac d.SetId("") return nil } + +// This wraps the general compute instance helper expandScheduling. +// Default value of OnHostMaintenance depends on the value of Preemptible, +// so we can't set a default in schema +func expandResourceComputeInstanceTemplateScheduling(d *schema.ResourceData, meta interface{}) (*computeBeta.Scheduling, error) { + v, ok := d.GetOk("scheduling") + if !ok || v == nil { + // We can't set defaults for lists (e.g. scheduling) + return &computeBeta.Scheduling{ + OnHostMaintenance: "MIGRATE", + }, nil + } + + expanded, err := expandScheduling(v) + if err != nil { + return nil, err + } + + // Make sure we have an appropriate value for OnHostMaintenance if Preemptible + if expanded.Preemptible && expanded.OnHostMaintenance == "" { + expanded.OnHostMaintenance = "TERMINATE" + } + return expanded, nil +} diff --git a/google/resource_compute_instance_template_test.go b/google/resource_compute_instance_template_test.go index c729623d..bd43e757 100644 --- a/google/resource_compute_instance_template_test.go +++ b/google/resource_compute_instance_template_test.go @@ -173,6 +173,7 @@ func TestAccComputeInstanceTemplate_networkIP(t *testing.T) { }, }) } + func TestAccComputeInstanceTemplate_networkIPAddress(t *testing.T) { t.Parallel() @@ -345,6 +346,7 @@ func TestAccComputeInstanceTemplate_metadata_startup_script(t *testing.T) { }, }) } + func TestAccComputeInstanceTemplate_primaryAliasIpRange(t *testing.T) { t.Parallel() @@ -498,6 +500,26 @@ func TestAccComputeInstanceTemplate_EncryptKMS(t *testing.T) { }) } +func TestAccComputeInstanceTemplate_soleTenantNodeAffinities(t *testing.T) { + t.Parallel() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeInstanceTemplateDestroy, + Steps: []resource.TestStep{ + { + Config: testAccComputeInstanceTemplate_soleTenantInstanceTemplate(), + }, + { + ResourceName: "google_compute_instance_template.foobar", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + func testAccCheckComputeInstanceTemplateDestroy(s *terraform.State) error { config := testAccProvider.Meta().(*Config) @@ -1449,3 +1471,40 @@ resource "google_compute_instance_template" "foobar" { } }`, acctest.RandString(10), kmsLink) } + +func testAccComputeInstanceTemplate_soleTenantInstanceTemplate() string { + return fmt.Sprintf(` +data "google_compute_image" "my_image" { + family = "debian-9" + project = "debian-cloud" +} + +resource "google_compute_instance_template" "foobar" { + name = "instancet-test-%s" + machine_type = "n1-standard-1" + + disk { + source_image = "${data.google_compute_image.my_image.self_link}" + auto_delete = true + boot = true + } + + network_interface { + network = "default" + } + + scheduling { + preemptible = false + automatic_restart = true + node_affinities { + key = "tfacc" + operator = "IN" + values = ["testinstancetemplate"] + } + } + + service_account { + scopes = ["userinfo-email", "compute-ro", "storage-ro"] + } +}`, acctest.RandString(10)) +} diff --git a/google/resource_compute_instance_test.go b/google/resource_compute_instance_test.go index 827ebd24..0b6b24cd 100644 --- a/google/resource_compute_instance_test.go +++ b/google/resource_compute_instance_test.go @@ -615,7 +615,7 @@ func TestAccComputeInstance_stopInstanceToUpdate(t *testing.T) { }) } -func TestAccComputeInstance_service_account(t *testing.T) { +func TestAccComputeInstance_serviceAccount(t *testing.T) { t.Parallel() var instance compute.Instance @@ -627,7 +627,7 @@ func TestAccComputeInstance_service_account(t *testing.T) { CheckDestroy: testAccCheckComputeInstanceDestroy, Steps: []resource.TestStep{ { - Config: testAccComputeInstance_service_account(instanceName), + Config: testAccComputeInstance_serviceAccount(instanceName), Check: resource.ComposeTestCheckFunc( testAccCheckComputeInstanceExists( "google_compute_instance.foobar", &instance), @@ -663,6 +663,38 @@ func TestAccComputeInstance_scheduling(t *testing.T) { ), }, computeInstanceImportStep("us-central1-a", instanceName, []string{}), + { + Config: testAccComputeInstance_schedulingUpdated(instanceName), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeInstanceExists( + "google_compute_instance.foobar", &instance), + ), + }, + computeInstanceImportStep("us-central1-a", instanceName, []string{}), + }, + }) +} + +func TestAccComputeInstance_soleTenantNodeAffinities(t *testing.T) { + t.Parallel() + + var instanceName = fmt.Sprintf("soletenanttest-%s", acctest.RandString(10)) + var templateName = fmt.Sprintf("nodetmpl-%s", acctest.RandString(10)) + var groupName = fmt.Sprintf("nodegroup-%s", acctest.RandString(10)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeInstanceDestroy, + Steps: []resource.TestStep{ + { + Config: testAccComputeInstance_soleTenantNodeAffinities(instanceName, templateName, groupName), + }, + computeInstanceImportStep("us-central1-a", instanceName, []string{}), + { + Config: testAccComputeInstance_soleTenantNodeAffinitiesUpdated(instanceName, templateName, groupName), + }, + computeInstanceImportStep("us-central1-a", instanceName, []string{}), }, }) } @@ -2378,7 +2410,7 @@ resource "google_compute_instance" "scratch" { `, instance) } -func testAccComputeInstance_service_account(instance string) string { +func testAccComputeInstance_serviceAccount(instance string) string { return fmt.Sprintf(` data "google_compute_image" "my_image" { family = "debian-9" @@ -2440,6 +2472,36 @@ resource "google_compute_instance" "foobar" { `, instance) } +func testAccComputeInstance_schedulingUpdated(instance string) string { + return fmt.Sprintf(` +data "google_compute_image" "my_image" { + family = "debian-9" + project = "debian-cloud" +} + +resource "google_compute_instance" "foobar" { + name = "%s" + machine_type = "n1-standard-1" + zone = "us-central1-a" + + boot_disk { + initialize_params{ + image = "${data.google_compute_image.my_image.self_link}" + } + } + + network_interface { + network = "default" + } + + scheduling { + automatic_restart = false + preemptible = true + } +} +`, instance) +} + func testAccComputeInstance_subnet_auto(instance string) string { return fmt.Sprintf(` data "google_compute_image" "my_image" { @@ -3055,3 +3117,129 @@ resource "google_compute_instance" "foobar" { } `, instance) } + +func testAccComputeInstance_soleTenantNodeAffinities(instance, nodeTemplate, nodeGroup string) string { + return fmt.Sprintf(` +data "google_compute_image" "my_image" { + family = "debian-9" + project = "debian-cloud" +} + +resource "google_compute_instance" "foobar" { + name = "%s" + machine_type = "n1-standard-2" + zone = "us-central1-a" + + boot_disk { + initialize_params { + image = "${data.google_compute_image.my_image.self_link}" + } + } + + network_interface { + network = "default" + } + + scheduling { + node_affinities { + key = "tfacc" + operator = "IN" + values = ["test"] + } + + node_affinities { + key = "compute.googleapis.com/node-group-name" + operator = "IN" + values = ["${google_compute_node_group.nodes.name}"] + } + } +} + +data "google_compute_node_types" "central1a" { + zone = "us-central1-a" +} + + +resource "google_compute_node_template" "nodetmpl" { + name = "%s" + region = "us-central1" + + node_affinity_labels = { + tfacc = "test" + } + + node_type = "${data.google_compute_node_types.central1a.names[0]}" +} + +resource "google_compute_node_group" "nodes" { + name = "%s" + zone = "us-central1-a" + + size = 1 + node_template = "${google_compute_node_template.nodetmpl.self_link}" +} +`, instance, nodeTemplate, nodeGroup) +} + +func testAccComputeInstance_soleTenantNodeAffinitiesUpdated(instance, nodeTemplate, nodeGroup string) string { + return fmt.Sprintf(` +data "google_compute_image" "my_image" { + family = "debian-9" + project = "debian-cloud" +} + +resource "google_compute_instance" "foobar" { + name = "%s" + machine_type = "n1-standard-2" + zone = "us-central1-a" + + boot_disk { + initialize_params { + image = "${data.google_compute_image.my_image.self_link}" + } + } + + network_interface { + network = "default" + } + + scheduling { + node_affinities { + key = "tfacc" + operator = "IN" + values = ["test", "updatedlabel"] + } + + node_affinities { + key = "compute.googleapis.com/node-group-name" + operator = "IN" + values = ["${google_compute_node_group.nodes.name}"] + } + } +} + +data "google_compute_node_types" "central1a" { + zone = "us-central1-a" +} + + +resource "google_compute_node_template" "nodetmpl" { + name = "%s" + region = "us-central1" + + node_affinity_labels = { + tfacc = "test" + } + + node_type = "${data.google_compute_node_types.central1a.names[0]}" +} + +resource "google_compute_node_group" "nodes" { + name = "%s" + zone = "us-central1-a" + + size = 1 + node_template = "${google_compute_node_template.nodetmpl.self_link}" +} +`, instance, nodeTemplate, nodeGroup) +} diff --git a/website/docs/r/compute_instance.html.markdown b/website/docs/r/compute_instance.html.markdown index 45c1f35e..5a706ef6 100644 --- a/website/docs/r/compute_instance.html.markdown +++ b/website/docs/r/compute_instance.html.markdown @@ -273,12 +273,27 @@ The `scheduling` block supports: restarted if it was terminated by Compute Engine (not a user). Defaults to true. +* `node_affinities` - (Optional) Specifies node affinities or anti-affinities + to determine which sole-tenant nodes your instances and managed instance + groups will use as host systems. Read more on sole-tenant node creation + [here](https://cloud.google.com/compute/docs/nodes/create-nodes). + Structure documented below. + The `guest_accelerator` block supports: * `type` (Required) - The accelerator type resource to expose to this instance. E.g. `nvidia-tesla-k80`. * `count` (Required) - The number of the guest accelerator cards exposed to this instance. +The `node_affinities` block supports: + +* `key` (Required) - The key for the node affinity label. + +* `operator` (Required) - The operator. Can be `IN` for node-affinities + or `NOT` for anti-affinities. + +* `value` (Required) - The values for the node affinity label. + ## Attributes Reference In addition to the arguments listed above, the following computed attributes are diff --git a/website/docs/r/compute_instance_template.html.markdown b/website/docs/r/compute_instance_template.html.markdown index fa537afe..e36128d9 100644 --- a/website/docs/r/compute_instance_template.html.markdown +++ b/website/docs/r/compute_instance_template.html.markdown @@ -374,6 +374,12 @@ The `scheduling` block supports: * `preemptible` - (Optional) Allows instance to be preempted. This defaults to false. Read more on this [here](https://cloud.google.com/compute/docs/instances/preemptible). + +* `node_affinities` - (Optional) Specifies node affinities or anti-affinities + to determine which sole-tenant nodes your instances and managed instance + groups will use as host systems. Read more on sole-tenant node creation + [here](https://cloud.google.com/compute/docs/nodes/create-nodes). + Structure documented below. The `guest_accelerator` block supports: @@ -381,6 +387,15 @@ The `guest_accelerator` block supports: * `count` (Required) - The number of the guest accelerator cards exposed to this instance. +The `node_affinities` block supports: + +* `key` (Required) - The key for the node affinity label. + +* `operator` (Required) - The operator. Can be `IN` for node-affinities + or `NOT` for anti-affinities. + +* `value` (Required) - The values for the node affinity label. + ## Attributes Reference In addition to the arguments listed above, the following computed attributes are