Skip to content

Commit 2dafb6b

Browse files
committed
Update the secret when secret template is updated
1 parent e2a8dc4 commit 2dafb6b

2 files changed

Lines changed: 156 additions & 0 deletions

File tree

internal/controller/postgresuser_controller.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"fmt"
66
"maps"
77
"net"
8+
"reflect"
89

910
"github.com/go-logr/logr"
1011
corev1 "k8s.io/api/core/v1"
@@ -295,6 +296,28 @@ func (r *PostgresUserReconciler) Reconcile(ctx context.Context, req ctrl.Request
295296
return r.requeue(ctx, instance, err)
296297
}
297298

299+
// Secret exists - check if it needs to be updated (e.g., secretTemplate changed)
300+
// Preserve existing credentials from the current secret
301+
existingPassword := string(found.Data["PASSWORD"])
302+
existingRole := string(found.Data["ROLE"])
303+
existingLogin := string(found.Data["LOGIN"])
304+
305+
// Regenerate secret with existing credentials to compare
306+
updatedSecret, err := r.newSecretForCR(reqLogger, instance, existingRole, existingPassword, existingLogin)
307+
if err != nil {
308+
return r.requeue(ctx, instance, err)
309+
}
310+
311+
// Compare data and update if different
312+
if !reflect.DeepEqual(found.Data, updatedSecret.Data) {
313+
reqLogger.Info("Updating secret", "Secret.Namespace", found.Namespace, "Secret.Name", found.Name)
314+
found.Data = updatedSecret.Data
315+
err = r.Update(ctx, found)
316+
if err != nil {
317+
return r.requeue(ctx, instance, err)
318+
}
319+
}
320+
298321
reqLogger.Info("Reconciling done")
299322
return ctrl.Result{}, nil
300323
}

