Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
4 changes: 4 additions & 0 deletions docs/resources/lke_node_pool.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,10 @@ The following arguments are supported:

* `labels` - (Optional) A map attribute containing key-value pairs to be added as labels to nodes in the node pool. Labels help classify your nodes and to easily select subsets of objects. To learn more, review [Add Labels and Taints to your LKE Node Pools](https://www.linode.com/docs/products/compute/kubernetes/guides/deploy-and-manage-cluster-with-the-linode-api/#add-labels-and-taints-to-your-lke-node-pools).

* `k8s_version` - (Optional) The k8s version of the nodes in this node pool. For LKE enterprise only and may not currently available to all users even under v4beta.

* `update_strategy` - (Optional) The strategy for updating the node pool k8s version. For LKE enterprise only and may not currently available to all users even under v4beta.

* [`autoscaler`](#autoscaler) - (Optional) If defined, an autoscaler will be enabled with the given configuration.

* [`taint`](#taint) - (Optional) Kubernetes taints to add to node pool nodes. Taints help control how pods are scheduled onto nodes, specifically allowing them to repel certain pods. To learn more, review [Add Labels and Taints to your LKE Node Pools](https://www.linode.com/docs/products/compute/kubernetes/guides/deploy-and-manage-cluster-with-the-linode-api/#add-labels-and-taints-to-your-lke-node-pools).
Expand Down
4 changes: 3 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ require (
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
github.com/zclconf/go-cty v1.16.2 // indirect
golang.org/x/mod v0.22.0 // indirect
golang.org/x/oauth2 v0.27.0 // indirect
golang.org/x/oauth2 v0.28.0 // indirect
golang.org/x/sys v0.31.0 // indirect
golang.org/x/term v0.30.0 // indirect
golang.org/x/text v0.23.0 // indirect
Expand All @@ -126,3 +126,5 @@ require (
sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect
sigs.k8s.io/yaml v1.3.0 // indirect
)

replace github.com/linode/linodego => /Users/yechen/linode/dx-devenv/repos/linodego
6 changes: 2 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -194,8 +194,6 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/linode/linodego v1.48.1 h1:Ojw1S+K5jJr1dggO8/H6r4FINxXnJbOU5GkbpaTfmhU=
github.com/linode/linodego v1.48.1/go.mod h1:fc3t60If8X+yZTFAebhCnNDFrhwQhq9HDU92WnBousQ=
github.com/linode/linodego/k8s v1.25.2 h1:PY6S0sAD3xANVvM9WY38bz9GqMTjIbytC8IJJ9Cv23o=
github.com/linode/linodego/k8s v1.25.2/go.mod h1:DC1XCSRZRGsmaa/ggpDPSDUmOM6aK1bhSIP6+f9Cwhc=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
Expand Down Expand Up @@ -299,8 +297,8 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c=
golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
golang.org/x/oauth2 v0.27.0 h1:da9Vo7/tDv5RH/7nZDz1eMGS/q1Vv1N/7FCrBhI9I3M=
golang.org/x/oauth2 v0.27.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8=
golang.org/x/oauth2 v0.28.0 h1:CrgCKl8PPAVtLnU3c+EDw6x11699EWlsDeWNWKdIOkc=
golang.org/x/oauth2 v0.28.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
Expand Down
145 changes: 128 additions & 17 deletions linode/lkenodepool/framework_models.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ type NodePoolModel struct {
Autoscaler []NodePoolAutoscalerModel `tfsdk:"autoscaler"`
Taints []NodePoolTaintModel `tfsdk:"taint"`
Labels types.Map `tfsdk:"labels"`
K8sVersion types.String `tfsdk:"k8s_version"`
UpdateStrategy types.String `tfsdk:"update_strategy"`
}

type NodePoolAutoscalerModel struct {
Expand Down Expand Up @@ -122,9 +124,22 @@ func (pool *NodePoolModel) FlattenLKENodePool(
diags.Append(errs...)
}
pool.Nodes = helper.KeepOrUpdateValue(pool.Nodes, *nodePoolLinodes, preserveKnown)

pool.K8sVersion = helper.KeepOrUpdateStringPointer(pool.K8sVersion, p.K8sVersion, preserveKnown)

if p.UpdateStrategy != nil {
pool.UpdateStrategy = helper.KeepOrUpdateString(pool.UpdateStrategy, string(*p.UpdateStrategy), preserveKnown)
} else {
pool.UpdateStrategy = helper.KeepOrUpdateString(pool.UpdateStrategy, "", preserveKnown)
}
}

func (pool *NodePoolModel) SetNodePoolCreateOptions(ctx context.Context, p *linodego.LKENodePoolCreateOptions, diags *diag.Diagnostics) {
func (pool *NodePoolModel) SetNodePoolCreateOptions(
ctx context.Context,
p *linodego.LKENodePoolCreateOptions,
diags *diag.Diagnostics,
tier string,
) {
p.Count = helper.FrameworkSafeInt64ToInt(
pool.Count.ValueInt64(),
diags,
Expand All @@ -143,33 +158,82 @@ func (pool *NodePoolModel) SetNodePoolCreateOptions(ctx context.Context, p *lino
p.Taints = pool.getLKENodePoolTaints()

pool.Labels.ElementsAs(ctx, &p.Labels, false)

if tier == "enterprise" {
p.K8sVersion = pool.K8sVersion.ValueStringPointer()
p.UpdateStrategy = linodego.Pointer(linodego.LKENodePoolUpdateStrategy(pool.UpdateStrategy.ValueString()))
}
}

func (pool *NodePoolModel) SetNodePoolUpdateOptions(ctx context.Context, p *linodego.LKENodePoolUpdateOptions, diags *diag.Diagnostics) {
p.Count = helper.FrameworkSafeInt64ToInt(
pool.Count.ValueInt64(),
diags,
)
func (pool *NodePoolModel) SetNodePoolUpdateOptions(
ctx context.Context,
p *linodego.LKENodePoolUpdateOptions,
diags *diag.Diagnostics,
state *NodePoolModel,
tier string,
) bool {
var shouldUpdate bool

if !state.Count.Equal(pool.Count) {
p.Count = helper.FrameworkSafeInt64ToInt(
pool.Count.ValueInt64(),
diags,
)
if diags.HasError() {
return false
}

shouldUpdate = true
}

if !state.Tags.Equal(pool.Tags) {
if !pool.Tags.IsNull() {
diags.Append(pool.Tags.ElementsAs(ctx, &p.Tags, false)...)
if diags.HasError() {
return false
}
}

shouldUpdate = true
}

autoscaler, asNeedsUpdate := pool.shouldUpdateLKENodePoolAutoscaler(p.Count, state, diags)
if diags.HasError() {
return
return false
}

if !pool.Tags.IsNull() {
diags.Append(pool.Tags.ElementsAs(ctx, &p.Tags, false)...)
if diags.HasError() {
return
if asNeedsUpdate {
p.Autoscaler = autoscaler
if p.Autoscaler.Enabled && p.Count == 0 {
p.Count = p.Autoscaler.Min
}
}

p.Autoscaler = pool.getLKENodePoolAutoscaler(p.Count, diags)
if p.Autoscaler.Enabled && p.Count == 0 {
p.Count = p.Autoscaler.Min
shouldUpdate = shouldUpdate || asNeedsUpdate

if !(len(state.Taints) == 0 && len(pool.Taints) == 0) {
taints := pool.getLKENodePoolTaints()
p.Taints = &taints
shouldUpdate = true
}

taints := pool.getLKENodePoolTaints()
p.Taints = &taints
if !state.Labels.Equal(pool.Labels) {
pool.Labels.ElementsAs(ctx, &p.Labels, false)
}

pool.Labels.ElementsAs(ctx, &p.Labels, false)
if tier == "enterprise" {
if !state.K8sVersion.Equal(pool.K8sVersion) {
p.K8sVersion = pool.K8sVersion.ValueStringPointer()
shouldUpdate = true
}

if !state.UpdateStrategy.Equal(pool.UpdateStrategy) {
p.UpdateStrategy = linodego.Pointer(linodego.LKENodePoolUpdateStrategy(pool.UpdateStrategy.ValueString()))
shouldUpdate = true
}
}

return shouldUpdate
}

func (pool *NodePoolModel) ExtractClusterAndNodePoolIDs(diags *diag.Diagnostics) (int, int) {
Expand All @@ -195,6 +259,35 @@ func (pool *NodePoolModel) getLKENodePoolAutoscaler(count int, diags *diag.Diagn
return &autoscaler
}

func (pool *NodePoolModel) shouldUpdateLKENodePoolAutoscaler(
count int,
state *NodePoolModel,
diags *diag.Diagnostics,
) (*linodego.LKENodePoolAutoscaler, bool) {
var autoscaler linodego.LKENodePoolAutoscaler
var shouldUpdate bool

if len(pool.Autoscaler) > 0 {
if len(state.Autoscaler) == 0 ||
(len(state.Autoscaler) > 0 && (!state.Autoscaler[0].Min.Equal(pool.Autoscaler[0].Min) ||
!state.Autoscaler[0].Max.Equal(pool.Autoscaler[0].Max))) {
autoscaler.Enabled = true
autoscaler.Min = helper.FrameworkSafeInt64ToInt(pool.Autoscaler[0].Min.ValueInt64(), diags)
autoscaler.Max = helper.FrameworkSafeInt64ToInt(pool.Autoscaler[0].Max.ValueInt64(), diags)

shouldUpdate = true
}
} else if len(state.Autoscaler) > 0 {
autoscaler.Enabled = false
autoscaler.Min = count
autoscaler.Max = count

shouldUpdate = true
}

return &autoscaler, shouldUpdate
}

func (taint NodePoolTaintModel) getLKENodePoolTaint() linodego.LKENodePoolTaint {
return linodego.LKENodePoolTaint{
Effect: linodego.LKENodePoolTaintEffect(taint.Effect.ValueString()),
Expand All @@ -212,3 +305,21 @@ func (pool *NodePoolModel) getLKENodePoolTaints() []linodego.LKENodePoolTaint {

return taints
}

func (data *NodePoolModel) CopyFrom(other NodePoolModel, preserveKnown bool) {
data.ID = helper.KeepOrUpdateValue(data.ID, other.ID, preserveKnown)
data.ClusterID = helper.KeepOrUpdateValue(data.ClusterID, other.ClusterID, preserveKnown)
data.Count = helper.KeepOrUpdateValue(data.Count, other.Count, preserveKnown)
data.Type = helper.KeepOrUpdateValue(data.Type, other.Type, preserveKnown)
data.DiskEncryption = helper.KeepOrUpdateValue(data.DiskEncryption, other.DiskEncryption, preserveKnown)
data.Tags = helper.KeepOrUpdateValue(data.Tags, other.Tags, preserveKnown)
data.Nodes = helper.KeepOrUpdateValue(data.Nodes, other.Nodes, preserveKnown)
data.Labels = helper.KeepOrUpdateValue(data.Labels, other.Labels, preserveKnown)
data.K8sVersion = helper.KeepOrUpdateValue(data.K8sVersion, other.K8sVersion, preserveKnown)
data.UpdateStrategy = helper.KeepOrUpdateValue(data.UpdateStrategy, other.UpdateStrategy, preserveKnown)

if !preserveKnown {
data.Autoscaler = other.Autoscaler
data.Taints = other.Taints
}
}
14 changes: 12 additions & 2 deletions linode/lkenodepool/framework_models_unit_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ func TestSetNodePoolCreateOptions(t *testing.T) {
var createOpts linodego.LKENodePoolCreateOptions
var diags diag.Diagnostics

nodePoolModel.SetNodePoolCreateOptions(context.Background(), &createOpts, &diags)
nodePoolModel.SetNodePoolCreateOptions(context.Background(), &createOpts, &diags, "enterprise")

assert.False(t, diags.HasError())
assert.Equal(t, 3, createOpts.Count)
Expand All @@ -77,24 +77,32 @@ func TestSetNodePoolCreateOptions(t *testing.T) {
assert.True(t, createOpts.Autoscaler.Enabled)
assert.Equal(t, 1, createOpts.Autoscaler.Min)
assert.Equal(t, 5, createOpts.Autoscaler.Max)

assert.Equal(t, "k8s_version", *createOpts.K8sVersion)
assert.Equal(t, "on_recycle", string(*createOpts.UpdateStrategy))
}

func TestSetNodePoolUpdateOptions(t *testing.T) {
nodePoolModel := createNodePoolModel()
state := NodePoolModel{ID: types.StringValue("123")}

var updateOpts linodego.LKENodePoolUpdateOptions
var diags diag.Diagnostics

nodePoolModel.SetNodePoolUpdateOptions(context.Background(), &updateOpts, &diags)
shouldUpdate := nodePoolModel.SetNodePoolUpdateOptions(context.Background(), &updateOpts, &diags, &state, "enterprise")

assert.False(t, diags.HasError())
assert.True(t, shouldUpdate)
assert.Equal(t, 3, updateOpts.Count)
assert.Contains(t, *updateOpts.Tags, "production")
assert.Contains(t, *updateOpts.Tags, "web-server")

assert.True(t, updateOpts.Autoscaler.Enabled)
assert.Equal(t, 1, updateOpts.Autoscaler.Min)
assert.Equal(t, 5, updateOpts.Autoscaler.Max)

assert.Equal(t, "k8s_version", *updateOpts.K8sVersion)
assert.Equal(t, "on_recycle", string(*updateOpts.UpdateStrategy))
}

func createNodePoolModel() *NodePoolModel {
Expand All @@ -117,6 +125,8 @@ func createNodePoolModel() *NodePoolModel {
Max: types.Int64Value(5),
},
},
K8sVersion: types.StringValue("k8s_version"),
UpdateStrategy: types.StringValue("on_recycle"),
}

nodePoolModel.Labels = types.MapValueMust(types.StringType, map[string]attr.Value{})
Expand Down
74 changes: 44 additions & 30 deletions linode/lkenodepool/framework_resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,18 +98,23 @@ func (r *Resource) Create(
return
}

var createOpts linodego.LKENodePoolCreateOptions

plan.SetNodePoolCreateOptions(ctx, &createOpts, &resp.Diagnostics)
if resp.Diagnostics.HasError() {
return
}

clusterID := helper.FrameworkSafeInt64ToInt(
plan.ClusterID.ValueInt64(),
&resp.Diagnostics,
)
if resp.Diagnostics.HasError() {
return
}

cluster, err := client.GetLKECluster(ctx, clusterID)
if err != nil {
resp.Diagnostics.AddError("error getting cluster", err.Error())
return
}

var createOpts linodego.LKENodePoolCreateOptions

plan.SetNodePoolCreateOptions(ctx, &createOpts, &resp.Diagnostics, cluster.Tier)
if resp.Diagnostics.HasError() {
return
}
Expand Down Expand Up @@ -172,41 +177,50 @@ func (r *Resource) Update(
return
}

var updateOpts linodego.LKENodePoolUpdateOptions

plan.SetNodePoolUpdateOptions(ctx, &updateOpts, &resp.Diagnostics)
if resp.Diagnostics.HasError() {
return
}

clusterID, poolID := state.ExtractClusterAndNodePoolIDs(&resp.Diagnostics)
if resp.Diagnostics.HasError() {
return
}

tflog.Debug(ctx, "client.UpdateLKENodePool(...)", map[string]any{
"cluster_id": clusterID,
"options": updateOpts,
})
pool, err := client.UpdateLKENodePool(ctx, clusterID, poolID, updateOpts)
cluster, err := client.GetLKECluster(ctx, clusterID)
if err != nil {
resp.Diagnostics.AddError("Error updating a Linode Node Pool", err.Error())
resp.Diagnostics.AddError("error getting cluster", err.Error())
return
}

tflog.Debug(ctx, "waiting for node pool to enter ready status")
readyPool, err := WaitForNodePoolReady(ctx,
*client,
int(r.Meta.Config.EventPollMilliseconds.ValueInt64()),
clusterID,
pool.ID,
)
if err != nil {
resp.Diagnostics.AddError("Linode Node Pool is not ready after update", err.Error())
var updateOpts linodego.LKENodePoolUpdateOptions

shouldUpdate := plan.SetNodePoolUpdateOptions(ctx, &updateOpts, &resp.Diagnostics, &state, cluster.Tier)
if resp.Diagnostics.HasError() {
return
}

plan.FlattenLKENodePool(ctx, readyPool, true, &resp.Diagnostics)
if shouldUpdate {
tflog.Debug(ctx, "client.UpdateLKENodePool(...)", map[string]any{
"cluster_id": clusterID,
"options": updateOpts,
})
pool, err := client.UpdateLKENodePool(ctx, clusterID, poolID, updateOpts)
if err != nil {
resp.Diagnostics.AddError("Error updating a Linode Node Pool", err.Error())
return
}

tflog.Debug(ctx, "waiting for node pool to enter ready status")
readyPool, err := WaitForNodePoolReady(ctx,
*client,
int(r.Meta.Config.EventPollMilliseconds.ValueInt64()),
clusterID,
pool.ID,
)
if err != nil {
resp.Diagnostics.AddError("Linode Node Pool is not ready after update", err.Error())
return
}
plan.FlattenLKENodePool(ctx, readyPool, true, &resp.Diagnostics)
}

plan.CopyFrom(state, true)

// Workaround for Crossplane issue where ID is not
// properly populated in plan
Expand Down
Loading
Loading