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
This commit is contained in:
Karl Fischer 2018-01-02 22:48:45 +01:00 committed by rob boll
parent 12b8b7237b
commit 5da5cd1c39
2 changed files with 74 additions and 16 deletions

View File

@ -23,23 +23,25 @@ repositories:
charts: charts:
# Published chart example # Published chart example
- name: vault # helm deployment name - name: vault # helm deployment name
namespace: vault # target namespace namespace: vault # target namespace
chart: roboll/vault-secret-manager # chart reference (repository) chart: roboll/vault-secret-manager # chart reference (repository)
values: [ vault.yaml ] # value files (--values) values: [ vault.yaml ] # value files (--values)
set: # values (--set) set: # values (--set)
- name: address - name: address
value: https://vault.example.com 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 - 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 # Local chart example
- name: grafana # helm deployment name - name: grafana # helm deployment name
namespace: another # target namespace namespace: another # target namespace
chart: ../my-charts/grafana # chart reference (relative path to manifest) chart: ../my-charts/grafana # chart reference (relative path to manifest)
values: 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.
``` ```

View File

@ -8,12 +8,14 @@ import (
"path/filepath" "path/filepath"
"strings" "strings"
"sync" "sync"
"text/template"
"github.com/roboll/helmfile/helmexec" "github.com/roboll/helmfile/helmexec"
yaml "gopkg.in/yaml.v1" yaml "gopkg.in/yaml.v1"
"path" "path"
"regexp" "regexp"
"bytes"
) )
type HelmState struct { type HelmState struct {
@ -28,14 +30,16 @@ type RepositorySpec struct {
} }
type ChartSpec struct { type ChartSpec struct {
Chart string `yaml:"chart"` Chart string `yaml:"chart"`
Version string `yaml:"version"` Version string `yaml:"version"`
Verify bool `yaml:"verify"` Verify bool `yaml:"verify"`
Name string `yaml:"name"` Name string `yaml:"name"`
Namespace string `yaml:"namespace"` Namespace string `yaml:"namespace"`
Values []string `yaml:"values"` Values []string `yaml:"values"`
SetValues []SetValue `yaml:"set"` 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"` EnvValues []SetValue `yaml:"env"`
} }
@ -59,6 +63,41 @@ func ReadFromFile(file string) (*HelmState, error) {
return &state, nil 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 { func (state *HelmState) SyncRepos(helm helmexec.Interface) []error {
var wg sync.WaitGroup var wg sync.WaitGroup
errs := []error{} errs := []error{}
@ -203,15 +242,28 @@ func flagsForChart(basePath string, chart *ChartSpec) ([]string, error) {
} }
for _, value := range chart.Values { for _, value := range chart.Values {
valfile := filepath.Join(basePath, value) 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 { if len(chart.SetValues) > 0 {
val := []string{} val := []string{}
for _, set := range chart.SetValues { 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, ",")) 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 { if len(chart.EnvValues) > 0 {
val := []string{} val := []string{}
envValErrs := []string{} envValErrs := []string{}
@ -231,5 +283,9 @@ func flagsForChart(basePath string, chart *ChartSpec) ([]string, error) {
} }
flags = append(flags, "--set", strings.Join(val, ",")) flags = append(flags, "--set", strings.Join(val, ","))
} }
/**************
* END 'env' section for backwards compatibility
**************/
return flags, nil return flags, nil
} }