@@ -18,10 +18,12 @@ import (
18
18
"fmt"
19
19
"reflect"
20
20
"strconv"
21
+ "strings"
21
22
"time"
22
23
23
24
ackcompare "github.com/aws-controllers-k8s/runtime/pkg/compare"
24
25
ackcondition "github.com/aws-controllers-k8s/runtime/pkg/condition"
26
+ ackerr "github.com/aws-controllers-k8s/runtime/pkg/errors"
25
27
ackrequeue "github.com/aws-controllers-k8s/runtime/pkg/requeue"
26
28
ackrtlog "github.com/aws-controllers-k8s/runtime/pkg/runtime/log"
27
29
svcsdk "github.com/aws/aws-sdk-go/service/eks"
@@ -30,6 +32,7 @@ import (
30
32
31
33
svcapitypes "github.com/aws-controllers-k8s/eks-controller/apis/v1alpha1"
32
34
"github.com/aws-controllers-k8s/eks-controller/pkg/tags"
35
+ "github.com/aws-controllers-k8s/eks-controller/pkg/util"
33
36
)
34
37
35
38
// Taken from the list of nodegroup statuses on the boto3 documentation
@@ -104,6 +107,20 @@ func customPreCompare(
104
107
}
105
108
}
106
109
}
110
+ // only compare releaseVersion if it's provided by the user. Note that there will always be a
111
+ // ReleaseVersion in the observed state (after a successful creation).
112
+ if a .ko .Spec .ReleaseVersion != nil && * a .ko .Spec .ReleaseVersion != "" {
113
+ if * a .ko .Spec .ReleaseVersion != * b .ko .Spec .ReleaseVersion {
114
+ delta .Add ("Spec.ReleaseVersion" , a .ko .Spec .ReleaseVersion , b .ko .Spec .ReleaseVersion )
115
+ }
116
+ }
117
+ // only compare version if it's provided by the user. Note that there will always be a
118
+ // Version in the observed state (after a successful creation).
119
+ if a .ko .Spec .Version != nil && * a .ko .Spec .Version != "" {
120
+ if * a .ko .Spec .Version != * b .ko .Spec .Version {
121
+ delta .Add ("Spec.Version" , a .ko .Spec .Version , b .ko .Spec .Version )
122
+ }
123
+ }
107
124
}
108
125
109
126
func getDesiredSizeManagedByAnnotation (nodegroup * svcapitypes.Nodegroup ) (string , bool ) {
@@ -239,6 +256,12 @@ func (rm *resourceManager) customUpdate(
239
256
rlog := ackrtlog .FromContext (ctx )
240
257
exit := rlog .Trace ("rm.customUpdate" )
241
258
defer exit (err )
259
+
260
+ if immutableFieldChanges := rm .getImmutableFieldChanges (delta ); len (immutableFieldChanges ) > 0 {
261
+ msg := fmt .Sprintf ("Immutable Spec fields have been modified: %s" , strings .Join (immutableFieldChanges , "," ))
262
+ return nil , ackerr .NewTerminalError (fmt .Errorf (msg ))
263
+ }
264
+
242
265
if delta .DifferentAt ("Spec.Tags" ) {
243
266
err := tags .SyncTags (
244
267
ctx , rm .sdkapi , rm .metrics ,
@@ -282,8 +305,55 @@ func (rm *resourceManager) customUpdate(
282
305
}
283
306
return returnNodegroupUpdating (updatedRes )
284
307
}
285
- if delta .DifferentAt ("Spec.Version" ) {
286
- if err := rm .updateVersion (ctx , desired ); err != nil {
308
+
309
+ // At the stage we know that at least one of Version, ReleaseVersion or
310
+ // LaunchTemplate has changed. The API does not allow using LaunchTemplate
311
+ // with either Version or ReleaseVersion. So we need to check if the user
312
+ // has provided a valid desired state.
313
+ // There is no need to manually set a terminal condition here as the
314
+ // controller will automatically set a terminal condition if the api
315
+ // returns an InvalidParameterException
316
+
317
+ if delta .DifferentAt ("Spec.Version" ) || delta .DifferentAt ("Spec.ReleaseVersion" ) || delta .DifferentAt ("Spec.LaunchTemplate" ) {
318
+ // Before trying to trigger a Nodegroup version update, we need to ensure that the user
319
+ // has provided a valid desired state. For context the EKS UpdateNodegroupVersion API
320
+ // accepts optional parameters Version and ReleaseVersion.
321
+ //
322
+ // The following are the valid combinations of the Version and ReleaseVersion parameters:
323
+ // 1. None of the parameters are provided
324
+ // 2. Only the Version parameter is provided
325
+ // 3. Only the ReleaseVersion parameter is provided
326
+ // 4. Both the Version and ReleaseVersion parameters are provided and they match
327
+ //
328
+ // The first case is not applicable here as it's counterintuitive in a declarative
329
+ // model to not provide a desired state and have the controller trigger a blind update.
330
+
331
+ // We need to set a terminal condition if the user provides both a version and release version
332
+ // and they do not match. This is needed because the controller could potentially start alternating
333
+ // between the non-matching version and release version in the spec and the observed state.
334
+ if desired .ko .Spec .Version != nil && desired .ko .Spec .ReleaseVersion != nil &&
335
+ * desired .ko .Spec .Version != "" && * desired .ko .Spec .ReleaseVersion != "" {
336
+
337
+ // First parse the user provided release version and desired release
338
+ desiredReleaseVersionTrimmed , err := util .GetEKSVersionFromReleaseVersion (* desired .ko .Spec .ReleaseVersion )
339
+ if err != nil {
340
+ return nil , ackerr .NewTerminalError (err )
341
+ }
342
+
343
+ // Set a terminal condition if the release version and version do not match.
344
+ // e.g if the user provides a release version of 1.16.8-20211201 and a version of 1.17
345
+ // They will either need to provide one of the following:
346
+ // 2. A version
347
+ // 1. A release version
348
+ // 3. A version and release version that matches (e.g 1.16 and 1.16.8-20211201)
349
+ if desiredReleaseVersionTrimmed != * desired .ko .Spec .Version {
350
+ return nil , ackerr .NewTerminalError (
351
+ fmt .Errorf ("version and release version do not match: %s and %s" , * desired .ko .Spec .Version , desiredReleaseVersionTrimmed ),
352
+ )
353
+ }
354
+ }
355
+
356
+ if err := rm .updateVersion (ctx , delta , desired ); err != nil {
287
357
return nil , err
288
358
}
289
359
return returnNodegroupUpdating (updatedRes )
@@ -380,39 +450,51 @@ func newUpdateTaintsPayload(
380
450
}
381
451
382
452
func newUpdateNodegroupVersionPayload (
453
+ delta * ackcompare.Delta ,
383
454
desired * resource ,
384
455
) * svcsdk.UpdateNodegroupVersionInput {
385
456
input := & svcsdk.UpdateNodegroupVersionInput {
386
- NodegroupName : desired .ko .Spec .Name ,
387
- ClusterName : desired .ko .Spec .ClusterName ,
388
- Version : desired .ko .Spec .Version ,
389
- ReleaseVersion : desired .ko .Spec .ReleaseVersion ,
457
+ NodegroupName : desired .ko .Spec .Name ,
458
+ ClusterName : desired .ko .Spec .ClusterName ,
459
+ }
460
+
461
+ if delta .DifferentAt ("Spec.Version" ) {
462
+ input .Version = desired .ko .Spec .Version
463
+ }
464
+
465
+ if delta .DifferentAt ("Spec.ReleaseVersion" ) {
466
+ input .ReleaseVersion = desired .ko .Spec .ReleaseVersion
390
467
}
391
468
392
- if desired .ko .Spec .LaunchTemplate != nil {
393
- input .SetLaunchTemplate (& svcsdk.LaunchTemplateSpecification {
394
- Id : desired .ko .Spec .LaunchTemplate .ID ,
395
- Name : desired .ko .Spec .LaunchTemplate .Name ,
396
- Version : desired .ko .Spec .LaunchTemplate .Version ,
397
- })
469
+ if delta .DifferentAt ("Spec.LaunchTemplate" ) {
470
+ // We need to be careful here to not access a nil pointer
471
+ if desired .ko .Spec .LaunchTemplate != nil {
472
+ input .SetLaunchTemplate (& svcsdk.LaunchTemplateSpecification {
473
+ Id : desired .ko .Spec .LaunchTemplate .ID ,
474
+ Name : desired .ko .Spec .LaunchTemplate .Name ,
475
+ Version : desired .ko .Spec .LaunchTemplate .Version ,
476
+ })
477
+ }
398
478
}
399
479
480
+ // If the force annotation is set, we set the force flag on the input
481
+ // payload.
400
482
if getUpdateNodeGroupForceAnnotation (desired .ko .ObjectMeta ) {
401
483
input .SetForce (true )
402
484
}
403
-
404
485
return input
405
486
}
406
487
407
488
func (rm * resourceManager ) updateVersion (
408
489
ctx context.Context ,
490
+ delta * ackcompare.Delta ,
409
491
r * resource ,
410
492
) (err error ) {
411
493
rlog := ackrtlog .FromContext (ctx )
412
494
exit := rlog .Trace ("rm.updateVersion" )
413
495
defer exit (err )
414
496
415
- input := newUpdateNodegroupVersionPayload (r )
497
+ input := newUpdateNodegroupVersionPayload (delta , r )
416
498
417
499
_ , err = rm .sdkapi .UpdateNodegroupVersionWithContext (ctx , input )
418
500
rm .metrics .RecordAPICall ("UPDATE" , "UpdateNodegroupVersion" , err )
0 commit comments