From 57ca3bf05e5dda36f0ffefd3b49fcdf8ede43e5a Mon Sep 17 00:00:00 2001 From: Hans Kristian Flaatten Date: Mon, 22 Dec 2025 14:56:39 +0100 Subject: [PATCH 1/4] Add Unleash integration support with configuration options --- internal/cmd/api/api.go | 4 +++- internal/cmd/api/config.go | 3 +++ internal/cmd/api/http.go | 2 +- internal/integration/manager.go | 4 +++- internal/kubernetes/watchers/watchers.go | 17 ++++++++++++++++- 5 files changed, 26 insertions(+), 4 deletions(-) diff --git a/internal/cmd/api/api.go b/internal/cmd/api/api.go index e198b1be6..c6daaf071 100644 --- a/internal/cmd/api/api.go +++ b/internal/cmd/api/api.go @@ -156,7 +156,9 @@ func run(ctx context.Context, cfg *Config, log logrus.FieldLogger) error { } defer mgmtWatcher.Stop() - watchers := watchers.SetupWatchers(ctx, watcherMgr, mgmtWatcher) + watchers := watchers.SetupWatchers(ctx, watcherMgr, mgmtWatcher, watchers.SetupWatchersOptions{ + UnleashEnabled: cfg.Unleash.Enabled, + }) pubsubClient, err := pubsub.NewClient(ctx, cfg.GoogleManagementProjectID) if err != nil { diff --git a/internal/cmd/api/config.go b/internal/cmd/api/config.go index 98f08da17..ff247c836 100644 --- a/internal/cmd/api/config.go +++ b/internal/cmd/api/config.go @@ -101,6 +101,9 @@ type oAuthConfig struct { } type unleashConfig struct { + // Enabled controls whether Unleash integration is enabled + Enabled bool `env:"UNLEASH_ENABLED,default=false"` + // BifrostApiEndpoint is the endpoint for the Bifrost API BifrostAPIURL string `env:"UNLEASH_BIFROST_API_URL,default=*fake*"` } diff --git a/internal/cmd/api/http.go b/internal/cmd/api/http.go index a4202c943..38ba4985e 100644 --- a/internal/cmd/api/http.go +++ b/internal/cmd/api/http.go @@ -342,7 +342,7 @@ func ConfigureGraph( ctx = environment.NewLoaderContext(ctx, pool) ctx = feature.NewLoaderContext( ctx, - watchers.UnleashWatcher.Enabled(), + watchers.UnleashEnabled(), watchers.ValkeyWatcher.Enabled(), watchers.KafkaTopicWatcher.Enabled(), watchers.OpenSearchWatcher.Enabled(), diff --git a/internal/integration/manager.go b/internal/integration/manager.go index d6b562155..2d2a39bef 100644 --- a/internal/integration/manager.go +++ b/internal/integration/manager.go @@ -141,7 +141,9 @@ func newManager(_ context.Context, container *postgres.PostgresContainer, connSt return ctx, nil, nil, fmt.Errorf("failed to create management watcher manager: %w", err) } - watchers := watchers.SetupWatchers(ctx, watcherMgr, managementWatcherMgr) + watchers := watchers.SetupWatchers(ctx, watcherMgr, managementWatcherMgr, watchers.SetupWatchersOptions{ + UnleashEnabled: true, // Enable Unleash in integration tests + }) lokiClient, err := loki.NewClient(clusters(), "tenant", log.WithField("subsystem", "loki_client"), loki.WithLocalLoki("http://127.0.0.1:3100")) if err != nil { diff --git a/internal/kubernetes/watchers/watchers.go b/internal/kubernetes/watchers/watchers.go index 7fc5161a6..b62d22029 100644 --- a/internal/kubernetes/watchers/watchers.go +++ b/internal/kubernetes/watchers/watchers.go @@ -58,11 +58,21 @@ type Watchers struct { UnleashWatcher *UnleashWatcher } +type SetupWatchersOptions struct { + UnleashEnabled bool +} + func SetupWatchers( ctx context.Context, watcherMgr *watcher.Manager, mgmtWatcherMgr *watcher.Manager, + opts SetupWatchersOptions, ) *Watchers { + var unleashWatcher *UnleashWatcher + if opts.UnleashEnabled { + unleashWatcher = unleash.NewWatcher(ctx, mgmtWatcherMgr) + } + return &Watchers{ AppWatcher: application.NewWatcher(ctx, watcherMgr), JobWatcher: job.NewWatcher(ctx, watcherMgr), @@ -78,6 +88,11 @@ func SetupWatchers( PodWatcher: workload.NewWatcher(ctx, watcherMgr), IngressWatcher: application.NewIngressWatcher(ctx, watcherMgr), NamespaceWatcher: team.NewNamespaceWatcher(ctx, watcherMgr), - UnleashWatcher: unleash.NewWatcher(ctx, mgmtWatcherMgr), + UnleashWatcher: unleashWatcher, } } + +// UnleashEnabled returns true if the UnleashWatcher is initialized and enabled +func (w *Watchers) UnleashEnabled() bool { + return w.UnleashWatcher != nil && w.UnleashWatcher.Enabled() +} From 615ffc060e2f8286a22a00e0d8e0e741aceacfd0 Mon Sep 17 00:00:00 2001 From: Hans Kristian Flaatten Date: Thu, 8 Jan 2026 22:02:17 +0100 Subject: [PATCH 2/4] refactor: remove Unleash integration configuration and simplify watcher setup --- internal/cmd/api/api.go | 4 +--- internal/cmd/api/config.go | 3 --- internal/cmd/api/http.go | 2 +- internal/integration/manager.go | 4 +--- internal/kubernetes/watchers/watchers.go | 17 +---------------- 5 files changed, 4 insertions(+), 26 deletions(-) diff --git a/internal/cmd/api/api.go b/internal/cmd/api/api.go index c6daaf071..e198b1be6 100644 --- a/internal/cmd/api/api.go +++ b/internal/cmd/api/api.go @@ -156,9 +156,7 @@ func run(ctx context.Context, cfg *Config, log logrus.FieldLogger) error { } defer mgmtWatcher.Stop() - watchers := watchers.SetupWatchers(ctx, watcherMgr, mgmtWatcher, watchers.SetupWatchersOptions{ - UnleashEnabled: cfg.Unleash.Enabled, - }) + watchers := watchers.SetupWatchers(ctx, watcherMgr, mgmtWatcher) pubsubClient, err := pubsub.NewClient(ctx, cfg.GoogleManagementProjectID) if err != nil { diff --git a/internal/cmd/api/config.go b/internal/cmd/api/config.go index ff247c836..98f08da17 100644 --- a/internal/cmd/api/config.go +++ b/internal/cmd/api/config.go @@ -101,9 +101,6 @@ type oAuthConfig struct { } type unleashConfig struct { - // Enabled controls whether Unleash integration is enabled - Enabled bool `env:"UNLEASH_ENABLED,default=false"` - // BifrostApiEndpoint is the endpoint for the Bifrost API BifrostAPIURL string `env:"UNLEASH_BIFROST_API_URL,default=*fake*"` } diff --git a/internal/cmd/api/http.go b/internal/cmd/api/http.go index 38ba4985e..a4202c943 100644 --- a/internal/cmd/api/http.go +++ b/internal/cmd/api/http.go @@ -342,7 +342,7 @@ func ConfigureGraph( ctx = environment.NewLoaderContext(ctx, pool) ctx = feature.NewLoaderContext( ctx, - watchers.UnleashEnabled(), + watchers.UnleashWatcher.Enabled(), watchers.ValkeyWatcher.Enabled(), watchers.KafkaTopicWatcher.Enabled(), watchers.OpenSearchWatcher.Enabled(), diff --git a/internal/integration/manager.go b/internal/integration/manager.go index 2d2a39bef..d6b562155 100644 --- a/internal/integration/manager.go +++ b/internal/integration/manager.go @@ -141,9 +141,7 @@ func newManager(_ context.Context, container *postgres.PostgresContainer, connSt return ctx, nil, nil, fmt.Errorf("failed to create management watcher manager: %w", err) } - watchers := watchers.SetupWatchers(ctx, watcherMgr, managementWatcherMgr, watchers.SetupWatchersOptions{ - UnleashEnabled: true, // Enable Unleash in integration tests - }) + watchers := watchers.SetupWatchers(ctx, watcherMgr, managementWatcherMgr) lokiClient, err := loki.NewClient(clusters(), "tenant", log.WithField("subsystem", "loki_client"), loki.WithLocalLoki("http://127.0.0.1:3100")) if err != nil { diff --git a/internal/kubernetes/watchers/watchers.go b/internal/kubernetes/watchers/watchers.go index b62d22029..7fc5161a6 100644 --- a/internal/kubernetes/watchers/watchers.go +++ b/internal/kubernetes/watchers/watchers.go @@ -58,21 +58,11 @@ type Watchers struct { UnleashWatcher *UnleashWatcher } -type SetupWatchersOptions struct { - UnleashEnabled bool -} - func SetupWatchers( ctx context.Context, watcherMgr *watcher.Manager, mgmtWatcherMgr *watcher.Manager, - opts SetupWatchersOptions, ) *Watchers { - var unleashWatcher *UnleashWatcher - if opts.UnleashEnabled { - unleashWatcher = unleash.NewWatcher(ctx, mgmtWatcherMgr) - } - return &Watchers{ AppWatcher: application.NewWatcher(ctx, watcherMgr), JobWatcher: job.NewWatcher(ctx, watcherMgr), @@ -88,11 +78,6 @@ func SetupWatchers( PodWatcher: workload.NewWatcher(ctx, watcherMgr), IngressWatcher: application.NewIngressWatcher(ctx, watcherMgr), NamespaceWatcher: team.NewNamespaceWatcher(ctx, watcherMgr), - UnleashWatcher: unleashWatcher, + UnleashWatcher: unleash.NewWatcher(ctx, mgmtWatcherMgr), } } - -// UnleashEnabled returns true if the UnleashWatcher is initialized and enabled -func (w *Watchers) UnleashEnabled() bool { - return w.UnleashWatcher != nil && w.UnleashWatcher.Enabled() -} From 5ada747e140261e1877153080d1f1c1e09d17689 Mon Sep 17 00:00:00 2001 From: Hans Kristian Flaatten Date: Thu, 8 Jan 2026 22:02:22 +0100 Subject: [PATCH 3/4] fix: ensure Docker environment variables are set for colima integration --- .mise-tasks/test/_default | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/.mise-tasks/test/_default b/.mise-tasks/test/_default index fb0c00856..244358788 100755 --- a/.mise-tasks/test/_default +++ b/.mise-tasks/test/_default @@ -4,6 +4,19 @@ set -e +# Export Docker environment variables for colima (if not already set) +if [ -z "$DOCKER_HOST" ]; then + if [ -S "$HOME/.colima/default/docker.sock" ]; then + export DOCKER_HOST="unix://$HOME/.colima/default/docker.sock" + # For testcontainers to work with colima, we need to tell it where the docker socket is + export TESTCONTAINERS_DOCKER_SOCKET_OVERRIDE="$HOME/.colima/default/docker.sock" + # This allows testcontainers to use the host docker socket directly + export TESTCONTAINERS_HOST_OVERRIDE="localhost" + # Disable the Ryuk container that manages test container cleanup with colima + export TESTCONTAINERS_RYUK_DISABLED="true" + fi +fi + # shellcheck disable=SC2154 if [ "$usage_coverage" = "true" ]; then rm -f hack/coverprofile.txt From 8980c9be5cb2a5c3c29e28cabc81693b71d91584 Mon Sep 17 00:00:00 2001 From: Hans Kristian Flaatten Date: Fri, 9 Jan 2026 08:55:33 +0100 Subject: [PATCH 4/4] fix: add permission check for resource listing in createInformer --- internal/kubernetes/watcher/object_manager.go | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/internal/kubernetes/watcher/object_manager.go b/internal/kubernetes/watcher/object_manager.go index a1d5ab7a4..5da8c53d7 100644 --- a/internal/kubernetes/watcher/object_manager.go +++ b/internal/kubernetes/watcher/object_manager.go @@ -1,6 +1,7 @@ package watcher import ( + "context" "fmt" "time" @@ -78,6 +79,16 @@ func (c *clusterManager) createInformer(obj runtime.Object, gvr *schema.GroupVer c.log.WithError(err).WithField("resource", gvr.String()).Error("resource not available in cluster") return nil, *gvr, fmt.Errorf("resource not available in cluster") } + + // Also verify we have permission to list the resource by doing a test list with limit=1 + // This prevents errors at runtime when the informer tries to watch resources we can't access + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + _, err = c.client.Resource(*gvr).List(ctx, v1.ListOptions{Limit: 1}) + if err != nil { + c.log.WithError(err).WithField("resource", gvr.String()).Warn("no permission to list resource, skipping watcher") + return nil, *gvr, fmt.Errorf("no permission to list resource: %w", err) + } } if lblSelector == "" {