Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
9 changes: 9 additions & 0 deletions pkg/controllers/application.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ import (
nais_io_v1 "github.com/nais/liberator/pkg/apis/nais.io/v1"
nais_io_v1alpha1 "github.com/nais/liberator/pkg/apis/nais.io/v1alpha1"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/builder"
"sigs.k8s.io/controller-runtime/pkg/handler"
"sigs.k8s.io/controller-runtime/pkg/predicate"
)

type ApplicationReconciler struct {
Expand All @@ -31,6 +33,13 @@ func (r *ApplicationReconciler) SetupWithManager(mgr ctrl.Manager, opts ...Optio
return ctrl.NewControllerManagedBy(mgr).
For(&nais_io_v1alpha1.Application{}).
Watches(&nais_io_v1.Image{}, handler.EnqueueRequestsFromMapFunc(mapImageToApplicationOrNaisjob)).
WatchesMetadata(
postgresPartialObjectMetadata(),
handler.EnqueueRequestsFromMapFunc(mapPostgresToApplications(mgr.GetClient())),
builder.WithPredicates(predicate.AnnotationChangedPredicate{}),
).
WithOptions(asControllerOptions(opts)).
Complete(r)
}

// +kubebuilder:rbac:groups=data.nais.io,resources=postgres,verbs=get;list;watch
7 changes: 7 additions & 0 deletions pkg/controllers/naisjob.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ import (

nais_io_v1 "github.com/nais/liberator/pkg/apis/nais.io/v1"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/builder"
"sigs.k8s.io/controller-runtime/pkg/handler"
"sigs.k8s.io/controller-runtime/pkg/predicate"
)

type NaisjobReconciler struct {
Expand All @@ -30,6 +32,11 @@ func (r *NaisjobReconciler) SetupWithManager(mgr ctrl.Manager, opts ...Option) e
return ctrl.NewControllerManagedBy(mgr).
For(&nais_io_v1.Naisjob{}).
Watches(&nais_io_v1.Image{}, handler.EnqueueRequestsFromMapFunc(mapImageToApplicationOrNaisjob)).
WatchesMetadata(
postgresPartialObjectMetadata(),
handler.EnqueueRequestsFromMapFunc(mapPostgresToNaisjobs(mgr.GetClient())),
builder.WithPredicates(predicate.AnnotationChangedPredicate{}),
).
WithOptions(asControllerOptions(opts)).
Complete(r)
}
69 changes: 69 additions & 0 deletions pkg/controllers/postgres_watch.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package controllers

import (
"context"

nais_io_v1 "github.com/nais/liberator/pkg/apis/nais.io/v1"
nais_io_v1alpha1 "github.com/nais/liberator/pkg/apis/nais.io/v1alpha1"
log "github.com/sirupsen/logrus"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
)

// postgresPartialObjectMetadata returns a PartialObjectMetadata configured to
// watch Postgres CRs. Using metadata-only watches avoids pulling full specs.
func postgresPartialObjectMetadata() *metav1.PartialObjectMetadata {
Comment thread
Starefossen marked this conversation as resolved.
Outdated
return &metav1.PartialObjectMetadata{
TypeMeta: metav1.TypeMeta{
APIVersion: "data.nais.io/v1",
Kind: "Postgres",
},
}
}

// mapPostgresToApplications returns a map function that enqueues Applications
// in the same namespace whose spec.postgres.clusterName matches the Postgres CR name.
func mapPostgresToApplications(kube client.Client) func(ctx context.Context, obj client.Object) []ctrl.Request {
return func(ctx context.Context, obj client.Object) []ctrl.Request {
var apps nais_io_v1alpha1.ApplicationList
err := kube.List(ctx, &apps, client.InNamespace(obj.GetNamespace()))
if err != nil {
log.Errorf("postgres watch: failed to list applications in namespace %s: %v", obj.GetNamespace(), err)
return nil
}

var requests []ctrl.Request
for _, app := range apps.Items {
if app.Spec.Postgres != nil && app.Spec.Postgres.ClusterName == obj.GetName() {
requests = append(requests, ctrl.Request{
NamespacedName: client.ObjectKeyFromObject(&app),
})
}
}
return requests
}
}

// mapPostgresToNaisjobs returns a map function that enqueues Naisjobs
// in the same namespace whose spec.postgres.clusterName matches the Postgres CR name.
func mapPostgresToNaisjobs(kube client.Client) func(ctx context.Context, obj client.Object) []ctrl.Request {
return func(ctx context.Context, obj client.Object) []ctrl.Request {
var jobs nais_io_v1.NaisjobList
err := kube.List(ctx, &jobs, client.InNamespace(obj.GetNamespace()))
if err != nil {
log.Errorf("postgres watch: failed to list naisjobs in namespace %s: %v", obj.GetNamespace(), err)
return nil
}

var requests []ctrl.Request
for _, job := range jobs.Items {
if job.Spec.Postgres != nil && job.Spec.Postgres.ClusterName == obj.GetName() {
requests = append(requests, ctrl.Request{
NamespacedName: client.ObjectKeyFromObject(&job),
})
}
}
return requests
}
}
172 changes: 172 additions & 0 deletions pkg/controllers/postgres_watch_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
package controllers

import (
"context"
"testing"

nais_io_v1 "github.com/nais/liberator/pkg/apis/nais.io/v1"
nais_io_v1alpha1 "github.com/nais/liberator/pkg/apis/nais.io/v1alpha1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"sigs.k8s.io/controller-runtime/pkg/client/fake"
)

func TestMapPostgresToApplications(t *testing.T) {
t.Parallel()

scheme := runtime.NewScheme()
_ = nais_io_v1alpha1.AddToScheme(scheme)
_ = nais_io_v1.AddToScheme(scheme)

matchingApp := &nais_io_v1alpha1.Application{
ObjectMeta: metav1.ObjectMeta{
Name: "my-app",
Namespace: "team-a",
},
Spec: nais_io_v1alpha1.ApplicationSpec{
Postgres: &nais_io_v1.Postgres{
ClusterName: "my-pg",
},
},
}

otherApp := &nais_io_v1alpha1.Application{
ObjectMeta: metav1.ObjectMeta{
Name: "other-app",
Namespace: "team-a",
},
Spec: nais_io_v1alpha1.ApplicationSpec{
Postgres: &nais_io_v1.Postgres{
ClusterName: "other-pg",
},
},
}

appWithoutPostgres := &nais_io_v1alpha1.Application{
ObjectMeta: metav1.ObjectMeta{
Name: "no-pg-app",
Namespace: "team-a",
},
Spec: nais_io_v1alpha1.ApplicationSpec{},
}

appDifferentNamespace := &nais_io_v1alpha1.Application{
ObjectMeta: metav1.ObjectMeta{
Name: "diff-ns-app",
Namespace: "team-b",
},
Spec: nais_io_v1alpha1.ApplicationSpec{
Postgres: &nais_io_v1.Postgres{
ClusterName: "my-pg",
},
},
}

kube := fake.NewClientBuilder().
WithScheme(scheme).
WithRuntimeObjects(matchingApp, otherApp, appWithoutPostgres, appDifferentNamespace).
Build()

pgObject := &metav1.PartialObjectMetadata{
ObjectMeta: metav1.ObjectMeta{
Name: "my-pg",
Namespace: "team-a",
},
}

mapFn := mapPostgresToApplications(kube)
requests := mapFn(context.Background(), pgObject)

if len(requests) != 1 {
t.Fatalf("expected 1 request, got %d: %v", len(requests), requests)
}
if requests[0].Name != "my-app" {
t.Errorf("expected request for 'my-app', got %q", requests[0].Name)
}
if requests[0].Namespace != "team-a" {
t.Errorf("expected namespace 'team-a', got %q", requests[0].Namespace)
}
}

func TestMapPostgresToNaisjobs(t *testing.T) {
t.Parallel()

scheme := runtime.NewScheme()
_ = nais_io_v1alpha1.AddToScheme(scheme)
_ = nais_io_v1.AddToScheme(scheme)

matchingJob := &nais_io_v1.Naisjob{
ObjectMeta: metav1.ObjectMeta{
Name: "my-job",
Namespace: "team-a",
},
Spec: nais_io_v1.NaisjobSpec{
Postgres: &nais_io_v1.Postgres{
ClusterName: "my-pg",
},
},
}

otherJob := &nais_io_v1.Naisjob{
ObjectMeta: metav1.ObjectMeta{
Name: "other-job",
Namespace: "team-a",
},
Spec: nais_io_v1.NaisjobSpec{
Postgres: &nais_io_v1.Postgres{
ClusterName: "other-pg",
},
},
}

kube := fake.NewClientBuilder().
WithScheme(scheme).
WithRuntimeObjects(matchingJob, otherJob).
Build()

pgObject := &metav1.PartialObjectMetadata{
ObjectMeta: metav1.ObjectMeta{
Name: "my-pg",
Namespace: "team-a",
},
}

mapFn := mapPostgresToNaisjobs(kube)
requests := mapFn(context.Background(), pgObject)

if len(requests) != 1 {
t.Fatalf("expected 1 request, got %d: %v", len(requests), requests)
}
if requests[0].Name != "my-job" {
t.Errorf("expected request for 'my-job', got %q", requests[0].Name)
}
if requests[0].Namespace != "team-a" {
t.Errorf("expected namespace 'team-a', got %q", requests[0].Namespace)
}
}

func TestMapPostgresToApplications_NoMatches(t *testing.T) {
t.Parallel()

scheme := runtime.NewScheme()
_ = nais_io_v1alpha1.AddToScheme(scheme)
_ = nais_io_v1.AddToScheme(scheme)

kube := fake.NewClientBuilder().
WithScheme(scheme).
Build()

pgObject := &metav1.PartialObjectMetadata{
ObjectMeta: metav1.ObjectMeta{
Name: "orphan-pg",
Namespace: "team-a",
},
}

mapFn := mapPostgresToApplications(kube)
requests := mapFn(context.Background(), pgObject)

if len(requests) != 0 {
t.Fatalf("expected 0 requests, got %d: %v", len(requests), requests)
}
}