Skip to content

Commit ad061b0

Browse files
Calculate and compare provider configMap cache before reconcile
Signed-off-by: Danil-Grigorev <[email protected]>
1 parent 95adca5 commit ad061b0

File tree

3 files changed

+177
-11
lines changed

3 files changed

+177
-11
lines changed

internal/controller/genericprovider_controller.go

Lines changed: 141 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import (
2323
"errors"
2424
"fmt"
2525
"hash"
26+
"strings"
2627

2728
corev1 "k8s.io/api/core/v1"
2829
apierrors "k8s.io/apimachinery/pkg/api/errors"
@@ -39,7 +40,10 @@ import (
3940
"sigs.k8s.io/controller-runtime/pkg/controller"
4041
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
4142
"sigs.k8s.io/controller-runtime/pkg/handler"
43+
"sigs.k8s.io/controller-runtime/pkg/log"
4244
"sigs.k8s.io/controller-runtime/pkg/reconcile"
45+
46+
utilyaml "sigs.k8s.io/cluster-api/util/yaml"
4347
)
4448

4549
type GenericProviderReconciler struct {
@@ -56,6 +60,7 @@ type GenericProviderReconciler struct {
5660

5761
const (
5862
appliedSpecHashAnnotation = "operator.cluster.x-k8s.io/applied-spec-hash"
63+
cacheOwner = "capi-operator"
5964
)
6065

6166
func (r *GenericProviderReconciler) BuildWithManager(ctx context.Context, mgr ctrl.Manager) (*ctrl.Builder, error) {
@@ -173,8 +178,15 @@ func (r *GenericProviderReconciler) Reconcile(ctx context.Context, req reconcile
173178
return ctrl.Result{}, err
174179
}
175180

176-
if r.Provider.GetAnnotations()[appliedSpecHashAnnotation] == specHash {
181+
// Check provider config map for changes
182+
cacheUsed, err := applyFromCache(ctx, r.Client, r.Provider)
183+
if err != nil {
184+
return ctrl.Result{}, err
185+
}
186+
187+
if r.Provider.GetAnnotations()[appliedSpecHashAnnotation] == specHash || cacheUsed {
177188
log.Info("No changes detected, skipping further steps")
189+
178190
return ctrl.Result{}, nil
179191
}
180192

@@ -316,17 +328,140 @@ func addObjectToHash(hash hash.Hash, object interface{}) error {
316328
return nil
317329
}
318330

331+
// providerHash calculates hash for provider and referenced objects.
332+
func providerHash(ctx context.Context, client client.Client, hash hash.Hash, provider genericprovider.GenericProvider) error {
333+
log := log.FromContext(ctx)
334+
335+
err := addObjectToHash(hash, provider.GetSpec())
336+
if err != nil {
337+
log.Error(err, "failed to calculate provider hash")
338+
339+
return err
340+
}
341+
342+
if err := addConfigSecretToHash(ctx, client, hash, provider); err != nil {
343+
log.Error(err, "failed to calculate secret hash")
344+
345+
return err
346+
}
347+
348+
return nil
349+
}
350+
319351
func calculateHash(ctx context.Context, k8sClient client.Client, provider genericprovider.GenericProvider) (string, error) {
320352
hash := sha256.New()
321353

322-
err := addObjectToHash(hash, provider.GetSpec())
354+
err := providerHash(ctx, k8sClient, hash, provider)
355+
356+
return fmt.Sprintf("%x", hash.Sum(nil)), err
357+
}
358+
359+
// applyFromCache applies provider configuration from cache and returns true if the cache did not change.
360+
func applyFromCache(ctx context.Context, cl client.Client, provider genericprovider.GenericProvider) (bool, error) {
361+
log := log.FromContext(ctx)
362+
363+
configMap, err := providerConfigMap(ctx, cl, provider)
323364
if err != nil {
324-
return "", err
365+
log.Error(err, "failed to get provider config map")
366+
367+
return false, err
368+
}
369+
370+
// config map does not exist, nothing to apply
371+
if configMap == nil {
372+
return false, nil
373+
}
374+
375+
// calculate combined hash for provider and config map cache
376+
hash := sha256.New()
377+
if err := providerHash(ctx, cl, hash, provider); err != nil {
378+
log.Error(err, "failed to calculate provider hash")
379+
380+
return false, err
381+
}
382+
383+
if err := addObjectToHash(hash, configMap.Data); err != nil {
384+
log.Error(err, "failed to calculate config map hash")
385+
386+
return false, err
325387
}
326388

327-
if err := addConfigSecretToHash(ctx, k8sClient, hash, provider); err != nil {
328-
return "", err
389+
cacheHash := fmt.Sprintf("%x", hash.Sum(nil))
390+
if configMap.GetAnnotations()[appliedSpecHashAnnotation] != cacheHash {
391+
log.Info("Provider or cache state has changed", "cacheHash", cacheHash, "providerHash", configMap.GetAnnotations()[appliedSpecHashAnnotation])
392+
393+
return false, nil
394+
}
395+
396+
components, err := getComponentsData(*configMap)
397+
if err != nil {
398+
log.Error(err, "failed to get provider components")
399+
400+
return false, err
329401
}
330402

331-
return fmt.Sprintf("%x", hash.Sum(nil)), nil
403+
additionalManifests, err := fetchAdditionalManifests(ctx, cl, provider)
404+
if err != nil {
405+
log.Error(err, "failed to get additional manifests")
406+
407+
return false, err
408+
}
409+
410+
if additionalManifests != "" {
411+
components = components + "\n---\n" + additionalManifests
412+
}
413+
414+
for _, manifests := range strings.Split(components, "---") {
415+
manifests, err := utilyaml.ToUnstructured([]byte(manifests))
416+
if err != nil {
417+
log.Error(err, "failed to convert yaml to unstructured")
418+
419+
return false, err
420+
}
421+
422+
if err := cl.Patch(ctx, &manifests[0], client.Apply, client.ForceOwnership, client.FieldOwner(cacheOwner)); err != nil {
423+
log.Error(err, "failed to apply object from cache")
424+
425+
return false, nil
426+
}
427+
}
428+
429+
log.Info("Applied all objects from cache")
430+
431+
return true, nil
432+
}
433+
434+
// setCacheHash calculates current provider and configMap hash, and updates it on the configMap.
435+
func setCacheHash(ctx context.Context, cl client.Client, provider genericprovider.GenericProvider) error {
436+
configMap, err := providerConfigMap(ctx, cl, provider)
437+
if err != nil {
438+
return err
439+
}
440+
441+
helper, err := patch.NewHelper(configMap, cl)
442+
if err != nil {
443+
return err
444+
}
445+
446+
hash := sha256.New()
447+
448+
if err := providerHash(ctx, cl, hash, provider); err != nil {
449+
return err
450+
}
451+
452+
if err := addObjectToHash(hash, configMap.Data); err != nil {
453+
return err
454+
}
455+
456+
cacheHash := fmt.Sprintf("%x", hash.Sum(nil))
457+
458+
annotations := configMap.GetAnnotations()
459+
if annotations == nil {
460+
annotations = map[string]string{}
461+
}
462+
463+
annotations[appliedSpecHashAnnotation] = cacheHash
464+
configMap.SetAnnotations(annotations)
465+
466+
return helper.Patch(ctx, configMap)
332467
}

internal/controller/manifests_downloader.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,16 @@ func (p *PhaseReconciler) checkConfigMapExists(ctx context.Context, labelSelecto
138138
return len(configMapList.Items) == 1, nil
139139
}
140140

141+
// finalize applies combined hash to a configMap, in order to mark provider provisioning completed.
142+
func (p *phaseReconciler) finalize(ctx context.Context) (reconcile.Result, error) {
143+
err := setCacheHash(ctx, p.ctrlClient, p.provider)
144+
if err != nil {
145+
ctrl.LoggerFrom(ctx).V(5).Error(err, "Failed to update providers ConfigMap hash")
146+
}
147+
148+
return reconcile.Result{}, wrapPhaseError(err, "failed to update providers ConfigMap hash", operatorv1.ProviderInstalledCondition)
149+
}
150+
141151
// prepareConfigMapLabels returns labels that identify a config map with downloaded manifests.
142152
func (p *PhaseReconciler) prepareConfigMapLabels() map[string]string {
143153
return ProviderLabels(p.provider)
@@ -285,6 +295,27 @@ func providerLabelSelector(provider operatorv1.GenericProvider) *metav1.LabelSel
285295
}
286296
}
287297

298+
// providerConfigMap finds a ConfigMap the given provider label selector.
299+
func providerConfigMap(ctx context.Context, cl client.Client, provider operatorv1.GenericProvider) (*corev1.ConfigMap, error) {
300+
labelSelector := providerLabelSelector(provider)
301+
labelSet := labels.Set(labelSelector.MatchLabels)
302+
listOpts := []client.ListOption{
303+
client.MatchingLabelsSelector{Selector: labels.SelectorFromSet(labelSet)},
304+
client.InNamespace(provider.GetNamespace()),
305+
}
306+
307+
configMapList := &corev1.ConfigMapList{}
308+
if err := cl.List(ctx, configMapList, listOpts...); err != nil {
309+
return nil, fmt.Errorf("failed to list ConfigMaps: %w", err)
310+
}
311+
312+
if len(configMapList.Items) > 1 {
313+
return nil, fmt.Errorf("multiple ConfigMaps found for provider %q", provider.GetName())
314+
}
315+
316+
return &configMapList.Items[0], nil
317+
}
318+
288319
// ProviderLabels returns default set of labels that identify a config map with downloaded manifests.
289320
func ProviderLabels(provider operatorv1.GenericProvider) map[string]string {
290321
labels := map[string]string{

internal/controller/phases.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -240,7 +240,7 @@ func (p *PhaseReconciler) Load(ctx context.Context) (*Result, error) {
240240
labelSelector = p.provider.GetSpec().FetchConfig.Selector
241241
}
242242

243-
additionalManifests, err := p.fetchAdditionalManifests(ctx)
243+
additionalManifests, err := fetchAdditionalManifests(ctx, p.ctrlClient, p.provider)
244244
if err != nil {
245245
return &Result{}, wrapPhaseError(err, "failed to load additional manifests", operatorv1.ProviderInstalledCondition)
246246
}
@@ -419,13 +419,13 @@ func (p *PhaseReconciler) configmapRepository(ctx context.Context, labelSelector
419419
return mr, nil
420420
}
421421

422-
func (p *PhaseReconciler) fetchAdditionalManifests(ctx context.Context) (string, error) {
422+
func fetchAdditionalManifests(ctx context.Context, cl client.Client, provider genericprovider.GenericProvider) (string, error) {
423423
cm := &corev1.ConfigMap{}
424424

425-
if p.provider.GetSpec().AdditionalManifestsRef != nil {
426-
key := types.NamespacedName{Namespace: p.provider.GetSpec().AdditionalManifestsRef.Namespace, Name: p.provider.GetSpec().AdditionalManifestsRef.Name}
425+
if provider.GetSpec().AdditionalManifestsRef != nil {
426+
key := types.NamespacedName{Namespace: provider.GetSpec().AdditionalManifestsRef.Namespace, Name: provider.GetSpec().AdditionalManifestsRef.Name}
427427

428-
if err := p.ctrlClient.Get(ctx, key, cm); err != nil {
428+
if err := cl.Get(ctx, key, cm); err != nil {
429429
return "", fmt.Errorf("failed to get ConfigMap %s/%s: %w", key.Namespace, key.Name, err)
430430
}
431431
}

0 commit comments

Comments
 (0)