Skip to content
Draft
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
181 changes: 181 additions & 0 deletions gridscale/resource_gridscale_k8s.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ const (
k8sLabelPrefix = "#gsk#"
k8sRocketStorageSupportRelease = "1.26"
k8sMultiNodePoolSupportRelease = "1.30"
k8sTaintKeyValueRegex = `^[a-zA-Z0-9-]+$`
)

// ResourceGridscaleK8sModeler struct represents a modeler of the gridscale k8s resource.
Expand Down Expand Up @@ -117,6 +118,33 @@ func (rgk8sm *ResourceGridscaleK8sModeler) buildInputSchema() map[string]*schema
Default: 0,
Description: "Rocket storage per worker node (in GiB).",
},
"taints": {
Type: schema.TypeList,
Optional: true,
Description: "List of taints to be applied to the nodes of this pool.",
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"key": {
Type: schema.TypeString,
Required: true,
Description: "The key of the taint.",
ValidateFunc: validation.StringMatch(regexp.MustCompile(k8sTaintKeyValueRegex), "key must consist of alphanumeric characters and hyphens only"),
},
"value": {
Type: schema.TypeString,
Required: true,
Description: "The value of the taint.",
ValidateFunc: validation.StringMatch(regexp.MustCompile(k8sTaintKeyValueRegex), "value must consist of alphanumeric characters and hyphens only"),
},
"effect": {
Type: schema.TypeString,
Required: true,
Description: "The effect of the taint.",
ValidateFunc: validation.StringInSlice([]string{"NoExecute", "NoSchedule", "PreferNoSchedule"}, false),
},
},
},
},
}
return map[string]*schema.Schema{
"name": {
Expand Down Expand Up @@ -852,6 +880,32 @@ func resourceGridscaleK8sRead(d *schema.ResourceData, meta interface{}) error {
if rocketStorage, isRocketStorageSet := nodePoolSet["rocket_storage"]; isRocketStorageSet {
nodePoolRead["rocket_storage"] = rocketStorage
}

// Handle taints
if taints, isTaintsSet := nodePoolSet["taints"]; isTaintsSet {
taintsList := taints.([]any)
taintsRead := make([]map[string]any, 0)

for _, taintInterface := range taintsList {
taint := taintInterface.(map[string]any)
taintRead := make(map[string]any)

if key, isKeySet := taint["key"]; isKeySet {
taintRead["key"] = key
}
if value, isValueSet := taint["value"]; isValueSet {
taintRead["value"] = value
}
if effect, isEffectSet := taint["effect"]; isEffectSet {
taintRead["effect"] = effect
}

taintsRead = append(taintsRead, taintRead)
}

nodePoolRead["taints"] = taintsRead
}

nodePools = append(nodePools, nodePoolRead)
}
}
Expand Down Expand Up @@ -952,6 +1006,31 @@ func resourceGridscaleK8sCreate(d *schema.ResourceData, meta interface{}) error
nodePool["storage_type"] = d.Get(fmt.Sprintf("node_pool.%d.storage_type", index))
nodePool["rocket_storage"] = d.Get(fmt.Sprintf("node_pool.%d.rocket_storage", index))

// Handle taints
if taintsInterface, isTaintsSet := d.GetOk(fmt.Sprintf("node_pool.%d.taints", index)); isTaintsSet {
taintsList := taintsInterface.([]any)
taintsRequest := make([]map[string]any, 0)

for _, taintInterface := range taintsList {
taint := taintInterface.(map[string]any)
taintRequest := make(map[string]any)

if key, isKeySet := taint["key"]; isKeySet {
taintRequest["key"] = key
}
if value, isValueSet := taint["value"]; isValueSet {
taintRequest["value"] = value
}
if effect, isEffectSet := taint["effect"]; isEffectSet {
taintRequest["effect"] = effect
}

taintsRequest = append(taintsRequest, taintRequest)
}

nodePool["taints"] = taintsRequest
}

nodePools = append(nodePools, nodePool)
}
parameters["pools"] = nodePools
Expand Down Expand Up @@ -1113,6 +1192,31 @@ func resourceGridscaleK8sUpdate(d *schema.ResourceData, meta interface{}) error
nodePool["storage_type"] = d.Get(fmt.Sprintf("node_pool.%d.storage_type", index))
nodePool["rocket_storage"] = d.Get(fmt.Sprintf("node_pool.%d.rocket_storage", index))

