From 88884b68dcbb0a5b01676e18bb53444cffa3c89b Mon Sep 17 00:00:00 2001 From: Yusuke Kuoka Date: Fri, 6 Nov 2020 09:23:49 +0900 Subject: [PATCH] feat: `helmfile template --skip-cleanup` (#1570) * feat: `helmfile template --skip-cleanup` Resolves #1517 --- main.go | 4 ++++ pkg/app/app.go | 6 +++++- pkg/app/app_test.go | 5 +++++ pkg/app/config.go | 1 + pkg/helmexec/exec.go | 27 ++++++++++++++++++++++----- pkg/helmexec/exec_test.go | 14 +++++++++++--- pkg/state/state.go | 17 ++++++++++++++++- 7 files changed, 64 insertions(+), 10 deletions(-) diff --git a/main.go b/main.go index 9a385e86..c75902ea 100644 --- a/main.go +++ b/main.go @@ -256,6 +256,10 @@ func main() { Name: "skip-deps", Usage: "skip running `helm repo update` and `helm dependency build`", }, + cli.BoolFlag{ + Name: "skip-cleanup", + Usage: "Stop cleaning up temporary values generated by helmfile and helm-secrets. Useful for debugging. Don't use in production for security", + }, }, Action: action(func(run *app.App, c configImpl) error { return run.Template(c) diff --git a/pkg/app/app.go b/pkg/app/app.go index 1c0e754f..26a18e87 100644 --- a/pkg/app/app.go +++ b/pkg/app/app.go @@ -218,6 +218,9 @@ func (a *App) Diff(c DiffConfigProvider) error { } func (a *App) Template(c TemplateConfigProvider) error { + + opts := []LoadOption{SetRetainValuesFiles(c.SkipCleanup())} + return a.ForEachState(func(run *Run) (ok bool, errs []error) { // `helm template` in helm v2 does not support local chart. // So, we set forceDownload=true for helm v2 only @@ -233,7 +236,7 @@ func (a *App) Template(c TemplateConfigProvider) error { } return - }) + }, opts...) } func (a *App) WriteValues(c WriteValuesConfigProvider) error { @@ -1437,6 +1440,7 @@ func (a *App) template(r *Run, c TemplateConfigProvider) (bool, []error) { Set: c.Set(), IncludeCRDs: c.IncludeCRDs(), OutputDirTemplate: c.OutputDirTemplate(), + SkipCleanup: c.SkipCleanup(), } return subst.TemplateReleases(helm, c.OutputDir(), c.Values(), args, c.Concurrency(), c.Validate(), opts) })) diff --git a/pkg/app/app_test.go b/pkg/app/app_test.go index b36fb159..741c7ae2 100644 --- a/pkg/app/app_test.go +++ b/pkg/app/app_test.go @@ -2241,6 +2241,7 @@ type configImpl struct { set []string output string includeCRDs bool + skipCleanup bool skipDeps bool } @@ -2264,6 +2265,10 @@ func (c configImpl) Validate() bool { return true } +func (c configImpl) SkipCleanup() bool { + return c.skipCleanup +} + func (c configImpl) SkipDeps() bool { return c.skipDeps } diff --git a/pkg/app/config.go b/pkg/app/config.go index a5d27f2b..9ba448e1 100644 --- a/pkg/app/config.go +++ b/pkg/app/config.go @@ -134,6 +134,7 @@ type TemplateConfigProvider interface { OutputDirTemplate() string Validate() bool SkipDeps() bool + SkipCleanup() bool OutputDir() string IncludeCRDs() bool diff --git a/pkg/helmexec/exec.go b/pkg/helmexec/exec.go index 6ea7bd0d..31a9fcfd 100644 --- a/pkg/helmexec/exec.go +++ b/pkg/helmexec/exec.go @@ -29,6 +29,7 @@ type execer struct { extra []string decryptedSecretMutex sync.Mutex decryptedSecrets map[string]*decryptedSecret + writeTempFile func([]byte) (string, error) } func NewLogger(writer io.Writer, logLevel string) *zap.SugaredLogger { @@ -278,16 +279,32 @@ func (helm *execer) DecryptSecret(context HelmContext, name string, flags ...str defer secret.mutex.RUnlock() } - tmpFile, err := ioutil.TempFile("", "secret") - if err != nil { - return "", err + tempFile := helm.writeTempFile + + if tempFile == nil { + tempFile = func(content []byte) (string, error) { + tmpFile, err := ioutil.TempFile("", "secret") + if err != nil { + return "", err + } + + _, err = tmpFile.Write(content) + if err != nil { + return "", err + } + + return tmpFile.Name(), nil + } } - _, err = tmpFile.Write(secret.bytes) + + tmpFileName, err := tempFile(secret.bytes) if err != nil { return "", err } - return tmpFile.Name(), err + helm.logger.Debugf("Decrypted %s into %s", absPath, tmpFileName) + + return tmpFileName, err } func (helm *execer) TemplateRelease(name string, chart string, flags ...string) error { diff --git a/pkg/helmexec/exec_test.go b/pkg/helmexec/exec_test.go index f82011cc..67fd3a7a 100644 --- a/pkg/helmexec/exec_test.go +++ b/pkg/helmexec/exec_test.go @@ -3,6 +3,7 @@ package helmexec import ( "bytes" "fmt" + "github.com/google/go-cmp/cmp" "os" "path" "path/filepath" @@ -257,6 +258,12 @@ func Test_DecryptSecret(t *testing.T) { var buffer bytes.Buffer logger := NewLogger(&buffer, "debug") helm := MockExecer(logger, "dev") + + tmpFilePath := "path/to/temp/file" + helm.writeTempFile = func(content []byte) (string, error) { + return tmpFilePath, nil + } + helm.DecryptSecret(HelmContext{}, "secretName") cwd, err := filepath.Abs(".") if err != nil { @@ -270,9 +277,10 @@ Decrypting secret %s/secretName exec: helm --kube-context dev secrets dec %s/secretName Preparing to decrypt secret %s/secretName Found secret in cache %s/secretName -`, cwd, cwd, cwd, cwd, cwd) - if buffer.String() != expected { - t.Errorf("helmexec.DecryptSecret()\nactual = %v\nexpect = %v", buffer.String(), expected) +Decrypted %s/secretName into path/to/temp/file +`, cwd, cwd, cwd, cwd, cwd, cwd) + if d := cmp.Diff(expected, buffer.String()); d != "" { + t.Errorf("helmexec.DecryptSecret(): want (-), got (+):\n%s", d) } } diff --git a/pkg/state/state.go b/pkg/state/state.go index 591392e8..450e4345 100644 --- a/pkg/state/state.go +++ b/pkg/state/state.go @@ -670,6 +670,10 @@ func (st *HelmState) SyncReleases(affectedReleases *AffectedReleases, helm helme preps, prepErrs := st.prepareSyncReleases(helm, additionalValues, workerLimit, opts) defer func() { + if opts.SkipCleanup { + return + } + for _, p := range preps { st.removeFiles(p.files) } @@ -1125,6 +1129,7 @@ func (st *HelmState) runHelmDepBuilds(helm helmexec.Interface, concurrency int, type TemplateOpts struct { Set []string + SkipCleanup bool OutputDirTemplate string IncludeCRDs bool } @@ -1136,7 +1141,9 @@ func (o *TemplateOpts) Apply(opts *TemplateOpts) { } // TemplateReleases wrapper for executing helm template on the releases -func (st *HelmState) TemplateReleases(helm helmexec.Interface, outputDir string, additionalValues []string, args []string, workerLimit int, validate bool, opt ...TemplateOpt) []error { +func (st *HelmState) TemplateReleases(helm helmexec.Interface, outputDir string, additionalValues []string, args []string, workerLimit int, + validate bool, opt ...TemplateOpt) []error { + opts := &TemplateOpts{} for _, o := range opt { o.Apply(opts) @@ -1156,6 +1163,10 @@ func (st *HelmState) TemplateReleases(helm helmexec.Interface, outputDir string, flags, files, err := st.flagsForTemplate(helm, release, 0) defer func() { + if opts.SkipCleanup { + return + } + st.removeFiles(files) }() @@ -1571,6 +1582,10 @@ func (st *HelmState) DiffReleases(helm helmexec.Interface, additionalValues []st preps, prepErrs := st.prepareDiffReleases(helm, additionalValues, workerLimit, detailedExitCode, includeTests, suppressSecrets, opts) defer func() { + if opts.SkipCleanup { + return + } + for _, p := range preps { st.removeFiles(p.files) }