Skip to content

Commit

Permalink
fix: GeneratedSecret reconciler (#1743)
Browse files Browse the repository at this point in the history
  • Loading branch information
zreigz authored Jan 9, 2025
1 parent 93ace93 commit 36e86ef
Show file tree
Hide file tree
Showing 2 changed files with 87 additions and 26 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,19 @@ apiVersion: deployments.plural.sh/v1alpha1
kind: GeneratedSecret
metadata:
labels:
app.kubernetes.io/name: generatedsecret
app.kubernetes.io/instance: generatedsecret-sample
app.kubernetes.io/part-of: controller
app.kubernetes.io/managed-by: kustomize
app.kubernetes.io/created-by: controller
name: generatedsecret-sample
plural.sh/managed-by: agent
managedFields: []
name: plrl-elastic-user
namespace: infra
spec:
template:
password: "{{ 10 | randAlphaNum }}"
destinations:
- name: test
namespace: default
- name: plrl-elastic-user
namespace: elastic
- name: plrl-elastic-user
namespace: infra
- name: plrl-elastic-user
namespace: plrl-deploy-operator
template:
password: "{{ 32 | randAlphaNum }}"
roles: admin
user: plrl
89 changes: 73 additions & 16 deletions go/controller/internal/controller/generatedsecret_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ package controller

import (
"context"
"fmt"
"reflect"
"strings"

"github.com/pluralsh/console/go/controller/api/v1alpha1"
"github.com/pluralsh/console/go/controller/internal/utils"
Expand All @@ -12,11 +14,21 @@ import (
"k8s.io/apimachinery/pkg/api/errors"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/builder"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
"sigs.k8s.io/controller-runtime/pkg/handler"
"sigs.k8s.io/controller-runtime/pkg/log"
"sigs.k8s.io/controller-runtime/pkg/predicate"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
)

const (
generatedSecretAnnotationName = "deployments.plural.sh/generated-secret"
GeneratedSecretFinalizer = "deployments.plural.sh/generated-secret-protection"
)

// GeneratedSecretReconciler reconciles a GeneratedSecret object
Expand All @@ -41,25 +53,23 @@ func (r *GeneratedSecretReconciler) Reconcile(ctx context.Context, req ctrl.Requ
if err := r.Get(ctx, req.NamespacedName, generatedSecret); err != nil {
return ctrl.Result{}, client.IgnoreNotFound(err)
}
if !generatedSecret.GetDeletionTimestamp().IsZero() {
return ctrl.Result{}, r.handleDelete(ctx, generatedSecret)
}
utils.MarkCondition(generatedSecret.SetCondition, v1alpha1.ReadyConditionType, v1.ConditionFalse, v1alpha1.ReadyConditionReason, "")
scope, err := NewDefaultScope(ctx, r.Client, generatedSecret)
if err != nil {
logger.Error(err, "failed to create scope")
utils.MarkCondition(generatedSecret.SetCondition, v1alpha1.ReadyConditionType, v1.ConditionFalse, v1alpha1.SynchronizedConditionReasonError, err.Error())
return ctrl.Result{}, err
}

// Always patch object when exiting this function, so we can persist any object changes.
defer func() {
if err := scope.PatchObject(); err != nil && reterr == nil {
reterr = err
}
}()

if !generatedSecret.GetDeletionTimestamp().IsZero() {
return r.handleDelete(ctx, generatedSecret)
}

bindings, err := r.prepareBindings(ctx, generatedSecret)
if err != nil {
utils.MarkCondition(generatedSecret.SetCondition, v1alpha1.ReadyConditionType, v1.ConditionFalse, v1alpha1.SynchronizedConditionReasonError, err.Error())
Expand All @@ -77,26 +87,23 @@ func (r *GeneratedSecretReconciler) Reconcile(ctx context.Context, req ctrl.Requ
}

for _, destination := range generatedSecret.Spec.Destinations {
destSecretRef := &corev1.SecretReference{Name: destination.Name, Namespace: generatedSecret.Namespace}
destSecretRef := &corev1.SecretReference{Name: destination.Name, Namespace: destination.Namespace}
destSecret, err := utils.GetSecret(ctx, r.Client, destSecretRef)
// create if it doesn't exist
if err != nil {
if !errors.IsNotFound(err) {
utils.MarkCondition(generatedSecret.SetCondition, v1alpha1.ReadyConditionType, v1.ConditionFalse, v1alpha1.SynchronizedConditionReasonError, err.Error())
return ctrl.Result{}, err
}
if err := r.ensureNamespace(ctx, generatedSecret.Namespace); err != nil {
utils.MarkCondition(generatedSecret.SetCondition, v1alpha1.ReadyConditionType, v1.ConditionFalse, v1alpha1.SynchronizedConditionReasonError, err.Error())
return ctrl.Result{}, err
}
if err := r.createSecret(ctx, destination.Namespace, destination.Name, data); err != nil {
if err := r.ensureNamespace(ctx, destination.Namespace); err != nil {
utils.MarkCondition(generatedSecret.SetCondition, v1alpha1.ReadyConditionType, v1.ConditionFalse, v1alpha1.SynchronizedConditionReasonError, err.Error())
return ctrl.Result{}, err
}
if err := r.tryAddSecretControllerRef(ctx, generatedSecret, destSecretRef); err != nil {
if err := r.createSecret(ctx, destination.Namespace, destination.Name, generatedSecret.Namespace, generatedSecret.Name, data); err != nil {
utils.MarkCondition(generatedSecret.SetCondition, v1alpha1.ReadyConditionType, v1.ConditionFalse, v1alpha1.SynchronizedConditionReasonError, err.Error())
return ctrl.Result{}, err
}

continue
}
// update destination secret if it's different then persisted data
Expand All @@ -111,6 +118,8 @@ func (r *GeneratedSecretReconciler) Reconcile(ctx context.Context, req ctrl.Requ

generatedSecret.Status.RenderedTemplateSecretRef = &corev1.LocalObjectReference{Name: generatedSecret.GetSecretName()}
utils.MarkCondition(generatedSecret.SetCondition, v1alpha1.ReadyConditionType, v1.ConditionTrue, v1alpha1.ReadyConditionReason, "")
controllerutil.AddFinalizer(generatedSecret, GeneratedSecretFinalizer)

return ctrl.Result{}, nil
}

Expand Down Expand Up @@ -145,7 +154,7 @@ func (r *GeneratedSecretReconciler) persistData(ctx context.Context, gs *v1alpha
utils.MarkCondition(gs.SetCondition, v1alpha1.ReadyConditionType, v1.ConditionFalse, v1alpha1.SynchronizedConditionReasonError, err.Error())
return nil, err
}
if err := r.createSecret(ctx, gs.Namespace, gs.GetSecretName(), data); err != nil {
if err := r.createSecret(ctx, gs.Namespace, gs.GetSecretName(), gs.Namespace, gs.GetSecretName(), data); err != nil {
return nil, err
}

Expand Down Expand Up @@ -181,11 +190,17 @@ func (r *GeneratedSecretReconciler) ensureNamespace(ctx context.Context, namespa
return nil
}

func (r *GeneratedSecretReconciler) createSecret(ctx context.Context, namespace, name string, data map[string][]byte) error {
func (r *GeneratedSecretReconciler) createSecret(ctx context.Context, namespace, name, generatedSecretNamespace, generatedSecretName string, data map[string][]byte) error {
if generatedSecretNamespace == "" {
generatedSecretNamespace = "default"
}
secret := &corev1.Secret{
ObjectMeta: v1.ObjectMeta{
Name: name,
Namespace: namespace,
Annotations: map[string]string{
generatedSecretAnnotationName: generatedSecretNamespace + "/" + generatedSecretName,
},
},
Data: data,
}
Expand Down Expand Up @@ -216,14 +231,56 @@ func templateData(tmp map[string]string, bindings map[string]interface{}) (map[s
return data, nil
}

func (r *GeneratedSecretReconciler) handleDelete(ctx context.Context, secret *v1alpha1.GeneratedSecret) (ctrl.Result, error) {
return ctrl.Result{}, nil
func (r *GeneratedSecretReconciler) handleDelete(ctx context.Context, generatedSecret *v1alpha1.GeneratedSecret) error {
logger := log.FromContext(ctx)
if !controllerutil.ContainsFinalizer(generatedSecret, GeneratedSecretFinalizer) {
return nil
}
for _, destination := range generatedSecret.Spec.Destinations {
destSecretRef := &corev1.SecretReference{Name: destination.Name, Namespace: destination.Namespace}
destSecret, err := utils.GetSecret(ctx, r.Client, destSecretRef)
if err != nil {
if !errors.IsNotFound(err) {
return err
}
logger.Info("Secret already deleted", "namespace", destination.Namespace, "name", destination.Name)
continue
}
if err := r.Delete(ctx, destSecret); err != nil {
return err
}
logger.Info("Secret deleted successfully", "namespace", destination.Namespace, "name", destination.Name)
}

return utils.TryRemoveFinalizer(ctx, r.Client, generatedSecret, GeneratedSecretFinalizer)
}

// requestFromSecret returns a reconcile.Request for the generated secret if changed secret has specific annotation.
func requestFromSecret() handler.EventHandler {
return handler.EnqueueRequestsFromMapFunc(func(ctx context.Context, obj client.Object) []reconcile.Request {
if obj.GetAnnotations() != nil && obj.GetAnnotations()[generatedSecretAnnotationName] != "" {
generatedSecret := obj.GetAnnotations()[generatedSecretAnnotationName]
namespaceName := strings.Split(generatedSecret, "/")
if len(namespaceName) != 2 {
err := fmt.Errorf("the annotation has wrong format %s", generatedSecret)
utilruntime.HandleError(err)
return nil
}
return []reconcile.Request{
{
NamespacedName: types.NamespacedName{Namespace: namespaceName[0], Name: namespaceName[1]},
},
}
}
return nil
})
}

// SetupWithManager sets up the controller with the Manager.
func (r *GeneratedSecretReconciler) SetupWithManager(mgr ctrl.Manager) error {
return ctrl.NewControllerManagedBy(mgr).
For(&v1alpha1.GeneratedSecret{}).
Owns(&corev1.Secret{}, builder.WithPredicates(predicate.ResourceVersionChangedPredicate{})).
Watches(&corev1.Secret{}, requestFromSecret()).
Complete(r)
}

0 comments on commit 36e86ef

Please sign in to comment.