Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

✨ Handle config secret updates #565

Merged
Merged
Show file tree
Hide file tree
Changes from all 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
71 changes: 41 additions & 30 deletions cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ var (
webhookPort int
webhookCertDir string
healthAddr string
watchConfigSecretChanges bool
diagnosticsOptions = flags.DiagnosticsOptions{}
)

Expand Down Expand Up @@ -98,6 +99,9 @@ func InitFlags(fs *pflag.FlagSet) {
fs.StringVar(&watchFilterValue, "watch-filter", "",
fmt.Sprintf("Label value that the controller watches to reconcile cluster-api objects. Label key is always %s. If unspecified, the controller watches for all cluster-api objects.", clusterv1.WatchLabel))

fs.BoolVar(&watchConfigSecretChanges, "watch-configsecret", false,
"Watch for changes to the ConfigSecret resource and reconcile all providers using it.")

fs.StringVar(&watchNamespace, "namespace", "",
"Namespace that the controller watches to reconcile cluster-api objects. If unspecified, the controller watches for cluster-api objects across all namespaces.")

Expand Down Expand Up @@ -185,7 +189,7 @@ func main() {
ctx := ctrl.SetupSignalHandler()

setupChecks(mgr)
setupReconcilers(mgr)
setupReconcilers(mgr, watchConfigSecretChanges)
setupWebhooks(mgr)

// +kubebuilder:scaffold:builder
Expand All @@ -209,72 +213,79 @@ func setupChecks(mgr ctrl.Manager) {
}
}

func setupReconcilers(mgr ctrl.Manager) {
func setupReconcilers(mgr ctrl.Manager, watchConfigSecretChanges bool) {
if err := (&providercontroller.GenericProviderReconciler{
Provider: &operatorv1.CoreProvider{},
ProviderList: &operatorv1.CoreProviderList{},
Client: mgr.GetClient(),
Config: mgr.GetConfig(),
Provider: &operatorv1.CoreProvider{},
ProviderList: &operatorv1.CoreProviderList{},
Client: mgr.GetClient(),
Config: mgr.GetConfig(),
WatchConfigSecretChanges: watchConfigSecretChanges,
}).SetupWithManager(mgr, concurrency(concurrencyNumber)); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "CoreProvider")
os.Exit(1)
}

if err := (&providercontroller.GenericProviderReconciler{
Provider: &operatorv1.InfrastructureProvider{},
ProviderList: &operatorv1.InfrastructureProviderList{},
Client: mgr.GetClient(),
Config: mgr.GetConfig(),
Provider: &operatorv1.InfrastructureProvider{},
ProviderList: &operatorv1.InfrastructureProviderList{},
Client: mgr.GetClient(),
Config: mgr.GetConfig(),
WatchConfigSecretChanges: watchConfigSecretChanges,
}).SetupWithManager(mgr, concurrency(concurrencyNumber)); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "InfrastructureProvider")
os.Exit(1)
}

if err := (&providercontroller.GenericProviderReconciler{
Provider: &operatorv1.BootstrapProvider{},
ProviderList: &operatorv1.BootstrapProviderList{},
Client: mgr.GetClient(),
Config: mgr.GetConfig(),
Provider: &operatorv1.BootstrapProvider{},
ProviderList: &operatorv1.BootstrapProviderList{},
Client: mgr.GetClient(),
Config: mgr.GetConfig(),
WatchConfigSecretChanges: watchConfigSecretChanges,
}).SetupWithManager(mgr, concurrency(concurrencyNumber)); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "BootstrapProvider")
os.Exit(1)
}

if err := (&providercontroller.GenericProviderReconciler{
Provider: &operatorv1.ControlPlaneProvider{},
ProviderList: &operatorv1.ControlPlaneProviderList{},
Client: mgr.GetClient(),
Config: mgr.GetConfig(),
Provider: &operatorv1.ControlPlaneProvider{},
ProviderList: &operatorv1.ControlPlaneProviderList{},
Client: mgr.GetClient(),
Config: mgr.GetConfig(),
WatchConfigSecretChanges: watchConfigSecretChanges,
}).SetupWithManager(mgr, concurrency(concurrencyNumber)); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "ControlPlaneProvider")
os.Exit(1)
}

if err := (&providercontroller.GenericProviderReconciler{
Provider: &operatorv1.AddonProvider{},
ProviderList: &operatorv1.AddonProviderList{},
Client: mgr.GetClient(),
Config: mgr.GetConfig(),
Provider: &operatorv1.AddonProvider{},
ProviderList: &operatorv1.AddonProviderList{},
Client: mgr.GetClient(),
Config: mgr.GetConfig(),
WatchConfigSecretChanges: watchConfigSecretChanges,
}).SetupWithManager(mgr, concurrency(concurrencyNumber)); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "AddonProvider")
os.Exit(1)
}

if err := (&providercontroller.GenericProviderReconciler{
Provider: &operatorv1.IPAMProvider{},
ProviderList: &operatorv1.IPAMProviderList{},
Client: mgr.GetClient(),
Config: mgr.GetConfig(),
Provider: &operatorv1.IPAMProvider{},
ProviderList: &operatorv1.IPAMProviderList{},
Client: mgr.GetClient(),
Config: mgr.GetConfig(),
WatchConfigSecretChanges: watchConfigSecretChanges,
}).SetupWithManager(mgr, concurrency(concurrencyNumber)); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "IPAMProvider")
os.Exit(1)
}

