Skip to content

Commit

Permalink
Remove limit for taints in kubernetes_node_taint resource (#2046)
Browse files Browse the repository at this point in the history
Co-authored-by: John Houston <[email protected]>
  • Loading branch information
BBBmau and jrhouston authored Apr 4, 2023
1 parent 26adb1f commit 927c705
Show file tree
Hide file tree
Showing 4 changed files with 202 additions and 127 deletions.
3 changes: 3 additions & 0 deletions .changelog/2046.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:enhancement
`kubernetes/resource_kubernetes_node_taint.go`: Remove MaxItems from taint attribute
```
152 changes: 31 additions & 121 deletions kubernetes/resource_kubernetes_node_taint.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@ package kubernetes
import (
"context"
"fmt"
"log"
"strings"

"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
Expand All @@ -17,8 +15,6 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/types"
"k8s.io/kubernetes/pkg/apis/core/helper"
"k8s.io/kubernetes/pkg/util/taints"
)

var taintMap = map[string]v1.TaintEffect{
Expand All @@ -33,6 +29,24 @@ func resourceKubernetesNodeTaint() *schema.Resource {
ReadContext: resourceKubernetesNodeTaintRead,
UpdateContext: resourceKubernetesNodeTaintUpdate,
DeleteContext: resourceKubernetesNodeTaintDelete,
CustomizeDiff: func(ctx context.Context, rd *schema.ResourceDiff, i interface{}) error {
if !rd.HasChange("taint") {
return nil
}
// check for duplicate taint keys
taintkeys := map[string]int{}
for _, t := range rd.Get("taint").([]interface{}) {
taint := t.(map[string]interface{})
key := taint["key"].(string)
taintkeys[key] = taintkeys[key] + 1
}
for k, v := range taintkeys {
if v > 1 {
return fmt.Errorf("taint: duplicate taint key %q: taint keys must be unique", k)
}
}
return nil
},
Schema: map[string]*schema.Schema{
"metadata": {
Type: schema.TypeList,
Expand Down Expand Up @@ -64,7 +78,6 @@ func resourceKubernetesNodeTaint() *schema.Resource {
"taint": {
Type: schema.TypeList,
Required: true,
MaxItems: 1,
Elem: &schema.Resource{
Schema: nodeTaintFields(),
},
Expand All @@ -89,10 +102,8 @@ func resourceKubernetesNodeTaintDelete(ctx context.Context, d *schema.ResourceDa
}

func resourceKubernetesNodeTaintRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
nodeName, idTaint, err := idToNodeTaint(d.Id())
if err != nil {
return diag.FromErr(err)
}
meta := expandMetadata(d.Get("metadata").([]interface{}))
nodeName := meta.Name

conn, err := m.(KubeClientsets).MainClientset()
if err != nil {
Expand All @@ -109,11 +120,8 @@ func resourceKubernetesNodeTaintRead(ctx context.Context, d *schema.ResourceData
d.SetId("")
return nil
}
if !hasTaint(nodeTaints, idTaint) {
d.SetId("")
return nil
}
d.Set("taint", flattenNodeTaints(*idTaint))

d.Set("taint", flattenNodeTaints(nodeTaints...))
return nil
}

Expand All @@ -127,7 +135,7 @@ func resourceKubernetesNodeTaintUpdate(ctx context.Context, d *schema.ResourceDa
}
nodeApi := conn.CoreV1().Nodes()

node, err := nodeApi.Get(ctx, nodeName, metav1.GetOptions{})
_, err = nodeApi.Get(ctx, nodeName, metav1.GetOptions{})
if err != nil {
if d.Id() == "" {
if statusErr, ok := err.(*errors.StatusError); ok && errors.IsNotFound(statusErr) {
Expand All @@ -139,28 +147,9 @@ func resourceKubernetesNodeTaintUpdate(ctx context.Context, d *schema.ResourceDa
}

taints := d.Get("taint").([]interface{})
newTaint, err := expandNodeTaint(taints[0].(map[string]interface{}))
if err != nil {
return diag.FromErr(err)
}
var newNode *v1.Node
if d.Id() == "" {
var removed bool
newNode, removed = removeTaint(node, newTaint)
if !removed {
return diag.Diagnostics{{
Severity: diag.Warning,
Summary: "Resource deleted",
Detail: fmt.Sprintf("Node %s does not have taint %+v. You should re-create it, or remove this resource from your configuration", nodeName, newTaint),
}}
}
} else {
log.Printf("[INFO] adding taint %+v to node %s", newTaint, nodeName)
var updated bool
newNode, updated = addOrUpdateTaint(node, newTaint)
if !updated {
return diag.Errorf("Node %s already has taint %+v", nodeName, newTaint)
}
// make taints an empty list if we're deleting the resource
taints = []interface{}{}
}
patchObj := map[string]interface{}{
"apiVersion": "v1",
Expand All @@ -169,7 +158,7 @@ func resourceKubernetesNodeTaintUpdate(ctx context.Context, d *schema.ResourceDa
"name": nodeName,
},
"spec": map[string]interface{}{
"taints": flattenNodeTaints(newNode.Spec.Taints...),
"taints": taints,
},
}
patch := unstructured.Unstructured{
Expand Down Expand Up @@ -197,93 +186,14 @@ func resourceKubernetesNodeTaintUpdate(ctx context.Context, d *schema.ResourceDa
if d.Id() == "" {
return nil
}
d.SetId(nodeTaintToId(nodeName, taints))
return resourceKubernetesNodeTaintRead(ctx, d, m)
}

func addOrUpdateTaint(node *v1.Node, taint *v1.Taint) (*v1.Node, bool) {
nodeTaints := node.Spec.Taints
newTaints := []v1.Taint{}
updated := false
for i := range nodeTaints {
log.Printf("[INFO] Checking taint: %+v", nodeTaints[i])
if taint.MatchTaint(&nodeTaints[i]) {
if helper.Semantic.DeepEqual(*taint, nodeTaints[i]) {
return node, false
}
newTaints = append(newTaints, *taint)
updated = true
continue
}
newTaints = append(newTaints, nodeTaints[i])
}
if !updated {
newTaints = append(newTaints, *taint)
log.Printf("[INFO] appended taint: %+v", taint)
updated = true
}
newNode := node.DeepCopy()
newNode.Spec.Taints = newTaints
return newNode, updated
}

func removeTaint(node *v1.Node, delTaint *v1.Taint) (*v1.Node, bool) {
taints := node.Spec.Taints
newTaints := []v1.Taint{}
deleted := false
for i := range taints {
if delTaint.MatchTaint(&taints[i]) {
deleted = true
continue
}
newTaints = append(newTaints, taints[i])
}
if !deleted {
return node, false
}
newNode := node.DeepCopy()
newNode.Spec.Taints = newTaints
return newNode, deleted
}

func hasTaint(taints []v1.Taint, taint *v1.Taint) bool {
for i := range taints {
if taint.MatchTaint(&taints[i]) {
return true
}
}
return false
}

func expandNodeTaint(t map[string]interface{}) (*v1.Taint, error) {
tt := expandStringMap(t)
taintEffect, ok := taintMap[tt["effect"]]
if !ok {
return nil, fmt.Errorf("Invalid taint effect '%s'", tt["effect"])
}
taint := &v1.Taint{
Key: tt["key"],
Value: tt["value"],
Effect: taintEffect,
}
return taint, nil
}

func nodeTaintToId(nodeName string, taints []interface{}) string {
t := taints[0].(map[string]interface{})
return fmt.Sprintf("%s,%s=%s:%s", nodeName, t["key"], t["value"], t["effect"])
}

func idToNodeTaint(id string) (string, *v1.Taint, error) {
idVals := strings.Split(id, ",")
nodeName := idVals[0]
taintStr := idVals[1]
taints, _, err := taints.ParseTaints([]string{taintStr})
if err != nil {
return "", nil, err
}
if len(taints) == 0 {
return "", nil, fmt.Errorf("failed to parse taint %s", taintStr)
var id string = fmt.Sprintf("%s", nodeName)
for _, t := range taints {
taint := t.(map[string]interface{})
id += fmt.Sprintf(",%s=%s:%s", taint["key"], taint["value"], taint["effect"])
}
return nodeName, &taints[0], nil
return id
}
Loading

0 comments on commit 927c705

Please sign in to comment.