Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add integration with Hashicorp Vault, AWS SSM, SecretsManager #906

Merged
merged 2 commits into from
Oct 25, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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