Skip to content

Commit 569de9f

Browse files
committed
Add cross resource watching on Postgres objects
1 parent d81d015 commit 569de9f

2 files changed

Lines changed: 174 additions & 0 deletions

File tree

internal/controller/postgresuser_controller.go

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,15 @@ import (
1414
"k8s.io/apimachinery/pkg/runtime"
1515
"k8s.io/apimachinery/pkg/types"
1616
ctrl "sigs.k8s.io/controller-runtime"
17+
"sigs.k8s.io/controller-runtime/pkg/builder"
1718
"sigs.k8s.io/controller-runtime/pkg/client"
1819
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
20+
"sigs.k8s.io/controller-runtime/pkg/event"
21+
"sigs.k8s.io/controller-runtime/pkg/handler"
1922
"sigs.k8s.io/controller-runtime/pkg/log"
2023
"sigs.k8s.io/controller-runtime/pkg/manager"
24+
"sigs.k8s.io/controller-runtime/pkg/predicate"
25+
"sigs.k8s.io/controller-runtime/pkg/reconcile"
2126

2227
dbv1alpha1 "github.com/movetokube/postgres-operator/api/v1alpha1"
2328
"github.com/movetokube/postgres-operator/pkg/config"
@@ -473,9 +478,70 @@ func (r *PostgresUserReconciler) finish(ctx context.Context, cr *dbv1alpha1.Post
473478
return ctrl.Result{}, nil
474479
}
475480

481+
// findPostgresUsersForPostgres returns reconcile requests for all PostgresUsers
482+
// that reference the given Postgres CR by name within the same namespace.
483+
// This enables eventual consistency when a Postgres CR is created after PostgresUsers
484+
// that reference it.
485+
func (r *PostgresUserReconciler) findPostgresUsersForPostgres(ctx context.Context, obj client.Object) []reconcile.Request {
486+
postgres := obj.(*dbv1alpha1.Postgres)
487+
logger := log.FromContext(ctx)
488+
489+
var userList dbv1alpha1.PostgresUserList
490+
if err := r.List(ctx, &userList, client.InNamespace(postgres.Namespace)); err != nil {
491+
logger.Error(err, "Failed to list PostgresUsers for Postgres CR", "postgres", postgres.Name)
492+
return nil
493+
}
494+
495+
var requests []reconcile.Request
496+
for _, user := range userList.Items {
497+
if user.Spec.Database == postgres.Name {
498+
requests = append(requests, reconcile.Request{
499+
NamespacedName: types.NamespacedName{
500+
Name: user.Name,
501+
Namespace: user.Namespace,
502+
},
503+
})
504+
}
505+
}
506+
507+
if len(requests) > 0 {
508+
logger.Info("Enqueuing PostgresUsers for Postgres CR change",
509+
"postgres", postgres.Name, "userCount", len(requests))
510+
}
511+
return requests
512+
}
513+
476514
// SetupWithManager sets up the controller with the Manager.
515+
// It watches PostgresUser CRs as the primary resource and also watches Postgres CRs
516+
// to trigger reconciliation of PostgresUsers when their referenced Postgres CR changes.
517+
// This ensures eventual consistency when PostgresUsers are created before their
518+
// referenced Postgres CR exists.
477519
func (r *PostgresUserReconciler) SetupWithManager(mgr ctrl.Manager) error {
478520
return ctrl.NewControllerManagedBy(mgr).
479521
For(&dbv1alpha1.PostgresUser{}).
522+
Watches(
523+
&dbv1alpha1.Postgres{},
524+
handler.EnqueueRequestsFromMapFunc(r.findPostgresUsersForPostgres),
525+
builder.WithPredicates(predicate.Funcs{
526+
CreateFunc: func(e event.CreateEvent) bool {
527+
// Trigger when Postgres CR is created and already succeeded
528+
pg := e.Object.(*dbv1alpha1.Postgres)
529+
return pg.Status.Succeeded
530+
},
531+
UpdateFunc: func(e event.UpdateEvent) bool {
532+
// Trigger when Postgres CR transitions to succeeded state
533+
oldPg := e.ObjectOld.(*dbv1alpha1.Postgres)
534+
newPg := e.ObjectNew.(*dbv1alpha1.Postgres)
535+
return !oldPg.Status.Succeeded && newPg.Status.Succeeded
536+
},
537+
DeleteFunc: func(e event.DeleteEvent) bool {
538+
// Trigger on deletion to allow cleanup of dependent resources
539+
return true
540+
},
541+
GenericFunc: func(e event.GenericEvent) bool {
542+
return false
543+
},
544+
}),
545+
).
480546
Complete(r)
481547
}

internal/controller/postgresuser_controller_test.go

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -916,4 +916,112 @@ var _ = Describe("PostgresUser Controller", func() {
916916
Expect(secret.Name).To(Equal("mysecret3"))
917917
})
918918
})
919+
920+
Describe("Cross-resource watching", func() {
921+
var (
922+
postgresDB *dbv1alpha1.Postgres
923+
postgresUser1 *dbv1alpha1.PostgresUser
924+
postgresUser2 *dbv1alpha1.PostgresUser
925+
)
926+
927+
BeforeEach(func() {
928+
postgresDB = &dbv1alpha1.Postgres{
929+
ObjectMeta: metav1.ObjectMeta{
930+
Name: databaseName,
931+
Namespace: namespace,
932+
},
933+
Spec: dbv1alpha1.PostgresSpec{
934+
Database: databaseName,
935+
},
936+
Status: dbv1alpha1.PostgresStatus{
937+
Succeeded: true,
938+
Roles: dbv1alpha1.PostgresRoles{
939+
Owner: databaseName + "-group",
940+
Reader: databaseName + "-reader",
941+
Writer: databaseName + "-writer",
942+
},
943+
},
944+
}
945+
946+
postgresUser1 = &dbv1alpha1.PostgresUser{
947+
ObjectMeta: metav1.ObjectMeta{
948+
Name: "user-for-db",
949+
Namespace: namespace,
950+
},
951+
Spec: dbv1alpha1.PostgresUserSpec{
952+
Database: databaseName,
953+
SecretName: secretName,
954+
Role: roleName,
955+
Privileges: "WRITE",
956+
},
957+
}
958+
959+
postgresUser2 = &dbv1alpha1.PostgresUser{
960+
ObjectMeta: metav1.ObjectMeta{
961+
Name: "user-for-other-db",
962+
Namespace: namespace,
963+
},
964+
Spec: dbv1alpha1.PostgresUserSpec{
965+
Database: "other-db",
966+
SecretName: secretName,
967+
Role: roleName,
968+
Privileges: "READ",
969+
},
970+
}
971+
})
972+
973+
Context("findPostgresUsersForPostgres mapping function", func() {
974+
It("should return reconcile requests for PostgresUsers referencing the Postgres CR", func() {
975+
// Create users first (without the Postgres CR)
976+
Expect(cl.Create(ctx, postgresUser1)).To(Succeed())
977+
Expect(cl.Create(ctx, postgresUser2)).To(Succeed())
978+
979+
// Call the mapping function
980+
requests := rp.findPostgresUsersForPostgres(ctx, postgresDB)
981+
982+
// Should only return the user that references our database
983+
Expect(requests).To(HaveLen(1))
984+
Expect(requests[0].Name).To(Equal("user-for-db"))
985+
Expect(requests[0].Namespace).To(Equal(namespace))
986+
})
987+
988+
It("should return empty list when no PostgresUsers reference the Postgres CR", func() {
989+
// Create a user that references a different database
990+
Expect(cl.Create(ctx, postgresUser2)).To(Succeed())
991+
992+
// Call the mapping function
993+
requests := rp.findPostgresUsersForPostgres(ctx, postgresDB)
994+
995+
// Should return empty list
996+
Expect(requests).To(BeEmpty())
997+
})
998+
999+
It("should return multiple requests when multiple PostgresUsers reference the same Postgres CR", func() {
1000+
// Create two users that reference the same database
1001+
Expect(cl.Create(ctx, postgresUser1)).To(Succeed())
1002+
1003+
anotherUser := &dbv1alpha1.PostgresUser{
1004+
ObjectMeta: metav1.ObjectMeta{
1005+
Name: "another-user-for-db",
1006+
Namespace: namespace,
1007+
},
1008+
Spec: dbv1alpha1.PostgresUserSpec{
1009+
Database: databaseName,
1010+
SecretName: "another-secret",
1011+
Role: "another-role",
1012+
Privileges: "READ",
1013+
},
1014+
}
1015+
Expect(cl.Create(ctx, anotherUser)).To(Succeed())
1016+
1017+
// Call the mapping function
1018+
requests := rp.findPostgresUsersForPostgres(ctx, postgresDB)
1019+
1020+
// Should return both users
1021+
Expect(requests).To(HaveLen(2))
1022+
names := []string{requests[0].Name, requests[1].Name}
1023+
Expect(names).To(ContainElements("user-for-db", "another-user-for-db"))
1024+
})
1025+
})
1026+
})
9191027
})

0 commit comments

Comments
 (0)