Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

resource/aws_eks_node_group: added support for taints #19482

Merged
merged 3 commits into from
May 25, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .changelog/19482.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:enhancement
resource/aws_eks_node_group: Add `taint` argument
```
162 changes: 161 additions & 1 deletion aws/resource_aws_eks_node_group.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package aws
import (
"fmt"
"log"
"reflect"
"strings"
"time"

Expand Down Expand Up @@ -219,6 +220,30 @@ func resourceAwsEksNodeGroup() *schema.Resource {
},
"tags": tagsSchema(),
"tags_all": tagsSchemaComputed(),
"taint": {
Type: schema.TypeSet,
Optional: true,
MaxItems: 50,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"key": {
Type: schema.TypeString,
Required: true,
ValidateFunc: validation.StringLenBetween(1, 63),
},
"value": {
Type: schema.TypeString,
Optional: true,
ValidateFunc: validation.StringLenBetween(0, 63),
},
"effect": {
Type: schema.TypeString,
Required: true,
ValidateFunc: validation.StringInSlice(eks.TaintEffect_Values(), false),
},
},
},
},
"version": {
Type: schema.TypeString,
Optional: true,
Expand Down Expand Up @@ -283,6 +308,10 @@ func resourceAwsEksNodeGroupCreate(d *schema.ResourceData, meta interface{}) err
input.Tags = tags.IgnoreAws().EksTags()
}

if v, ok := d.GetOk("taint"); ok && v.(*schema.Set).Len() > 0 {
input.Taints = expandEksTaints(v.(*schema.Set).List())
}

if v, ok := d.GetOk("version"); ok {
input.Version = aws.String(v.(string))
}
Expand Down Expand Up @@ -396,6 +425,10 @@ func resourceAwsEksNodeGroupRead(d *schema.ResourceData, meta interface{}) error
return fmt.Errorf("error setting tags_all: %w", err)
}

if err := d.Set("taint", flattenEksTaints(nodeGroup.Taints)); err != nil {
return fmt.Errorf("error setting taint: %w", err)
}

d.Set("version", nodeGroup.Version)

return nil
Expand All @@ -410,7 +443,7 @@ func resourceAwsEksNodeGroupUpdate(d *schema.ResourceData, meta interface{}) err
return err
}

if d.HasChanges("labels", "scaling_config") {
if d.HasChanges("labels", "scaling_config", "taint") {
oldLabelsRaw, newLabelsRaw := d.GetChange("labels")

input := &eks.UpdateNodegroupConfigInput{
Expand All @@ -424,6 +457,9 @@ func resourceAwsEksNodeGroupUpdate(d *schema.ResourceData, meta interface{}) err
input.ScalingConfig = expandEksNodegroupScalingConfig(v)
}

oldTaintsRaw, newTaintsRaw := d.GetChange("taint")
input.Taints = expandEksUpdateTaintsPayload(oldTaintsRaw.(*schema.Set).List(), newTaintsRaw.(*schema.Set).List())

output, err := conn.UpdateNodegroupConfig(input)

if err != nil {
Expand Down Expand Up @@ -585,6 +621,108 @@ func expandEksNodegroupScalingConfig(l []interface{}) *eks.NodegroupScalingConfi
return config
}

func expandEksTaints(l []interface{}) []*eks.Taint {
if len(l) == 0 {
return nil
}

var taints []*eks.Taint

for _, raw := range l {
t, ok := raw.(map[string]interface{})

if !ok {
continue
}

taint := &eks.Taint{}

if k, ok := t["key"].(string); ok {
taint.Key = aws.String(k)
}

if v, ok := t["value"].(string); ok {
taint.Value = aws.String(v)
}

if e, ok := t["effect"].(string); ok {
taint.Effect = aws.String(e)
}

taints = append(taints, taint)
}

return taints
}

func expandEksUpdateTaintsPayload(oldTaintsRaw, newTaintsRaw []interface{}) *eks.UpdateTaintsPayload {
oldTaints := expandEksTaints(oldTaintsRaw)
newTaints := expandEksTaints(newTaintsRaw)

var removedTaints []*eks.Taint
for _, ot := range oldTaints {
if ot == nil {
continue
}

removed := true
for _, nt := range newTaints {
if nt == nil {
continue
}

// if both taint.key and taint.effect are the same, we don't need to remove it.
if aws.StringValue(nt.Key) == aws.StringValue(ot.Key) &&
aws.StringValue(nt.Effect) == aws.StringValue(ot.Effect) {
removed = false
break
}
}

if removed {
removedTaints = append(removedTaints, ot)
}
}

var updatedTaints []*eks.Taint
for _, nt := range newTaints {
if nt == nil {
continue
}

updated := true
for _, ot := range oldTaints {
if nt == nil {
continue
}

if reflect.DeepEqual(nt, ot) {
updated = false
break
}
}
if updated {
updatedTaints = append(updatedTaints, nt)
}
}

if len(removedTaints) == 0 && len(updatedTaints) == 0 {
return nil
}

updateTaintsPayload := &eks.UpdateTaintsPayload{}

if len(removedTaints) > 0 {
updateTaintsPayload.RemoveTaints = removedTaints
}

if len(updatedTaints) > 0 {
updateTaintsPayload.AddOrUpdateTaints = updatedTaints
}

return updateTaintsPayload
}

func expandEksRemoteAccessConfig(l []interface{}) *eks.RemoteAccessConfig {
if len(l) == 0 || l[0] == nil {
return nil
Expand Down Expand Up @@ -710,6 +848,28 @@ func flattenEksRemoteAccessConfig(config *eks.RemoteAccessConfig) []map[string]i
return []map[string]interface{}{m}
}

func flattenEksTaints(taints []*eks.Taint) []interface{} {
if len(taints) == 0 {
return nil
}

var results []interface{}

for _, taint := range taints {
if taint == nil {
continue
}

t := make(map[string]interface{})
t["key"] = aws.StringValue(taint.Key)
t["value"] = aws.StringValue(taint.Value)
t["effect"] = aws.StringValue(taint.Effect)

results = append(results, t)
}
return results
}

func refreshEksNodeGroupStatus(conn *eks.EKS, clusterName string, nodeGroupName string) resource.StateRefreshFunc {
return func() (interface{}, string, error) {
input := &eks.DescribeNodegroupInput{
Expand Down
128 changes: 128 additions & 0 deletions aws/resource_aws_eks_node_group_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ func TestAccAWSEksNodeGroup_basic(t *testing.T) {
resource.TestCheckResourceAttr(resourceName, "status", eks.NodegroupStatusActive),
resource.TestCheckResourceAttr(resourceName, "subnet_ids.#", "2"),
resource.TestCheckResourceAttr(resourceName, "tags.%", "0"),
resource.TestCheckResourceAttr(resourceName, "taint.#", "0"),
resource.TestCheckResourceAttrPair(resourceName, "version", eksClusterResourceName, "version"),
),
},
Expand Down Expand Up @@ -818,6 +819,69 @@ func TestAccAWSEksNodeGroup_Tags(t *testing.T) {
})
}

func TestAccAWSEksNodeGroup_Taints(t *testing.T) {
var nodeGroup1 eks.Nodegroup
rName := acctest.RandomWithPrefix("tf-acc-test")
resourceName := "aws_eks_node_group.test"

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t); testAccPreCheckAWSEks(t) },
ErrorCheck: testAccErrorCheck(t, eks.EndpointsID),
Providers: testAccProviders,
CheckDestroy: testAccCheckAWSEksNodeGroupDestroy,
Steps: []resource.TestStep{
{
Config: testAccAWSEksNodeGroupConfigTaints1(rName, "key1", "value1", "NO_SCHEDULE"),
Check: resource.ComposeTestCheckFunc(
testAccCheckAWSEksNodeGroupExists(resourceName, &nodeGroup1),
resource.TestCheckResourceAttr(resourceName, "taint.#", "1"),
resource.TestCheckTypeSetElemNestedAttrs(resourceName, "taint.*", map[string]string{
"key": "key1",
"value": "value1",
"effect": "NO_SCHEDULE",
}),
),
},
{
ResourceName: resourceName,
ImportState: true,
ImportStateVerify: true,
},
{
Config: testAccAWSEksNodeGroupConfigTaints2(rName,
"key1", "value1updated", "NO_EXECUTE",
"key2", "value2", "NO_SCHEDULE"),
Check: resource.ComposeTestCheckFunc(
testAccCheckAWSEksNodeGroupExists(resourceName, &nodeGroup1),
resource.TestCheckResourceAttr(resourceName, "taint.#", "2"),
resource.TestCheckTypeSetElemNestedAttrs(resourceName, "taint.*", map[string]string{
"key": "key1",
"value": "value1updated",
"effect": "NO_EXECUTE",
}),
resource.TestCheckTypeSetElemNestedAttrs(resourceName, "taint.*", map[string]string{
"key": "key2",
"value": "value2",
"effect": "NO_SCHEDULE",
}),
),
},
{
Config: testAccAWSEksNodeGroupConfigTaints1(rName, "key2", "value2", "NO_SCHEDULE"),
Check: resource.ComposeTestCheckFunc(
testAccCheckAWSEksNodeGroupExists(resourceName, &nodeGroup1),
resource.TestCheckResourceAttr(resourceName, "taint.#", "1"),
resource.TestCheckTypeSetElemNestedAttrs(resourceName, "taint.*", map[string]string{
"key": "key2",
"value": "value2",
"effect": "NO_SCHEDULE",
}),
),
},
},
})
}

func TestAccAWSEksNodeGroup_Version(t *testing.T) {
var nodeGroup1, nodeGroup2 eks.Nodegroup
rName := acctest.RandomWithPrefix("tf-acc-test")
Expand Down Expand Up @@ -1900,6 +1964,70 @@ resource "aws_eks_node_group" "test" {
`, rName, tagKey1, tagValue1, tagKey2, tagValue2))
}

