Feature: allow system components on nodepools (#591)

* feat: allow system components on nodepools

* docs: generated docs for ske

* lint: sort imports

* revert changes
This commit is contained in:
Mauritz Uphoff 2024-11-13 11:44:55 +01:00 committed by GitHub
parent 2bf6a8dce7
commit 3ac1d50253
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 155 additions and 59 deletions

View file

@ -114,6 +114,7 @@ Read-Only:
Read-Only:
- `allow_system_components` (Boolean) Allow system components to run on this node pool.
- `availability_zones` (List of String) Specify a list of availability zones.
- `cri` (String) Specifies the container runtime.
- `labels` (Map of String) Labels to add to each node.

View file

@ -79,6 +79,7 @@ Required:
Optional:
- `allow_system_components` (Boolean) Allow system components to run on this node pool.
- `cri` (String) Specifies the container runtime. Defaults to `containerd`
- `labels` (Map of String) Labels to add to each node.
- `max_surge` (Number) Maximum number of additional VMs that are created during an update. If set (larger than 0), then it must be at least the amount of zones configured for the nodepool. The `max_surge` and `max_unavailable` fields cannot both be unset at the same time.

View file

@ -202,6 +202,10 @@ func (r *clusterDataSource) Schema(_ context.Context, _ datasource.SchemaRequest
ElementType: types.StringType,
Computed: true,
},
"allow_system_components": schema.BoolAttribute{
Description: "Allow system components to run on this node pool.",
Computed: true,
},
},
},
},

View file