internal/controller/postgresuser_controller_test.go

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -531,6 +531,139 @@ var _ = Describe("PostgresUser Controller", func() {
531531
Expect(uriArgsFilterCombined).To(Equal("postgres://foobar?logging=true&sslmode=disable"))
532532

533533
})
534+
535+
It("should update the secret when secretTemplate is modified", func() {
536+
// Mock expected calls for initial creation
537+
pg.EXPECT().GetDefaultDatabase().Return("postgres").AnyTimes()
538+
pg.EXPECT().CreateUserRole(gomock.Any(), gomock.Any()).Return("app-mockedRole", nil)
539+
pg.EXPECT().GrantRole(gomock.Any(), gomock.Any()).Return(nil)
540+
pg.EXPECT().AlterDefaultLoginRole(gomock.Any(), gomock.Any()).Return(nil)
541+
542+
rp.pgUriArgs = "sslmode=disable"
543+
544+
// Call Reconcile to create the user
545+
err := runReconcile(rp, ctx, req)
546+
Expect(err).NotTo(HaveOccurred())
547+
548+
// Get the user and mark it as succeeded
549+
foundUser := &dbv1alpha1.PostgresUser{}
550+
err = cl.Get(ctx, types.NamespacedName{Name: name, Namespace: namespace}, foundUser)
551+
Expect(err).NotTo(HaveOccurred())
552+
foundUser.Status.Succeeded = true
553+
err = cl.Status().Update(ctx, foundUser)
554+
Expect(err).NotTo(HaveOccurred())
555+
556+
// Run reconcile to create the secret
557+
err = runReconcile(rp, ctx, req)
558+
Expect(err).NotTo(HaveOccurred())
559+
560+
// Get the created secret and verify initial state
561+
foundSecret := &corev1.Secret{}
562+
secretFullName := fmt.Sprintf("%s-%s", secretName, name)
563+
err = cl.Get(ctx, types.NamespacedName{Name: secretFullName, Namespace: namespace}, foundSecret)
564+
Expect(err).NotTo(HaveOccurred())
565+
566+
// Verify initial template was applied
567+
Expect(foundSecret.Data).To(HaveKey("CUSTOM_KEY"))
568+
initialCustomKey := string(foundSecret.Data["CUSTOM_KEY"])
569+
Expect(initialCustomKey).To(ContainSubstring("User:"))
570+
571+
// Store the original password, role, and ResourceVersion to verify behavior
572+
originalPassword := string(foundSecret.Data["PASSWORD"])
573+
originalRole := string(foundSecret.Data["ROLE"])
574+
originalResourceVersion := foundSecret.ResourceVersion
575+
Expect(originalPassword).NotTo(BeEmpty())
576+
Expect(originalRole).NotTo(BeEmpty())
577+
578+
// Now update the secretTemplate on the user CR
579+
err = cl.Get(ctx, types.NamespacedName{Name: name, Namespace: namespace}, foundUser)
580+
Expect(err).NotTo(HaveOccurred())
581+
foundUser.Spec.SecretTemplate = map[string]string{
582+
"NEW_CUSTOM_KEY": "NewValue: {{.Role}}",
583+
"ANOTHER_KEY": "Database: {{.Database}}",
584+
}
585+
err = cl.Update(ctx, foundUser)
586+
Expect(err).NotTo(HaveOccurred())
587+
588+
// Run reconcile again - this should update the secret
589+
err = runReconcile(rp, ctx, req)
590+
Expect(err).NotTo(HaveOccurred())
591+
592+
// Get the updated secret
593+
err = cl.Get(ctx, types.NamespacedName{Name: secretFullName, Namespace: namespace}, foundSecret)
594+
Expect(err).NotTo(HaveOccurred())
595+
596+
// Verify the ResourceVersion has changed (update occurred)
597+
Expect(foundSecret.ResourceVersion).NotTo(Equal(originalResourceVersion))
598+
599+
// Verify the new template keys are present
600+
Expect(foundSecret.Data).To(HaveKey("NEW_CUSTOM_KEY"))
601+
Expect(foundSecret.Data).To(HaveKey("ANOTHER_KEY"))
602+
newCustomKey := string(foundSecret.Data["NEW_CUSTOM_KEY"])
603+
Expect(newCustomKey).To(Equal("NewValue: " + originalRole))
604+
anotherKey := string(foundSecret.Data["ANOTHER_KEY"])
605+
Expect(anotherKey).To(Equal("Database: " + databaseName))
606+
607+
// Verify old template keys are no longer present
608+
Expect(foundSecret.Data).NotTo(HaveKey("CUSTOM_KEY"))
609+
Expect(foundSecret.Data).NotTo(HaveKey("PGPASSWORD"))
610+
611+
// Most importantly: verify password and role are preserved
612+
Expect(string(foundSecret.Data["PASSWORD"])).To(Equal(originalPassword))
613+
Expect(string(foundSecret.Data["ROLE"])).To(Equal(originalRole))
614+
})
615+
616+
It("should not update the secret when nothing has changed", func() {
617+
// Mock expected calls for initial creation
618+
pg.EXPECT().GetDefaultDatabase().Return("postgres").AnyTimes()
619+
pg.EXPECT().CreateUserRole(gomock.Any(), gomock.Any()).Return("app-mockedRole", nil)
620+
pg.EXPECT().GrantRole(gomock.Any(), gomock.Any()).Return(nil)
621+
pg.EXPECT().AlterDefaultLoginRole(gomock.Any(), gomock.Any()).Return(nil)
622+
623+
rp.pgUriArgs = "sslmode=disable"
624+
625+
// Call Reconcile to create the user
626+
err := runReconcile(rp, ctx, req)
627+
Expect(err).NotTo(HaveOccurred())
628+
629+
// Get the user and mark it as succeeded
630+
foundUser := &dbv1alpha1.PostgresUser{}
631+
err = cl.Get(ctx, types.NamespacedName{Name: name, Namespace: namespace}, foundUser)
632+
Expect(err).NotTo(HaveOccurred())
633+
foundUser.Status.Succeeded = true
634+
err = cl.Status().Update(ctx, foundUser)
635+
Expect(err).NotTo(HaveOccurred())
636+
637+
// Run reconcile to create the secret
638+
err = runReconcile(rp, ctx, req)
639+
Expect(err).NotTo(HaveOccurred())
640+
641+
// Get the created secret and store its ResourceVersion
642+
foundSecret := &corev1.Secret{}
643+
secretFullName := fmt.Sprintf("%s-%s", secretName, name)
644+
err = cl.Get(ctx, types.NamespacedName{Name: secretFullName, Namespace: namespace}, foundSecret)
645+
Expect(err).NotTo(HaveOccurred())
646+
647+
originalResourceVersion := foundSecret.ResourceVersion
648+
originalData := make(map[string][]byte)
649+
for k, v := range foundSecret.Data {
650+
originalData[k] = v
651+
}
652+
653+
// Run reconcile again WITHOUT any changes
654+
err = runReconcile(rp, ctx, req)
655+
Expect(err).NotTo(HaveOccurred())
656+
657+
// Get the secret again
658+
err = cl.Get(ctx, types.NamespacedName{Name: secretFullName, Namespace: namespace}, foundSecret)
659+
Expect(err).NotTo(HaveOccurred())
660+
661+
// Verify the ResourceVersion has not changed (no update occurred)
662+
Expect(foundSecret.ResourceVersion).To(Equal(originalResourceVersion))
663+
664+
// Verify the data is still the same
665+
Expect(foundSecret.Data).To(Equal(originalData))
666+
})
534667
})
535668
})
536669

0 commit comments

Comments
 (0)