// Handle taints
if taintsInterface, isTaintsSet := d.GetOk(fmt.Sprintf("node_pool.%d.taints", index)); isTaintsSet {
taintsList := taintsInterface.([]any)
taintsRequest := make([]map[string]any, 0)

for _, taintInterface := range taintsList {
taint := taintInterface.(map[string]any)
taintRequest := make(map[string]any)

if key, isKeySet := taint["key"]; isKeySet {
taintRequest["key"] = key
}
if value, isValueSet := taint["value"]; isValueSet {
taintRequest["value"] = value
}
if effect, isEffectSet := taint["effect"]; isEffectSet {
taintRequest["effect"] = effect
}

taintsRequest = append(taintsRequest, taintRequest)
}

nodePool["taints"] = taintsRequest
}

nodePools = append(nodePools, nodePool)
}
parameters["pools"] = nodePools
Expand Down Expand Up @@ -1378,6 +1482,83 @@ func validateK8sParameters(d *schema.ResourceDiff, template gsclient.PaaSTemplat
)
}
}

// Validate taints
nodePoolParameterTaints, taints_ok := templateParameterNodePools.Schema.Schema["taints"]
if taints_ok {
if taintsInterface, isTaintsSet := d.GetOk(fmt.Sprintf("node_pool.%d.taints", index)); isTaintsSet {
taintsList := taintsInterface.([]any)

// Check if taints list is empty when it's allowed to be
if len(taintsList) == 0 && !nodePoolParameterTaints.Empty {
errorMessages = append(
errorMessages,
fmt.Sprintf("Invalid 'node_pool.%d.taints' value. Taints list cannot be empty.\n", index),
)
}

// Validate each taint
for _, taintInterface := range taintsList {
taint := taintInterface.(map[string]any)

// Validate key
if key, isKeySet := taint["key"]; isKeySet {
keyStr := key.(string)
if !regexp.MustCompile(k8sTaintKeyValueRegex).MatchString(keyStr) {
errorMessages = append(
errorMessages,
fmt.Sprintf("Invalid 'node_pool.%d.taints.key' value. Key must consist of alphanumeric characters and hyphens only.\n", index),
)
}
} else {
errorMessages = append(
errorMessages,
fmt.Sprintf("Invalid 'node_pool.%d.taints' value. Key is required.\n", index),
)
}

// Validate value
if value, isValueSet := taint["value"]; isValueSet {
valueStr := value.(string)
if !regexp.MustCompile(k8sTaintKeyValueRegex).MatchString(valueStr) {
errorMessages = append(
errorMessages,
fmt.Sprintf("Invalid 'node_pool.%d.taints.value' value. Value must consist of alphanumeric characters and hyphens only.\n", index),
)
}
} else {
errorMessages = append(
errorMessages,
fmt.Sprintf("Invalid 'node_pool.%d.taints' value. Value is required.\n", index),
)
}

// Validate effect
if effect, isEffectSet := taint["effect"]; isEffectSet {
effectStr := effect.(string)
validEffects := []string{"NoExecute", "NoSchedule", "PreferNoSchedule"}
isValidEffect := false
for _, validEffect := range validEffects {
if effectStr == validEffect {
isValidEffect = true
break
}
}
if !isValidEffect {
errorMessages = append(
errorMessages,
fmt.Sprintf("Invalid 'node_pool.%d.taints.effect' value. Effect must be one of: %s.\n", index, strings.Join(validEffects, ", ")),
)
}
} else {
errorMessages = append(
errorMessages,
fmt.Sprintf("Invalid 'node_pool.%d.taints' value. Effect is required.\n", index),
)
}
}
}
}
}
}

