@@ -27,18 +27,21 @@ import (
27
27
corev1 "k8s.io/api/core/v1"
28
28
apierrors "k8s.io/apimachinery/pkg/api/errors"
29
29
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
30
+ "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
30
31
kerrors "k8s.io/apimachinery/pkg/util/errors"
31
32
"k8s.io/client-go/rest"
32
33
operatorv1 "sigs.k8s.io/cluster-api-operator/api/v1alpha2"
33
34
"sigs.k8s.io/cluster-api-operator/internal/controller/genericprovider"
34
35
clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
36
+ configclient "sigs.k8s.io/cluster-api/cmd/clusterctl/client/config"
35
37
"sigs.k8s.io/cluster-api/util/conditions"
36
38
"sigs.k8s.io/cluster-api/util/patch"
37
39
ctrl "sigs.k8s.io/controller-runtime"
38
40
"sigs.k8s.io/controller-runtime/pkg/client"
39
41
"sigs.k8s.io/controller-runtime/pkg/controller"
40
42
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
41
43
"sigs.k8s.io/controller-runtime/pkg/handler"
44
+ "sigs.k8s.io/controller-runtime/pkg/log"
42
45
"sigs.k8s.io/controller-runtime/pkg/reconcile"
43
46
)
44
47
@@ -56,6 +59,7 @@ type GenericProviderReconciler struct {
56
59
57
60
const (
58
61
appliedSpecHashAnnotation = "operator.cluster.x-k8s.io/applied-spec-hash"
62
+ cacheOwner = "capi-operator"
59
63
)
60
64
61
65
func (r * GenericProviderReconciler ) BuildWithManager (ctx context.Context , mgr ctrl.Manager ) (* ctrl.Builder , error ) {
@@ -88,14 +92,17 @@ func (r *GenericProviderReconciler) BuildWithManager(ctx context.Context, mgr ct
88
92
reconciler := NewPhaseReconciler (* r , r .Provider , r .ProviderList )
89
93
90
94
r .ReconcilePhases = []PhaseFn {
95
+ reconciler .ApplyFromCache ,
91
96
reconciler .PreflightChecks ,
92
97
reconciler .InitializePhaseReconciler ,
93
98
reconciler .DownloadManifests ,
94
99
reconciler .Load ,
95
100
reconciler .Fetch ,
101
+ reconciler .Store ,
96
102
reconciler .Upgrade ,
97
103
reconciler .Install ,
98
104
reconciler .ReportStatus ,
105
+ reconciler .Finalize ,
99
106
}
100
107
101
108
r .DeletePhases = []PhaseFn {
@@ -175,6 +182,7 @@ func (r *GenericProviderReconciler) Reconcile(ctx context.Context, req reconcile
175
182
176
183
if r .Provider .GetAnnotations ()[appliedSpecHashAnnotation ] == specHash {
177
184
log .Info ("No changes detected, skipping further steps" )
185
+
178
186
return ctrl.Result {}, nil
179
187
}
180
188
@@ -316,17 +324,184 @@ func addObjectToHash(hash hash.Hash, object interface{}) error {
316
324
return nil
317
325
}
318
326
327
+ // providerHash calculates hash for provider and referenced objects.
328
+ func providerHash (ctx context.Context , client client.Client , hash hash.Hash , provider genericprovider.GenericProvider ) error {
329
+ log := log .FromContext (ctx )
330
+
331
+ err := addObjectToHash (hash , provider .GetSpec ())
332
+ if err != nil {
333
+ log .Error (err , "failed to calculate provider hash" )
334
+
335
+ return err
336
+ }
337
+
338
+ if err := addConfigSecretToHash (ctx , client , hash , provider ); err != nil {
339
+ log .Error (err , "failed to calculate secret hash" )
340
+
341
+ return err
342
+ }
343
+
344
+ return nil
345
+ }
346
+
319
347
func calculateHash (ctx context.Context , k8sClient client.Client , provider genericprovider.GenericProvider ) (string , error ) {
320
348
hash := sha256 .New ()
321
349
322
- err := addObjectToHash (hash , provider .GetSpec ())
350
+ err := providerHash (ctx , k8sClient , hash , provider )
351
+
352
+ return fmt .Sprintf ("%x" , hash .Sum (nil )), err
353
+ }
354
+
355
+ // ApplyFromCache applies provider configuration from cache and returns true if the cache did not change.
356
+ func (p * PhaseReconciler ) ApplyFromCache (ctx context.Context ) (* Result , error ) {
357
+ log := log .FromContext (ctx )
358
+
359
+ secret := & corev1.Secret {}
360
+ if err := p .ctrlClient .Get (ctx , client.ObjectKey {Name : ProviderCacheName (p .provider ), Namespace : p .provider .GetNamespace ()}, secret ); apierrors .IsNotFound (err ) {
361
+ // secret does not exist, nothing to apply
362
+ return & Result {}, nil
363
+ } else if err != nil {
364
+ log .Error (err , "failed to get provider cache" )
365
+
366
+ return & Result {}, fmt .Errorf ("failed to get provider cache: %w" , err )
367
+ }
368
+
369
+ // calculate combined hash for provider and config map cache
370
+ hash := sha256 .New ()
371
+ if err := providerHash (ctx , p .ctrlClient , hash , p .provider ); err != nil {
372
+ log .Error (err , "failed to calculate provider hash" )
373
+
374
+ return & Result {}, err
375
+ }
376
+
377
+ if err := addObjectToHash (hash , secret .Data ); err != nil {
378
+ log .Error (err , "failed to calculate config map hash" )
379
+
380
+ return & Result {}, err
381
+ }
382
+
383
+ cacheHash := fmt .Sprintf ("%x" , hash .Sum (nil ))
384
+ if secret .GetAnnotations ()[appliedSpecHashAnnotation ] != cacheHash || p .provider .GetAnnotations ()[appliedSpecHashAnnotation ] != cacheHash {
385
+ log .Info ("Provider or cache state has changed" , "cacheHash" , cacheHash , "providerHash" , secret .GetAnnotations ()[appliedSpecHashAnnotation ])
386
+
387
+ return & Result {}, nil
388
+ }
389
+
390
+ log .Info ("Applying provider configuration from cache" )
391
+
392
+ errs := []error {}
393
+
394
+ mr := configclient .NewMemoryReader ()
395
+
396
+ if err := mr .Init (ctx , "" ); err != nil {
397
+ return & Result {}, err
398
+ }
399
+
400
+ // Fetch configuration variables from the secret. See API field docs for more info.
401
+ if err := initReaderVariables (ctx , p .ctrlClient , mr , p .provider ); err != nil {
402
+ return & Result {}, err
403
+ }
404
+
405
+ for _ , manifest := range secret .Data {
406
+ if secret .GetAnnotations ()[operatorv1 .CompressedAnnotation ] == operatorv1 .TrueValue {
407
+ break
408
+ }
409
+
410
+ manifests := []unstructured.Unstructured {}
411
+
412
+ err := json .Unmarshal (manifest , & manifests )
413
+ if err != nil {
414
+ log .Error (err , "failed to convert yaml to unstructured" )
415
+
416
+ return & Result {}, err
417
+ }
418
+
419
+ for _ , manifest := range manifests {
420
+ if err := p .ctrlClient .Patch (ctx , & manifest , client .Apply , client .ForceOwnership , client .FieldOwner (cacheOwner )); err != nil {
421
+ errs = append (errs , err )
422
+ }
423
+ }
424
+ }
425
+
426
+ for _ , binaryManifest := range secret .Data {
427
+ if secret .GetAnnotations ()[operatorv1 .CompressedAnnotation ] != operatorv1 .TrueValue {
428
+ break
429
+ }
430
+
431
+ manifest , err := decompressData (binaryManifest )
432
+ if err != nil {
433
+ log .Error (err , "failed to decompress yaml" )
434
+
435
+ return & Result {}, err
436
+ }
437
+
438
+ manifests := []unstructured.Unstructured {}
439
+
440
+ err = json .Unmarshal (manifest , & manifests )
441
+ if err != nil {
442
+ log .Error (err , "failed to convert yaml to unstructured" )
443
+
444
+ return & Result {}, err
445
+ }
446
+
447
+ for _ , manifest := range manifests {
448
+ if err := p .ctrlClient .Patch (ctx , & manifest , client .Apply , client .ForceOwnership , client .FieldOwner (cacheOwner )); err != nil {
449
+ errs = append (errs , err )
450
+ }
451
+ }
452
+ }
453
+
454
+ if err := kerrors .NewAggregate (errs ); err != nil {
455
+ log .Error (err , "failed to apply objects from cache" )
456
+
457
+ return & Result {}, err
458
+ }
459
+
460
+ log .Info ("Applied all objects from cache" )
461
+
462
+ return & Result {Completed : true }, nil
463
+ }
464
+
465
+ // setCacheHash calculates current provider and secret hash, and updates it on the secret.
466
+ func setCacheHash (ctx context.Context , cl client.Client , provider genericprovider.GenericProvider ) error {
467
+ secret := & corev1.Secret {}
468
+ if err := cl .Get (ctx , client.ObjectKey {Name : ProviderCacheName (provider ), Namespace : provider .GetNamespace ()}, secret ); err != nil {
469
+ return fmt .Errorf ("failed to get cache secret: %w" , err )
470
+ }
471
+
472
+ helper , err := patch .NewHelper (secret , cl )
323
473
if err != nil {
324
- return "" , err
474
+ return err
325
475
}
326
476
327
- if err := addConfigSecretToHash (ctx , k8sClient , hash , provider ); err != nil {
328
- return "" , err
477
+ hash := sha256 .New ()
478
+
479
+ if err := providerHash (ctx , cl , hash , provider ); err != nil {
480
+ return err
481
+ }
482
+
483
+ if err := addObjectToHash (hash , secret .Data ); err != nil {
484
+ return err
329
485
}
330
486
331
- return fmt .Sprintf ("%x" , hash .Sum (nil )), nil
487
+ cacheHash := fmt .Sprintf ("%x" , hash .Sum (nil ))
488
+
489
+ annotations := secret .GetAnnotations ()
490
+ if annotations == nil {
491
+ annotations = map [string ]string {}
492
+ }
493
+
494
+ annotations [appliedSpecHashAnnotation ] = cacheHash
495
+ secret .SetAnnotations (annotations )
496
+
497
+ // Set hash on the provider to avoid cache re-use on re-creation
498
+ annotations = provider .GetAnnotations ()
499
+ if annotations == nil {
500
+ annotations = map [string ]string {}
501
+ }
502
+
503
+ annotations [appliedSpecHashAnnotation ] = cacheHash
504
+ provider .SetAnnotations (annotations )
505
+
506
+ return helper .Patch (ctx , secret )
332
507
}
0 commit comments