func testAccAWSEksNodeGroupConfigTaints1(rName, taintKey1, taintValue1, taintEffect1 string) string {
return composeConfig(testAccAWSEksNodeGroupConfigBase(rName), fmt.Sprintf(`
resource "aws_eks_node_group" "test" {
cluster_name = aws_eks_cluster.test.name
node_group_name = %[1]q
node_role_arn = aws_iam_role.node.arn
subnet_ids = aws_subnet.test[*].id

taint {
key = %[2]q
value = %[3]q
effect = %[4]q
}

scaling_config {
desired_size = 1
max_size = 1
min_size = 1
}

depends_on = [
aws_iam_role_policy_attachment.node-AmazonEKSWorkerNodePolicy,
aws_iam_role_policy_attachment.node-AmazonEKS_CNI_Policy,
aws_iam_role_policy_attachment.node-AmazonEC2ContainerRegistryReadOnly,
]
}
`, rName, taintKey1, taintValue1, taintEffect1))
}

func testAccAWSEksNodeGroupConfigTaints2(rName, taintKey1, taintValue1, taintEffect1, taintKey2, taintValue2, taintEffect2 string) string {
return composeConfig(testAccAWSEksNodeGroupConfigBase(rName), fmt.Sprintf(`
resource "aws_eks_node_group" "test" {
cluster_name = aws_eks_cluster.test.name
node_group_name = %[1]q
node_role_arn = aws_iam_role.node.arn
subnet_ids = aws_subnet.test[*].id

taint {
key = %[2]q
value = %[3]q
effect = %[4]q
}

taint {
key = %[5]q
value = %[6]q
effect = %[7]q
}

scaling_config {
desired_size = 1
max_size = 1
min_size = 1
}

depends_on = [
aws_iam_role_policy_attachment.node-AmazonEKSWorkerNodePolicy,
aws_iam_role_policy_attachment.node-AmazonEKS_CNI_Policy,
aws_iam_role_policy_attachment.node-AmazonEC2ContainerRegistryReadOnly,
]
}
`, rName, taintKey1, taintValue1, taintEffect1, taintKey2, taintValue2, taintEffect2))
}

