Skip to content
Merged
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
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(
postgresMetadata,
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(
postgresMetadata,
handler.EnqueueRequestsFromMapFunc(mapPostgresToNaisjobs(mgr.GetClient())),
builder.WithPredicates(predicate.AnnotationChangedPredicate{}),
).
WithOptions(asControllerOptions(opts)).
Complete(r)
}
67 changes: 67 additions & 0 deletions pkg/controllers/postgres_watch.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
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"
)

// postgresMetadata is a PartialObjectMetadata for watching Postgres CRs.
// Using metadata-only watches avoids pulling full specs.
var postgresMetadata = &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)
}
}