Skip to content
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ require (
replace github.com/go-openapi/validate => github.com/flant/go-openapi-validate v0.19.12-flant.0

require (
github.com/caarlos0/env/v11 v11.3.1
github.com/deckhouse/module-sdk v0.10.4
github.com/gojuno/minimock/v3 v3.4.7
github.com/itchyny/gojq v0.12.17
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ 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/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM=
github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ=
github.com/caarlos0/env/v11 v11.3.1 h1:cArPWC15hWmEt+gWk7YBi7lEXTXCvpaSdCiZE2X5mCA=
github.com/caarlos0/env/v11 v11.3.1/go.mod h1:qupehSf/Y0TUTsxKywqRt/vJjN5nz6vauiYEUUr8P4U=
github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM=
github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
Expand Down
253 changes: 253 additions & 0 deletions pkg/app/app_config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,253 @@
package app

import (
"fmt"
"time"

env "github.com/caarlos0/env/v11"

Check failure on line 8 in pkg/app/app_config.go

View workflow job for this annotation

GitHub Actions / Run linter

File is not properly formatted (gci)
"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 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_"`
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(),
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(&DebugKeepTmpFiles, cfg.DebugConfig.KeepTemporaryFiles == "true" || cfg.DebugConfig.KeepTemporaryFiles == "yes")
setIfNotEmpty(&DebugKubernetesAPI, cfg.DebugConfig.KubernetesAPI)
setIfNotEmpty(&DebugUnixSocket, cfg.DebugConfig.UnixSocket)

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(&ValidatingWebhookConfigurationName, cfg.ValidatingWebhookConfig.ConfigurationName)
setIfNotEmpty(&ValidatingWebhookServiceName, cfg.ValidatingWebhookConfig.ServiceName)
setIfNotEmpty(&ValidatingWebhookServerCert, cfg.ValidatingWebhookConfig.ServerCert)
setIfNotEmpty(&ValidatingWebhookServerKey, cfg.ValidatingWebhookConfig.ServerKey)
setIfNotEmpty(&ValidatingWebhookCA, cfg.ValidatingWebhookConfig.CA)
setSliceIfNotEmpty(&ValidatingWebhookClientCA, cfg.ValidatingWebhookConfig.ClientCA)
setIfNotEmpty(&ValidatingWebhookFailurePolicy, cfg.ValidatingWebhookConfig.FailurePolicy)
setIfNotEmpty(&ValidatingWebhookListenPort, cfg.ValidatingWebhookConfig.ListenPort)
setIfNotEmpty(&ValidatingWebhookListenAddress, cfg.ValidatingWebhookConfig.ListenAddress)

setIfNotEmpty(&ConversionWebhookServiceName, cfg.ConversionWebhookConfig.ServiceName)
setIfNotEmpty(&ConversionWebhookServerCert, cfg.ConversionWebhookConfig.ServerCert)
setIfNotEmpty(&ConversionWebhookServerKey, cfg.ConversionWebhookConfig.ServerKey)
setIfNotEmpty(&ConversionWebhookCA, cfg.ConversionWebhookConfig.CA)
setSliceIfNotEmpty(&ConversionWebhookClientCA, cfg.ConversionWebhookConfig.ClientCA)
setIfNotEmpty(&ConversionWebhookListenPort, cfg.ConversionWebhookConfig.ListenPort)
setIfNotEmpty(&ConversionWebhookListenAddress, cfg.ConversionWebhookConfig.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)
}
Loading