diff --git a/integration/quarks_secret_copy_test.go b/integration/quarks_secret_copy_test.go index 2c1ea580..2a4b234d 100644 --- a/integration/quarks_secret_copy_test.go +++ b/integration/quarks_secret_copy_test.go @@ -3,6 +3,7 @@ package integration_test import ( "bytes" "fmt" + "reflect" "time" . "github.com/onsi/ginkgo" @@ -171,6 +172,59 @@ var _ = Describe("QuarksCopies", func() { })) Expect(secret.StringData["password"]).NotTo(BeNil()) }) + + It("should copy the updated generated secret to the copy namespace when qsec is found", func() { + By("Checking the quarkssecret status") + checkStatus() + checkCopyStatus() + + secret, err := env.CollectSecret(env.Namespace, sourceSecret) + Expect(err).NotTo(HaveOccurred()) + Expect(secret.Labels).To(Equal(map[string]string{ + "quarks.cloudfoundry.org/secret-kind": "generated", + })) + oldSecretData := secret.Data["password"] + + By("Add labels to `SecretLabels` spec of quarkssecret") + qs, err := env.GetQuarksSecret(env.Namespace, qsecName) + Expect(err).NotTo(HaveOccurred()) + qs.Spec.SecretLabels = map[string]string{ + "LabelKeyv2": "LabelValuev2", + } + qs, err = env.UpdateQuarksSecret(env.Namespace, qs) + Expect(err).NotTo(HaveOccurred()) + Expect(qs).NotTo(Equal(nil)) + + Eventually(func() bool { + secret, err = env.CollectSecret(env.Namespace, qs.Spec.SecretName) + Expect(err).NotTo(HaveOccurred()) + return reflect.DeepEqual(secret.Labels, map[string]string{ + "LabelKeyv2": "LabelValuev2", + "quarks.cloudfoundry.org/secret-kind": "generated", + }) + }, 10*time.Second).Should(Equal(true)) + secret, err = env.CollectSecret(env.Namespace, qs.Spec.SecretName) + Expect(err).NotTo(HaveOccurred()) + Expect(secret.Data["password"]).To(MatchRegexp("^\\w{64}$")) + Expect(oldSecretData).To(Equal(secret.Data["password"])) + + By("Checking the quarkssecret status") + checkCopyStatus() + + By("Check for the updated copy secret") + Eventually(func() bool { + secret, err = env.CollectSecret(qs.Spec.Copies[0].Namespace, qs.Spec.Copies[0].Name) + Expect(err).NotTo(HaveOccurred()) + return reflect.DeepEqual(secret.Labels, map[string]string{ + "LabelKeyv2": "LabelValuev2", + "quarks.cloudfoundry.org/secret-kind": "generated", + }) + }, 10*time.Second).Should(Equal(true)) + + secret, err = env.CollectSecret(qs.Spec.Copies[0].Namespace, qs.Spec.Copies[0].Name) + Expect(err).NotTo(HaveOccurred()) + Expect(oldSecretData).To(Equal(secret.Data["password"])) + }) }) Context("the secret is generated by operator for qsec copies", func() { diff --git a/integration/quarks_secret_test.go b/integration/quarks_secret_test.go index f8434757..e7865e63 100644 --- a/integration/quarks_secret_test.go +++ b/integration/quarks_secret_test.go @@ -1,9 +1,6 @@ package integration_test import ( - "strconv" - "time" - . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" corev1 "k8s.io/api/core/v1" @@ -184,73 +181,4 @@ var _ = Describe("QuarksSecret", func() { deleteQuarksSecret() }) }) - - When("updating the qsec", func() { - var oldSecret *corev1.Secret - - BeforeEach(func() { - qs = env.DefaultQuarksSecret(qsName) - secretName = qs.Spec.SecretName - }) - - JustBeforeEach(func() { - var err error - oldSecret, err = env.CollectSecret(env.Namespace, secretName) - Expect(err).NotTo(HaveOccurred()) - - By("Getting the current version") - qs, err = env.GetQuarksSecret(env.Namespace, qs.Name) - Expect(err).NotTo(HaveOccurred()) - }) - - incResourceVersion := func(v string) string { - n, err := strconv.Atoi(v) - Expect(err).NotTo(HaveOccurred()) - n++ - return strconv.Itoa(n) - } - - When("updating the spec", func() { - JustBeforeEach(func() { - var err error - By("Updating the quarks secret") - qs.Spec.SecretLabels = map[string]string{"sec": "label"} - qs, err = env.UpdateQuarksSecret(env.Namespace, qs) - Expect(err).NotTo(HaveOccurred()) - - old := incResourceVersion(qs.GetResourceVersion()) - err = env.WaitForQuarksSecretChange(env.Namespace, qs.Name, 10*time.Second, func(qsec qsv1a1.QuarksSecret) bool { - return old < qsec.GetResourceVersion() - }) - Expect(err).NotTo(HaveOccurred()) - - }) - - It("updates the secret", func() { - secret, err := env.CollectSecret(env.Namespace, secretName) - Expect(err).NotTo(HaveOccurred()) - - Expect(secret.Data["password"]).NotTo(Equal(oldSecret.Data["password"])) - qs, err = env.GetQuarksSecret(env.Namespace, qs.Name) - Expect(err).NotTo(HaveOccurred()) - }) - }) - - When("updating a label", func() { - JustBeforeEach(func() { - var err error - qs.Labels = map[string]string{"qsec": "label"} - qs, err = env.UpdateQuarksSecret(env.Namespace, qs) - Expect(err).NotTo(HaveOccurred()) - }) - - It("does not update the secret", func() { - old := incResourceVersion(qs.GetResourceVersion()) - err := env.WaitForQuarksSecretChange(env.Namespace, qs.Name, 10*time.Second, func(qsec qsv1a1.QuarksSecret) bool { - return old < qsec.GetResourceVersion() - }) - Expect(err).To(HaveOccurred(), "check for update should fail") - }) - }) - }) }) diff --git a/integration/quarks_secret_update_test.go b/integration/quarks_secret_update_test.go new file mode 100644 index 00000000..a929f4a3 --- /dev/null +++ b/integration/quarks_secret_update_test.go @@ -0,0 +1,129 @@ +package integration_test + +import ( + "reflect" + "strconv" + "time" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + corev1 "k8s.io/api/core/v1" + + qsv1a1 "code.cloudfoundry.org/quarks-secret/pkg/kube/apis/quarkssecret/v1alpha1" + "code.cloudfoundry.org/quarks-utils/testing/machine" +) + +var _ = Describe("QuarksSecret", func() { + var ( + secretName string + qs qsv1a1.QuarksSecret + qsName = "qsec-test" + tearDowns []machine.TearDownFunc + ) + + JustBeforeEach(func() { + By("Creating the quarks secret", func() { + qsec, tearDown, err := env.CreateQuarksSecret(env.Namespace, qs) + qs = *qsec + Expect(err).NotTo(HaveOccurred()) + Expect(qs).NotTo(Equal(nil)) + tearDowns = append(tearDowns, tearDown) + }) + }) + + AfterEach(func() { + Expect(env.TearDownAll(tearDowns)).To(Succeed()) + }) + + When("updating the qsec", func() { + var oldSecret *corev1.Secret + + BeforeEach(func() { + qs = env.DefaultQuarksSecretWithLabels(qsName) + secretName = qs.Spec.SecretName + }) + + JustBeforeEach(func() { + var err error + oldSecret, err = env.CollectSecret(env.Namespace, secretName) + Expect(err).NotTo(HaveOccurred()) + + By("Getting the current version") + qs, err = env.GetQuarksSecret(env.Namespace, qs.Name) + Expect(err).NotTo(HaveOccurred()) + }) + + incResourceVersion := func(v string) string { + n, err := strconv.Atoi(v) + Expect(err).NotTo(HaveOccurred()) + n++ + return strconv.Itoa(n) + } + + When("updating a label", func() { + JustBeforeEach(func() { + var err error + qs.Labels = map[string]string{"qsec": "label"} + qs, err = env.UpdateQuarksSecret(env.Namespace, qs) + Expect(err).NotTo(HaveOccurred()) + }) + + It("does not update the secret", func() { + old := incResourceVersion(qs.GetResourceVersion()) + err := env.WaitForQuarksSecretChange(env.Namespace, qs.Name, 10*time.Second, func(qsec qsv1a1.QuarksSecret) bool { + return old < qsec.GetResourceVersion() + }) + Expect(err).To(HaveOccurred(), "check for update should fail") + }) + }) + + When("updating `SecretLabels` key", func() { + JustBeforeEach(func() { + var err error + qs.Spec.SecretLabels["LabelKeyv2"] = "LabelValuev2" + qs, err = env.UpdateQuarksSecret(env.Namespace, qs) + Expect(err).NotTo(HaveOccurred()) + }) + + It("does not rotate/regenerate secret, when quarks secret's `SecretLabels` key is changed", func() { + By("Check for the updated secret") + secret, err := env.CollectSecret(env.Namespace, secretName) + Expect(err).NotTo(HaveOccurred()) + + Eventually(func() bool { + secret, err = env.CollectSecret(env.Namespace, secretName) + Expect(err).NotTo(HaveOccurred()) + return reflect.DeepEqual(secret.Labels, map[string]string{ + "LabelKey": "LabelValue", + "LabelKeyv2": "LabelValuev2", + "quarks.cloudfoundry.org/secret-kind": "generated", + }) + }, 10*time.Second).Should(Equal(true)) + secret, err = env.CollectSecret(env.Namespace, secretName) + Expect(err).NotTo(HaveOccurred()) + Expect(secret.Data["password"]).To(MatchRegexp("^\\w{64}$")) + Expect(oldSecret.Data["password"]).To(Equal(secret.Data["password"])) + + By("Remove labels to `SecretLabels` spec of quarkssecret") + qs, err = env.GetQuarksSecret(env.Namespace, qsName) + Expect(err).NotTo(HaveOccurred()) + qs.Spec.SecretLabels = nil + qs, err = env.UpdateQuarksSecret(env.Namespace, qs) + Expect(err).NotTo(HaveOccurred()) + Expect(qs).NotTo(Equal(nil)) + + By("Check for the updated secret") + Eventually(func() bool { + secret, err = env.CollectSecret(env.Namespace, secretName) + Expect(err).NotTo(HaveOccurred()) + return reflect.DeepEqual(secret.Labels, map[string]string{ + "quarks.cloudfoundry.org/secret-kind": "generated", + }) + }, 10*time.Second).Should(Equal(true)) + secret, err = env.CollectSecret(env.Namespace, secretName) + Expect(err).NotTo(HaveOccurred()) + Expect(oldSecret.Data["password"]).To(Equal(secret.Data["password"])) + }) + }) + }) +}) diff --git a/pkg/kube/controllers/controllers.go b/pkg/kube/controllers/controllers.go index 92155d0c..cec9fea6 100644 --- a/pkg/kube/controllers/controllers.go +++ b/pkg/kube/controllers/controllers.go @@ -19,6 +19,7 @@ var addToManagerFuncs = []func(context.Context, *config.Config, manager.Manager) quarkssecret.AddCopy, quarkssecret.AddQuarksSecret, quarkssecret.AddSecretRotation, + quarkssecret.AddQuarksSecretSecretMeta, } var addToSchemes = runtime.SchemeBuilder{ diff --git a/pkg/kube/controllers/quarkssecret/copy_reconciler.go b/pkg/kube/controllers/quarkssecret/copy_reconciler.go index 256a9e09..b10e6046 100644 --- a/pkg/kube/controllers/quarkssecret/copy_reconciler.go +++ b/pkg/kube/controllers/quarkssecret/copy_reconciler.go @@ -18,6 +18,7 @@ import ( "code.cloudfoundry.org/quarks-secret/pkg/credsgen" qsv1a1 "code.cloudfoundry.org/quarks-secret/pkg/kube/apis/quarkssecret/v1alpha1" + "code.cloudfoundry.org/quarks-secret/pkg/kube/util/mutate" "code.cloudfoundry.org/quarks-utils/pkg/config" "code.cloudfoundry.org/quarks-utils/pkg/ctxlog" "code.cloudfoundry.org/quarks-utils/pkg/pointers" @@ -92,7 +93,7 @@ func (r *ReconcileCopy) updateCopyStatus(ctx context.Context, qsec *qsv1a1.Quark func (r *ReconcileCopy) handleQuarksSecretCopies(ctx context.Context, sourceQuarksSecret *qsv1a1.QuarksSecret) error { for _, copy := range sourceQuarksSecret.Spec.Copies { - sourceSecret, err := r.getSourceSecret(ctx, sourceQuarksSecret) + sourceSecret, err := GetSourceSecret(ctx, r.client, sourceQuarksSecret) if err != nil { return err } @@ -134,10 +135,11 @@ func (r *ReconcileCopy) handleQuarksSecretCopies(ctx context.Context, sourceQuar return nil } -func (r *ReconcileCopy) getSourceSecret(ctx context.Context, qsec *qsv1a1.QuarksSecret) (*corev1.Secret, error) { +// GetSourceSecret fetches the secret generated by QuarkSecret +func GetSourceSecret(ctx context.Context, client client.Client, qsec *qsv1a1.QuarksSecret) (*corev1.Secret, error) { secretName := qsec.Spec.SecretName sourceSecret := &corev1.Secret{} - err := r.client.Get(ctx, types.NamespacedName{Name: secretName, Namespace: qsec.GetNamespace()}, sourceSecret) + err := client.Get(ctx, types.NamespacedName{Name: secretName, Namespace: qsec.GetNamespace()}, sourceSecret) if err != nil { return nil, errors.Wrapf(err, "could not fetch source secret") } @@ -227,7 +229,7 @@ func (r *ReconcileCopy) createUpdateCopySecret(ctx context.Context, targetSecret } } - op, err := controllerutil.CreateOrUpdate(ctx, r.client, targetSecret, func() error { return nil }) + op, err := controllerutil.CreateOrUpdate(ctx, r.client, targetSecret, mutate.SecretMutateFn(targetSecret)) if err != nil { return errors.Wrapf(err, "could not create or update target secret '%s/%s'", targetSecret.Namespace, targetSecret.GetName()) } diff --git a/pkg/kube/controllers/quarkssecret/quarkssecret_controller.go b/pkg/kube/controllers/quarkssecret/quarkssecret_controller.go index 583d3ef8..ab5de3d3 100644 --- a/pkg/kube/controllers/quarkssecret/quarkssecret_controller.go +++ b/pkg/kube/controllers/quarkssecret/quarkssecret_controller.go @@ -64,12 +64,27 @@ func AddQuarksSecret(ctx context.Context, config *config.Config, mgr manager.Man n := e.ObjectNew.(*qsv1a1.QuarksSecret) o := e.ObjectOld.(*qsv1a1.QuarksSecret) - // reconcile if it was already generated and the spec changed + // reconcile if it was already generated and the spec changed except for `SecretLabels` & `SecretAnnotations` + if o.Status.IsGenerated() { + for _, key := range []string{"Type", "Request", "SecretName", "Copies"} { + old := reflect.ValueOf(o.Spec).FieldByName(key) + new := reflect.ValueOf(n.Spec).FieldByName(key) + + if !reflect.DeepEqual(old.Interface(), new.Interface()) { + ctxlog.NewPredicateEvent(e.ObjectNew).Debug( + ctx, e.MetaNew, "qsv1a1.QuarksSecret", + fmt.Sprintf("Update predicate passed for '%s/%s'.", e.MetaNew.GetNamespace(), e.MetaNew.GetName()), + ) + return true + } + } + } + // reconcile if it was already generated and controller requested update - if o.Status.IsGenerated() && (!reflect.DeepEqual(o.Spec, n.Spec) || n.Status.NotGenerated()) { + if n.Status.NotGenerated() { ctxlog.NewPredicateEvent(e.ObjectNew).Debug( ctx, e.MetaNew, "qsv1a1.QuarksSecret", - fmt.Sprintf("Update predicate passed for '%s/%s'.", e.MetaNew.GetNamespace(), e.MetaNew.GetName()), + fmt.Sprintf("Update predicate passed for '%s/%s': new object is not generated", e.MetaNew.GetNamespace(), e.MetaNew.GetName()), ) return true } diff --git a/pkg/kube/controllers/quarkssecret/quarkssecret_secretmeta_controller.go b/pkg/kube/controllers/quarkssecret/quarkssecret_secretmeta_controller.go new file mode 100644 index 00000000..b7868043 --- /dev/null +++ b/pkg/kube/controllers/quarkssecret/quarkssecret_secretmeta_controller.go @@ -0,0 +1,65 @@ +package quarkssecret + +import ( + "context" + "fmt" + "reflect" + + "github.com/pkg/errors" + "sigs.k8s.io/controller-runtime/pkg/controller" + "sigs.k8s.io/controller-runtime/pkg/event" + "sigs.k8s.io/controller-runtime/pkg/handler" + "sigs.k8s.io/controller-runtime/pkg/manager" + "sigs.k8s.io/controller-runtime/pkg/predicate" + "sigs.k8s.io/controller-runtime/pkg/source" + + credsgen "code.cloudfoundry.org/quarks-secret/pkg/credsgen/in_memory_generator" + qsv1a1 "code.cloudfoundry.org/quarks-secret/pkg/kube/apis/quarkssecret/v1alpha1" + "code.cloudfoundry.org/quarks-utils/pkg/config" + "code.cloudfoundry.org/quarks-utils/pkg/ctxlog" +) + +// AddQuarksSecretSecretMeta creates a new QuarksSecrets controller to watch for the +// custom resource changes for `SecretLabels` and `SecretAnnotations`. +func AddQuarksSecretSecretMeta(ctx context.Context, config *config.Config, mgr manager.Manager) error { + ctx = ctxlog.NewContextWithRecorder(ctx, "quarkssecret-secretmeta-reconciler", mgr.GetEventRecorderFor("quarkssecret-secretmeta-recorder")) + log := ctxlog.ExtractLogger(ctx) + r := NewQuarksSecretSecretMetaReconciler(ctx, config, mgr, credsgen.NewInMemoryGenerator(log)) + + // Create a new controller + c, err := controller.New("quarkssecret-secretmeta-controller", mgr, controller.Options{ + Reconciler: r, + MaxConcurrentReconciles: config.MaxQuarksSecretWorkers, + }) + if err != nil { + return errors.Wrap(err, "Adding secret metadata controller to manager failed.") + } + + nsPred := newNSPredicate(ctx, mgr.GetClient(), config.MonitoredID) + + // Watch for changes to QuarksSecret's `SecretLabels` and `SecretAnnotations` fields. + p := predicate.Funcs{ + CreateFunc: func(e event.CreateEvent) bool { return false }, + DeleteFunc: func(e event.DeleteEvent) bool { return false }, + GenericFunc: func(e event.GenericEvent) bool { return false }, + UpdateFunc: func(e event.UpdateEvent) bool { + n := e.ObjectNew.(*qsv1a1.QuarksSecret) + o := e.ObjectOld.(*qsv1a1.QuarksSecret) + + if !reflect.DeepEqual(o.Spec.SecretLabels, n.Spec.SecretLabels) || !reflect.DeepEqual(o.Spec.SecretAnnotations, n.Spec.SecretAnnotations) { + ctxlog.NewPredicateEvent(e.ObjectNew).Debug( + ctx, e.MetaNew, "qsv1a1.QuarksSecret", + fmt.Sprintf("Update predicate passed for '%s/%s'.", e.MetaNew.GetNamespace(), e.MetaNew.GetName()), + ) + return true + } + return false + }, + } + err = c.Watch(&source.Kind{Type: &qsv1a1.QuarksSecret{}}, &handler.EnqueueRequestForObject{}, nsPred, p) + if err != nil { + return errors.Wrapf(err, "Watching quarks secrets failed in quarkssecret-secretmeta controller.") + } + + return nil +} diff --git a/pkg/kube/controllers/quarkssecret/quarkssecret_secretmeta_reconciler.go b/pkg/kube/controllers/quarkssecret/quarkssecret_secretmeta_reconciler.go new file mode 100644 index 00000000..c80fa483 --- /dev/null +++ b/pkg/kube/controllers/quarkssecret/quarkssecret_secretmeta_reconciler.go @@ -0,0 +1,110 @@ +package quarkssecret + +import ( + "context" + "reflect" + + "github.com/pkg/errors" + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/runtime" + + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/manager" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + + "code.cloudfoundry.org/quarks-secret/pkg/credsgen" + qsv1a1 "code.cloudfoundry.org/quarks-secret/pkg/kube/apis/quarkssecret/v1alpha1" + "code.cloudfoundry.org/quarks-utils/pkg/config" + "code.cloudfoundry.org/quarks-utils/pkg/ctxlog" + "code.cloudfoundry.org/quarks-utils/pkg/pointers" +) + +// NewQuarksSecretSecretMetaReconciler returns a new ReconcileQuarksSecretSecretMeta +func NewQuarksSecretSecretMetaReconciler(ctx context.Context, config *config.Config, mgr manager.Manager, generator credsgen.Generator) reconcile.Reconciler { + return &ReconcileQuarksSecretSecretMeta{ + ctx: ctx, + config: config, + client: mgr.GetClient(), + scheme: mgr.GetScheme(), + generator: generator, + } +} + +// ReconcileQuarksSecretSecretMeta reconciles an QuarksSecret object +type ReconcileQuarksSecretSecretMeta struct { + ctx context.Context + client client.Client + generator credsgen.Generator + scheme *runtime.Scheme + config *config.Config +} + +// Reconcile applies the `SecretLabels` and `SecretAnnotations` from QuarksSecret to the generated Secret. +func (r *ReconcileQuarksSecretSecretMeta) Reconcile(request reconcile.Request) (reconcile.Result, error) { + quarksSecret := &qsv1a1.QuarksSecret{} + + // Set the ctx to be Background, as the top-level context for incoming requests. + ctx, cancel := context.WithTimeout(r.ctx, r.config.CtxTimeOut) + defer cancel() + + ctxlog.Infof(ctx, "Reconciling QuarksSecret %s", request.NamespacedName) + err := r.client.Get(ctx, request.NamespacedName, quarksSecret) + if err != nil { + if apierrors.IsNotFound(err) { + // Request object not found, could have been deleted after reconcile request. + // Owned objects are automatically garbage collected. For additional cleanup logic use finalizers. + // Return and don't requeue + ctxlog.Info(ctx, "Skip reconcile: quarks secret not found") + return reconcile.Result{}, nil + } + // Error reading the object - requeue the request. + ctxlog.Info(ctx, "Error reading the object") + return reconcile.Result{}, errors.Wrap(err, "Error reading quarksSecret") + } + + secret, err := GetSourceSecret(ctx, r.client, quarksSecret) + if err != nil { + return reconcile.Result{}, err + } + + newSecretAnnotations := quarksSecret.Spec.SecretAnnotations + newSecretLabels := quarksSecret.Spec.SecretLabels + + if newSecretLabels == nil { + newSecretLabels = map[string]string{} + } + if newSecretAnnotations == nil { + newSecretAnnotations = map[string]string{} + } + + _, ok := secret.GetLabels()[qsv1a1.LabelKind] + if ok { + newSecretLabels[qsv1a1.LabelKind] = secret.GetLabels()[qsv1a1.LabelKind] + } + + if !reflect.DeepEqual(newSecretLabels, secret.Labels) || !reflect.DeepEqual(newSecretAnnotations, secret.Annotations) { + secret.SetLabels(newSecretLabels) + secret.SetAnnotations(newSecretAnnotations) + err = r.client.Update(ctx, secret) + if err != nil { + return reconcile.Result{}, errors.Wrapf(err, "could not update secret '%s/%s'", secret.Namespace, secret.GetName()) + } + + err = r.updateStatus(ctx, quarksSecret) + if err != nil { + return reconcile.Result{}, err + } + ctxlog.Infof(ctx, "Updated secret '%s'/'%s'", secret.Name, secret.Namespace) + } + + return reconcile.Result{}, nil +} + +func (r *ReconcileQuarksSecretSecretMeta) updateStatus(ctx context.Context, qsec *qsv1a1.QuarksSecret) error { + qsec.Status.Copied = pointers.Bool(false) + err := r.client.Status().Update(ctx, qsec) + if err != nil { + return err + } + return nil +} diff --git a/pkg/kube/controllers/quarkssecret/quarkssecret_secretmeta_reconciler_test.go b/pkg/kube/controllers/quarkssecret/quarkssecret_secretmeta_reconciler_test.go new file mode 100644 index 00000000..185fa9b5 --- /dev/null +++ b/pkg/kube/controllers/quarkssecret/quarkssecret_secretmeta_reconciler_test.go @@ -0,0 +1,127 @@ +package quarkssecret_test + +import ( + "context" + "time" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + "go.uber.org/zap" + + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/types" + crc "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + + generatorfakes "code.cloudfoundry.org/quarks-secret/pkg/credsgen/fakes" + qsv1a1 "code.cloudfoundry.org/quarks-secret/pkg/kube/apis/quarkssecret/v1alpha1" + "code.cloudfoundry.org/quarks-secret/pkg/kube/client/clientset/versioned/scheme" + "code.cloudfoundry.org/quarks-secret/pkg/kube/controllers" + cfakes "code.cloudfoundry.org/quarks-secret/pkg/kube/controllers/fakes" + qscontroller "code.cloudfoundry.org/quarks-secret/pkg/kube/controllers/quarkssecret" + cfcfg "code.cloudfoundry.org/quarks-utils/pkg/config" + "code.cloudfoundry.org/quarks-utils/pkg/ctxlog" + helper "code.cloudfoundry.org/quarks-utils/testing/testhelper" +) + +var _ = Describe("ReconcileQuarksSecretSecretMeta", func() { + var ( + manager *cfakes.FakeManager + reconciler reconcile.Reconciler + request reconcile.Request + ctx context.Context + log *zap.SugaredLogger + config *cfcfg.Config + client *cfakes.FakeClient + generator *generatorfakes.FakeGenerator + qSecret *qsv1a1.QuarksSecret + secret *corev1.Secret + ) + + BeforeEach(func() { + err := controllers.AddToScheme(scheme.Scheme) + Expect(err).ToNot(HaveOccurred()) + manager = &cfakes.FakeManager{} + request = reconcile.Request{NamespacedName: types.NamespacedName{Name: "foo", Namespace: "default"}} + config = &cfcfg.Config{CtxTimeOut: 10 * time.Second} + _, log = helper.NewTestLogger() + ctx = ctxlog.NewParentContext(log) + qSecret = &qsv1a1.QuarksSecret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + Namespace: "default", + }, + Spec: qsv1a1.QuarksSecretSpec{ + Type: "password", + SecretName: "generated-secret", + SecretLabels: map[string]string{ + "LabelKey": "LabelValue", + }, + }, + } + + secret = &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "generated-secret", + Namespace: "default", + }, + StringData: map[string]string{ + "password": "password", + }, + } + generator = &generatorfakes.FakeGenerator{} + client = &cfakes.FakeClient{} + client.GetCalls(func(context context.Context, nn types.NamespacedName, object runtime.Object) error { + switch object := object.(type) { + case *qsv1a1.QuarksSecret: + qSecret.DeepCopyInto(object) + case *corev1.Secret: + return errors.NewNotFound(schema.GroupResource{}, "not found") + } + return nil + }) + client.StatusCalls(func() crc.StatusWriter { return &cfakes.FakeStatusWriter{} }) + manager.GetClientReturns(client) + }) + + JustBeforeEach(func() { + reconciler = qscontroller.NewQuarksSecretSecretMetaReconciler(ctx, config, manager, generator) + }) + + Context("if the source secret is not found ", func() { + It("should return an error", func() { + _, err := reconciler.Reconcile(request) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("could not fetch source secret")) + }) + }) + + Context("if the source secret is found", func() { + It("should have the correct labels", func() { + client.GetCalls(func(context context.Context, nn types.NamespacedName, object runtime.Object) error { + switch object := object.(type) { + case *qsv1a1.QuarksSecret: + qSecret.DeepCopyInto(object) + case *corev1.Secret: + secret.DeepCopyInto(object) + } + return nil + }) + + client.CreateCalls(func(context context.Context, object runtime.Object, _ ...crc.CreateOption) error { + secret := object.(*corev1.Secret) + Expect(secret.GetLabels()).To(Equal(map[string]string{ + "LabelKey": "LabelValue", + })) + return nil + }) + + _, err := reconciler.Reconcile(request) + Expect(err).NotTo(HaveOccurred()) + }) + }) +}) diff --git a/testing/catalog.go b/testing/catalog.go index f432c6cb..d6f30aac 100644 --- a/testing/catalog.go +++ b/testing/catalog.go @@ -25,6 +25,44 @@ func (c *Catalog) DefaultQuarksSecret(name string) qsv1a1.QuarksSecret { } } +// DefaultQuarksSecretWithLabels for use in tests +func (c *Catalog) DefaultQuarksSecretWithLabels(name string) qsv1a1.QuarksSecret { + return qsv1a1.QuarksSecret{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + }, + Spec: qsv1a1.QuarksSecretSpec{ + Type: "password", + SecretName: "generated-secret", + SecretLabels: map[string]string{ + "LabelKey": "LabelValue", + }, + }, + } +} + +// DefaultQuarksSecretWithCopyAndLabel for use in tests +func (c *Catalog) DefaultQuarksSecretWithCopyAndLabel(name string, copynamespace string) qsv1a1.QuarksSecret { + return qsv1a1.QuarksSecret{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + }, + Spec: qsv1a1.QuarksSecretSpec{ + Type: "password", + SecretName: "generated-secret", + SecretLabels: map[string]string{ + "LabelKey": "LabelValue", + }, + Copies: []qsv1a1.Copy{ + { + Name: "generated-secret-copy", + Namespace: copynamespace, + }, + }, + }, + } +} + // DefaultQuarksSecretWithCopy for use in tests func (c *Catalog) DefaultQuarksSecretWithCopy(name string, copynamespace string) qsv1a1.QuarksSecret { return qsv1a1.QuarksSecret{