Skip to content

Commit 55137a0

Browse files
authored
Support enterprise feature in node pool resource and improve its update logic (#1832)
* enterprise change * fix unit test * update linodego
1 parent b1726bc commit 55137a0

File tree

10 files changed

+348
-57
lines changed

10 files changed

+348
-57
lines changed

docs/resources/lke_node_pool.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,10 @@ The following arguments are supported:
9393

9494
* `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).
9595

96+
* `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.
97+
98+
* `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.
99+
96100
* [`autoscaler`](#autoscaler) - (Optional) If defined, an autoscaler will be enabled with the given configuration.
97101

98102
* [`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).

go.mod

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ require (
2626
github.com/hashicorp/terraform-plugin-mux v0.18.0
2727
github.com/hashicorp/terraform-plugin-sdk/v2 v2.36.1
2828
github.com/hashicorp/terraform-plugin-testing v1.12.0
29-
github.com/linode/linodego v1.48.1
29+
github.com/linode/linodego v1.49.0
3030
github.com/linode/linodego/k8s v1.25.2
3131
github.com/stretchr/testify v1.10.0
3232
golang.org/x/crypto v0.37.0
@@ -102,7 +102,7 @@ require (
102102
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
103103
github.com/zclconf/go-cty v1.16.2 // indirect
104104
golang.org/x/mod v0.22.0 // indirect
105-
golang.org/x/oauth2 v0.27.0 // indirect
105+
golang.org/x/oauth2 v0.28.0 // indirect
106106
golang.org/x/sys v0.32.0 // indirect
107107
golang.org/x/term v0.31.0 // indirect
108108
golang.org/x/text v0.24.0 // indirect

go.sum

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -194,8 +194,8 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
194194
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
195195
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
196196
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
197-
github.com/linode/linodego v1.48.1 h1:Ojw1S+K5jJr1dggO8/H6r4FINxXnJbOU5GkbpaTfmhU=
198-
github.com/linode/linodego v1.48.1/go.mod h1:fc3t60If8X+yZTFAebhCnNDFrhwQhq9HDU92WnBousQ=
197+
github.com/linode/linodego v1.49.0 h1:MNd3qwvQzbXB5mCpvdCqlUIu1RPA9oC+50LyB9kK+GQ=
198+
github.com/linode/linodego v1.49.0/go.mod h1:B+HAM3//4w1wOS0BwdaQBKwBxlfe6kYJ7bSC6jJ/xtc=
199199
github.com/linode/linodego/k8s v1.25.2 h1:PY6S0sAD3xANVvM9WY38bz9GqMTjIbytC8IJJ9Cv23o=
200200
github.com/linode/linodego/k8s v1.25.2/go.mod h1:DC1XCSRZRGsmaa/ggpDPSDUmOM6aK1bhSIP6+f9Cwhc=
201201
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
@@ -299,8 +299,8 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v
299299
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
300300
golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=
301301
golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
302-
golang.org/x/oauth2 v0.27.0 h1:da9Vo7/tDv5RH/7nZDz1eMGS/q1Vv1N/7FCrBhI9I3M=
303-
golang.org/x/oauth2 v0.27.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8=
302+
golang.org/x/oauth2 v0.28.0 h1:CrgCKl8PPAVtLnU3c+EDw6x11699EWlsDeWNWKdIOkc=
303+
golang.org/x/oauth2 v0.28.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8=
304304
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
305305
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
306306
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=

linode/lkenodepool/framework_models.go

Lines changed: 128 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ type NodePoolModel struct {
2424
Autoscaler []NodePoolAutoscalerModel `tfsdk:"autoscaler"`
2525
Taints []NodePoolTaintModel `tfsdk:"taint"`
2626
Labels types.Map `tfsdk:"labels"`
27+
K8sVersion types.String `tfsdk:"k8s_version"`
28+
UpdateStrategy types.String `tfsdk:"update_strategy"`
2729
}
2830

2931
type NodePoolAutoscalerModel struct {
@@ -122,9 +124,22 @@ func (pool *NodePoolModel) FlattenLKENodePool(
122124
diags.Append(errs...)
123125
}
124126
pool.Nodes = helper.KeepOrUpdateValue(pool.Nodes, *nodePoolLinodes, preserveKnown)
127+
128+
pool.K8sVersion = helper.KeepOrUpdateStringPointer(pool.K8sVersion, p.K8sVersion, preserveKnown)
129+
130+
if p.UpdateStrategy != nil {
131+
pool.UpdateStrategy = helper.KeepOrUpdateString(pool.UpdateStrategy, string(*p.UpdateStrategy), preserveKnown)
132+
} else {
133+
pool.UpdateStrategy = helper.KeepOrUpdateString(pool.UpdateStrategy, "", preserveKnown)
134+
}
125135
}
126136

127-
func (pool *NodePoolModel) SetNodePoolCreateOptions(ctx context.Context, p *linodego.LKENodePoolCreateOptions, diags *diag.Diagnostics) {
137+
func (pool *NodePoolModel) SetNodePoolCreateOptions(
138+
ctx context.Context,
139+
p *linodego.LKENodePoolCreateOptions,
140+
diags *diag.Diagnostics,
141+
tier string,
142+
) {
128143
p.Count = helper.FrameworkSafeInt64ToInt(
129144
pool.Count.ValueInt64(),
130145
diags,
@@ -143,33 +158,82 @@ func (pool *NodePoolModel) SetNodePoolCreateOptions(ctx context.Context, p *lino
143158
p.Taints = pool.getLKENodePoolTaints()
144159

145160
pool.Labels.ElementsAs(ctx, &p.Labels, false)
161+
162+
if tier == "enterprise" {
163+
p.K8sVersion = pool.K8sVersion.ValueStringPointer()
164+
p.UpdateStrategy = linodego.Pointer(linodego.LKENodePoolUpdateStrategy(pool.UpdateStrategy.ValueString()))
165+
}
146166
}
147167

148-
func (pool *NodePoolModel) SetNodePoolUpdateOptions(ctx context.Context, p *linodego.LKENodePoolUpdateOptions, diags *diag.Diagnostics) {
149-
p.Count = helper.FrameworkSafeInt64ToInt(
150-
pool.Count.ValueInt64(),
151-
diags,
152-
)
168+
func (pool *NodePoolModel) SetNodePoolUpdateOptions(
169+
ctx context.Context,
170+
p *linodego.LKENodePoolUpdateOptions,
171+
diags *diag.Diagnostics,
172+
state *NodePoolModel,
173+
tier string,
174+
) bool {
175+
var shouldUpdate bool
176+
177+
if !state.Count.Equal(pool.Count) {
178+
p.Count = helper.FrameworkSafeInt64ToInt(
179+
pool.Count.ValueInt64(),
180+
diags,
181+
)
182+
if diags.HasError() {
183+
return false
184+
}
185+
186+
shouldUpdate = true
187+
}
188+
189+
if !state.Tags.Equal(pool.Tags) {
190+
if !pool.Tags.IsNull() {
191+
diags.Append(pool.Tags.ElementsAs(ctx, &p.Tags, false)...)
192+
if diags.HasError() {
193+
return false
194+
}
195+
}
196+
197+
shouldUpdate = true
198+
}
199+
200+
autoscaler, asNeedsUpdate := pool.shouldUpdateLKENodePoolAutoscaler(p.Count, state, diags)
153201
if diags.HasError() {
154-
return
202+
return false
155203
}
156204

157-
if !pool.Tags.IsNull() {
158-
diags.Append(pool.Tags.ElementsAs(ctx, &p.Tags, false)...)
159-
if diags.HasError() {
160-
return
205+
if asNeedsUpdate {
206+
p.Autoscaler = autoscaler
207+
if p.Autoscaler.Enabled && p.Count == 0 {
208+
p.Count = p.Autoscaler.Min
161209
}
162210
}
163211

164-
p.Autoscaler = pool.getLKENodePoolAutoscaler(p.Count, diags)
165-
if p.Autoscaler.Enabled && p.Count == 0 {
166-
p.Count = p.Autoscaler.Min
212+
shouldUpdate = shouldUpdate || asNeedsUpdate
213+
214+
if !(len(state.Taints) == 0 && len(pool.Taints) == 0) {
215+
taints := pool.getLKENodePoolTaints()
216+
p.Taints = &taints
217+
shouldUpdate = true
167218
}
168219

169-
taints := pool.getLKENodePoolTaints()
170-
p.Taints = &taints
220+
if !state.Labels.Equal(pool.Labels) {
221+
pool.Labels.ElementsAs(ctx, &p.Labels, false)
222+
}
171223

172-
pool.Labels.ElementsAs(ctx, &p.Labels, false)
224+
if tier == "enterprise" {
225+
if !state.K8sVersion.Equal(pool.K8sVersion) {
226+
p.K8sVersion = pool.K8sVersion.ValueStringPointer()
227+
shouldUpdate = true
228+
}
229+
230+
if !state.UpdateStrategy.Equal(pool.UpdateStrategy) {
231+
p.UpdateStrategy = linodego.Pointer(linodego.LKENodePoolUpdateStrategy(pool.UpdateStrategy.ValueString()))
232+
shouldUpdate = true
233+
}
234+
}
235+
236+
return shouldUpdate
173237
}
174238

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

262+
func (pool *NodePoolModel) shouldUpdateLKENodePoolAutoscaler(
263+
count int,
264+
state *NodePoolModel,
265+
diags *diag.Diagnostics,
266+
) (*linodego.LKENodePoolAutoscaler, bool) {
267+
var autoscaler linodego.LKENodePoolAutoscaler
268+
var shouldUpdate bool
269+
270+
if len(pool.Autoscaler) > 0 {
271+
if len(state.Autoscaler) == 0 ||
272+
(len(state.Autoscaler) > 0 && (!state.Autoscaler[0].Min.Equal(pool.Autoscaler[0].Min) ||
273+
!state.Autoscaler[0].Max.Equal(pool.Autoscaler[0].Max))) {
274+
autoscaler.Enabled = true
275+
autoscaler.Min = helper.FrameworkSafeInt64ToInt(pool.Autoscaler[0].Min.ValueInt64(), diags)
276+
autoscaler.Max = helper.FrameworkSafeInt64ToInt(pool.Autoscaler[0].Max.ValueInt64(), diags)
277+
278+
shouldUpdate = true
279+
}
280+
} else if len(state.Autoscaler) > 0 {
281+
autoscaler.Enabled = false
282+
autoscaler.Min = count
283+
autoscaler.Max = count
284+
285+
shouldUpdate = true
286+
}
287+
288+
return &autoscaler, shouldUpdate
289+
}
290+
198291
func (taint NodePoolTaintModel) getLKENodePoolTaint() linodego.LKENodePoolTaint {
199292
return linodego.LKENodePoolTaint{
200293
Effect: linodego.LKENodePoolTaintEffect(taint.Effect.ValueString()),
@@ -212,3 +305,21 @@ func (pool *NodePoolModel) getLKENodePoolTaints() []linodego.LKENodePoolTaint {
212305

213306
return taints
214307
}
308+
309+
func (data *NodePoolModel) CopyFrom(other NodePoolModel, preserveKnown bool) {
310+
data.ID = helper.KeepOrUpdateValue(data.ID, other.ID, preserveKnown)
311+
data.ClusterID = helper.KeepOrUpdateValue(data.ClusterID, other.ClusterID, preserveKnown)
312+
data.Count = helper.KeepOrUpdateValue(data.Count, other.Count, preserveKnown)
313+
data.Type = helper.KeepOrUpdateValue(data.Type, other.Type, preserveKnown)
314+
data.DiskEncryption = helper.KeepOrUpdateValue(data.DiskEncryption, other.DiskEncryption, preserveKnown)
315+
data.Tags = helper.KeepOrUpdateValue(data.Tags, other.Tags, preserveKnown)
316+
data.Nodes = helper.KeepOrUpdateValue(data.Nodes, other.Nodes, preserveKnown)
317+
data.Labels = helper.KeepOrUpdateValue(data.Labels, other.Labels, preserveKnown)
318+
data.K8sVersion = helper.KeepOrUpdateValue(data.K8sVersion, other.K8sVersion, preserveKnown)
319+
data.UpdateStrategy = helper.KeepOrUpdateValue(data.UpdateStrategy, other.UpdateStrategy, preserveKnown)
320+
321+
if !preserveKnown {
322+
data.Autoscaler = other.Autoscaler
323+
data.Taints = other.Taints
324+
}
325+
}

linode/lkenodepool/framework_models_unit_test.go

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ func TestSetNodePoolCreateOptions(t *testing.T) {
6666
var createOpts linodego.LKENodePoolCreateOptions
6767
var diags diag.Diagnostics
6868

69-
nodePoolModel.SetNodePoolCreateOptions(context.Background(), &createOpts, &diags)
69+
nodePoolModel.SetNodePoolCreateOptions(context.Background(), &createOpts, &diags, "enterprise")
7070

7171
assert.False(t, diags.HasError())
7272
assert.Equal(t, 3, createOpts.Count)
@@ -77,24 +77,32 @@ func TestSetNodePoolCreateOptions(t *testing.T) {
7777
assert.True(t, createOpts.Autoscaler.Enabled)
7878
assert.Equal(t, 1, createOpts.Autoscaler.Min)
7979
assert.Equal(t, 5, createOpts.Autoscaler.Max)
80+
81+
assert.Equal(t, "k8s_version", *createOpts.K8sVersion)
82+
assert.Equal(t, "on_recycle", string(*createOpts.UpdateStrategy))
8083
}
8184

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

8589
var updateOpts linodego.LKENodePoolUpdateOptions
8690
var diags diag.Diagnostics
8791

88-
nodePoolModel.SetNodePoolUpdateOptions(context.Background(), &updateOpts, &diags)
92+
shouldUpdate := nodePoolModel.SetNodePoolUpdateOptions(context.Background(), &updateOpts, &diags, &state, "enterprise")
8993

9094
assert.False(t, diags.HasError())
95+
assert.True(t, shouldUpdate)
9196
assert.Equal(t, 3, updateOpts.Count)
9297
assert.Contains(t, *updateOpts.Tags, "production")
9398
assert.Contains(t, *updateOpts.Tags, "web-server")
9499

95100
assert.True(t, updateOpts.Autoscaler.Enabled)
96101
assert.Equal(t, 1, updateOpts.Autoscaler.Min)
97102
assert.Equal(t, 5, updateOpts.Autoscaler.Max)
103+
104+
assert.Equal(t, "k8s_version", *updateOpts.K8sVersion)
105+
assert.Equal(t, "on_recycle", string(*updateOpts.UpdateStrategy))
98106
}
99107

100108
func createNodePoolModel() *NodePoolModel {
@@ -117,6 +125,8 @@ func createNodePoolModel() *NodePoolModel {
117125
Max: types.Int64Value(5),
118126
},
119127
},
128+
K8sVersion: types.StringValue("k8s_version"),
129+
UpdateStrategy: types.StringValue("on_recycle"),
120130
}
121131

122132
nodePoolModel.Labels = types.MapValueMust(types.StringType, map[string]attr.Value{})

linode/lkenodepool/framework_resource.go

Lines changed: 44 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -98,18 +98,23 @@ func (r *Resource) Create(
9898
return
9999
}
100100

101-
var createOpts linodego.LKENodePoolCreateOptions
102-
103-
plan.SetNodePoolCreateOptions(ctx, &createOpts, &resp.Diagnostics)
104-
if resp.Diagnostics.HasError() {
105-
return
106-
}
107-
108101
clusterID := helper.FrameworkSafeInt64ToInt(
109102
plan.ClusterID.ValueInt64(),
110103
&resp.Diagnostics,
111104
)
105+
if resp.Diagnostics.HasError() {
106+
return
107+
}
108+
109+
cluster, err := client.GetLKECluster(ctx, clusterID)
110+
if err != nil {
111+
resp.Diagnostics.AddError("error getting cluster", err.Error())
112+
return
113+
}
114+
115+
var createOpts linodego.LKENodePoolCreateOptions
112116

117+
plan.SetNodePoolCreateOptions(ctx, &createOpts, &resp.Diagnostics, cluster.Tier)
113118
if resp.Diagnostics.HasError() {
114119
return
115120
}
@@ -172,41 +177,50 @@ func (r *Resource) Update(
172177
return
173178
}
174179

175-
var updateOpts linodego.LKENodePoolUpdateOptions
176-
177-
plan.SetNodePoolUpdateOptions(ctx, &updateOpts, &resp.Diagnostics)
178-
if resp.Diagnostics.HasError() {
179-
return
180-
}
181-
182180
clusterID, poolID := state.ExtractClusterAndNodePoolIDs(&resp.Diagnostics)
183181
if resp.Diagnostics.HasError() {
184182
return
185183
}
186184

187-
tflog.Debug(ctx, "client.UpdateLKENodePool(...)", map[string]any{
188-
"cluster_id": clusterID,
189-
"options": updateOpts,
190-
})
191-
pool, err := client.UpdateLKENodePool(ctx, clusterID, poolID, updateOpts)
185+
cluster, err := client.GetLKECluster(ctx, clusterID)
192186
if err != nil {
193-
resp.Diagnostics.AddError("Error updating a Linode Node Pool", err.Error())
187+
resp.Diagnostics.AddError("error getting cluster", err.Error())
194188
return
195189
}
196190

197-
tflog.Debug(ctx, "waiting for node pool to enter ready status")
198-
readyPool, err := WaitForNodePoolReady(ctx,
199-
*client,
200-
int(r.Meta.Config.EventPollMilliseconds.ValueInt64()),
201-
clusterID,
202-
pool.ID,
203-
)
204-
if err != nil {
205-
resp.Diagnostics.AddError("Linode Node Pool is not ready after update", err.Error())
191+
var updateOpts linodego.LKENodePoolUpdateOptions
192+
193+
shouldUpdate := plan.SetNodePoolUpdateOptions(ctx, &updateOpts, &resp.Diagnostics, &state, cluster.Tier)
194+
if resp.Diagnostics.HasError() {
206195
return
207196
}
208197

209-
plan.FlattenLKENodePool(ctx, readyPool, true, &resp.Diagnostics)
198+
if shouldUpdate {
199+
tflog.Debug(ctx, "client.UpdateLKENodePool(...)", map[string]any{
200+
"cluster_id": clusterID,
201+
"options": updateOpts,
202+
})
203+
pool, err := client.UpdateLKENodePool(ctx, clusterID, poolID, updateOpts)
204+
if err != nil {
205+
resp.Diagnostics.AddError("Error updating a Linode Node Pool", err.Error())
206+
return
207+
}
208+
209+
tflog.Debug(ctx, "waiting for node pool to enter ready status")
210+
readyPool, err := WaitForNodePoolReady(ctx,
211+
*client,
212+
int(r.Meta.Config.EventPollMilliseconds.ValueInt64()),
213+
clusterID,
214+
pool.ID,
215+
)
216+
if err != nil {
217+
resp.Diagnostics.AddError("Linode Node Pool is not ready after update", err.Error())
218+
return
219+
}
220+
plan.FlattenLKENodePool(ctx, readyPool, true, &resp.Diagnostics)
221+
}
222+
223+
plan.CopyFrom(state, true)
210224

211225
// Workaround for Crossplane issue where ID is not
212226
// properly populated in plan

0 commit comments

Comments
 (0)