Skip to content
Open
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
33 changes: 30 additions & 3 deletions pkg/controller/cluster/postgresql/role/reconciler.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ import (
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller"
"sigs.k8s.io/controller-runtime/pkg/handler"
"sigs.k8s.io/controller-runtime/pkg/reconcile"

xpv1 "github.com/crossplane/crossplane-runtime/v2/apis/common/v1"
xpcontroller "github.com/crossplane/crossplane-runtime/v2/pkg/controller"
Expand Down Expand Up @@ -65,6 +67,20 @@ const (
maxConcurrency = 5
)

func clusterSecretMapper(kube client.Client, s *corev1.Secret) []reconcile.Request {
var rl v1alpha1.RoleList
if err := kube.List(context.Background(), &rl); err != nil {
return nil
}
reqs := make([]reconcile.Request, 0)
for _, role := range rl.Items {
if role.Spec.ForProvider.PasswordSecretRef != nil && role.Spec.ForProvider.PasswordSecretRef.Name == s.Name && role.Spec.ForProvider.PasswordSecretRef.Namespace == s.Namespace {
reqs = append(reqs, reconcile.Request{NamespacedName: types.NamespacedName{Namespace: role.Namespace, Name: role.Name}})
}
}
return reqs
}

// Setup adds a controller that reconciles Role managed resources.
func Setup(mgr ctrl.Manager, o xpcontroller.Options) error {
name := managed.ControllerName(v1alpha1.RoleGroupKind)
Expand All @@ -89,13 +105,24 @@ func Setup(mgr ctrl.Manager, o xpcontroller.Options) error {
)); err != nil {
return err
}
return ctrl.NewControllerManagedBy(mgr).
builder := ctrl.NewControllerManagedBy(mgr).
Named(name).
For(&v1alpha1.Role{}).
WithOptions(controller.Options{
MaxConcurrentReconciles: maxConcurrency,
}).
Complete(r)
})

// Watch Secrets and enqueue cluster Roles that reference the secret as
// their PasswordSecretRef so password Secret changes trigger reconciliation.
builder = builder.Watches(&corev1.Secret{}, handler.TypedEnqueueRequestsFromMapFunc(func(ctx context.Context, obj client.Object) []reconcile.Request {
s, ok := obj.(*corev1.Secret)
if !ok {
return nil
}
return clusterSecretMapper(mgr.GetClient(), s)
}))

return builder.Complete(r)
}

