diff --git a/go.mod b/go.mod index fcd7cb4e..2d446f2b 100644 --- a/go.mod +++ b/go.mod @@ -33,7 +33,10 @@ require ( // Remove 'in body' from errors, fix for Go 1.16 (https://github.com/go-openapi/validate/pull/138). replace github.com/go-openapi/validate => github.com/flant/go-openapi-validate v0.19.12-flant.0 -require github.com/gojuno/minimock/v3 v3.4.1 +require ( + github.com/caarlos0/env/v11 v11.2.2 + github.com/gojuno/minimock/v3 v3.4.1 +) require ( github.com/DataDog/gostackparse v0.7.0 // indirect diff --git a/go.sum b/go.sum index a1efe696..70ea202d 100644 --- a/go.sum +++ b/go.sum @@ -18,6 +18,8 @@ github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535/go.mod h1:o github.com/aws/aws-sdk-go v1.34.28/go.mod h1:H7NKnBqNVzoTJpGfLrQkkD+ytBA93eiDYi/+8rV9s48= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/caarlos0/env/v11 v11.2.2 h1:95fApNrUyueipoZN/EhA8mMxiNxrBwDa+oAZrMWl3Kg= +github.com/caarlos0/env/v11 v11.2.2/go.mod h1:JBfcdeQiBoI3Zh1QRAWfe+tpiNTmDtcCj/hHHHMx0vc= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= diff --git a/pkg/app/app_config.go b/pkg/app/app_config.go new file mode 100644 index 00000000..2bfe0e7c --- /dev/null +++ b/pkg/app/app_config.go @@ -0,0 +1,266 @@ +package app + +import ( + "fmt" + "time" + + env "github.com/caarlos0/env/v11" + + "github.com/deckhouse/deckhouse/pkg/log" +) + +type appConfig struct { + HooksDir string `env:"HOOKS_DIR"` + TmpDir string `env:"TMP_DIR"` + ListenAddress string `env:"LISTEN_ADDRESS"` + ListenPort string `env:"LISTEN_PORT"` + PrometheusMetricsPrefix string `env:"PROMETHEUS_METRICS_PREFIX"` + // unused? + HooksMetricsListenPort string `env:"HOOK_METRICS_LISTEN_PORT"` + Namespace string `env:"NAMESPACE"` +} + +func newAppConfig() *appConfig { + return &appConfig{} +} + +type debugConfig struct { + HTTPServerAddress string `env:"HTTP_SERVER_ADDR"` + KeepTemporaryFiles string `env:"KEEP_TMP_FILES"` + KubernetesAPI bool `env:"KUBERNETES_API"` + UnixSocket string `env:"UNIX_SOCKET"` +} + +func newDebugConfig() *debugConfig { + return &debugConfig{} +} + +type jqConfig struct { + LibraryPath string `env:"LIBRARY_PATH"` +} + +func newJQConfig() *jqConfig { + return &jqConfig{} +} + +type kubeConfig struct { + // Settings for Kubernetes connection. + ContextName string `env:"CONTEXT"` + ConfigPath string `env:"CONFIG"` + ServerAddress string `env:"SERVER"` + // Rate limit settings for 'main' kube client + ClientQPS float32 `env:"CLIENT_QPS"` + ClientBurst int `env:"CLIENT_BURST"` +} + +func newKubeConfig() *kubeConfig { + return &kubeConfig{} +} + +type objectPatcherConfig struct { + // Settings for 'object_patcher' kube client + KubeClientQPS float32 `env:"KUBE_CLIENT_QPS"` + KubeClisntBurst int `env:"KUBE_CLIENT_BURST"` + KubeClientTimeout time.Duration `env:"KUBE_CLIENT_TIMEOUT"` +} + +func newObjectPatcherConfig() *objectPatcherConfig { + return &objectPatcherConfig{} +} + +type validatingWebhookConfig struct { + ConfigurationName string `env:"CONFIGURATION_NAME"` + ServiceName string `env:"SERVICE_NAME"` + ServerCert string `env:"SERVER_CERT"` + ServerKey string `env:"SERVER_KEY"` + CA string `env:"CA"` + // check separator? + ClientCA []string `env:"CLIENT_CA" envSeparator:","` + // enum "Fail" || "Ignore" + FailurePolicy string `env:"FAILURE_POLICY"` + ListenPort string `env:"LISTEN_PORT"` + ListenAddress string `env:"LISTEN_ADDRESS"` +} + +func newValidatingWebhookConfig() *validatingWebhookConfig { + return &validatingWebhookConfig{} +} + +type conversionWebhookConfig struct { + ServiceName string `env:"SERVICE_NAME"` + ServerCert string `env:"SERVER_CERT"` + ServerKey string `env:"SERVER_KEY"` + CA string `env:"CA"` + // check separator? + ClientCA []string `env:"CLIENT_CA" envSeparator:","` + ListenPort string `env:"LISTEN_PORT"` + ListenAddress string `env:"LISTEN_ADDRESS"` +} + +func newConversionWebhookConfig() *conversionWebhookConfig { + return &conversionWebhookConfig{} +} + +type logConfig struct { + Level string `env:"LEVEL"` + Type string `env:"TYPE"` + NoTime bool `env:"NO_TIME"` + ProxyHookJson bool `env:"PROXY_HOOK_JSON"` +} + +func newLogConfig() *logConfig { + return &logConfig{} +} + +type Config struct { + AppConfig *appConfig `envPrefix:"SHELL_OPERATOR_"` + JQConfig *jqConfig `envPrefix:"JQ_"` + KubeConfig *kubeConfig `envPrefix:"KUBE_"` + ObjectPatcherConfig *objectPatcherConfig `envPrefix:"OBJECT_PATCHER_"` + ValidatingWebhookConfig *validatingWebhookConfig `envPrefix:"VALIDATING_WEBHOOK_"` + ConversionWebhookConfig *conversionWebhookConfig `envPrefix:"CONVERSION_WEBHOOK_"` + + DebugConfig *debugConfig `envPrefix:"DEBUG_"` + + LogConfig *logConfig `envPrefix:"LOG_"` + LogLevel log.Level `env:"-"` + + ready bool +} + +func NewConfig() *Config { + return &Config{ + AppConfig: newAppConfig(), + JQConfig: newJQConfig(), + KubeConfig: newKubeConfig(), + ObjectPatcherConfig: newObjectPatcherConfig(), + ValidatingWebhookConfig: newValidatingWebhookConfig(), + ConversionWebhookConfig: newConversionWebhookConfig(), + DebugConfig: newDebugConfig(), + LogConfig: newLogConfig(), + } +} + +func (cfg *Config) Parse() error { + if cfg.IsReady() { + return nil + } + + opts := env.Options{ + Prefix: "", + } + + err := env.ParseWithOptions(cfg, opts) + if err != nil { + return fmt.Errorf("failed to parse config: %w", err) + } + + cfg.LogLevel = log.LogLevelFromStr(cfg.LogConfig.Level) + + return nil +} + +func (cfg *Config) SetupGlobalVars() { + if cfg.IsReady() { + return + } + + setIfNotEmpty(&HooksDir, cfg.AppConfig.HooksDir) + setIfNotEmpty(&TempDir, cfg.AppConfig.TmpDir) + setIfNotEmpty(&ListenAddress, cfg.AppConfig.ListenAddress) + setIfNotEmpty(&ListenPort, cfg.AppConfig.ListenPort) + setIfNotEmpty(&PrometheusMetricsPrefix, cfg.AppConfig.PrometheusMetricsPrefix) + setIfNotEmpty(&Namespace, cfg.AppConfig.Namespace) + + setIfNotEmpty(&DebugHttpServerAddr, cfg.DebugConfig.HTTPServerAddress) + setIfNotEmpty(&DebugKeepTmpFilesVar, cfg.DebugConfig.KeepTemporaryFiles) + setIfNotEmpty(&DebugKeepTmpFiles, cfg.DebugConfig.KeepTemporaryFiles == "true" || cfg.DebugConfig.KeepTemporaryFiles == "yes") + setIfNotEmpty(&DebugKubernetesAPI, cfg.DebugConfig.KubernetesAPI) + setIfNotEmpty(&DebugUnixSocket, cfg.DebugConfig.UnixSocket) + + setIfNotEmpty(&JqLibraryPath, cfg.JQConfig.LibraryPath) + + setIfNotEmpty(&KubeContext, cfg.KubeConfig.ContextName) + setIfNotEmpty(&KubeConfig, cfg.KubeConfig.ConfigPath) + setIfNotEmpty(&KubeServer, cfg.KubeConfig.ServerAddress) + setIfNotEmpty(&KubeClientQps, cfg.KubeConfig.ClientQPS) + setIfNotEmpty(&KubeClientBurst, cfg.KubeConfig.ClientBurst) + + setIfNotEmpty(&ObjectPatcherKubeClientQps, cfg.ObjectPatcherConfig.KubeClientQPS) + setIfNotEmpty(&ObjectPatcherKubeClientBurst, cfg.ObjectPatcherConfig.KubeClisntBurst) + setIfNotEmpty(&ObjectPatcherKubeClientTimeout, cfg.ObjectPatcherConfig.KubeClientTimeout) + + setIfNotEmpty(&LogLevel, cfg.LogConfig.Level) + setIfNotEmpty(&LogNoTime, cfg.LogConfig.NoTime) + setIfNotEmpty(&LogType, cfg.LogConfig.Type) + setIfNotEmpty(&LogProxyHookJSON, cfg.LogConfig.ProxyHookJson) + + setIfNotEmpty(&ValidatingWebhookSettings.ConfigurationName, cfg.ValidatingWebhookConfig.ConfigurationName) + setIfNotEmpty(&ValidatingWebhookSettings.ServiceName, cfg.ValidatingWebhookConfig.ServiceName) + setIfNotEmpty(&ValidatingWebhookSettings.ServerCertPath, cfg.ValidatingWebhookConfig.ServerCert) + setIfNotEmpty(&ValidatingWebhookSettings.ServerKeyPath, cfg.ValidatingWebhookConfig.ServerKey) + setIfNotEmpty(&ValidatingWebhookSettings.CAPath, cfg.ValidatingWebhookConfig.CA) + setSliceIfNotEmpty(&ValidatingWebhookSettings.ClientCAPaths, cfg.ValidatingWebhookConfig.ClientCA) + setIfNotEmpty(&ValidatingWebhookSettings.DefaultFailurePolicy, cfg.ValidatingWebhookConfig.FailurePolicy) + setIfNotEmpty(&ValidatingWebhookSettings.ListenPort, cfg.ValidatingWebhookConfig.ListenPort) + setIfNotEmpty(&ValidatingWebhookSettings.ListenAddr, cfg.ValidatingWebhookConfig.ListenAddress) + + setIfNotEmpty(&ConversionWebhookSettings.ServiceName, cfg.ValidatingWebhookConfig.ServiceName) + setIfNotEmpty(&ConversionWebhookSettings.ServerCertPath, cfg.ValidatingWebhookConfig.ServerCert) + setIfNotEmpty(&ConversionWebhookSettings.ServerKeyPath, cfg.ValidatingWebhookConfig.ServerKey) + setIfNotEmpty(&ConversionWebhookSettings.CAPath, cfg.ValidatingWebhookConfig.CA) + setSliceIfNotEmpty(&ConversionWebhookSettings.ClientCAPaths, cfg.ValidatingWebhookConfig.ClientCA) + setIfNotEmpty(&ConversionWebhookSettings.ListenPort, cfg.ValidatingWebhookConfig.ListenPort) + setIfNotEmpty(&ConversionWebhookSettings.ListenAddr, cfg.ValidatingWebhookConfig.ListenAddress) +} + +func (cfg *Config) IsReady() bool { + return cfg.ready +} + +func (cfg *Config) SetReady() { + cfg.ready = true +} + +var configInstance *Config + +func MustGetConfig() *Config { + cfg, err := GetConfig() + if err != nil { + panic(err) + } + + return cfg +} + +func GetConfig() (*Config, error) { + if configInstance != nil { + return configInstance, nil + } + + cfg := NewConfig() + err := cfg.Parse() + if err != nil { + return nil, err + } + + configInstance = cfg + + return configInstance, nil +} + +func setIfNotEmpty[T comparable](v *T, env T) { + if !isZero(env) { + *v = env + } +} + +func setSliceIfNotEmpty[T any](v *[]T, env []T) { + if len(env) != 0 { + *v = env + } +} + +func isZero[T comparable](v T) bool { + return v == *new(T) +} diff --git a/pkg/app/debug.go b/pkg/app/debug.go index 19860a08..434300a3 100644 --- a/pkg/app/debug.go +++ b/pkg/app/debug.go @@ -30,11 +30,7 @@ func DefineDebugFlags(kpApp *kingpin.Application, cmd *kingpin.CmdClause) { cmd.Flag("debug-keep-tmp-files", "set to yes to disable cleanup of temporary files"). Envar("DEBUG_KEEP_TMP_FILES"). Hidden(). - Default(DebugKeepTmpFilesVar).Action(func(_ *kingpin.ParseContext) error { - DebugKeepTmpFiles = DebugKeepTmpFilesVar == "yes" - - return nil - }). + Default(DebugKeepTmpFilesVar). StringVar(&DebugKeepTmpFilesVar) cmd.Flag("debug-kubernetes-api", "enable client-go debug messages").