mirror of
https://github.com/stakater/Reloader.git
synced 2026-08-22 05:26:18 +00:00
added support for multiple watch namespaces
This commit is contained in:
@@ -264,9 +264,11 @@ func BindFlags(fs *pflag.FlagSet, cfg *Config) {
|
||||
_ = v.BindEnv("alert-proxy", "ALERT_PROXY", "ALERT_WEBHOOK_PROXY")
|
||||
}
|
||||
|
||||
// ApplyFlags applies flag values from viper to the config struct.
|
||||
// Call this after parsing flags.
|
||||
func ApplyFlags(cfg *Config) error {
|
||||
// ApplyFlags applies flag values from viper to the config struct. Call this
|
||||
// after parsing flags. It returns any human-readable warnings produced while
|
||||
// finalizing namespace scope (see ApplyNamespaceScope) so the caller can log
|
||||
// them once a logger is available.
|
||||
func ApplyFlags(cfg *Config) ([]string, error) {
|
||||
// Boolean flags
|
||||
cfg.AutoReloadAll = v.GetBool("auto-reload-all")
|
||||
cfg.SyncAfterRestart = v.GetBool("sync-after-restart")
|
||||
@@ -365,7 +367,7 @@ func ApplyFlags(cfg *Config) error {
|
||||
joinedNS := strings.Join(nsSelectors, ",")
|
||||
selector, err := labels.Parse(joinedNS)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid selector %q: %w", joinedNS, err)
|
||||
return nil, fmt.Errorf("invalid selector %q: %w", joinedNS, err)
|
||||
}
|
||||
cfg.NamespaceSelectors = []labels.Selector{selector}
|
||||
}
|
||||
@@ -373,7 +375,7 @@ func ApplyFlags(cfg *Config) error {
|
||||
joinedRes := strings.Join(resSelectors, ",")
|
||||
selector, err := labels.Parse(joinedRes)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid selector %q: %w", joinedRes, err)
|
||||
return nil, fmt.Errorf("invalid selector %q: %w", joinedRes, err)
|
||||
}
|
||||
cfg.ResourceSelectors = []labels.Selector{selector}
|
||||
}
|
||||
@@ -389,7 +391,11 @@ func ApplyFlags(cfg *Config) error {
|
||||
cfg.LeaderElection.RetryPeriod = 2 * time.Second
|
||||
}
|
||||
|
||||
return nil
|
||||
// Enforce namespace-scope semantics here so the finalized config is
|
||||
// self-consistent for every caller: selector/ignore lists are only honored
|
||||
// in global mode. Warnings are returned for the caller to log once logging
|
||||
// is set up.
|
||||
return cfg.ApplyNamespaceScope(), nil
|
||||
}
|
||||
|
||||
// parseBoolString parses a string as a boolean, defaulting to false.
|
||||
|
||||
@@ -92,7 +92,7 @@ func TestBindFlags_DefaultValues(t *testing.T) {
|
||||
t.Fatalf("Parse() error = %v", err)
|
||||
}
|
||||
|
||||
if err := ApplyFlags(cfg); err != nil {
|
||||
if _, err := ApplyFlags(cfg); err != nil {
|
||||
t.Fatalf("ApplyFlags() error = %v", err)
|
||||
}
|
||||
|
||||
@@ -126,7 +126,7 @@ func TestBindFlags_CustomValues(t *testing.T) {
|
||||
t.Fatalf("Parse() error = %v", err)
|
||||
}
|
||||
|
||||
if err := ApplyFlags(cfg); err != nil {
|
||||
if _, err := ApplyFlags(cfg); err != nil {
|
||||
t.Fatalf("ApplyFlags() error = %v", err)
|
||||
}
|
||||
|
||||
@@ -168,7 +168,7 @@ func TestApplyFlags_SecretProviderClassAnnotations(t *testing.T) {
|
||||
if err := fs.Parse(nil); err != nil {
|
||||
t.Fatalf("Parse() error = %v", err)
|
||||
}
|
||||
if err := ApplyFlags(cfg); err != nil {
|
||||
if _, err := ApplyFlags(cfg); err != nil {
|
||||
t.Fatalf("ApplyFlags() error = %v", err)
|
||||
}
|
||||
defaults := DefaultAnnotations()
|
||||
@@ -195,7 +195,7 @@ func TestApplyFlags_SecretProviderClassAnnotations(t *testing.T) {
|
||||
if err := fs.Parse(args); err != nil {
|
||||
t.Fatalf("Parse() error = %v", err)
|
||||
}
|
||||
if err := ApplyFlags(cfg); err != nil {
|
||||
if _, err := ApplyFlags(cfg); err != nil {
|
||||
t.Fatalf("ApplyFlags() error = %v", err)
|
||||
}
|
||||
if cfg.Annotations.SecretProviderClassAuto != "spc.example.com/auto" {
|
||||
@@ -218,7 +218,7 @@ func TestApplyFlags_ExcludeAnnotations(t *testing.T) {
|
||||
if err := fs.Parse(nil); err != nil {
|
||||
t.Fatalf("Parse() error = %v", err)
|
||||
}
|
||||
if err := ApplyFlags(cfg); err != nil {
|
||||
if _, err := ApplyFlags(cfg); err != nil {
|
||||
t.Fatalf("ApplyFlags() error = %v", err)
|
||||
}
|
||||
defaults := DefaultAnnotations()
|
||||
@@ -241,7 +241,7 @@ func TestApplyFlags_ExcludeAnnotations(t *testing.T) {
|
||||
if err := fs.Parse(args); err != nil {
|
||||
t.Fatalf("Parse() error = %v", err)
|
||||
}
|
||||
if err := ApplyFlags(cfg); err != nil {
|
||||
if _, err := ApplyFlags(cfg); err != nil {
|
||||
t.Fatalf("ApplyFlags() error = %v", err)
|
||||
}
|
||||
if cfg.Annotations.ConfigmapExclude != "cm.example.com/exclude" {
|
||||
@@ -261,7 +261,7 @@ func TestApplyFlags_IgnoreAnnotation(t *testing.T) {
|
||||
if err := fs.Parse(nil); err != nil {
|
||||
t.Fatalf("Parse() error = %v", err)
|
||||
}
|
||||
if err := ApplyFlags(cfg); err != nil {
|
||||
if _, err := ApplyFlags(cfg); err != nil {
|
||||
t.Fatalf("ApplyFlags() error = %v", err)
|
||||
}
|
||||
if cfg.Annotations.Ignore != DefaultAnnotations().Ignore {
|
||||
@@ -276,7 +276,7 @@ func TestApplyFlags_IgnoreAnnotation(t *testing.T) {
|
||||
if err := fs.Parse([]string{"--ignore-annotation=my.company.com/reloader-ignore"}); err != nil {
|
||||
t.Fatalf("Parse() error = %v", err)
|
||||
}
|
||||
if err := ApplyFlags(cfg); err != nil {
|
||||
if _, err := ApplyFlags(cfg); err != nil {
|
||||
t.Fatalf("ApplyFlags() error = %v", err)
|
||||
}
|
||||
if cfg.Annotations.Ignore != "my.company.com/reloader-ignore" {
|
||||
@@ -313,7 +313,7 @@ func TestApplyFlags_BooleanStrings(t *testing.T) {
|
||||
t.Fatalf("Parse() error = %v", err)
|
||||
}
|
||||
|
||||
err := ApplyFlags(cfg)
|
||||
_, err := ApplyFlags(cfg)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("ApplyFlags() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
@@ -343,7 +343,7 @@ func TestApplyFlags_CommaSeparatedLists(t *testing.T) {
|
||||
t.Fatalf("Parse() error = %v", err)
|
||||
}
|
||||
|
||||
if err := ApplyFlags(cfg); err != nil {
|
||||
if _, err := ApplyFlags(cfg); err != nil {
|
||||
t.Fatalf("ApplyFlags() error = %v", err)
|
||||
}
|
||||
|
||||
@@ -378,7 +378,7 @@ func TestApplyFlags_Selectors(t *testing.T) {
|
||||
t.Fatalf("Parse() error = %v", err)
|
||||
}
|
||||
|
||||
if err := ApplyFlags(cfg); err != nil {
|
||||
if _, err := ApplyFlags(cfg); err != nil {
|
||||
t.Fatalf("ApplyFlags() error = %v", err)
|
||||
}
|
||||
|
||||
@@ -409,7 +409,7 @@ func TestApplyFlags_InvalidSelector(t *testing.T) {
|
||||
t.Fatalf("Parse() error = %v", err)
|
||||
}
|
||||
|
||||
err := ApplyFlags(cfg)
|
||||
_, err := ApplyFlags(cfg)
|
||||
if err == nil {
|
||||
t.Error("ApplyFlags() should return error for invalid selector")
|
||||
}
|
||||
@@ -461,7 +461,7 @@ func TestApplyFlags_AlertingEnvVars(t *testing.T) {
|
||||
t.Fatalf("Parse() error = %v", err)
|
||||
}
|
||||
|
||||
if err := ApplyFlags(cfg); err != nil {
|
||||
if _, err := ApplyFlags(cfg); err != nil {
|
||||
t.Fatalf("ApplyFlags() error = %v", err)
|
||||
}
|
||||
|
||||
@@ -494,7 +494,7 @@ func TestApplyFlags_LegacyProxyEnvVar(t *testing.T) {
|
||||
t.Fatalf("Parse() error = %v", err)
|
||||
}
|
||||
|
||||
if err := ApplyFlags(cfg); err != nil {
|
||||
if _, err := ApplyFlags(cfg); err != nil {
|
||||
t.Fatalf("ApplyFlags() error = %v", err)
|
||||
}
|
||||
|
||||
@@ -511,7 +511,7 @@ func TestApplyFlagsCSIIntegration(t *testing.T) {
|
||||
if err := fs.Parse([]string{"--enable-csi-integration=true"}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := ApplyFlags(cfg); err != nil {
|
||||
if _, err := ApplyFlags(cfg); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !cfg.CSIIntegrationEnabled {
|
||||
@@ -592,7 +592,7 @@ func TestApplyFlags_NamespacesScoped(t *testing.T) {
|
||||
if err := fs.Parse([]string{"--namespaces=team-a,team-b"}); err != nil {
|
||||
t.Fatalf("Parse() error = %v", err)
|
||||
}
|
||||
if err := ApplyFlags(cfg); err != nil {
|
||||
if _, err := ApplyFlags(cfg); err != nil {
|
||||
t.Fatalf("ApplyFlags() error = %v", err)
|
||||
}
|
||||
|
||||
@@ -617,7 +617,7 @@ func TestApplyFlags_NamespacesFromEnv(t *testing.T) {
|
||||
if err := fs.Parse([]string{}); err != nil {
|
||||
t.Fatalf("Parse() error = %v", err)
|
||||
}
|
||||
if err := ApplyFlags(cfg); err != nil {
|
||||
if _, err := ApplyFlags(cfg); err != nil {
|
||||
t.Fatalf("ApplyFlags() error = %v", err)
|
||||
}
|
||||
|
||||
@@ -636,7 +636,7 @@ func TestApplyFlags_NamespacesGlobal(t *testing.T) {
|
||||
if err := fs.Parse([]string{}); err != nil {
|
||||
t.Fatalf("Parse() error = %v", err)
|
||||
}
|
||||
if err := ApplyFlags(cfg); err != nil {
|
||||
if _, err := ApplyFlags(cfg); err != nil {
|
||||
t.Fatalf("ApplyFlags() error = %v", err)
|
||||
}
|
||||
|
||||
@@ -657,7 +657,7 @@ func TestApplyFlags_NamespacesTrimsEmptyEntries(t *testing.T) {
|
||||
if err := fs.Parse([]string{"--namespaces=team-a, ,team-b,"}); err != nil {
|
||||
t.Fatalf("Parse() error = %v", err)
|
||||
}
|
||||
if err := ApplyFlags(cfg); err != nil {
|
||||
if _, err := ApplyFlags(cfg); err != nil {
|
||||
t.Fatalf("ApplyFlags() error = %v", err)
|
||||
}
|
||||
|
||||
@@ -682,7 +682,7 @@ func TestApplyFlags_NamespacesAllEmptyIsGlobal(t *testing.T) {
|
||||
if err := fs.Parse([]string{"--namespaces=, ,"}); err != nil {
|
||||
t.Fatalf("Parse() error = %v", err)
|
||||
}
|
||||
if err := ApplyFlags(cfg); err != nil {
|
||||
if _, err := ApplyFlags(cfg); err != nil {
|
||||
t.Fatalf("ApplyFlags() error = %v", err)
|
||||
}
|
||||
|
||||
@@ -693,3 +693,64 @@ func TestApplyFlags_NamespacesAllEmptyIsGlobal(t *testing.T) {
|
||||
t.Errorf("all-empty namespaces should be global mode")
|
||||
}
|
||||
}
|
||||
|
||||
// ApplyFlags must finalize a self-consistent config: in scoped mode it enforces
|
||||
// namespace-scope semantics (clears selector/ignore lists) and returns warnings,
|
||||
// without the caller having to invoke ApplyNamespaceScope separately.
|
||||
func TestApplyFlags_ScopedClearsSelectorsAndIgnores(t *testing.T) {
|
||||
resetViper()
|
||||
cfg := NewDefault()
|
||||
fs := pflag.NewFlagSet("test", pflag.ContinueOnError)
|
||||
BindFlags(fs, cfg)
|
||||
|
||||
if err := fs.Parse([]string{
|
||||
"--namespaces=team-a",
|
||||
"--namespace-selector=env=prod",
|
||||
"--namespaces-to-ignore=kube-system",
|
||||
}); err != nil {
|
||||
t.Fatalf("Parse() error = %v", err)
|
||||
}
|
||||
warnings, err := ApplyFlags(cfg)
|
||||
if err != nil {
|
||||
t.Fatalf("ApplyFlags() error = %v", err)
|
||||
}
|
||||
|
||||
if len(cfg.NamespaceSelectors) != 0 || len(cfg.NamespaceSelectorStrings) != 0 {
|
||||
t.Errorf("scoped mode should clear namespace selectors, got %v", cfg.NamespaceSelectorStrings)
|
||||
}
|
||||
if len(cfg.IgnoredNamespaces) != 0 {
|
||||
t.Errorf("scoped mode should clear ignored namespaces, got %v", cfg.IgnoredNamespaces)
|
||||
}
|
||||
if len(warnings) != 2 {
|
||||
t.Errorf("expected 2 scope warnings, got %v", warnings)
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplyFlags_GlobalKeepsSelectorsNoWarnings(t *testing.T) {
|
||||
resetViper()
|
||||
t.Setenv("KUBERNETES_NAMESPACE", "")
|
||||
cfg := NewDefault()
|
||||
fs := pflag.NewFlagSet("test", pflag.ContinueOnError)
|
||||
BindFlags(fs, cfg)
|
||||
|
||||
if err := fs.Parse([]string{
|
||||
"--namespace-selector=env=prod",
|
||||
"--namespaces-to-ignore=kube-system",
|
||||
}); err != nil {
|
||||
t.Fatalf("Parse() error = %v", err)
|
||||
}
|
||||
warnings, err := ApplyFlags(cfg)
|
||||
if err != nil {
|
||||
t.Fatalf("ApplyFlags() error = %v", err)
|
||||
}
|
||||
|
||||
if !cfg.IsGlobalMode() {
|
||||
t.Fatalf("no --namespaces should be global mode")
|
||||
}
|
||||
if len(cfg.NamespaceSelectors) != 1 || len(cfg.IgnoredNamespaces) != 1 {
|
||||
t.Errorf("global mode should keep selectors and ignored namespaces")
|
||||
}
|
||||
if len(warnings) != 0 {
|
||||
t.Errorf("global mode should produce no warnings, got %v", warnings)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,9 +66,18 @@ type ManagerOptions struct {
|
||||
Collectors *metrics.Collectors
|
||||
}
|
||||
|
||||
// NewManager creates a new controller-runtime manager with the given options.
|
||||
// This follows controller-runtime and operator-sdk conventions for leader election.
|
||||
// NewManager creates a new controller-runtime manager using the ambient cluster
|
||||
// configuration (in-cluster or kubeconfig). It follows controller-runtime and
|
||||
// operator-sdk conventions for leader election.
|
||||
func NewManager(opts ManagerOptions) (ctrl.Manager, error) {
|
||||
return NewManagerWithRestConfig(opts, ctrl.GetConfigOrDie())
|
||||
}
|
||||
|
||||
// NewManagerWithRestConfig creates a new controller-runtime manager with the
|
||||
// given rest.Config. NewManager delegates here; tests can call it directly with
|
||||
// a pre-existing cluster configuration (set cfg.MetricsAddr/HealthAddr to "0" to
|
||||
// disable those servers and avoid port conflicts).
|
||||
func NewManagerWithRestConfig(opts ManagerOptions, restConfig *rest.Config) (ctrl.Manager, error) {
|
||||
cfg := opts.Config
|
||||
le := cfg.LeaderElection
|
||||
|
||||
@@ -100,7 +109,7 @@ func NewManager(opts ManagerOptions) (ctrl.Manager, error) {
|
||||
opts.Log.Info("namespace filtering enabled", "namespaces", cfg.WatchedNamespaces)
|
||||
}
|
||||
|
||||
mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), mgrOpts)
|
||||
mgr, err := ctrl.NewManager(restConfig, mgrOpts)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("creating manager: %w", err)
|
||||
}
|
||||
@@ -119,43 +128,6 @@ func NewManager(opts ManagerOptions) (ctrl.Manager, error) {
|
||||
return mgr, nil
|
||||
}
|
||||
|
||||
// NewManagerWithRestConfig creates a new controller-runtime manager with the given rest.Config.
|
||||
// This is useful for testing where you have a pre-existing cluster configuration.
|
||||
func NewManagerWithRestConfig(opts ManagerOptions, restConfig *rest.Config) (ctrl.Manager, error) {
|
||||
cfg := opts.Config
|
||||
le := cfg.LeaderElection
|
||||
|
||||
mgrOpts := ctrl.Options{
|
||||
Scheme: runtimeScheme,
|
||||
Metrics: ctrlmetrics.Options{
|
||||
BindAddress: "0", // Disable metrics server in tests
|
||||
},
|
||||
HealthProbeBindAddress: "0", // Disable health probes in tests
|
||||
|
||||
// Leader election configuration
|
||||
LeaderElection: cfg.EnableHA,
|
||||
LeaderElectionID: le.LockName,
|
||||
LeaderElectionNamespace: le.Namespace,
|
||||
LeaderElectionReleaseOnCancel: le.ReleaseOnCancel,
|
||||
LeaseDuration: &le.LeaseDuration,
|
||||
RenewDeadline: &le.RenewDeadline,
|
||||
RetryPeriod: &le.RetryPeriod,
|
||||
}
|
||||
|
||||
if nsScope := buildDefaultNamespaces(cfg.WatchedNamespaces); nsScope != nil {
|
||||
mgrOpts.Cache = cache.Options{
|
||||
DefaultNamespaces: nsScope,
|
||||
}
|
||||
}
|
||||
|
||||
mgr, err := ctrl.NewManager(restConfig, mgrOpts)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("creating manager: %w", err)
|
||||
}
|
||||
|
||||
return mgr, nil
|
||||
}
|
||||
|
||||
// SetupReconcilers sets up all reconcilers with the manager.
|
||||
func SetupReconcilers(mgr ctrl.Manager, cfg *config.Config, log logr.Logger, collectors *metrics.Collectors) error {
|
||||
registry := workload.NewRegistry(
|
||||
|
||||
Reference in New Issue
Block a user