package config import ( "fmt" "strings" "k8s.io/apimachinery/pkg/labels" ) // ValidationError represents a configuration validation error. type ValidationError struct { Field string Message string } func (e ValidationError) Error() string { return fmt.Sprintf("config.%s: %s", e.Field, e.Message) } // ValidationErrors is a collection of validation errors. type ValidationErrors []ValidationError func (e ValidationErrors) Error() string { if len(e) == 0 { return "" } if len(e) == 1 { return e[0].Error() } var b strings.Builder b.WriteString("multiple configuration errors:\n") for _, err := range e { b.WriteString(" - ") b.WriteString(err.Error()) b.WriteString("\n") } return b.String() } // Validate checks the configuration for errors and normalizes values. func (c *Config) Validate() error { var errs ValidationErrors // Validate ReloadStrategy switch c.ReloadStrategy { case ReloadStrategyEnvVars, ReloadStrategyAnnotations: // valid case "": c.ReloadStrategy = ReloadStrategyEnvVars default: errs = append(errs, ValidationError{ Field: "ReloadStrategy", Message: fmt.Sprintf("invalid value %q, must be %q or %q", c.ReloadStrategy, ReloadStrategyEnvVars, ReloadStrategyAnnotations), }) } // Validate ArgoRolloutStrategy switch c.ArgoRolloutStrategy { case ArgoRolloutStrategyRestart, ArgoRolloutStrategyRollout: // valid case "": c.ArgoRolloutStrategy = ArgoRolloutStrategyRollout default: errs = append(errs, ValidationError{ Field: "ArgoRolloutStrategy", Message: fmt.Sprintf("invalid value %q, must be %q or %q", c.ArgoRolloutStrategy, ArgoRolloutStrategyRestart, ArgoRolloutStrategyRollout), }) } // Validate LogLevel switch strings.ToLower(c.LogLevel) { case "trace", "debug", "info", "warn", "warning", "error", "fatal", "panic", "": // valid default: errs = append(errs, ValidationError{ Field: "LogLevel", Message: fmt.Sprintf("invalid log level %q", c.LogLevel), }) } // Validate LogFormat switch strings.ToLower(c.LogFormat) { case "json", "": // valid default: errs = append(errs, ValidationError{ Field: "LogFormat", Message: fmt.Sprintf("invalid log format %q, must be \"json\" or empty", c.LogFormat), }) } // Normalize IgnoredResources to lowercase for consistent comparison c.IgnoredResources = normalizeToLower(c.IgnoredResources) // Normalize IgnoredWorkloads to lowercase c.IgnoredWorkloads = normalizeToLower(c.IgnoredWorkloads) if len(errs) > 0 { return errs } return nil } // normalizeToLower converts all strings in the slice to lowercase and removes empty strings. func normalizeToLower(items []string) []string { if len(items) == 0 { return items } result := make([]string, 0, len(items)) for _, item := range items { item = strings.TrimSpace(strings.ToLower(item)) if item != "" { result = append(result, item) } } return result } // ParseSelectors parses a slice of selector strings into label selectors. func ParseSelectors(selectorStrings []string) ([]labels.Selector, error) { if len(selectorStrings) == 0 { return nil, nil } selectors := make([]labels.Selector, 0, len(selectorStrings)) for _, s := range selectorStrings { s = strings.TrimSpace(s) if s == "" { continue } selector, err := labels.Parse(s) if err != nil { return nil, fmt.Errorf("invalid selector %q: %w", s, err) } selectors = append(selectors, selector) } return selectors, nil } // MustParseSelectors parses selectors and panics on error. // Use only when selectors are known to be valid (e.g., from validated config). func MustParseSelectors(selectorStrings []string) []labels.Selector { selectors, err := ParseSelectors(selectorStrings) if err != nil { panic(err) } return selectors }