type connector struct {
Expand Down
64 changes: 64 additions & 0 deletions pkg/controller/cluster/postgresql/role/reconciler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,70 @@ func (m mockDB) GetServerVersion(ctx context.Context) (int, error) {
return m.MockGetServerVersion(ctx)
}

func Test_clusterSecretMapper(t *testing.T) {
secret := &corev1.Secret{}
secret.Name = "pwsecret"
secret.Namespace = "ns-a"

mock := &test.MockClient{
MockList: func(ctx context.Context, list client.ObjectList, opts ...client.ListOption) error {
rs := &v1alpha1.RoleList{
Items: []v1alpha1.Role{
{ObjectMeta: v1.ObjectMeta{Namespace: "ns-a", Name: "r1"}, Spec: v1alpha1.RoleSpec{ForProvider: v1alpha1.RoleParameters{PasswordSecretRef: &xpv1.SecretKeySelector{SecretReference: xpv1.SecretReference{Name: "pwsecret", Namespace: "ns-a"}}}}},
{ObjectMeta: v1.ObjectMeta{Namespace: "ns-b", Name: "r2"}, Spec: v1alpha1.RoleSpec{ForProvider: v1alpha1.RoleParameters{PasswordSecretRef: &xpv1.SecretKeySelector{SecretReference: xpv1.SecretReference{Name: "other", Namespace: "ns-b"}}}}},
},
}
*list.(*v1alpha1.RoleList) = *rs
return nil
},
}

reqs := clusterSecretMapper(mock, secret)
if len(reqs) != 1 {
t.Fatalf("expected 1 request, got %d", len(reqs))
}
if reqs[0].Name != "r1" {
t.Fatalf("unexpected reconcile name: %v", reqs[0])
}
}

func Test_clusterSecretMapper_SecretDataChange(t *testing.T) {
secret := &corev1.Secret{
ObjectMeta: v1.ObjectMeta{Name: "pwsecret", Namespace: "ns-a"},
Data: map[string][]byte{"password": []byte("old")},
}

mock := &test.MockClient{
MockList: func(ctx context.Context, list client.ObjectList, opts ...client.ListOption) error {
rs := &v1alpha1.RoleList{
Items: []v1alpha1.Role{
{ObjectMeta: v1.ObjectMeta{Namespace: "ns-a", Name: "r1"}, Spec: v1alpha1.RoleSpec{ForProvider: v1alpha1.RoleParameters{PasswordSecretRef: &xpv1.SecretKeySelector{SecretReference: xpv1.SecretReference{Name: "pwsecret", Namespace: "ns-a"}}}}},
{ObjectMeta: v1.ObjectMeta{Namespace: "ns-b", Name: "r2"}, Spec: v1alpha1.RoleSpec{ForProvider: v1alpha1.RoleParameters{PasswordSecretRef: &xpv1.SecretKeySelector{SecretReference: xpv1.SecretReference{Name: "other", Namespace: "ns-b"}}}}},
},
}
*list.(*v1alpha1.RoleList) = *rs
return nil
},
}

reqs := clusterSecretMapper(mock, secret)
if len(reqs) != 1 {
t.Fatalf("expected 1 request, got %d", len(reqs))
}
if reqs[0].Name != "r1" {
t.Fatalf("unexpected reconcile name: %v", reqs[0])
}

secret.Data["password"] = []byte("new")
reqs = clusterSecretMapper(mock, secret)
if len(reqs) != 1 {
t.Fatalf("expected 1 request after password change, got %d", len(reqs))
}
if reqs[0].Name != "r1" {
t.Fatalf("unexpected reconcile name after password change: %v", reqs[0])
}
}

func TestConnect(t *testing.T) {
errBoom := errors.New("boom")
nopUsage := func(ctx context.Context, mg resource.LegacyManaged) error { return nil }
Expand Down
37 changes: 34 additions & 3 deletions pkg/controller/namespaced/postgresql/role/reconciler.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@ import (
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller"
"sigs.k8s.io/controller-runtime/pkg/handler"
"sigs.k8s.io/controller-runtime/pkg/reconcile"

corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/types"

xpv1 "github.com/crossplane/crossplane-runtime/v2/apis/common/v1"
xpcontroller "github.com/crossplane/crossplane-runtime/v2/pkg/controller"
Expand Down Expand Up @@ -61,6 +66,20 @@ const (
maxConcurrency = 5
)

func namespacedSecretMapper(kube client.Client, s *corev1.Secret) []reconcile.Request {
var rl namespacedv1alpha1.RoleList
if err := kube.List(context.Background(), &rl, client.InNamespace(s.Namespace)); err != nil {
return nil
}
reqs := make([]reconcile.Request, 0)
for _, role := range rl.Items {
if role.Spec.ForProvider.PasswordSecretRef != nil && role.Spec.ForProvider.PasswordSecretRef.Name == s.Name {
reqs = append(reqs, reconcile.Request{NamespacedName: types.NamespacedName{Namespace: role.Namespace, Name: role.Name}})
}
}
return reqs
}

// Setup adds a controller that reconciles Database managed resources.
func Setup(mgr ctrl.Manager, o xpcontroller.Options) error {
name := managed.ControllerName(namespacedv1alpha1.RoleGroupKind)
Expand All @@ -86,13 +105,25 @@ func Setup(mgr ctrl.Manager, o xpcontroller.Options) error {
)); err != nil {
return err
}
return ctrl.NewControllerManagedBy(mgr).
builder := ctrl.NewControllerManagedBy(mgr).
Named(name).
For(&namespacedv1alpha1.Role{}).
WithOptions(controller.Options{
MaxConcurrentReconciles: maxConcurrency,
}).
Complete(r)
})

// Watch Secrets and enqueue Roles that reference the secret as their
// PasswordSecretRef so that changes to the password Secret trigger
// reconciliation of the Role that depends on it.
builder = builder.Watches(&corev1.Secret{}, handler.TypedEnqueueRequestsFromMapFunc(func(ctx context.Context, obj client.Object) []reconcile.Request {
s, ok := obj.(*corev1.Secret)
if !ok {
return nil
}
return namespacedSecretMapper(mgr.GetClient(), s)
}))

return builder.Complete(r)
}