if err := (&providercontroller.GenericProviderReconciler{
Provider: &operatorv1.RuntimeExtensionProvider{},
ProviderList: &operatorv1.RuntimeExtensionProviderList{},
Client: mgr.GetClient(),
Config: mgr.GetConfig(),
Provider: &operatorv1.RuntimeExtensionProvider{},
ProviderList: &operatorv1.RuntimeExtensionProviderList{},
Client: mgr.GetClient(),
Config: mgr.GetConfig(),
WatchConfigSecretChanges: watchConfigSecretChanges,
}).SetupWithManager(mgr, concurrency(concurrencyNumber)); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "RuntimeExtensionProvider")
os.Exit(1)
Expand Down
3 changes: 3 additions & 0 deletions hack/charts/cluster-api-operator/templates/deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,9 @@ spec:
{{- if .Values.insecureDiagnostics }}
- --insecure-diagnostics={{ .Values.insecureDiagnostics }}
{{- end }}
{{- if .Values.watchConfigSecret }}
- --watch-configsecret
{{- end }}
{{- with .Values.leaderElection }}
- --leader-elect={{ .enabled }}
{{- if .leaseDuration }}
Expand Down
1 change: 1 addition & 0 deletions hack/charts/cluster-api-operator/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ healthAddr: ":8081"
metricsBindAddr: "127.0.0.1:8080"
diagnosticsAddress: "8443"
insecureDiagnostics: false
watchConfigSecret: false
imagePullSecrets: {}
resources:
manager:
Expand Down
80 changes: 66 additions & 14 deletions internal/controller/genericprovider_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,11 @@ import (
"encoding/json"
"errors"
"fmt"
"hash"

corev1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
kerrors "k8s.io/apimachinery/pkg/util/errors"
"k8s.io/client-go/rest"
operatorv1 "sigs.k8s.io/cluster-api-operator/api/v1alpha2"
Expand All @@ -35,24 +38,33 @@ import (
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
"sigs.k8s.io/controller-runtime/pkg/handler"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
)

type GenericProviderReconciler struct {
Provider genericprovider.GenericProvider
ProviderList genericprovider.GenericProviderList
Client client.Client
Config *rest.Config
Provider genericprovider.GenericProvider
ProviderList genericprovider.GenericProviderList
Client client.Client
Config *rest.Config
WatchConfigSecretChanges bool
}

const (
appliedSpecHashAnnotation = "operator.cluster.x-k8s.io/applied-spec-hash"
)

func (r *GenericProviderReconciler) SetupWithManager(mgr ctrl.Manager, options controller.Options) error {
return ctrl.NewControllerManagedBy(mgr).
For(r.Provider).
WithOptions(options).
builder := ctrl.NewControllerManagedBy(mgr).
For(r.Provider)
if r.WatchConfigSecretChanges {
builder.Watches(
&corev1.Secret{},
handler.EnqueueRequestsFromMapFunc(newSecretToProviderFuncMapForProviderList(r.Client, r.ProviderList)),
)
}

return builder.WithOptions(options).
Complete(r)
}

Expand Down Expand Up @@ -102,14 +114,13 @@ func (r *GenericProviderReconciler) Reconcile(ctx context.Context, req reconcile
}

// Check if spec hash stays the same and don't go further in this case.
specHash, err := calculateHash(r.Provider.GetSpec())
specHash, err := calculateHash(ctx, r.Client, r.Provider)
if err != nil {
return ctrl.Result{}, err
}

if r.Provider.GetAnnotations()[appliedSpecHashAnnotation] == specHash {
log.Info("No changes detected, skipping further steps")

return ctrl.Result{}, nil
}

Expand All @@ -123,7 +134,7 @@ func (r *GenericProviderReconciler) Reconcile(ctx context.Context, req reconcile
// Set the spec hash annotation if reconciliation was successful or reset it otherwise.
if res.IsZero() && err == nil {
// Recalculate spec hash in case it was changed during reconciliation process.
specHash, err = calculateHash(r.Provider.GetSpec())
specHash, err := calculateHash(ctx, r.Client, r.Provider)
if err != nil {
return ctrl.Result{}, err
}
Expand Down Expand Up @@ -218,16 +229,57 @@ func (r *GenericProviderReconciler) reconcileDelete(ctx context.Context, provide
return res, nil
}

func calculateHash(object interface{}) (string, error) {
func addConfigSecretToHash(ctx context.Context, k8sClient client.Client, hash hash.Hash, provider genericprovider.GenericProvider) error {
if provider.GetSpec().ConfigSecret != nil {
secret := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Namespace: provider.GetSpec().ConfigSecret.Namespace,
Name: provider.GetSpec().ConfigSecret.Name,
},
}
if secret.Namespace == "" {
secret.Namespace = provider.GetNamespace()
}

err := k8sClient.Get(ctx, client.ObjectKeyFromObject(secret), secret)
if err != nil {
return err
}

err = addObjectToHash(hash, secret.Data)
if err != nil {
return err
}

return nil
}

return nil
}

func addObjectToHash(hash hash.Hash, object interface{}) error {
jsonData, err := json.Marshal(object)
if err != nil {
return "", fmt.Errorf("cannot parse provider spec: %w", err)
return fmt.Errorf("cannot marshal object: %w", err)
}

if _, err = hash.Write(jsonData); err != nil {
return fmt.Errorf("cannot calculate object hash: %w", err)
}

return nil
}

func calculateHash(ctx context.Context, k8sClient client.Client, provider genericprovider.GenericProvider) (string, error) {
hash := sha256.New()

if _, err = hash.Write(jsonData); err != nil {
return "", fmt.Errorf("cannot calculate provider spec hash: %w", err)
err := addObjectToHash(hash, provider.GetSpec())
if err != nil {
return "", err
}

if err := addConfigSecretToHash(ctx, k8sClient, hash, provider); err != nil {
return "", err
}

return fmt.Sprintf("%x", hash.Sum(nil)), nil
Expand Down
Loading