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
16 changes: 10 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
18 changes: 13 additions & 5 deletions cmd/deployment-tracker/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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-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()

// Cannot use both
if namespace != "" && excludeNamespaces != "" {
slog.Error("Cannot set both -namespace and -exclude-namespaces")
os.Exit(1)
}

// Validate worker count
if workers < 1 || workers > 100 {
slog.Error("Invalid worker count, must be between 1 and 100",
Expand Down Expand Up @@ -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)
Expand Down
65 changes: 52 additions & 13 deletions internal/controller/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()

Expand Down Expand Up @@ -488,6 +477,56 @@ 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
switch {
case namespace != "":
slog.Info("Namespace to watch",
"namespace",
namespace,
)
factory = informers.NewSharedInformerFactoryWithOptions(
clientset,
30*time.Second,
informers.WithNamespace(namespace),
)
case excludeNamespaces != "":
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(fieldSelectorParts, ",")
}

factory = informers.NewSharedInformerFactoryWithOptions(
clientset,
30*time.Second,
informers.WithTweakListOptions(tweakListOptions),
)
default:
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!
Expand Down