Skip to content

Commit

Permalink
Add integration with Hashicorp Vault, AWS SSM, SecretsManager (#906)
Browse files Browse the repository at this point in the history
* feat: Add integration with Hashicorp Vault, AWS SSM, SecretsManager

Fields which are rendered: Release.Values, Release.SetValues.Value, Release.SetValues.Values

Example:
```
values:
- foo: ref+vault://mykv/foo?address=http://127.0.0.1:8200#/mykey
set:
- name: xyz
  values:
  - ref+vault://mykv/foo?address=http://127.0.0.1:8200#/mykey3
```

Resolves #881

* feat: Update integration with variantdev/vals

New ref+.\* secret formats are used:
https://github.com/variantdev/vals/tree/6565695a031f407a1066bdd31ae313db6a33ec0a#suported-backends

Resolves #881
  • Loading branch information
klebediev authored and mumoshu committed Oct 25, 2019
1 parent 849ed63 commit 4680010
Show file tree
Hide file tree
Showing 9 changed files with 306 additions and 45 deletions.
5 changes: 2 additions & 3 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,14 @@ require (
github.com/hashicorp/go-getter v1.3.0
github.com/huandu/xstrings v1.2.0 // indirect
github.com/imdario/mergo v0.3.6
github.com/mitchellh/copystructure v1.0.0 // indirect
github.com/pkg/errors v0.8.1 // indirect
github.com/r3labs/diff v0.0.0-20190801153147-a71de73c46ad
github.com/tatsushid/go-prettytable v0.0.0-20141013043238-ed2d14c29939
github.com/urfave/cli v0.0.0-20160620154522-6011f165dc28
github.com/variantdev/vals v0.0.0-20191025124021-e86de6f8cd7d
go.uber.org/atomic v1.3.2 // indirect
go.uber.org/multierr v1.1.0
go.uber.org/zap v1.8.0
gopkg.in/yaml.v2 v2.2.1
gopkg.in/yaml.v2 v2.2.2
gotest.tools v2.2.0+incompatible
)

Expand Down
182 changes: 182 additions & 0 deletions go.sum

Large diffs are not rendered by default.

16 changes: 16 additions & 0 deletions pkg/app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,19 @@ import (
"github.com/roboll/helmfile/pkg/helmexec"
"github.com/roboll/helmfile/pkg/remote"
"github.com/roboll/helmfile/pkg/state"
"github.com/variantdev/vals"

"go.uber.org/zap"

"path/filepath"
"sort"
)

const (
// cache size for improving performance of ref+.* secrets rendering
valsCacheSize = 512
)

type App struct {
KubeContext string
Logger *zap.SugaredLogger
Expand Down Expand Up @@ -49,6 +55,8 @@ type App struct {
remote *remote.Remote

helmExecer helmexec.Interface

valsRuntime vals.Evaluator
}

func New(conf ConfigProvider) *App {
Expand Down Expand Up @@ -78,6 +86,13 @@ func Init(app *App) *App {
app.fileExistsAt = fileExistsAt
app.fileExists = fileExists
app.directoryExistsAt = directoryExistsAt

var err error
app.valsRuntime, err = vals.New(valsCacheSize)
if err != nil {
panic(fmt.Sprintf("Failed to initialize vals runtime: %v", err))
}

return app
}

Expand Down Expand Up @@ -274,6 +289,7 @@ func (a *App) loadDesiredStateFromYaml(file string, opts ...LoadOpts) (*state.He
KubeContext: a.KubeContext,
glob: a.glob,
helm: a.helmExecer,
valsRuntime: a.valsRuntime,
}

var op LoadOpts
Expand Down
11 changes: 9 additions & 2 deletions pkg/app/app_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"github.com/roboll/helmfile/pkg/helmexec"
"github.com/roboll/helmfile/pkg/state"
"github.com/roboll/helmfile/pkg/testhelper"
"github.com/variantdev/vals"

"go.uber.org/zap"
"gotest.tools/env"
Expand Down Expand Up @@ -440,8 +441,8 @@ func TestVisitDesiredStatesWithReleasesFiltered_EmbeddedSelectors(t *testing.T)
helmfiles:
- path: helmfile.d/a*.yaml
selectors:
- name=prometheus
- name=zipkin
- name=prometheus
- name=zipkin
- helmfile.d/b*.yaml
- path: helmfile.d/c*.yaml
selectors: []
Expand Down Expand Up @@ -1944,6 +1945,11 @@ releases:
var buffer bytes.Buffer
logger := helmexec.NewLogger(&buffer, "debug")

valsRuntime, err := vals.New(32)
if err != nil {
t.Errorf("unexpected error creating vals runtime: %v", err)
}

app := appWithFs(&App{
glob: filepath.Glob,
abs: filepath.Abs,
Expand All @@ -1952,6 +1958,7 @@ releases:
Logger: logger,
helmExecer: helm,
Namespace: "testNamespace",
valsRuntime: valsRuntime,
}, files)
app.Template(configImpl{set: []string{"foo=a", "bar=b"}})

Expand Down
8 changes: 5 additions & 3 deletions pkg/app/desired_state_file_loader.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"github.com/roboll/helmfile/pkg/environment"
"github.com/roboll/helmfile/pkg/helmexec"
"github.com/roboll/helmfile/pkg/state"
"github.com/variantdev/vals"
"go.uber.org/zap"
)

Expand All @@ -26,8 +27,9 @@ type desiredStateLoader struct {
abs func(string) (string, error)
glob func(string) ([]string, error)

logger *zap.SugaredLogger
helm helmexec.Interface
logger *zap.SugaredLogger
helm helmexec.Interface
valsRuntime vals.Evaluator
}

func (ld *desiredStateLoader) Load(f string, opts LoadOpts) (*state.HelmState, error) {
Expand Down Expand Up @@ -128,7 +130,7 @@ func (ld *desiredStateLoader) loadFileWithOverrides(inheritedEnv, overrodeEnv *e
}

func (a *desiredStateLoader) underlying() *state.StateCreator {
c := state.NewCreator(a.logger, a.readFile, a.fileExists, a.abs, a.glob, a.helm)
c := state.NewCreator(a.logger, a.readFile, a.fileExists, a.abs, a.glob, a.helm, a.valsRuntime)
c.LoadFile = a.loadFile
return c
}
Expand Down
32 changes: 18 additions & 14 deletions pkg/state/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"github.com/roboll/helmfile/pkg/environment"
"github.com/roboll/helmfile/pkg/helmexec"
"github.com/roboll/helmfile/pkg/maputil"
"github.com/variantdev/vals"
"go.uber.org/zap"
"gopkg.in/yaml.v2"
)
Expand All @@ -33,27 +34,29 @@ func (e *UndefinedEnvError) Error() string {
}

type StateCreator struct {
logger *zap.SugaredLogger
readFile func(string) ([]byte, error)
fileExists func(string) (bool, error)
abs func(string) (string, error)
glob func(string) ([]string, error)
helm helmexec.Interface
logger *zap.SugaredLogger
readFile func(string) ([]byte, error)
fileExists func(string) (bool, error)
abs func(string) (string, error)
glob func(string) ([]string, error)
helm helmexec.Interface
valsRuntime vals.Evaluator

Strict bool

LoadFile func(inheritedEnv *environment.Environment, baseDir, file string, evaluateBases bool) (*HelmState, error)
}

func NewCreator(logger *zap.SugaredLogger, readFile func(string) ([]byte, error), fileExists func(string) (bool, error), abs func(string) (string, error), glob func(string) ([]string, error), helm helmexec.Interface) *StateCreator {
func NewCreator(logger *zap.SugaredLogger, readFile func(string) ([]byte, error), fileExists func(string) (bool, error), abs func(string) (string, error), glob func(string) ([]string, error), helm helmexec.Interface, valsRuntime vals.Evaluator) *StateCreator {
return &StateCreator{
logger: logger,
readFile: readFile,
fileExists: fileExists,
abs: abs,
glob: glob,
Strict: true,
helm: helm,
logger: logger,
readFile: readFile,
fileExists: fileExists,
abs: abs,
glob: glob,
Strict: true,
helm: helm,
valsRuntime: valsRuntime,
}
}

Expand Down Expand Up @@ -107,6 +110,7 @@ func (c *StateCreator) Parse(content []byte, baseDir, file string) (*HelmState,
state.removeFile = os.Remove
state.fileExists = c.fileExists
state.glob = c.glob
state.valsRuntime = c.valsRuntime

return &state, nil
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/state/create_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ bar: {{ readFile "bar.txt" }}
})
testFs.Cwd = "/example/path/to"

state, err := NewCreator(logger, testFs.ReadFile, testFs.FileExists, testFs.Abs, testFs.Glob, nil).ParseAndLoad(yamlContent, filepath.Dir(yamlFile), yamlFile, "production", false, nil)
state, err := NewCreator(logger, testFs.ReadFile, testFs.FileExists, testFs.Abs, testFs.Glob, nil, nil).ParseAndLoad(yamlContent, filepath.Dir(yamlFile), yamlFile, "production", false, nil)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
Expand Down
55 changes: 48 additions & 7 deletions pkg/state/state.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (
"regexp"

"github.com/tatsushid/go-prettytable"
"github.com/variantdev/vals"
"go.uber.org/zap"
"gopkg.in/yaml.v2"
)
Expand Down Expand Up @@ -61,8 +62,9 @@ type HelmState struct {
glob func(string) ([]string, error)
tempDir func(string, string) (string, error)

runner helmexec.Runner
helm helmexec.Interface
runner helmexec.Runner
helm helmexec.Interface
valsRuntime vals.Evaluator
}

// SubHelmfileSpec defines the subhelmfile path and options
Expand Down Expand Up @@ -1521,7 +1523,7 @@ func (st *HelmState) generateTemporaryValuesFiles(values []interface{}, missingF
}
st.logger.Debugf("successfully generated the value file at %s. produced:\n%s", path, string(yamlBytes))
generatedFiles = append(generatedFiles, valfile.Name())
case map[interface{}]interface{}:
case map[interface{}]interface{}, map[string]interface{}:
valfile, err := ioutil.TempFile("", "values")
if err != nil {
return nil, err
Expand Down Expand Up @@ -1557,7 +1559,17 @@ func (st *HelmState) namespaceAndValuesFlags(helm helmexec.Interface, release *R
}
}

generatedFiles, err := st.generateTemporaryValuesFiles(values, release.MissingFileHandler)
valuesMapSecretsRendered, err := st.valsRuntime.Eval(map[string]interface{}{"values": values})
if err != nil {
return nil, err
}

valuesSecretsRendered, ok := valuesMapSecretsRendered["values"].([]interface{})
if !ok {
return nil, fmt.Errorf("Failed to render values in %s for release %s: type %T isn't supported", st.FilePath, release.Name, valuesMapSecretsRendered["values"])
}

generatedFiles, err := st.generateTemporaryValuesFiles(valuesSecretsRendered, release.MissingFileHandler)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -1594,12 +1606,20 @@ func (st *HelmState) namespaceAndValuesFlags(helm helmexec.Interface, release *R
if len(release.SetValues) > 0 {
for _, set := range release.SetValues {
if set.Value != "" {
flags = append(flags, "--set", fmt.Sprintf("%s=%s", escape(set.Name), escape(set.Value)))
renderedValue, err := renderValsSecrets(st.valsRuntime, set.Value)
if err != nil {
return nil, fmt.Errorf("Failed to render set value entry in %s for release %s: %v", st.FilePath, release.Name, err)
}
flags = append(flags, "--set", fmt.Sprintf("%s=%s", escape(set.Name), escape(renderedValue[0])))
} else if set.File != "" {
flags = append(flags, "--set-file", fmt.Sprintf("%s=%s", escape(set.Name), st.storage().normalizePath(set.File)))
} else if len(set.Values) > 0 {
items := make([]string, len(set.Values))
for i, raw := range set.Values {
renderedValues, err := renderValsSecrets(st.valsRuntime, set.Values...)
if err != nil {
return nil, fmt.Errorf("Failed to render set values entry in %s for release %s: %v", st.FilePath, release.Name, err)
}
items := make([]string, len(renderedValues))
for i, raw := range renderedValues {
items[i] = escape(raw)
}
v := strings.Join(items, ",")
Expand Down Expand Up @@ -1638,6 +1658,27 @@ func (st *HelmState) namespaceAndValuesFlags(helm helmexec.Interface, release *R
return flags, nil
}

// renderValsSecrets helper function which renders 'ref+.*' secrets
func renderValsSecrets(e vals.Evaluator, input ...string) ([]string, error) {
output := make([]string, len(input))
if len(input) > 0 {
mapRendered, err := e.Eval(map[string]interface{}{"values": input})
if err != nil {
return nil, err
}

rendered, ok := mapRendered["values"].([]interface{})
if !ok {
return nil, fmt.Errorf("type %T isn't supported", mapRendered["values"])
}

for i := 0; i < len(rendered); i++ {
output[i] = fmt.Sprintf("%v", rendered[i])
}
}
return output, nil
}

// DisplayAffectedReleases logs the upgraded, deleted and in error releases
func (ar *AffectedReleases) DisplayAffectedReleases(logger *zap.SugaredLogger) {
if ar.Upgraded != nil {
Expand Down
Loading

0 comments on commit 4680010

Please sign in to comment.