mirror of
https://github.com/stakater/Reloader.git
synced 2026-08-20 04:26:28 +00:00
feat: Use viper for config handling and flags
This commit is contained in:
@@ -39,6 +39,7 @@ require (
|
||||
github.com/go-openapi/swag/stringutils v0.25.4 // indirect
|
||||
github.com/go-openapi/swag/typeutils v0.25.4 // indirect
|
||||
github.com/go-openapi/swag/yamlutils v0.25.4 // indirect
|
||||
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/google/btree v1.1.3 // indirect
|
||||
github.com/google/gnostic-models v0.7.1 // indirect
|
||||
@@ -52,9 +53,16 @@ require (
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/prometheus/common v0.67.4 // indirect
|
||||
github.com/prometheus/procfs v0.19.2 // indirect
|
||||
github.com/sagikazarmark/locafero v0.11.0 // indirect
|
||||
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect
|
||||
github.com/spf13/afero v1.15.0 // indirect
|
||||
github.com/spf13/cast v1.10.0 // indirect
|
||||
github.com/spf13/viper v1.21.0 // indirect
|
||||
github.com/subosito/gotenv v1.6.0 // indirect
|
||||
github.com/x448/float16 v0.8.4 // indirect
|
||||
go.yaml.in/yaml/v2 v2.4.3 // indirect
|
||||
go.yaml.in/yaml/v3 v3.0.4 // indirect
|
||||
|
||||
@@ -65,6 +65,8 @@ github.com/go-openapi/testify/v2 v2.0.2 h1:X999g3jeLcoY8qctY/c/Z8iBHTbwLz7R2WXd6
|
||||
github.com/go-openapi/testify/v2 v2.0.2/go.mod h1:HCPmvFFnheKK2BuwSA0TbbdxJ3I16pjwMkYkP4Ywn54=
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
|
||||
github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs=
|
||||
github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
@@ -114,6 +116,8 @@ github.com/onsi/ginkgo/v2 v2.27.2 h1:LzwLj0b89qtIy6SSASkzlNvX6WktqurSHwkk2ipF/Ns
|
||||
github.com/onsi/ginkgo/v2 v2.27.2/go.mod h1:ArE1D/XhNXBXCBkKOLkbsb2c81dQHCRcF5zwn/ykDRo=
|
||||
github.com/onsi/gomega v1.38.2 h1:eZCjf2xjZAqe+LeWvKb5weQ+NcPwX84kqJ0cZNxok2A=
|
||||
github.com/onsi/gomega v1.38.2/go.mod h1:W2MJcYxRGV63b418Ai34Ud0hEdTVXq9NW9+Sx6uXf3k=
|
||||
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
|
||||
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
@@ -132,17 +136,29 @@ github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
|
||||
github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY=
|
||||
github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/sagikazarmark/locafero v0.11.0 h1:1iurJgmM9G3PA/I+wWYIOw/5SyBtxapeHDcg+AAIFXc=
|
||||
github.com/sagikazarmark/locafero v0.11.0/go.mod h1:nVIGvgyzw595SUSUE6tvCp3YYTeHs15MvlmU87WwIik=
|
||||
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 h1:+jumHNA0Wrelhe64i8F6HNlS8pkoyMv5sreGx2Ry5Rw=
|
||||
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8/go.mod h1:3n1Cwaq1E1/1lhQhtRK2ts/ZwZEhjcQeJQ1RuC6Q/8U=
|
||||
github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I=
|
||||
github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg=
|
||||
github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY=
|
||||
github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo=
|
||||
github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU=
|
||||
github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4=
|
||||
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
|
||||
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/viper v1.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU=
|
||||
github.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjbTCAY=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
|
||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
|
||||
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
|
||||
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
|
||||
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
|
||||
+176
-164
@@ -2,215 +2,211 @@ package config
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/spf13/pflag"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
// flagValues holds intermediate string values from CLI flags
|
||||
// that need further parsing into the Config struct.
|
||||
type flagValues struct {
|
||||
namespaceSelectors string
|
||||
resourceSelectors string
|
||||
ignoredResources string
|
||||
ignoredWorkloads string
|
||||
ignoredNamespaces string
|
||||
isArgoRollouts string
|
||||
reloadOnCreate string
|
||||
reloadOnDelete string
|
||||
}
|
||||
// v is the viper instance for configuration.
|
||||
var v *viper.Viper
|
||||
|
||||
var fv flagValues
|
||||
func init() {
|
||||
v = viper.New()
|
||||
// Convert flag names like "alert-webhook-url" to env vars like "ALERT_WEBHOOK_URL"
|
||||
v.SetEnvKeyReplacer(strings.NewReplacer("-", "_"))
|
||||
v.AutomaticEnv()
|
||||
}
|
||||
|
||||
// BindFlags binds configuration flags to the provided flag set.
|
||||
// Call this before parsing flags, then call ApplyFlags after parsing.
|
||||
func BindFlags(fs *pflag.FlagSet, cfg *Config) {
|
||||
// Auto reload
|
||||
fs.BoolVar(
|
||||
&cfg.AutoReloadAll, "auto-reload-all", cfg.AutoReloadAll,
|
||||
"Automatically reload all resources when their configmaps/secrets are updated, without requiring annotations",
|
||||
)
|
||||
fs.Bool("auto-reload-all", cfg.AutoReloadAll,
|
||||
"Automatically reload all resources when their configmaps/secrets are updated, without requiring annotations")
|
||||
|
||||
// Reload strategy
|
||||
fs.StringVar(
|
||||
(*string)(&cfg.ReloadStrategy), "reload-strategy", string(cfg.ReloadStrategy),
|
||||
"Strategy for triggering workload restart: 'env-vars' (default, GitOps friendly) or 'annotations'",
|
||||
)
|
||||
fs.String("reload-strategy", string(cfg.ReloadStrategy),
|
||||
"Strategy for triggering workload restart: 'env-vars' (default, GitOps friendly) or 'annotations'")
|
||||
|
||||
// Argo Rollouts
|
||||
fs.StringVar(
|
||||
&fv.isArgoRollouts, "is-Argo-Rollouts", "false",
|
||||
"Enable Argo Rollouts support (true/false)",
|
||||
)
|
||||
fs.String("is-Argo-Rollouts", "false",
|
||||
"Enable Argo Rollouts support (true/false)")
|
||||
|
||||
// Event watching
|
||||
fs.StringVar(
|
||||
&fv.reloadOnCreate, "reload-on-create", "false",
|
||||
"Reload when configmaps/secrets are created (true/false)",
|
||||
)
|
||||
fs.StringVar(
|
||||
&fv.reloadOnDelete, "reload-on-delete", "false",
|
||||
"Reload when configmaps/secrets are deleted (true/false)",
|
||||
)
|
||||
fs.String("reload-on-create", "false",
|
||||
"Reload when configmaps/secrets are created (true/false)")
|
||||
fs.String("reload-on-delete", "false",
|
||||
"Reload when configmaps/secrets are deleted (true/false)")
|
||||
|
||||
// Sync after restart
|
||||
fs.BoolVar(
|
||||
&cfg.SyncAfterRestart, "sync-after-restart", cfg.SyncAfterRestart,
|
||||
"Trigger sync operation after restart",
|
||||
)
|
||||
fs.Bool("sync-after-restart", cfg.SyncAfterRestart,
|
||||
"Trigger sync operation after restart")
|
||||
|
||||
// High availability / Leader election
|
||||
fs.BoolVar(
|
||||
&cfg.EnableHA, "enable-ha", cfg.EnableHA,
|
||||
"Enable high-availability mode with leader election",
|
||||
)
|
||||
fs.StringVar(
|
||||
&cfg.LeaderElection.LockName, "leader-election-id", cfg.LeaderElection.LockName,
|
||||
"Name of the lease resource for leader election",
|
||||
)
|
||||
fs.StringVar(
|
||||
&cfg.LeaderElection.Namespace, "leader-election-namespace", cfg.LeaderElection.Namespace,
|
||||
"Namespace for the leader election lease (defaults to pod namespace)",
|
||||
)
|
||||
fs.DurationVar(
|
||||
&cfg.LeaderElection.LeaseDuration, "leader-election-lease-duration", cfg.LeaderElection.LeaseDuration,
|
||||
"Duration that non-leader candidates will wait before attempting to acquire leadership",
|
||||
)
|
||||
fs.DurationVar(
|
||||
&cfg.LeaderElection.RenewDeadline, "leader-election-renew-deadline", cfg.LeaderElection.RenewDeadline,
|
||||
"Duration that the acting leader will retry refreshing leadership before giving up",
|
||||
)
|
||||
fs.DurationVar(
|
||||
&cfg.LeaderElection.RetryPeriod, "leader-election-retry-period", cfg.LeaderElection.RetryPeriod,
|
||||
"Duration between leader election retries",
|
||||
)
|
||||
fs.BoolVar(
|
||||
&cfg.LeaderElection.ReleaseOnCancel, "leader-election-release-on-cancel", cfg.LeaderElection.ReleaseOnCancel,
|
||||
"Release the leader lock when the manager is stopped",
|
||||
)
|
||||
fs.Bool("enable-ha", cfg.EnableHA,
|
||||
"Enable high-availability mode with leader election")
|
||||
fs.String("leader-election-id", cfg.LeaderElection.LockName,
|
||||
"Name of the lease resource for leader election")
|
||||
fs.String("leader-election-namespace", cfg.LeaderElection.Namespace,
|
||||
"Namespace for the leader election lease (defaults to pod namespace)")
|
||||
fs.Duration("leader-election-lease-duration", cfg.LeaderElection.LeaseDuration,
|
||||
"Duration that non-leader candidates will wait before attempting to acquire leadership")
|
||||
fs.Duration("leader-election-renew-deadline", cfg.LeaderElection.RenewDeadline,
|
||||
"Duration that the acting leader will retry refreshing leadership before giving up")
|
||||
fs.Duration("leader-election-retry-period", cfg.LeaderElection.RetryPeriod,
|
||||
"Duration between leader election retries")
|
||||
fs.Bool("leader-election-release-on-cancel", cfg.LeaderElection.ReleaseOnCancel,
|
||||
"Release the leader lock when the manager is stopped")
|
||||
|
||||
// Webhook
|
||||
fs.StringVar(
|
||||
&cfg.WebhookURL, "webhook-url", cfg.WebhookURL,
|
||||
"URL to send notification instead of triggering reload",
|
||||
)
|
||||
fs.String("webhook-url", cfg.WebhookURL,
|
||||
"URL to send notification instead of triggering reload")
|
||||
|
||||
// Filtering - resources (use StringVar not StringSliceVar for simpler parsing)
|
||||
fs.StringVar(
|
||||
&fv.ignoredResources, "resources-to-ignore", "",
|
||||
"Comma-separated list of resources to ignore (valid options: 'configMaps' or 'secrets')",
|
||||
)
|
||||
fs.StringVar(
|
||||
&fv.ignoredWorkloads, "ignored-workload-types", "",
|
||||
"Comma-separated list of workload types to ignore (valid options: 'jobs', 'cronjobs', or both)",
|
||||
)
|
||||
fs.StringVar(
|
||||
&fv.ignoredNamespaces, "namespaces-to-ignore", "",
|
||||
"Comma-separated list of namespaces to ignore",
|
||||
)
|
||||
// Filtering - resources
|
||||
fs.String("resources-to-ignore", "",
|
||||
"Comma-separated list of resources to ignore (valid options: 'configMaps' or 'secrets')")
|
||||
fs.String("ignored-workload-types", "",
|
||||
"Comma-separated list of workload types to ignore (valid options: 'jobs', 'cronjobs', or both)")
|
||||
fs.String("namespaces-to-ignore", "",
|
||||
"Comma-separated list of namespaces to ignore")
|
||||
|
||||
// Filtering - selectors
|
||||
fs.StringVar(
|
||||
&fv.namespaceSelectors, "namespace-selector", "",
|
||||
"Comma-separated list of namespace label selectors",
|
||||
)
|
||||
fs.StringVar(
|
||||
&fv.resourceSelectors, "resource-label-selector", "",
|
||||
"Comma-separated list of resource label selectors",
|
||||
)
|
||||
fs.String("namespace-selector", "",
|
||||
"Comma-separated list of namespace label selectors")
|
||||
fs.String("resource-label-selector", "",
|
||||
"Comma-separated list of resource label selectors")
|
||||
|
||||
// Logging
|
||||
fs.StringVar(
|
||||
&cfg.LogFormat, "log-format", cfg.LogFormat,
|
||||
"Log format: 'json' or empty for default",
|
||||
)
|
||||
fs.StringVar(
|
||||
&cfg.LogLevel, "log-level", cfg.LogLevel,
|
||||
"Log level: trace, debug, info, warning, error, fatal, panic",
|
||||
)
|
||||
fs.String("log-format", cfg.LogFormat,
|
||||
"Log format: 'json' or empty for default")
|
||||
fs.String("log-level", cfg.LogLevel,
|
||||
"Log level: trace, debug, info, warning, error, fatal, panic")
|
||||
|
||||
// Metrics
|
||||
fs.StringVar(
|
||||
&cfg.MetricsAddr, "metrics-addr", cfg.MetricsAddr,
|
||||
"Address to serve metrics on",
|
||||
)
|
||||
fs.String("metrics-addr", cfg.MetricsAddr,
|
||||
"Address to serve metrics on")
|
||||
|
||||
// Health probes
|
||||
fs.StringVar(
|
||||
&cfg.HealthAddr, "health-addr", cfg.HealthAddr,
|
||||
"Address to serve health probes on",
|
||||
)
|
||||
fs.String("health-addr", cfg.HealthAddr,
|
||||
"Address to serve health probes on")
|
||||
|
||||
// Profiling
|
||||
fs.BoolVar(
|
||||
&cfg.EnablePProf, "enable-pprof", cfg.EnablePProf,
|
||||
"Enable pprof profiling server",
|
||||
)
|
||||
fs.StringVar(
|
||||
&cfg.PProfAddr, "pprof-addr", cfg.PProfAddr,
|
||||
"Address for pprof server",
|
||||
)
|
||||
fs.Bool("enable-pprof", cfg.EnablePProf,
|
||||
"Enable pprof profiling server")
|
||||
fs.String("pprof-addr", cfg.PProfAddr,
|
||||
"Address for pprof server")
|
||||
|
||||
// Annotation customization (flag names match v1 for backward compatibility)
|
||||
fs.StringVar(
|
||||
&cfg.Annotations.Auto, "auto-annotation", cfg.Annotations.Auto,
|
||||
"Annotation to detect changes in secrets/configmaps",
|
||||
)
|
||||
fs.StringVar(
|
||||
&cfg.Annotations.ConfigmapAuto, "configmap-auto-annotation", cfg.Annotations.ConfigmapAuto,
|
||||
"Annotation to detect changes in configmaps",
|
||||
)
|
||||
fs.StringVar(
|
||||
&cfg.Annotations.SecretAuto, "secret-auto-annotation", cfg.Annotations.SecretAuto,
|
||||
"Annotation to detect changes in secrets",
|
||||
)
|
||||
fs.StringVar(
|
||||
&cfg.Annotations.ConfigmapReload, "configmap-annotation", cfg.Annotations.ConfigmapReload,
|
||||
"Annotation to detect changes in configmaps, specified by name",
|
||||
)
|
||||
fs.StringVar(
|
||||
&cfg.Annotations.SecretReload, "secret-annotation", cfg.Annotations.SecretReload,
|
||||
"Annotation to detect changes in secrets, specified by name",
|
||||
)
|
||||
fs.StringVar(
|
||||
&cfg.Annotations.Search, "auto-search-annotation", cfg.Annotations.Search,
|
||||
"Annotation to detect changes in configmaps or secrets tagged with special match annotation",
|
||||
)
|
||||
fs.StringVar(
|
||||
&cfg.Annotations.Match, "search-match-annotation", cfg.Annotations.Match,
|
||||
"Annotation to mark secrets or configmaps to match the search",
|
||||
)
|
||||
fs.StringVar(
|
||||
&cfg.Annotations.PausePeriod, "pause-deployment-annotation", cfg.Annotations.PausePeriod,
|
||||
"Annotation to define the time period to pause a deployment after a configmap/secret change",
|
||||
)
|
||||
fs.StringVar(
|
||||
&cfg.Annotations.PausedAt, "pause-deployment-time-annotation", cfg.Annotations.PausedAt,
|
||||
"Annotation to indicate when a deployment was paused by Reloader",
|
||||
)
|
||||
fs.String("auto-annotation", cfg.Annotations.Auto,
|
||||
"Annotation to detect changes in secrets/configmaps")
|
||||
fs.String("configmap-auto-annotation", cfg.Annotations.ConfigmapAuto,
|
||||
"Annotation to detect changes in configmaps")
|
||||
fs.String("secret-auto-annotation", cfg.Annotations.SecretAuto,
|
||||
"Annotation to detect changes in secrets")
|
||||
fs.String("configmap-annotation", cfg.Annotations.ConfigmapReload,
|
||||
"Annotation to detect changes in configmaps, specified by name")
|
||||
fs.String("secret-annotation", cfg.Annotations.SecretReload,
|
||||
"Annotation to detect changes in secrets, specified by name")
|
||||
fs.String("auto-search-annotation", cfg.Annotations.Search,
|
||||
"Annotation to detect changes in configmaps or secrets tagged with special match annotation")
|
||||
fs.String("search-match-annotation", cfg.Annotations.Match,
|
||||
"Annotation to mark secrets or configmaps to match the search")
|
||||
fs.String("pause-deployment-annotation", cfg.Annotations.PausePeriod,
|
||||
"Annotation to define the time period to pause a deployment after a configmap/secret change")
|
||||
fs.String("pause-deployment-time-annotation", cfg.Annotations.PausedAt,
|
||||
"Annotation to indicate when a deployment was paused by Reloader")
|
||||
|
||||
// Watched namespace (for single-namespace mode)
|
||||
fs.StringVar(
|
||||
&cfg.WatchedNamespace, "watch-namespace", cfg.WatchedNamespace,
|
||||
"Namespace to watch (empty for all namespaces)",
|
||||
)
|
||||
fs.String("watch-namespace", cfg.WatchedNamespace,
|
||||
"Namespace to watch (empty for all namespaces)")
|
||||
|
||||
// Alerting
|
||||
fs.Bool("alert-on-reload", cfg.Alerting.Enabled,
|
||||
"Enable sending alerts when resources are reloaded")
|
||||
fs.String("alert-webhook-url", cfg.Alerting.WebhookURL,
|
||||
"Webhook URL to send alerts to")
|
||||
fs.String("alert-sink", cfg.Alerting.Sink,
|
||||
"Alert sink type: 'slack', 'teams', 'gchat', or 'raw' (default)")
|
||||
fs.String("alert-proxy", cfg.Alerting.Proxy,
|
||||
"Proxy URL for alert webhook requests")
|
||||
fs.String("alert-additional-info", cfg.Alerting.Additional,
|
||||
"Additional info to include in alerts (e.g., cluster name)")
|
||||
fs.Bool("alert-structured", cfg.Alerting.Structured,
|
||||
"For raw sink: send structured JSON instead of plain text")
|
||||
|
||||
// Bind pflags to viper
|
||||
_ = v.BindPFlags(fs)
|
||||
|
||||
// Bind legacy env var names that don't match the automatic conversion
|
||||
// (flag "alert-proxy" -> env "ALERT_PROXY", but legacy is "ALERT_WEBHOOK_PROXY")
|
||||
_ = v.BindEnv("alert-proxy", "ALERT_PROXY", "ALERT_WEBHOOK_PROXY")
|
||||
}
|
||||
|
||||
// ApplyFlags applies flag values that need post-processing.
|
||||
// ApplyFlags applies flag values from viper to the config struct.
|
||||
// Call this after parsing flags.
|
||||
func ApplyFlags(cfg *Config) error {
|
||||
// Parse boolean string flags
|
||||
cfg.ArgoRolloutsEnabled = parseBoolString(fv.isArgoRollouts)
|
||||
cfg.ReloadOnCreate = parseBoolString(fv.reloadOnCreate)
|
||||
cfg.ReloadOnDelete = parseBoolString(fv.reloadOnDelete)
|
||||
// Boolean flags
|
||||
cfg.AutoReloadAll = v.GetBool("auto-reload-all")
|
||||
cfg.SyncAfterRestart = v.GetBool("sync-after-restart")
|
||||
cfg.EnableHA = v.GetBool("enable-ha")
|
||||
cfg.EnablePProf = v.GetBool("enable-pprof")
|
||||
|
||||
// Boolean string flags (legacy format: "true"/"false" strings)
|
||||
cfg.ArgoRolloutsEnabled = parseBoolString(v.GetString("is-Argo-Rollouts"))
|
||||
cfg.ReloadOnCreate = parseBoolString(v.GetString("reload-on-create"))
|
||||
cfg.ReloadOnDelete = parseBoolString(v.GetString("reload-on-delete"))
|
||||
|
||||
// String flags
|
||||
cfg.ReloadStrategy = ReloadStrategy(v.GetString("reload-strategy"))
|
||||
cfg.WebhookURL = v.GetString("webhook-url")
|
||||
cfg.LogFormat = v.GetString("log-format")
|
||||
cfg.LogLevel = v.GetString("log-level")
|
||||
cfg.MetricsAddr = v.GetString("metrics-addr")
|
||||
cfg.HealthAddr = v.GetString("health-addr")
|
||||
cfg.PProfAddr = v.GetString("pprof-addr")
|
||||
cfg.WatchedNamespace = v.GetString("watch-namespace")
|
||||
|
||||
// Leader election
|
||||
cfg.LeaderElection.LockName = v.GetString("leader-election-id")
|
||||
cfg.LeaderElection.Namespace = v.GetString("leader-election-namespace")
|
||||
cfg.LeaderElection.LeaseDuration = v.GetDuration("leader-election-lease-duration")
|
||||
cfg.LeaderElection.RenewDeadline = v.GetDuration("leader-election-renew-deadline")
|
||||
cfg.LeaderElection.RetryPeriod = v.GetDuration("leader-election-retry-period")
|
||||
cfg.LeaderElection.ReleaseOnCancel = v.GetBool("leader-election-release-on-cancel")
|
||||
|
||||
// Annotations
|
||||
cfg.Annotations.Auto = v.GetString("auto-annotation")
|
||||
cfg.Annotations.ConfigmapAuto = v.GetString("configmap-auto-annotation")
|
||||
cfg.Annotations.SecretAuto = v.GetString("secret-auto-annotation")
|
||||
cfg.Annotations.ConfigmapReload = v.GetString("configmap-annotation")
|
||||
cfg.Annotations.SecretReload = v.GetString("secret-annotation")
|
||||
cfg.Annotations.Search = v.GetString("auto-search-annotation")
|
||||
cfg.Annotations.Match = v.GetString("search-match-annotation")
|
||||
cfg.Annotations.PausePeriod = v.GetString("pause-deployment-annotation")
|
||||
cfg.Annotations.PausedAt = v.GetString("pause-deployment-time-annotation")
|
||||
|
||||
// Alerting
|
||||
cfg.Alerting.Enabled = v.GetBool("alert-on-reload")
|
||||
cfg.Alerting.WebhookURL = v.GetString("alert-webhook-url")
|
||||
cfg.Alerting.Sink = strings.ToLower(v.GetString("alert-sink"))
|
||||
cfg.Alerting.Proxy = v.GetString("alert-proxy")
|
||||
cfg.Alerting.Additional = v.GetString("alert-additional-info")
|
||||
cfg.Alerting.Structured = v.GetBool("alert-structured")
|
||||
|
||||
// Special case: if webhook URL is set, auto-enable alerting
|
||||
if cfg.Alerting.WebhookURL != "" {
|
||||
cfg.Alerting.Enabled = true
|
||||
}
|
||||
|
||||
// Parse comma-separated lists
|
||||
cfg.IgnoredResources = splitAndTrim(fv.ignoredResources)
|
||||
cfg.IgnoredWorkloads = splitAndTrim(fv.ignoredWorkloads)
|
||||
cfg.IgnoredNamespaces = splitAndTrim(fv.ignoredNamespaces)
|
||||
cfg.IgnoredResources = splitAndTrim(v.GetString("resources-to-ignore"))
|
||||
cfg.IgnoredWorkloads = splitAndTrim(v.GetString("ignored-workload-types"))
|
||||
cfg.IgnoredNamespaces = splitAndTrim(v.GetString("namespaces-to-ignore"))
|
||||
|
||||
// Store raw selector strings
|
||||
cfg.NamespaceSelectorStrings = splitAndTrim(fv.namespaceSelectors)
|
||||
cfg.ResourceSelectorStrings = splitAndTrim(fv.resourceSelectors)
|
||||
cfg.NamespaceSelectorStrings = splitAndTrim(v.GetString("namespace-selector"))
|
||||
cfg.ResourceSelectorStrings = splitAndTrim(v.GetString("resource-label-selector"))
|
||||
|
||||
// Parse selectors into labels.Selector
|
||||
var err error
|
||||
@@ -223,9 +219,25 @@ func ApplyFlags(cfg *Config) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// Ensure duration defaults are preserved if not set
|
||||
if cfg.LeaderElection.LeaseDuration == 0 {
|
||||
cfg.LeaderElection.LeaseDuration = 15 * time.Second
|
||||
}
|
||||
if cfg.LeaderElection.RenewDeadline == 0 {
|
||||
cfg.LeaderElection.RenewDeadline = 10 * time.Second
|
||||
}
|
||||
if cfg.LeaderElection.RetryPeriod == 0 {
|
||||
cfg.LeaderElection.RetryPeriod = 2 * time.Second
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetViper returns the viper instance for testing or advanced configuration.
|
||||
func GetViper() *viper.Viper {
|
||||
return v
|
||||
}
|
||||
|
||||
// parseBoolString parses a string as a boolean, defaulting to false.
|
||||
func parseBoolString(s string) bool {
|
||||
s = strings.ToLower(strings.TrimSpace(s))
|
||||
|
||||
@@ -1,12 +1,23 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/spf13/pflag"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
// resetViper resets the viper instance for testing.
|
||||
func resetViper() {
|
||||
v = viper.New()
|
||||
v.SetEnvKeyReplacer(strings.NewReplacer("-", "_"))
|
||||
v.AutomaticEnv()
|
||||
}
|
||||
|
||||
func TestBindFlags(t *testing.T) {
|
||||
resetViper()
|
||||
cfg := NewDefault()
|
||||
fs := pflag.NewFlagSet("test", pflag.ContinueOnError)
|
||||
|
||||
@@ -48,6 +59,12 @@ func TestBindFlags(t *testing.T) {
|
||||
"pause-deployment-annotation",
|
||||
"pause-deployment-time-annotation",
|
||||
"watch-namespace",
|
||||
"alert-on-reload",
|
||||
"alert-webhook-url",
|
||||
"alert-sink",
|
||||
"alert-proxy",
|
||||
"alert-additional-info",
|
||||
"alert-structured",
|
||||
}
|
||||
|
||||
for _, flagName := range expectedFlags {
|
||||
@@ -58,6 +75,7 @@ func TestBindFlags(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestBindFlags_DefaultValues(t *testing.T) {
|
||||
resetViper()
|
||||
cfg := NewDefault()
|
||||
fs := pflag.NewFlagSet("test", pflag.ContinueOnError)
|
||||
|
||||
@@ -67,6 +85,10 @@ func TestBindFlags_DefaultValues(t *testing.T) {
|
||||
t.Fatalf("Parse() error = %v", err)
|
||||
}
|
||||
|
||||
if err := ApplyFlags(cfg); err != nil {
|
||||
t.Fatalf("ApplyFlags() error = %v", err)
|
||||
}
|
||||
|
||||
if cfg.ReloadStrategy != ReloadStrategyEnvVars {
|
||||
t.Errorf("ReloadStrategy = %v, want %v", cfg.ReloadStrategy, ReloadStrategyEnvVars)
|
||||
}
|
||||
@@ -77,6 +99,7 @@ func TestBindFlags_DefaultValues(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestBindFlags_CustomValues(t *testing.T) {
|
||||
resetViper()
|
||||
cfg := NewDefault()
|
||||
fs := pflag.NewFlagSet("test", pflag.ContinueOnError)
|
||||
|
||||
@@ -96,6 +119,10 @@ func TestBindFlags_CustomValues(t *testing.T) {
|
||||
t.Fatalf("Parse() error = %v", err)
|
||||
}
|
||||
|
||||
if err := ApplyFlags(cfg); err != nil {
|
||||
t.Fatalf("ApplyFlags() error = %v", err)
|
||||
}
|
||||
|
||||
if !cfg.AutoReloadAll {
|
||||
t.Error("AutoReloadAll should be true")
|
||||
}
|
||||
@@ -143,35 +170,31 @@ func TestApplyFlags_BooleanStrings(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(
|
||||
tt.name, func(t *testing.T) {
|
||||
fv = flagValues{}
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
resetViper()
|
||||
cfg := NewDefault()
|
||||
fs := pflag.NewFlagSet("test", pflag.ContinueOnError)
|
||||
BindFlags(fs, cfg)
|
||||
|
||||
cfg := NewDefault()
|
||||
fs := pflag.NewFlagSet("test", pflag.ContinueOnError)
|
||||
BindFlags(fs, cfg)
|
||||
if err := fs.Parse(tt.args); err != nil {
|
||||
t.Fatalf("Parse() error = %v", err)
|
||||
}
|
||||
|
||||
if err := fs.Parse(tt.args); err != nil {
|
||||
t.Fatalf("Parse() error = %v", err)
|
||||
}
|
||||
err := ApplyFlags(cfg)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("ApplyFlags() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
|
||||
err := ApplyFlags(cfg)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("ApplyFlags() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
|
||||
if cfg.ArgoRolloutsEnabled != tt.want {
|
||||
t.Errorf("ArgoRolloutsEnabled = %v, want %v", cfg.ArgoRolloutsEnabled, tt.want)
|
||||
}
|
||||
},
|
||||
)
|
||||
if cfg.ArgoRolloutsEnabled != tt.want {
|
||||
t.Errorf("ArgoRolloutsEnabled = %v, want %v", cfg.ArgoRolloutsEnabled, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplyFlags_CommaSeparatedLists(t *testing.T) {
|
||||
fv = flagValues{}
|
||||
|
||||
resetViper()
|
||||
cfg := NewDefault()
|
||||
fs := pflag.NewFlagSet("test", pflag.ContinueOnError)
|
||||
BindFlags(fs, cfg)
|
||||
@@ -201,15 +224,13 @@ func TestApplyFlags_CommaSeparatedLists(t *testing.T) {
|
||||
t.Errorf("IgnoredWorkloads length = %d, want 2", len(cfg.IgnoredWorkloads))
|
||||
}
|
||||
|
||||
// Check ignored namespaces
|
||||
if len(cfg.IgnoredNamespaces) != 2 {
|
||||
t.Errorf("IgnoredNamespaces length = %d, want 2", len(cfg.IgnoredNamespaces))
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplyFlags_Selectors(t *testing.T) {
|
||||
fv = flagValues{}
|
||||
|
||||
resetViper()
|
||||
cfg := NewDefault()
|
||||
fs := pflag.NewFlagSet("test", pflag.ContinueOnError)
|
||||
BindFlags(fs, cfg)
|
||||
@@ -241,8 +262,7 @@ func TestApplyFlags_Selectors(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestApplyFlags_InvalidSelector(t *testing.T) {
|
||||
fv = flagValues{}
|
||||
|
||||
resetViper()
|
||||
cfg := NewDefault()
|
||||
fs := pflag.NewFlagSet("test", pflag.ContinueOnError)
|
||||
BindFlags(fs, cfg)
|
||||
@@ -261,6 +281,100 @@ func TestApplyFlags_InvalidSelector(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplyFlags_AlertingEnvVars(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
envVars map[string]string
|
||||
wantURL string
|
||||
wantSink string
|
||||
wantEnable bool
|
||||
}{
|
||||
{
|
||||
name: "ALERT_WEBHOOK_URL enables alerting",
|
||||
envVars: map[string]string{
|
||||
"ALERT_WEBHOOK_URL": "https://hooks.example.com",
|
||||
},
|
||||
wantURL: "https://hooks.example.com",
|
||||
wantEnable: true,
|
||||
},
|
||||
{
|
||||
name: "all alert env vars",
|
||||
envVars: map[string]string{
|
||||
"ALERT_WEBHOOK_URL": "https://hooks.example.com",
|
||||
"ALERT_SINK": "slack",
|
||||
"ALERT_WEBHOOK_PROXY": "http://proxy:8080",
|
||||
},
|
||||
wantURL: "https://hooks.example.com",
|
||||
wantSink: "slack",
|
||||
wantEnable: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// Clear env and reset viper
|
||||
for k := range tt.envVars {
|
||||
os.Unsetenv(k)
|
||||
}
|
||||
resetViper()
|
||||
|
||||
// Set env vars
|
||||
for k, val := range tt.envVars {
|
||||
os.Setenv(k, val)
|
||||
defer os.Unsetenv(k)
|
||||
}
|
||||
|
||||
cfg := NewDefault()
|
||||
fs := pflag.NewFlagSet("test", pflag.ContinueOnError)
|
||||
BindFlags(fs, cfg)
|
||||
|
||||
if err := fs.Parse([]string{}); err != nil {
|
||||
t.Fatalf("Parse() error = %v", err)
|
||||
}
|
||||
|
||||
if err := ApplyFlags(cfg); err != nil {
|
||||
t.Fatalf("ApplyFlags() error = %v", err)
|
||||
}
|
||||
|
||||
if cfg.Alerting.WebhookURL != tt.wantURL {
|
||||
t.Errorf("Alerting.WebhookURL = %q, want %q", cfg.Alerting.WebhookURL, tt.wantURL)
|
||||
}
|
||||
|
||||
if tt.wantSink != "" && cfg.Alerting.Sink != tt.wantSink {
|
||||
t.Errorf("Alerting.Sink = %q, want %q", cfg.Alerting.Sink, tt.wantSink)
|
||||
}
|
||||
|
||||
if cfg.Alerting.Enabled != tt.wantEnable {
|
||||
t.Errorf("Alerting.Enabled = %v, want %v", cfg.Alerting.Enabled, tt.wantEnable)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplyFlags_LegacyProxyEnvVar(t *testing.T) {
|
||||
resetViper()
|
||||
|
||||
// Set legacy env var
|
||||
os.Setenv("ALERT_WEBHOOK_PROXY", "http://legacy-proxy:8080")
|
||||
defer os.Unsetenv("ALERT_WEBHOOK_PROXY")
|
||||
|
||||
cfg := NewDefault()
|
||||
fs := pflag.NewFlagSet("test", pflag.ContinueOnError)
|
||||
BindFlags(fs, cfg)
|
||||
|
||||
if err := fs.Parse([]string{}); err != nil {
|
||||
t.Fatalf("Parse() error = %v", err)
|
||||
}
|
||||
|
||||
if err := ApplyFlags(cfg); err != nil {
|
||||
t.Fatalf("ApplyFlags() error = %v", err)
|
||||
}
|
||||
|
||||
if cfg.Alerting.Proxy != "http://legacy-proxy:8080" {
|
||||
t.Errorf("Alerting.Proxy = %q, want %q", cfg.Alerting.Proxy, "http://legacy-proxy:8080")
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseBoolString(t *testing.T) {
|
||||
tests := []struct {
|
||||
input string
|
||||
@@ -282,14 +396,12 @@ func TestParseBoolString(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(
|
||||
tt.input, func(t *testing.T) {
|
||||
got := parseBoolString(tt.input)
|
||||
if got != tt.want {
|
||||
t.Errorf("parseBoolString(%q) = %v, want %v", tt.input, got, tt.want)
|
||||
}
|
||||
},
|
||||
)
|
||||
t.Run(tt.input, func(t *testing.T) {
|
||||
got := parseBoolString(tt.input)
|
||||
if got != tt.want {
|
||||
t.Errorf("parseBoolString(%q) = %v, want %v", tt.input, got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -308,19 +420,17 @@ func TestSplitAndTrim(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(
|
||||
tt.name, func(t *testing.T) {
|
||||
got := splitAndTrim(tt.input)
|
||||
if len(got) != len(tt.want) {
|
||||
t.Errorf("splitAndTrim(%q) length = %d, want %d", tt.input, len(got), len(tt.want))
|
||||
return
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := splitAndTrim(tt.input)
|
||||
if len(got) != len(tt.want) {
|
||||
t.Errorf("splitAndTrim(%q) length = %d, want %d", tt.input, len(got), len(tt.want))
|
||||
return
|
||||
}
|
||||
for i := range got {
|
||||
if got[i] != tt.want[i] {
|
||||
t.Errorf("splitAndTrim(%q)[%d] = %q, want %q", tt.input, i, got[i], tt.want[i])
|
||||
}
|
||||
for i := range got {
|
||||
if got[i] != tt.want[i] {
|
||||
t.Errorf("splitAndTrim(%q)[%d] = %q, want %q", tt.input, i, got[i], tt.want[i])
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user