From 5da5cd1c391337578f61d404a7e5f18b9348ed28 Mon Sep 17 00:00:00 2001 From: Karl Fischer Date: Tue, 2 Jan 2018 22:48:45 +0100 Subject: [PATCH] Allow env var interpolation in `set` and `values` (#20) * Allow env var interpolation in `set` and `values` * Use go templating instead of regex * Re-add 'env' section for backwards compatibility * Fix typo --- README.md | 24 +++++++++--------- state/state.go | 66 ++++++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 74 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 4f8081a4..c2cb9303 100644 --- a/README.md +++ b/README.md @@ -23,23 +23,25 @@ repositories: charts: # Published chart example - - name: vault # helm deployment name - namespace: vault # target namespace - chart: roboll/vault-secret-manager # chart reference (repository) - values: [ vault.yaml ] # value files (--values) - set: # values (--set) + - name: vault # helm deployment name + namespace: vault # target namespace + chart: roboll/vault-secret-manager # chart reference (repository) + values: [ vault.yaml ] # value files (--values) + set: # values (--set) - name: address value: https://vault.example.com - env: # values (--set) but value will be pulled from environment variables. Will throw an error if the environment variable is not set. - name: db.password - value: DB_PASSWORD # $DB_PASSOWRD needs to be set in the calling environment ex: export DB_PASSWORD='password1' + value: {{ env "DB_PASSWORD" }} # value taken from environment variable. Will throw an error if the environment variable is not set. $DB_PASSWORD needs to be set in the calling environment ex: export DB_PASSWORD='password1' + - name: proxy.domain + value: "{{ env \"PLATFORM_ID\" }}.my-domain.com" # Interpolate environment variable with a fixed string # Local chart example - - name: grafana # helm deployment name - namespace: another # target namespace - chart: ../my-charts/grafana # chart reference (relative path to manifest) + - name: grafana # helm deployment name + namespace: another # target namespace + chart: ../my-charts/grafana # chart reference (relative path to manifest) values: - - ../../my-values/grafana/values.yaml # Values file (relative path to manifest) + - "../../my-values/grafana/values.yaml" # Values file (relative path to manifest) + - "./values/{{ env \"PLATFORM_ENV\" }}/config.yaml" # Values file taken from path with environment variable. $PLATFORM_ENV must be set in the calling environment. ``` diff --git a/state/state.go b/state/state.go index 34586eee..ffd2f03c 100644 --- a/state/state.go +++ b/state/state.go @@ -8,12 +8,14 @@ import ( "path/filepath" "strings" "sync" + "text/template" "github.com/roboll/helmfile/helmexec" yaml "gopkg.in/yaml.v1" "path" "regexp" + "bytes" ) type HelmState struct { @@ -28,14 +30,16 @@ type RepositorySpec struct { } type ChartSpec struct { - Chart string `yaml:"chart"` - Version string `yaml:"version"` - Verify bool `yaml:"verify"` + Chart string `yaml:"chart"` + Version string `yaml:"version"` + Verify bool `yaml:"verify"` Name string `yaml:"name"` Namespace string `yaml:"namespace"` Values []string `yaml:"values"` SetValues []SetValue `yaml:"set"` + + // The 'env' section is not really necessary any longer, as 'set' would now provide the same functionality EnvValues []SetValue `yaml:"env"` } @@ -59,6 +63,41 @@ func ReadFromFile(file string) (*HelmState, error) { return &state, nil } +var /* const */ + stringTemplateFuncMap = template.FuncMap{ + "env": getEnvVar, + } + +var /* const */ + stringTemplate = template.New("stringTemplate").Funcs(stringTemplateFuncMap) + +func getEnvVar(envVarName string) (string, error) { + envVarValue, isSet := os.LookupEnv(envVarName) + + if !isSet { + errMsg := fmt.Sprintf("Environment Variable '%s' is not set. Please make sure it is set and try again.", envVarName) + return "", errors.New(errMsg) + } + + return envVarValue, nil +} + +func renderTemplateString(s string) (string, error) { + var t, parseErr = stringTemplate.Parse(s) + if parseErr != nil { + return "", parseErr + } + + var tplString bytes.Buffer + var execErr = t.Execute(&tplString, nil) + + if execErr != nil { + return "", execErr + } + + return tplString.String(), nil +} + func (state *HelmState) SyncRepos(helm helmexec.Interface) []error { var wg sync.WaitGroup errs := []error{} @@ -203,15 +242,28 @@ func flagsForChart(basePath string, chart *ChartSpec) ([]string, error) { } for _, value := range chart.Values { valfile := filepath.Join(basePath, value) - flags = append(flags, "--values", valfile) + valfileRendered, err := renderTemplateString(valfile) + if err != nil { + return nil, err + } + flags = append(flags, "--values", valfileRendered) } if len(chart.SetValues) > 0 { val := []string{} for _, set := range chart.SetValues { - val = append(val, fmt.Sprintf("%s=%s", set.Name, set.Value)) + renderedValue, err := renderTemplateString(set.Value) + if err != nil { + return nil, err + } + val = append(val, fmt.Sprintf("%s=%s", set.Name, renderedValue)) } flags = append(flags, "--set", strings.Join(val, ",")) } + + /*********** + * START 'env' section for backwards compatibility + ***********/ + // The 'env' section is not really necessary any longer, as 'set' would now provide the same functionality if len(chart.EnvValues) > 0 { val := []string{} envValErrs := []string{} @@ -231,5 +283,9 @@ func flagsForChart(basePath string, chart *ChartSpec) ([]string, error) { } flags = append(flags, "--set", strings.Join(val, ",")) } + /************** + * END 'env' section for backwards compatibility + **************/ + return flags, nil }