Expand Down
113 changes: 113 additions & 0 deletions gridscale/resource_gridscale_k8s_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,70 @@ func TestAccResourceGridscaleK8sBasic(t *testing.T) {
"gridscale_k8s.foopaas", "surge_node", "true"),
),
},
{
Config: testAccCheckResourceGridscaleK8sConfigAddTaint(),
Check: resource.ComposeTestCheckFunc(
testAccCheckResourceGridscalePaaSExists("gridscale_k8s.foopaas", &object),
resource.TestCheckResourceAttr(
"gridscale_k8s.foopaas", "node_pool.0.name", "my-node-pool"),
resource.TestCheckResourceAttr(
"gridscale_k8s.foopaas", "node_pool.0.node_count", "1"),
resource.TestCheckResourceAttr(
"gridscale_k8s.foopaas", "node_pool.0.cores", "2"),
resource.TestCheckResourceAttr(
"gridscale_k8s.foopaas", "node_pool.0.memory", "4"),
resource.TestCheckResourceAttr(
"gridscale_k8s.foopaas", "node_pool.0.storage", "50"),
resource.TestCheckResourceAttr(
"gridscale_k8s.foopaas", "node_pool.0.storage_type", "storage_insane"),
resource.TestCheckResourceAttr(
"gridscale_k8s.foopaas", "node_pool.0.rocket_storage", "10"),
resource.TestCheckResourceAttr(
"gridscale_k8s.foopaas", "node_pool.0.taints.#", "2"),
resource.TestCheckResourceAttr(
"gridscale_k8s.foopaas", "node_pool.0.taints.0.key", "example-key"),
resource.TestCheckResourceAttr(
"gridscale_k8s.foopaas", "node_pool.0.taints.0.value", "example-value"),
resource.TestCheckResourceAttr(
"gridscale_k8s.foopaas", "node_pool.0.taints.0.effect", "NoSchedule"),
resource.TestCheckResourceAttr(
"gridscale_k8s.foopaas", "node_pool.0.taints.1.key", "another-key"),
resource.TestCheckResourceAttr(
"gridscale_k8s.foopaas", "node_pool.0.taints.1.value", "another-value"),
resource.TestCheckResourceAttr(
"gridscale_k8s.foopaas", "node_pool.0.taints.1.effect", "NoExecute"),
resource.TestCheckResourceAttr(
"gridscale_k8s.foopaas", "k8s_hubble", "true"),
resource.TestCheckResourceAttr(
"gridscale_k8s.foopaas", "surge_node", "true"),
),
},
{
Config: testAccCheckResourceGridscaleK8sConfigRemoveTaint(),
Check: resource.ComposeTestCheckFunc(
testAccCheckResourceGridscalePaaSExists("gridscale_k8s.foopaas", &object),
resource.TestCheckResourceAttr(
"gridscale_k8s.foopaas", "node_pool.0.name", "my-node-pool"),
resource.TestCheckResourceAttr(
"gridscale_k8s.foopaas", "node_pool.0.node_count", "1"),
resource.TestCheckResourceAttr(
"gridscale_k8s.foopaas", "node_pool.0.cores", "2"),
resource.TestCheckResourceAttr(
"gridscale_k8s.foopaas", "node_pool.0.memory", "4"),
resource.TestCheckResourceAttr(
"gridscale_k8s.foopaas", "node_pool.0.storage", "50"),
resource.TestCheckResourceAttr(
"gridscale_k8s.foopaas", "node_pool.0.storage_type", "storage_insane"),
resource.TestCheckResourceAttr(
"gridscale_k8s.foopaas", "node_pool.0.rocket_storage", "10"),
resource.TestCheckResourceAttr(
"gridscale_k8s.foopaas", "node_pool.0.taints.#", "0"),
resource.TestCheckResourceAttr(
"gridscale_k8s.foopaas", "k8s_hubble", "true"),
resource.TestCheckResourceAttr(
"gridscale_k8s.foopaas", "surge_node", "true"),
),
},
{
Config: testAccCheckResourceGridscaleK8sConfigNodeCountIncrease(),
Check: resource.ComposeTestCheckFunc(
Expand Down Expand Up @@ -292,6 +356,55 @@ func testAccCheckResourceGridscaleK8sConfigNodeCountIncrease() string {
`
}

func testAccCheckResourceGridscaleK8sConfigAddTaint() string {
return `
resource "gridscale_k8s" "foopaas" {
name = "newname"
release = "1.30"
node_pool {
name = "my-node-pool"
node_count = 1
cores = 2
memory = 4
storage = 50
storage_type = "storage_insane"
rocket_storage = 10
taints {
key = "example-key"
value = "example-value"
effect = "NoSchedule"
}
taints {
key = "another-key"
value = "another-value"
effect = "NoExecute"
}
}
k8s_hubble = true
}
`
}

func testAccCheckResourceGridscaleK8sConfigRemoveTaint() string {
return `
resource "gridscale_k8s" "foopaas" {
name = "newname"
release = "1.30"
node_pool {
name = "my-node-pool"
node_count = 1
cores = 2
memory = 4
storage = 50
storage_type = "storage_insane"
rocket_storage = 10
taints = []
}
k8s_hubble = true
}
`
}

func testAccCheckResourceGridscaleK8sConfigNodeCountDecrease() string {
return `
resource "gridscale_k8s" "foopaas" {
Expand Down
Loading
Loading