@ -82,42 +82,44 @@ type Model struct {
// Struct corresponding to Model.NodePools[i]
type nodePool struct {
Name types.String `tfsdk:"name"`
MachineType types.String `tfsdk:"machine_type"`
OSName types.String `tfsdk:"os_name"`
OSVersionMin types.String `tfsdk:"os_version_min"`
OSVersion types.String `tfsdk:"os_version"`
OSVersionUsed types.String `tfsdk:"os_version_used"`
Minimum types.Int64 `tfsdk:"minimum"`
Maximum types.Int64 `tfsdk:"maximum"`
MaxSurge types.Int64 `tfsdk:"max_surge"`
MaxUnavailable types.Int64 `tfsdk:"max_unavailable"`
VolumeType types.String `tfsdk:"volume_type"`
VolumeSize types.Int64 `tfsdk:"volume_size"`
Labels types.Map `tfsdk:"labels"`
Taints types.List `tfsdk:"taints"`
CRI types.String `tfsdk:"cri"`
AvailabilityZones types.List `tfsdk:"availability_zones"`
Name types.String `tfsdk:"name"`
MachineType types.String `tfsdk:"machine_type"`
OSName types.String `tfsdk:"os_name"`
OSVersionMin types.String `tfsdk:"os_version_min"`
OSVersion types.String `tfsdk:"os_version"`
OSVersionUsed types.String `tfsdk:"os_version_used"`
Minimum types.Int64 `tfsdk:"minimum"`
Maximum types.Int64 `tfsdk:"maximum"`
MaxSurge types.Int64 `tfsdk:"max_surge"`
MaxUnavailable types.Int64 `tfsdk:"max_unavailable"`
VolumeType types.String `tfsdk:"volume_type"`
VolumeSize types.Int64 `tfsdk:"volume_size"`
Labels types.Map `tfsdk:"labels"`
Taints types.List `tfsdk:"taints"`
CRI types.String `tfsdk:"cri"`
AvailabilityZones types.List `tfsdk:"availability_zones"`
AllowSystemComponents types.Bool `tfsdk:"allow_system_components"`
}
// Types corresponding to nodePool
var nodePoolTypes = map[string]attr.Type{
"name": basetypes.StringType{},
"machine_type": basetypes.StringType{},
"os_name": basetypes.StringType{},
"os_version_min": basetypes.StringType{},
"os_version": basetypes.StringType{},
"os_version_used": basetypes.StringType{},
"minimum": basetypes.Int64Type{},
"maximum": basetypes.Int64Type{},
"max_surge": basetypes.Int64Type{},
"max_unavailable": basetypes.Int64Type{},
"volume_type": basetypes.StringType{},
"volume_size": basetypes.Int64Type{},
"labels": basetypes.MapType{ElemType: types.StringType},
"taints": basetypes.ListType{ElemType: types.ObjectType{AttrTypes: taintTypes}},
"cri": basetypes.StringType{},
"availability_zones": basetypes.ListType{ElemType: types.StringType},
"name": basetypes.StringType{},
"machine_type": basetypes.StringType{},
"os_name": basetypes.StringType{},
"os_version_min": basetypes.StringType{},
"os_version": basetypes.StringType{},
"os_version_used": basetypes.StringType{},
"minimum": basetypes.Int64Type{},
"maximum": basetypes.Int64Type{},
"max_surge": basetypes.Int64Type{},
"max_unavailable": basetypes.Int64Type{},
"volume_type": basetypes.StringType{},
"volume_size": basetypes.Int64Type{},
"labels": basetypes.MapType{ElemType: types.StringType},
"taints": basetypes.ListType{ElemType: types.ObjectType{AttrTypes: taintTypes}},
"cri": basetypes.StringType{},
"availability_zones": basetypes.ListType{ElemType: types.StringType},
"allow_system_components": basetypes.BoolType{},
}
// Struct corresponding to nodePool.Taints[i]
@ -391,6 +393,12 @@ func (r *clusterResource) Schema(_ context.Context, _ resource.SchemaRequest, re
Required: true,
ElementType: types.StringType,
},
"allow_system_components": schema.BoolAttribute{
Description: "Allow system components to run on this node pool.",
Optional: true,
Computed: true,
Default: booldefault.StaticBool(true),
},
"minimum": schema.Int64Attribute{
Description: "Minimum number of nodes in the pool.",
Required: true,
@ -994,16 +1002,32 @@ func toNodepoolsPayload(ctx context.Context, m *Model, availableMachineVersions
Type: conversion.StringValueToPointer(nodePool.VolumeType),
Size: conversion.Int64ValueToPointer(nodePool.VolumeSize),
},
Taints: &ts,
Cri: &cn,
Labels: ls,
AvailabilityZones: &zs,
Taints: &ts,
Cri: &cn,
Labels: ls,
AvailabilityZones: &zs,
AllowSystemComponents: conversion.BoolValueToPointer(nodePool.AllowSystemComponents),
}
cnps = append(cnps, cnp)
}
if err := verifySystemComponentsInNodePools(cnps); err != nil {
return nil, nil, err
}
return cnps, deprecatedVersionsUsed, nil
}
// verifySystemComponentsInNodePools checks if at least one node pool has the allow_system_components attribute set to true.
func verifySystemComponentsInNodePools(nodePools []ske.Nodepool) error {
for _, nodePool := range nodePools {
if nodePool.AllowSystemComponents != nil && *nodePool.AllowSystemComponents {
return nil // A node pool allowing system components was found
}
}
return fmt.Errorf("at least one node_pool must allow system components")
}
// latestMatchingMachineVersion determines the latest machine image version for the create/update payload.
// It considers the available versions for the specified OS (OSName), the minimum version configured by the user,
// and the current version in the cluster. The function's behavior is as follows:
@ -1374,20 +1398,21 @@ func mapNodePools(ctx context.Context, cl *ske.Cluster, m *Model) error {
nodePools := []attr.Value{}
for i, nodePoolResp := range *cl.Nodepools {
nodePool := map[string]attr.Value{
"name": types.StringPointerValue(nodePoolResp.Name),
"machine_type": types.StringPointerValue(nodePoolResp.Machine.Type),
"os_name": types.StringNull(),
"os_version_min": modelNodePoolOSVersionMin[*nodePoolResp.Name],
"os_version": modelNodePoolOSVersion[*nodePoolResp.Name],
"minimum": types.Int64PointerValue(nodePoolResp.Minimum),
"maximum": types.Int64PointerValue(nodePoolResp.Maximum),
"max_surge": types.Int64PointerValue(nodePoolResp.MaxSurge),
"max_unavailable": types.Int64PointerValue(nodePoolResp.MaxUnavailable),
"volume_type": types.StringNull(),
"volume_size": types.Int64PointerValue(nodePoolResp.Volume.Size),
"labels": types.MapNull(types.StringType),
"cri": types.StringNull(),
"availability_zones": types.ListNull(types.StringType),
"name": types.StringPointerValue(nodePoolResp.Name),
"machine_type": types.StringPointerValue(nodePoolResp.Machine.Type),
"os_name": types.StringNull(),
"os_version_min": modelNodePoolOSVersionMin[*nodePoolResp.Name],
"os_version": modelNodePoolOSVersion[*nodePoolResp.Name],
"minimum": types.Int64PointerValue(nodePoolResp.Minimum),
"maximum": types.Int64PointerValue(nodePoolResp.Maximum),
"max_surge": types.Int64PointerValue(nodePoolResp.MaxSurge),
"max_unavailable": types.Int64PointerValue(nodePoolResp.MaxUnavailable),
"volume_type": types.StringNull(),
"volume_size": types.Int64PointerValue(nodePoolResp.Volume.Size),
"labels": types.MapNull(types.StringType),
"cri": types.StringNull(),
"availability_zones": types.ListNull(types.StringType),
"allow_system_components": types.BoolPointerValue(nodePoolResp.AllowSystemComponents),
}
if nodePoolResp.Machine != nil && nodePoolResp.Machine.Image != nil {

View file

@ -8,8 +8,10 @@ import (
"github.com/google/go-cmp/cmp"
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
"github.com/stackitcloud/stackit-sdk-go/core/utils"
"github.com/stackitcloud/stackit-sdk-go/services/ske"
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/conversion"
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core"
)
@ -106,7 +108,8 @@ func TestMapFields(t *testing.T) {
Name: utils.Ptr("name"),
Nodepools: &[]ske.Nodepool{
{
AvailabilityZones: &[]string{"z1", "z2"},
AllowSystemComponents: utils.Ptr(true),
AvailabilityZones: &[]string{"z1", "z2"},
Cri: &ske.CRI{
Name: utils.Ptr("cri"),
},
@ -195,6 +198,7 @@ func TestMapFields(t *testing.T) {
types.StringValue("z2"),
},
),
"allow_system_components": types.BoolValue(true),
},
),
},
@ -481,6 +485,7 @@ func TestMapFields(t *testing.T) {
types.StringValue("z2"),
},
),
"allow_system_components": types.BoolValue(true),
},
),
},
@ -599,6 +604,7 @@ func TestMapFields(t *testing.T) {
types.StringValue("z2"),
},
),
"allow_system_components": types.BoolNull(),
},
),
},
@ -2284,3 +2290,57 @@ func TestToNetworkPayload(t *testing.T) {
})
}
}
func TestVerifySystemComponentNodepools(t *testing.T) {
tests := []struct {
description string
nodePools []ske.Nodepool
isValid bool
}{
{
description: "all pools allow system components",
nodePools: []ske.Nodepool{
{
AllowSystemComponents: conversion.BoolValueToPointer(basetypes.NewBoolValue(true)),
},
{
AllowSystemComponents: conversion.BoolValueToPointer(basetypes.NewBoolValue(true)),
},
},
isValid: true,
},
{
description: "one pool allows system components",
nodePools: []ske.Nodepool{
{
AllowSystemComponents: conversion.BoolValueToPointer(basetypes.NewBoolValue(true)),
},
{
AllowSystemComponents: conversion.BoolValueToPointer(basetypes.NewBoolValue(false)),
},
},
isValid: true,
},
{
description: "no pool allows system components",
nodePools: []ske.Nodepool{
{
AllowSystemComponents: conversion.BoolValueToPointer(basetypes.NewBoolValue(false)),
},
{
AllowSystemComponents: conversion.BoolValueToPointer(basetypes.NewBoolValue(false)),
},
},
isValid: false,
},
}
for _, tt := range tests {
t.Run(tt.description, func(t *testing.T) {
err := verifySystemComponentsInNodePools(tt.nodePools)
if (err == nil) != tt.isValid {
t.Errorf("expected validity to be %v, but got error: %v", tt.isValid, err)
}
})
}
}