func testAccAWSEksNodeGroupConfigVersion(rName, version string) string {
return composeConfig(testAccAWSEksNodeGroupConfigBaseVersion(rName, version), fmt.Sprintf(`
resource "aws_eks_node_group" "test" {
Expand Down
7 changes: 7 additions & 0 deletions website/docs/r/eks_node_group.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ The following arguments are optional:
* `release_version` – (Optional) AMI version of the EKS Node Group. Defaults to latest version for Kubernetes version.
* `remote_access` - (Optional) Configuration block with remote access settings. Detailed below.
* `tags` - (Optional) Key-value map of resource tags. If configured with a provider [`default_tags` configuration block](https://www.terraform.io/docs/providers/aws/index.html#default_tags-configuration-block) present, tags with matching keys will overwrite those defined at the provider-level.
* `taint` - (Optional) The Kubernetes taints to be applied to the nodes in the node group. Maximum of 50 taints per node group. Detailed below.
* `version` – (Optional) Kubernetes version. Defaults to EKS Cluster Kubernetes version. Terraform will only perform drift detection if a configuration value is provided.

### launch_template Configuration Block
Expand All @@ -155,6 +156,12 @@ The following arguments are optional:
* `max_size` - (Required) Maximum number of worker nodes.
* `min_size` - (Required) Minimum number of worker nodes.

### taint Configuration Block

* `key` - (Required) The key of the taint. Maximum length of 63.
* `value` - (Optional) The value of the taint. Maximum length of 63.
* `effect` - (Required) The effect of the taint. Valid values: `NO_SCHEDULE`, `NO_EXECUTE`, `PREFER_NO_SCHEDULE`.

## Attributes Reference

In addition to all arguments above, the following attributes are exported:
Expand Down