type connector struct {
Expand Down
68 changes: 68 additions & 0 deletions pkg/controller/namespaced/postgresql/role/reconciler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,74 @@ func (m mockDB) GetServerVersion(ctx context.Context) (int, error) {
return m.MockGetServerVersion(ctx)
}

func Test_namespacedSecretMapper(t *testing.T) {
secret := &corev1.Secret{}
secret.Name = "pwsecret"
secret.Namespace = "myns"

mock := &test.MockClient{
MockList: func(ctx context.Context, list client.ObjectList, opts ...client.ListOption) error {
rs := &v1alpha1.RoleList{
Items: []v1alpha1.Role{
{
ObjectMeta: metav1.ObjectMeta{Namespace: "myns", Name: "r1"},
Spec: v1alpha1.RoleSpec{ForProvider: v1alpha1.RoleParameters{PasswordSecretRef: &common.LocalSecretKeySelector{LocalSecretReference: common.LocalSecretReference{Name: "pwsecret"}}}},
},
},
}
*list.(*v1alpha1.RoleList) = *rs
return nil
},
}

reqs := namespacedSecretMapper(mock, secret)
if len(reqs) != 1 {
t.Fatalf("expected 1 request, got %d", len(reqs))
}
if reqs[0].Name != "r1" {
t.Fatalf("unexpected reconcile name: %v", reqs[0])
}
}

func Test_namespacedSecretMapper_SecretDataChange(t *testing.T) {
secret := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{Name: "pwsecret", Namespace: "myns"},
Data: map[string][]byte{"password": []byte("old")},
}

mock := &test.MockClient{
MockList: func(ctx context.Context, list client.ObjectList, opts ...client.ListOption) error {
rs := &v1alpha1.RoleList{
Items: []v1alpha1.Role{
{
ObjectMeta: metav1.ObjectMeta{Namespace: "myns", Name: "r1"},
Spec: v1alpha1.RoleSpec{ForProvider: v1alpha1.RoleParameters{PasswordSecretRef: &common.LocalSecretKeySelector{LocalSecretReference: common.LocalSecretReference{Name: "pwsecret"}}}},
},
},
}
*list.(*v1alpha1.RoleList) = *rs
return nil
},
}

reqs := namespacedSecretMapper(mock, secret)
if len(reqs) != 1 {
t.Fatalf("expected 1 request, got %d", len(reqs))
}
if reqs[0].Name != "r1" {
t.Fatalf("unexpected reconcile name: %v", reqs[0])
}

secret.Data["password"] = []byte("new")
reqs = namespacedSecretMapper(mock, secret)
if len(reqs) != 1 {
t.Fatalf("expected 1 request after password change, got %d", len(reqs))
}
if reqs[0].Name != "r1" {
t.Fatalf("unexpected reconcile name after password change: %v", reqs[0])
}
}

func TestConnect(t *testing.T) {
errBoom := errors.New("boom")
nopUsage := func(ctx context.Context, mg resource.ModernManaged) error { return nil }
Expand Down