diff --git a/README.md b/README.md index 12a39dbc..835bd12c 100644 --- a/README.md +++ b/README.md @@ -435,6 +435,34 @@ domain: {{ .Environment.Values.domain | default "dev.example.com" }} `helmfile sync` installs `myapp` with the value `domain=dev.example.com`, whereas `helmfile --environment production sync` installs the app with the value `domain=production.example.com`. +## Environment Secrets + +Environment Secrets are encrypted versions of `Environment Values`. +You can list any number of `secrets.yaml` files created using `helm secrets` or `sops`, so that +helmfile could automatically decrypt and merge the secrets into the environment values. + +Suppose you have environment secrets defined in `hemlfile.yaml`: + +```yaml +environments: + production: + secrets: + - environments/produdction/secrets.yaml + +releases: +- name: myapp + chart: mychart + values: + - values.yaml.gotmpl +``` + +an environment secret `foo.bar` can be referenced by the below template expression in your `values.yaml.gotmpl`: + +```yaml +{{ .Values.foo.bar } +``` + + ## Separating helmfile.yaml into multiple independent files Once your `helmfile.yaml` got to contain too many releases, diff --git a/state/create.go b/state/create.go index 64444d89..42347514 100644 --- a/state/create.go +++ b/state/create.go @@ -4,10 +4,12 @@ import ( "fmt" "github.com/imdario/mergo" "github.com/roboll/helmfile/environment" + "github.com/roboll/helmfile/helmexec" "github.com/roboll/helmfile/valuesfile" "go.uber.org/zap" "gopkg.in/yaml.v2" "io/ioutil" + "os" "path/filepath" ) @@ -63,6 +65,32 @@ func (state *HelmState) loadEnv(name string, readFile func(string) ([]byte, erro return nil, fmt.Errorf("failed to load \"%s\": %v", envvalFile, err) } } + + if len(envSpec.Secrets) > 0 { + helm := helmexec.New(state.logger, "") + for _, secFile := range envSpec.Secrets { + path := filepath.Join(state.basePath, secFile) + if _, err := os.Stat(path); os.IsNotExist(err) { + return nil, err + } + + decFile, err := helm.DecryptSecret(path) + if err != nil { + return nil, err + } + bytes, err := readFile(decFile) + if err != nil { + return nil, fmt.Errorf("failed to load environment secrets file \"%s\": %v", secFile, err) + } + m := map[string]interface{}{} + if err := yaml.Unmarshal(bytes, &m); err != nil { + return nil, fmt.Errorf("failed to load environment secrets file \"%s\": %v", secFile, err) + } + if err := mergo.Merge(&envVals, &m, mergo.WithOverride); err != nil { + return nil, fmt.Errorf("failed to load \"%s\": %v", secFile, err) + } + } + } } else if name != DefaultEnv { return nil, fmt.Errorf("environment \"%s\" is not defined in \"%s\"", name, state.FilePath) } diff --git a/state/environment.go b/state/environment.go index 4349a77d..6833c9f9 100644 --- a/state/environment.go +++ b/state/environment.go @@ -1,5 +1,6 @@ package state type EnvironmentSpec struct { - Values []string `yaml:"values"` + Values []string `yaml:"values"` + Secrets []string `yaml:"secrets"` }