View file

@ -21,17 +21,17 @@ var clusterResource = map[string]string{
"project_id": testutil.ProjectId,
"name": fmt.Sprintf("cl-%s", acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum)),
"name_min": fmt.Sprintf("cl-min-%s", acctest.RandStringFromCharSet(3, acctest.CharSetAlphaNum)),
"kubernetes_version_min": "1.28",
"kubernetes_version_used": "1.28.11",
"kubernetes_version_min_new": "1.29",
"kubernetes_version_used_new": "1.29.6",
"kubernetes_version_min": "1.29",
"kubernetes_version_used": "1.29.10",
"kubernetes_version_min_new": "1.30",
"kubernetes_version_used_new": "1.30.6",
"nodepool_name": "np-acc-test",
"nodepool_name_min": "np-acc-min-test",
"nodepool_machine_type": "b1.2",
"nodepool_os_version_min": "3815.2",
"nodepool_os_version_used": "3815.2.3",
"nodepool_os_version_min_new": "3815.2.3",
"nodepool_os_version_used_new": "3815.2.3",
"nodepool_os_version_min": "3975.2",
"nodepool_os_version_used": "3975.2.0",
"nodepool_os_version_min_new": "3975.2.1",
"nodepool_os_version_used_new": "3975.2.1",
"nodepool_os_name": "flatcar",
"nodepool_minimum": "2",
"nodepool_maximum": "3",
@ -46,6 +46,7 @@ var clusterResource = map[string]string{
"nodepool_taints_effect": "PreferNoSchedule",
"nodepool_taints_key": "tkey",
"nodepool_taints_value": "tvalue",
"nodepool_allow_system_components": "true",
"extensions_acl_enabled": "true",
"extensions_acl_cidrs": "192.168.0.0/24",
"extensions_argus_enabled": "false",
@ -96,6 +97,7 @@ func getConfig(kubernetesVersion, nodePoolMachineOSVersion string, maintenanceEn
key = "%s"
value = "%s"
}]
allow_system_components = %s
}]
extensions = {
acl = {
@ -169,6 +171,7 @@ func getConfig(kubernetesVersion, nodePoolMachineOSVersion string, maintenanceEn
clusterResource["nodepool_taints_effect"],
clusterResource["nodepool_taints_key"],
clusterResource["nodepool_taints_value"],
clusterResource["nodepool_allow_system_components"],
clusterResource["extensions_acl_enabled"],
clusterResource["extensions_acl_cidrs"],
clusterResource["extensions_argus_enabled"],
@ -233,6 +236,7 @@ func TestAccSKE(t *testing.T) {
resource.TestCheckResourceAttr("stackit_ske_cluster.cluster", "node_pools.0.taints.0.effect", clusterResource["nodepool_taints_effect"]),
resource.TestCheckResourceAttr("stackit_ske_cluster.cluster", "node_pools.0.taints.0.key", clusterResource["nodepool_taints_key"]),
resource.TestCheckResourceAttr("stackit_ske_cluster.cluster", "node_pools.0.taints.0.value", clusterResource["nodepool_taints_value"]),
resource.TestCheckResourceAttr("stackit_ske_cluster.cluster", "node_pools.0.allow_system_components", clusterResource["nodepool_allow_system_components"]),
resource.TestCheckResourceAttr("stackit_ske_cluster.cluster", "node_pools.0.cri", clusterResource["nodepool_cri"]),
resource.TestCheckResourceAttr("stackit_ske_cluster.cluster", "extensions.acl.enabled", clusterResource["extensions_acl_enabled"]),
resource.TestCheckResourceAttr("stackit_ske_cluster.cluster", "extensions.acl.allowed_cidrs.#", "1"),
@ -343,6 +347,7 @@ func TestAccSKE(t *testing.T) {
resource.TestCheckResourceAttr("data.stackit_ske_cluster.cluster", "node_pools.0.taints.0.effect", clusterResource["nodepool_taints_effect"]),
resource.TestCheckResourceAttr("data.stackit_ske_cluster.cluster", "node_pools.0.taints.0.key", clusterResource["nodepool_taints_key"]),
resource.TestCheckResourceAttr("data.stackit_ske_cluster.cluster", "node_pools.0.taints.0.value", clusterResource["nodepool_taints_value"]),
resource.TestCheckResourceAttr("data.stackit_ske_cluster.cluster", "node_pools.0.allow_system_components", clusterResource["nodepool_allow_system_components"]),
resource.TestCheckResourceAttr("data.stackit_ske_cluster.cluster", "node_pools.0.cri", clusterResource["nodepool_cri"]),
resource.TestCheckResourceAttr("data.stackit_ske_cluster.cluster", "extensions.acl.enabled", clusterResource["extensions_acl_enabled"]),
resource.TestCheckResourceAttr("data.stackit_ske_cluster.cluster", "extensions.acl.allowed_cidrs.#", "1"),