From 9800cfa51856db32fdd26270547af74832c27bda Mon Sep 17 00:00:00 2001 From: Eric Pickard Date: Tue, 3 Feb 2026 11:09:51 -0500 Subject: [PATCH 1/6] add exclude namespaces flag --- cmd/deployment-tracker/main.go | 18 ++++++++---- internal/controller/controller.go | 49 +++++++++++++++++++++++-------- 2 files changed, 49 insertions(+), 18 deletions(-) diff --git a/cmd/deployment-tracker/main.go b/cmd/deployment-tracker/main.go index b2a18f3..d46eabb 100644 --- a/cmd/deployment-tracker/main.go +++ b/cmd/deployment-tracker/main.go @@ -33,18 +33,26 @@ func getEnvOrDefault(key, defaultValue string) string { func main() { var ( - kubeconfig string - namespace string - workers int - metricsPort string + kubeconfig string + namespace string + excludeNamespaces string + workers int + metricsPort string ) flag.StringVar(&kubeconfig, "kubeconfig", "", "path to kubeconfig file (uses in-cluster config if not set)") flag.StringVar(&namespace, "namespace", "", "namespace to monitor (empty for all namespaces)") + flag.StringVar(&excludeNamespaces, "exclude-namespace", "", "namespace to exclude from monitoring (empty to include all namespaces)") flag.IntVar(&workers, "workers", 2, "number of worker goroutines") flag.StringVar(&metricsPort, "metrics-port", "9090", "port to listen to for metrics") flag.Parse() + // Cannot use both + if namespace != "" && excludeNamespaces != "" { + slog.Error("Cannot set both --namespace and --exclude-namespace") + os.Exit(1) + } + // Validate worker count if workers < 1 || workers > 100 { slog.Error("Invalid worker count, must be between 1 and 100", @@ -143,7 +151,7 @@ func main() { cancel() }() - cntrl, err := controller.New(clientset, namespace, &cntrlCfg) + cntrl, err := controller.New(clientset, namespace, excludeNamespaces, &cntrlCfg) if err != nil { slog.Error("Failed to create controller", "error", err) diff --git a/internal/controller/controller.go b/internal/controller/controller.go index 4a0655b..1b81225 100644 --- a/internal/controller/controller.go +++ b/internal/controller/controller.go @@ -52,20 +52,9 @@ type Controller struct { } // New creates a new deployment tracker controller. -func New(clientset kubernetes.Interface, namespace string, cfg *Config) (*Controller, error) { +func New(clientset kubernetes.Interface, namespace string, excludeNamespaces string, cfg *Config) (*Controller, error) { // Create informer factory - var factory informers.SharedInformerFactory - if namespace == "" { - factory = informers.NewSharedInformerFactory(clientset, - 30*time.Second, - ) - } else { - factory = informers.NewSharedInformerFactoryWithOptions( - clientset, - 30*time.Second, - informers.WithNamespace(namespace), - ) - } + factory := createInformerFactory(clientset, namespace, excludeNamespaces) podInformer := factory.Core().V1().Pods().Informer() @@ -488,6 +477,40 @@ func getCacheKey(dn, digest string) string { return dn + "||" + digest } +// createInformerFactory creates a shared informer factory with the given resync period. +// If excludeNamespaces is non-empty, it will exclude those namespaces from being watched. +// If namespace is non-empty, it will only watch that namespace. +func createInformerFactory(clientset kubernetes.Interface, namespace string, excludeNamespaces string) informers.SharedInformerFactory { + var factory informers.SharedInformerFactory + if namespace != "" { + factory = informers.NewSharedInformerFactoryWithOptions( + clientset, + 30*time.Second, + informers.WithNamespace(namespace), + ) + } else if excludeNamespaces != "" { + excludedNamespacesList := strings.Split(excludeNamespaces, ",") + for i := 0; i < len(excludedNamespacesList); i++ { + excludedNamespacesList[i] = fmt.Sprintf("metadata.namespace!=%s", strings.TrimSpace(excludedNamespacesList[i])) + } + tweakListOptions := func(options *metav1.ListOptions) { + options.FieldSelector = strings.Join(excludedNamespacesList, ",") + } + + factory = informers.NewSharedInformerFactoryWithOptions( + clientset, + 30*time.Second, + informers.WithTweakListOptions(tweakListOptions), + ) + } else { + factory = informers.NewSharedInformerFactory(clientset, + 30*time.Second, + ) + } + + return factory +} + // getARDeploymentName converts the pod's metadata into the correct format // for the deployment name for the artifact registry (this is not the same // as the K8s deployment's name! From ce90b194e367a07b0263c2bfa2bfb2d3e6a356db Mon Sep 17 00:00:00 2001 From: Eric Pickard Date: Tue, 3 Feb 2026 12:11:14 -0500 Subject: [PATCH 2/6] adjust flag description --- cmd/deployment-tracker/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/deployment-tracker/main.go b/cmd/deployment-tracker/main.go index d46eabb..6ed99a4 100644 --- a/cmd/deployment-tracker/main.go +++ b/cmd/deployment-tracker/main.go @@ -42,7 +42,7 @@ func main() { flag.StringVar(&kubeconfig, "kubeconfig", "", "path to kubeconfig file (uses in-cluster config if not set)") flag.StringVar(&namespace, "namespace", "", "namespace to monitor (empty for all namespaces)") - flag.StringVar(&excludeNamespaces, "exclude-namespace", "", "namespace to exclude from monitoring (empty to include all namespaces)") + flag.StringVar(&excludeNamespaces, "exclude-namespace", "", "comma separated list of namespaces to exclude from monitoring (empty to include all namespaces)") flag.IntVar(&workers, "workers", 2, "number of worker goroutines") flag.StringVar(&metricsPort, "metrics-port", "9090", "port to listen to for metrics") flag.Parse() From 6cfea403448d1d1d9f46aa72b1a97bf4c57975c0 Mon Sep 17 00:00:00 2001 From: Eric Pickard Date: Tue, 3 Feb 2026 12:17:11 -0500 Subject: [PATCH 3/6] adjust flag --- cmd/deployment-tracker/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/deployment-tracker/main.go b/cmd/deployment-tracker/main.go index 6ed99a4..3ad90a0 100644 --- a/cmd/deployment-tracker/main.go +++ b/cmd/deployment-tracker/main.go @@ -42,7 +42,7 @@ func main() { flag.StringVar(&kubeconfig, "kubeconfig", "", "path to kubeconfig file (uses in-cluster config if not set)") flag.StringVar(&namespace, "namespace", "", "namespace to monitor (empty for all namespaces)") - flag.StringVar(&excludeNamespaces, "exclude-namespace", "", "comma separated list of namespaces to exclude from monitoring (empty to include all namespaces)") + flag.StringVar(&excludeNamespaces, "exclude-namespaces", "", "comma separated list of namespaces to exclude from monitoring (empty to include all namespaces)") flag.IntVar(&workers, "workers", 2, "number of worker goroutines") flag.StringVar(&metricsPort, "metrics-port", "9090", "port to listen to for metrics") flag.Parse() From 3f059dcce71fb32ee3797446454431b916c335fb Mon Sep 17 00:00:00 2001 From: Eric Pickard Date: Tue, 3 Feb 2026 12:55:03 -0500 Subject: [PATCH 4/6] update docs --- README.md | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 5972d8e..ac39a2c 100644 --- a/README.md +++ b/README.md @@ -49,12 +49,16 @@ Two modes of authentication are supported: ## Command Line Options -| Flag | Description | Default | -|-----------------|--------------------------------------|--------------------------------------------| -| `-kubeconfig` | Path to kubeconfig file | Uses in-cluster config or `~/.kube/config` | -| `-namespace` | Namespace to monitor (empty for all) | `""` (all namespaces) | -| `-workers` | Number of worker goroutines | `2` | -| `-metrics-port` | Port number for Prometheus metrics | 9090 | +| Flag | Description | Default | +|-----------------------|---------------------------------------------------------------|--------------------------------------------| +| `-kubeconfig` | Path to kubeconfig file | Uses in-cluster config or `~/.kube/config` | +| `-namespace` | Namespace to monitor (empty for all) | `""` (all namespaces) | +| `-exclude-namespaces` | Comma-separated list of namespaces to exclude (empty for all) | `""` (all namespaces) | +| `-workers` | Number of worker goroutines | `2` | +| `-metrics-port` | Port number for Prometheus metrics | 9090 | + +> [!NOTE] +> The `-namespace` and `-exclude-namespaces` flags cannot be used together. ## Environment Variables From 0a6ed1ef4768d7e8416dd6cb9acd98630a9d7856 Mon Sep 17 00:00:00 2001 From: Eric Pickard Date: Tue, 3 Feb 2026 15:42:48 -0500 Subject: [PATCH 5/6] address linter comments --- internal/controller/controller.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/internal/controller/controller.go b/internal/controller/controller.go index 1b81225..aab8e8b 100644 --- a/internal/controller/controller.go +++ b/internal/controller/controller.go @@ -482,13 +482,14 @@ func getCacheKey(dn, digest string) string { // If namespace is non-empty, it will only watch that namespace. func createInformerFactory(clientset kubernetes.Interface, namespace string, excludeNamespaces string) informers.SharedInformerFactory { var factory informers.SharedInformerFactory - if namespace != "" { + switch { + case namespace != "": factory = informers.NewSharedInformerFactoryWithOptions( clientset, 30*time.Second, informers.WithNamespace(namespace), ) - } else if excludeNamespaces != "" { + case excludeNamespaces != "": excludedNamespacesList := strings.Split(excludeNamespaces, ",") for i := 0; i < len(excludedNamespacesList); i++ { excludedNamespacesList[i] = fmt.Sprintf("metadata.namespace!=%s", strings.TrimSpace(excludedNamespacesList[i])) @@ -502,7 +503,7 @@ func createInformerFactory(clientset kubernetes.Interface, namespace string, exc 30*time.Second, informers.WithTweakListOptions(tweakListOptions), ) - } else { + default: factory = informers.NewSharedInformerFactory(clientset, 30*time.Second, ) From 7e4b0aeed6c6fa1dfe48166f6c2e7d513982aa35 Mon Sep 17 00:00:00 2001 From: Eric Pickard Date: Tue, 3 Feb 2026 16:35:29 -0500 Subject: [PATCH 6/6] address comments --- cmd/deployment-tracker/main.go | 2 +- internal/controller/controller.go | 23 +++++++++++++++++++---- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/cmd/deployment-tracker/main.go b/cmd/deployment-tracker/main.go index 3ad90a0..d4fc349 100644 --- a/cmd/deployment-tracker/main.go +++ b/cmd/deployment-tracker/main.go @@ -49,7 +49,7 @@ func main() { // Cannot use both if namespace != "" && excludeNamespaces != "" { - slog.Error("Cannot set both --namespace and --exclude-namespace") + slog.Error("Cannot set both -namespace and -exclude-namespaces") os.Exit(1) } diff --git a/internal/controller/controller.go b/internal/controller/controller.go index aab8e8b..2319110 100644 --- a/internal/controller/controller.go +++ b/internal/controller/controller.go @@ -484,18 +484,33 @@ func createInformerFactory(clientset kubernetes.Interface, namespace string, exc var factory informers.SharedInformerFactory switch { case namespace != "": + slog.Info("Namespace to watch", + "namespace", + namespace, + ) factory = informers.NewSharedInformerFactoryWithOptions( clientset, 30*time.Second, informers.WithNamespace(namespace), ) case excludeNamespaces != "": - excludedNamespacesList := strings.Split(excludeNamespaces, ",") - for i := 0; i < len(excludedNamespacesList); i++ { - excludedNamespacesList[i] = fmt.Sprintf("metadata.namespace!=%s", strings.TrimSpace(excludedNamespacesList[i])) + seenNamespaces := make(map[string]bool) + fieldSelectorParts := make([]string, 0) + + for _, ns := range strings.Split(excludeNamespaces, ",") { + ns = strings.TrimSpace(ns) + if ns != "" && !seenNamespaces[ns] { + seenNamespaces[ns] = true + fieldSelectorParts = append(fieldSelectorParts, fmt.Sprintf("metadata.namespace!=%s", ns)) + } } + + slog.Info("Excluding namespaces from watch", + "field_selector", + strings.Join(fieldSelectorParts, ","), + ) tweakListOptions := func(options *metav1.ListOptions) { - options.FieldSelector = strings.Join(excludedNamespacesList, ",") + options.FieldSelector = strings.Join(fieldSelectorParts, ",") } factory = informers.NewSharedInformerFactoryWithOptions(