feat: Use viper for config handling and flags

This commit is contained in:
TheiLLeniumStudios
2025-12-28 11:31:27 +01:00
parent 9a5fbf190d
commit 2f9633c3f1
4 changed files with 358 additions and 212 deletions
+176 -164
View File
@@ -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))
+158 -48
View File
@@ -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])
}
}
},
)
}
})
}
}