package env import ( "fmt" "io" "os" "regexp" "strings" "github.com/kelseyhightower/envconfig" ) const ( defaultHistoryMax = 10 ) var ( justNumbers = regexp.MustCompile(`^\d+$`) deprecatedVars = []string{"PURGE", "RECREATE_PODS", "TILLER_NS", "UPGRADE", "CANARY_IMAGE", "CLIENT_ONLY", "STABLE_REPO_URL"} ) // The Config struct captures the `settings` and `environment` blocks in the application's drone // config. Configuration in drone's `settings` block arrives as uppercase env vars matching the // config key, prefixed with `PLUGIN_`. Config from the `environment` block is uppercased, but does // not have the `PLUGIN_` prefix. type Config struct { // Configuration for drone-helm itself Command string `envconfig:"mode"` // Helm command to run DroneEvent string `envconfig:"drone_build_event"` // Drone event that invoked this plugin. UpdateDependencies bool `split_words:"true"` // [Deprecated] Call `helm dependency update` before the main command (deprecated, use dependencies_action: update instead) DependenciesAction string `split_words:"true"` // Call `helm dependency build` or `helm dependency update` before the main command AddRepos []string `split_words:"true"` // Call `helm repo add` before the main command RepoCertificate string `envconfig:"repo_certificate"` // The Helm chart repository's self-signed certificate (must be base64-encoded) RepoCACertificate string `envconfig:"repo_ca_certificate"` // The Helm chart repository CA's self-signed certificate (must be base64-encoded) Debug bool `` // Generate debug output and pass --debug to all helm commands Values string `` // Argument to pass to --set in applicable helm commands StringValues string `split_words:"true"` // Argument to pass to --set-string in applicable helm commands ValuesFiles []string `split_words:"true"` // Arguments to pass to --values in applicable helm commands Namespace string `` // Kubernetes namespace for all helm commands CreateNamespace bool `split_words:"true"` // Pass --create-namespace to `helm upgrade` KubeToken string `split_words:"true"` // Kubernetes authentication token to put in .kube/config SkipKubeconfig bool `envconfig:"skip_kubeconfig"` // Skip kubeconfig creation SkipTLSVerify bool `envconfig:"skip_tls_verify"` // Put insecure-skip-tls-verify in .kube/config Certificate string `envconfig:"kube_certificate"` // The Kubernetes cluster CA's self-signed certificate (must be base64-encoded) APIServer string `envconfig:"kube_api_server"` // The Kubernetes cluster's API endpoint ServiceAccount string `envconfig:"kube_service_account"` // Account to use for connecting to the Kubernetes cluster ChartVersion string `split_words:"true"` // Specific chart version to use in `helm upgrade` DryRun bool `split_words:"true"` // Pass --dry-run to applicable helm commands Wait bool `envconfig:"wait_for_upgrade"` // Pass --wait to applicable helm commands ReuseValues bool `split_words:"true"` // Pass --reuse-values to `helm upgrade` KeepHistory bool `split_words:"true"` // Pass --keep-history to `helm uninstall` HistoryMax int `split_words:"true"` // Pass --history-max option Timeout string `` // Argument to pass to --timeout in applicable helm commands Chart string `` // Chart argument to use in applicable helm commands Release string `` // Release argument to use in applicable helm commands Force bool `envconfig:"force_upgrade"` // Pass --force to applicable helm commands AtomicUpgrade bool `split_words:"true"` // Pass --atomic to `helm upgrade` CleanupOnFail bool `envconfig:"cleanup_failed_upgrade"` // Pass --cleanup-on-fail to `helm upgrade` LintStrictly bool `split_words:"true"` // Pass --strict to `helm lint` SkipCrds bool `split_words:"true"` // Pass --skip-crds to `helm upgrade` Stdout io.Writer `ignored:"true"` Stderr io.Writer `ignored:"true"` } // NewConfig creates a Config and reads environment variables into it, accounting for several possible formats. func NewConfig(stdout, stderr io.Writer) (*Config, error) { var aliases settingAliases if err := envconfig.Process("plugin", &aliases); err != nil { return nil, err } if err := envconfig.Process("", &aliases); err != nil { return nil, err } cfg := Config{ Command: aliases.Command, AddRepos: aliases.AddRepos, APIServer: aliases.APIServer, ServiceAccount: aliases.ServiceAccount, Wait: aliases.Wait, Force: aliases.Force, KubeToken: aliases.KubeToken, Certificate: aliases.Certificate, // set to same default as helm CLI HistoryMax: defaultHistoryMax, Stdout: stdout, Stderr: stderr, } if err := envconfig.Process("plugin", &cfg); err != nil { return nil, err } if err := envconfig.Process("", &cfg); err != nil { return nil, err } if cfg.SkipKubeconfig { if cfg.KubeToken != "" || cfg.Certificate != "" || cfg.APIServer != "" || cfg.ServiceAccount != "" || cfg.SkipTLSVerify { fmt.Fprintf(cfg.Stderr, "Warning: skip_kubeconfig is set. The following kubeconfig-related settings will be ignored: kube_config, kube_certificate, kube_api_server, kube_service_account, skip_tls_verify.") } } if justNumbers.MatchString(cfg.Timeout) { cfg.Timeout = fmt.Sprintf("%ss", cfg.Timeout) } cfg.loadValuesSecrets() if cfg.Debug && cfg.Stderr != nil { cfg.logDebug() } cfg.deprecationWarn() return &cfg, nil } func (cfg *Config) loadValuesSecrets() { findVar := regexp.MustCompile(`\$\{?(\w+)\}?`) replacer := func(varName string) string { sigils := regexp.MustCompile(`[${}]`) varName = sigils.ReplaceAllString(varName, "") if value, ok := os.LookupEnv(varName); ok { return value } if cfg.Debug { fmt.Fprintf(cfg.Stderr, "$%s not present in environment, replaced with \"\"\n", varName) } return "" } cfg.Values = findVar.ReplaceAllStringFunc(cfg.Values, replacer) cfg.StringValues = findVar.ReplaceAllStringFunc(cfg.StringValues, replacer) for i := 0; i < len(cfg.AddRepos); i++ { cfg.AddRepos[i] = findVar.ReplaceAllStringFunc(cfg.AddRepos[i], replacer) } } func (cfg Config) logDebug() { if cfg.KubeToken != "" { cfg.KubeToken = "(redacted)" } fmt.Fprintf(cfg.Stderr, "Generated config: %+v\n", cfg) } func (cfg *Config) deprecationWarn() { for _, varname := range deprecatedVars { _, barePresent := os.LookupEnv(varname) _, prefixedPresent := os.LookupEnv("PLUGIN_" + varname) if barePresent || prefixedPresent { fmt.Fprintf(cfg.Stderr, "Warning: ignoring deprecated '%s' setting\n", strings.ToLower(varname)) } } } // settingAliases provides alternate environment variable names for certain settings, either because // they were renamed during drone-helm3's lifetime or for backward-compatibility with the original // drone-helm. Most config options don't need to be included here; adding them to the main Config // struct is sufficient. type settingAliases struct { Command string `envconfig:"helm_command"` AddRepos []string `envconfig:"helm_repos"` APIServer string `envconfig:"api_server"` ServiceAccount string `split_words:"true"` Wait bool `` Force bool `` KubeToken string `envconfig:"kubernetes_token"` Certificate string `envconfig:"kubernetes_certificate"` }