feat: add --output-dir on template command (#693)
It generates templates in a subdirectory named "stateFileName-stateFileHash-releaseName"
This commit is contained in:
		
							parent
							
								
									63b5040ec4
								
							
						
					
					
						commit
						2f9f52033c
					
				
							
								
								
									
										8
									
								
								main.go
								
								
								
								
							
							
						
						
									
										8
									
								
								main.go
								
								
								
								
							|  | @ -200,6 +200,10 @@ func main() { | ||||||
| 					Name:  "values", | 					Name:  "values", | ||||||
| 					Usage: "additional value files to be merged into the command", | 					Usage: "additional value files to be merged into the command", | ||||||
| 				}, | 				}, | ||||||
|  | 				cli.StringFlag{ | ||||||
|  | 					Name:  "output-dir", | ||||||
|  | 					Usage: "output directory to pass to helm template (helm template --output-dir)", | ||||||
|  | 				}, | ||||||
| 				cli.IntFlag{ | 				cli.IntFlag{ | ||||||
| 					Name:  "concurrency", | 					Name:  "concurrency", | ||||||
| 					Value: 0, | 					Value: 0, | ||||||
|  | @ -440,6 +444,10 @@ func (c configImpl) Args() string { | ||||||
| 	return c.c.String("args") | 	return c.c.String("args") | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | func (c configImpl) OutputDir() string { | ||||||
|  | 	return c.c.String("output-dir") | ||||||
|  | } | ||||||
|  | 
 | ||||||
| func (c configImpl) Concurrency() int { | func (c configImpl) Concurrency() int { | ||||||
| 	return c.c.Int("concurrency") | 	return c.c.Int("concurrency") | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -45,6 +45,8 @@ type App struct { | ||||||
| 	chdir func(string) error | 	chdir func(string) error | ||||||
| 
 | 
 | ||||||
| 	remote *remote.Remote | 	remote *remote.Remote | ||||||
|  | 
 | ||||||
|  | 	helmExecer helmexec.Interface | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func New(conf ConfigProvider) *App { | func New(conf ConfigProvider) *App { | ||||||
|  | @ -59,6 +61,9 @@ func New(conf ConfigProvider) *App { | ||||||
| 		FileOrDir:   conf.FileOrDir(), | 		FileOrDir:   conf.FileOrDir(), | ||||||
| 		ValuesFiles: conf.ValuesFiles(), | 		ValuesFiles: conf.ValuesFiles(), | ||||||
| 		Set:         conf.Set(), | 		Set:         conf.Set(), | ||||||
|  | 		helmExecer: helmexec.New(conf.Logger(), conf.KubeContext(), &helmexec.ShellRunner{ | ||||||
|  | 			Logger: conf.Logger(), | ||||||
|  | 		}), | ||||||
| 	}) | 	}) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -274,7 +279,7 @@ func (a *App) visitStates(fileOrDir string, defOpts LoadOpts, converge func(*sta | ||||||
| 
 | 
 | ||||||
| 		ctx := context{a, st} | 		ctx := context{a, st} | ||||||
| 
 | 
 | ||||||
| 		helm := helmexec.New(a.Logger, a.KubeContext) | 		helm := a.helmExecer | ||||||
| 
 | 
 | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			switch stateLoadErr := err.(type) { | 			switch stateLoadErr := err.(type) { | ||||||
|  |  | ||||||
|  | @ -1,6 +1,7 @@ | ||||||
| package app | package app | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
|  | 	"bytes" | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"github.com/roboll/helmfile/pkg/helmexec" | 	"github.com/roboll/helmfile/pkg/helmexec" | ||||||
| 	"github.com/roboll/helmfile/pkg/state" | 	"github.com/roboll/helmfile/pkg/state" | ||||||
|  | @ -8,8 +9,10 @@ import ( | ||||||
| 	"os" | 	"os" | ||||||
| 	"path/filepath" | 	"path/filepath" | ||||||
| 	"reflect" | 	"reflect" | ||||||
|  | 	"regexp" | ||||||
| 	"testing" | 	"testing" | ||||||
| 
 | 
 | ||||||
|  | 	"go.uber.org/zap" | ||||||
| 	"gotest.tools/env" | 	"gotest.tools/env" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
|  | @ -1738,3 +1741,158 @@ services: | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | type configImpl struct { | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (c configImpl) Values() []string { | ||||||
|  | 	return []string{} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (c configImpl) Args() string { | ||||||
|  | 	return "some args" | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (c configImpl) SkipDeps() bool { | ||||||
|  | 	return true | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (c configImpl) OutputDir() string { | ||||||
|  | 	return "output/subdir" | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (c configImpl) Concurrency() int { | ||||||
|  | 	return 1 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Mocking the command-line runner
 | ||||||
|  | 
 | ||||||
|  | type mockRunner struct { | ||||||
|  | 	output []byte | ||||||
|  | 	err    error | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (mock *mockRunner) Execute(cmd string, args []string, env map[string]string) ([]byte, error) { | ||||||
|  | 	return []byte{}, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func MockExecer(logger *zap.SugaredLogger, kubeContext string) helmexec.Interface { | ||||||
|  | 	execer := helmexec.New(logger, kubeContext, &mockRunner{}) | ||||||
|  | 	return execer | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // mocking helmexec.Interface
 | ||||||
|  | 
 | ||||||
|  | type listKey struct { | ||||||
|  | 	filter string | ||||||
|  | 	flags  string | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type mockHelmExec struct { | ||||||
|  | 	templated []mockTemplates | ||||||
|  | 
 | ||||||
|  | 	updateDepsCallbacks map[string]func(string) error | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type mockTemplates struct { | ||||||
|  | 	flags []string | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (helm *mockHelmExec) TemplateRelease(chart string, flags ...string) error { | ||||||
|  | 	helm.templated = append(helm.templated, mockTemplates{flags: flags}) | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (helm *mockHelmExec) UpdateDeps(chart string) error { | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (helm *mockHelmExec) BuildDeps(chart string) error { | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (helm *mockHelmExec) SetExtraArgs(args ...string) { | ||||||
|  | 	return | ||||||
|  | } | ||||||
|  | func (helm *mockHelmExec) SetHelmBinary(bin string) { | ||||||
|  | 	return | ||||||
|  | } | ||||||
|  | func (helm *mockHelmExec) AddRepo(name, repository, certfile, keyfile, username, password string) error { | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | func (helm *mockHelmExec) UpdateRepo() error { | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | func (helm *mockHelmExec) SyncRelease(context helmexec.HelmContext, name, chart string, flags ...string) error { | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | func (helm *mockHelmExec) DiffRelease(context helmexec.HelmContext, name, chart string, flags ...string) error { | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | func (helm *mockHelmExec) ReleaseStatus(context helmexec.HelmContext, release string, flags ...string) error { | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | func (helm *mockHelmExec) DeleteRelease(context helmexec.HelmContext, name string, flags ...string) error { | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | func (helm *mockHelmExec) List(context helmexec.HelmContext, filter string, flags ...string) (string, error) { | ||||||
|  | 	return "", nil | ||||||
|  | } | ||||||
|  | func (helm *mockHelmExec) DecryptSecret(context helmexec.HelmContext, name string, flags ...string) (string, error) { | ||||||
|  | 	return "", nil | ||||||
|  | } | ||||||
|  | func (helm *mockHelmExec) TestRelease(context helmexec.HelmContext, name string, flags ...string) error { | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | func (helm *mockHelmExec) Fetch(chart string, flags ...string) error { | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | func (helm *mockHelmExec) Lint(chart string, flags ...string) error { | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func TestTemplate_SingleStateFile(t *testing.T) { | ||||||
|  | 	files := map[string]string{ | ||||||
|  | 		"/path/to/helmfile.yaml": ` | ||||||
|  | releases: | ||||||
|  | - name: myrelease1 | ||||||
|  |   chart: mychart1 | ||||||
|  | - name: myrelease2 | ||||||
|  |   chart: mychart1 | ||||||
|  | `, | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	var helm = &mockHelmExec{} | ||||||
|  | 	var wantReleases = []mockTemplates{ | ||||||
|  | 		{[]string{"--name", "myrelease1", "--output-dir", "output/subdir/helmfile-[a-z0-9]{8}-myrelease1"}}, | ||||||
|  | 		{[]string{"--name", "myrelease2", "--output-dir", "output/subdir/helmfile-[a-z0-9]{8}-myrelease2"}}, | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	var buffer bytes.Buffer | ||||||
|  | 	logger := helmexec.NewLogger(&buffer, "debug") | ||||||
|  | 
 | ||||||
|  | 	app := appWithFs(&App{ | ||||||
|  | 		glob:        filepath.Glob, | ||||||
|  | 		abs:         filepath.Abs, | ||||||
|  | 		KubeContext: "default", | ||||||
|  | 		Env:         "default", | ||||||
|  | 		Logger:      logger, | ||||||
|  | 		helmExecer:  helm, | ||||||
|  | 	}, files) | ||||||
|  | 	app.Template(configImpl{}) | ||||||
|  | 
 | ||||||
|  | 	for i := range wantReleases { | ||||||
|  | 		for j := range wantReleases[i].flags { | ||||||
|  | 			if j == 3 { | ||||||
|  | 				matched, _ := regexp.Match(wantReleases[i].flags[j], []byte(helm.templated[i].flags[j])) | ||||||
|  | 				if !matched { | ||||||
|  | 					t.Errorf("HelmState.TemplateReleases() = [%v], want %v", helm.templated[i].flags[j], wantReleases[i].flags[j]) | ||||||
|  | 				} | ||||||
|  | 			} else if wantReleases[i].flags[j] != helm.templated[i].flags[j] { | ||||||
|  | 				t.Errorf("HelmState.TemplateReleases() = [%v], want %v", helm.templated[i].flags[j], wantReleases[i].flags[j]) | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @ -109,6 +109,7 @@ type TemplateConfigProvider interface { | ||||||
| 
 | 
 | ||||||
| 	Values() []string | 	Values() []string | ||||||
| 	SkipDeps() bool | 	SkipDeps() bool | ||||||
|  | 	OutputDir() string | ||||||
| 
 | 
 | ||||||
| 	concurrencyConfig | 	concurrencyConfig | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -270,7 +270,7 @@ func (r *Run) Template(c TemplateConfigProvider) []error { | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	args := argparser.GetArgs(c.Args(), state) | 	args := argparser.GetArgs(c.Args(), state) | ||||||
| 	return state.TemplateReleases(helm, c.Values(), args, c.Concurrency()) | 	return state.TemplateReleases(helm, c.OutputDir(), c.Values(), args, c.Concurrency()) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (r *Run) Test(c TestConfigProvider) []error { | func (r *Run) Test(c TestConfigProvider) []error { | ||||||
|  |  | ||||||
|  | @ -44,14 +44,12 @@ func NewLogger(writer io.Writer, logLevel string) *zap.SugaredLogger { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // New for running helm commands
 | // New for running helm commands
 | ||||||
| func New(logger *zap.SugaredLogger, kubeContext string) *execer { | func New(logger *zap.SugaredLogger, kubeContext string, runner Runner) *execer { | ||||||
| 	return &execer{ | 	return &execer{ | ||||||
| 		helmBinary:  command, | 		helmBinary:  command, | ||||||
| 		logger:      logger, | 		logger:      logger, | ||||||
| 		kubeContext: kubeContext, | 		kubeContext: kubeContext, | ||||||
| 		runner: &ShellRunner{ | 		runner:      runner, | ||||||
| 			logger: logger, |  | ||||||
| 		}, |  | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -24,8 +24,7 @@ func (mock *mockRunner) Execute(cmd string, args []string, env map[string]string | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func MockExecer(logger *zap.SugaredLogger, kubeContext string) *execer { | func MockExecer(logger *zap.SugaredLogger, kubeContext string) *execer { | ||||||
| 	execer := New(logger, kubeContext) | 	execer := New(logger, kubeContext, &mockRunner{}) | ||||||
| 	execer.runner = &mockRunner{} |  | ||||||
| 	return execer | 	return execer | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -34,7 +33,9 @@ func MockExecer(logger *zap.SugaredLogger, kubeContext string) *execer { | ||||||
| func TestNewHelmExec(t *testing.T) { | func TestNewHelmExec(t *testing.T) { | ||||||
| 	buffer := bytes.NewBufferString("something") | 	buffer := bytes.NewBufferString("something") | ||||||
| 	logger := NewLogger(buffer, "debug") | 	logger := NewLogger(buffer, "debug") | ||||||
| 	helm := New(logger, "dev") | 	helm := New(logger, "dev", &ShellRunner{ | ||||||
|  | 		Logger: logger, | ||||||
|  | 	}) | ||||||
| 	if helm.kubeContext != "dev" { | 	if helm.kubeContext != "dev" { | ||||||
| 		t.Error("helmexec.New() - kubeContext") | 		t.Error("helmexec.New() - kubeContext") | ||||||
| 	} | 	} | ||||||
|  | @ -47,7 +48,11 @@ func TestNewHelmExec(t *testing.T) { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func Test_SetExtraArgs(t *testing.T) { | func Test_SetExtraArgs(t *testing.T) { | ||||||
| 	helm := New(NewLogger(os.Stdout, "info"), "dev") | 	buffer := bytes.NewBufferString("something") | ||||||
|  | 	logger := NewLogger(buffer, "debug") | ||||||
|  | 	helm := New(NewLogger(os.Stdout, "info"), "dev", &ShellRunner{ | ||||||
|  | 		Logger: logger, | ||||||
|  | 	}) | ||||||
| 	helm.SetExtraArgs() | 	helm.SetExtraArgs() | ||||||
| 	if len(helm.extra) != 0 { | 	if len(helm.extra) != 0 { | ||||||
| 		t.Error("helmexec.SetExtraArgs() - passing no arguments should not change extra field") | 		t.Error("helmexec.SetExtraArgs() - passing no arguments should not change extra field") | ||||||
|  | @ -63,7 +68,11 @@ func Test_SetExtraArgs(t *testing.T) { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func Test_SetHelmBinary(t *testing.T) { | func Test_SetHelmBinary(t *testing.T) { | ||||||
| 	helm := New(NewLogger(os.Stdout, "info"), "dev") | 	buffer := bytes.NewBufferString("something") | ||||||
|  | 	logger := NewLogger(buffer, "debug") | ||||||
|  | 	helm := New(NewLogger(os.Stdout, "info"), "dev", &ShellRunner{ | ||||||
|  | 		Logger: logger, | ||||||
|  | 	}) | ||||||
| 	if helm.helmBinary != "helm" { | 	if helm.helmBinary != "helm" { | ||||||
| 		t.Error("helmexec.command - default command is not helm") | 		t.Error("helmexec.command - default command is not helm") | ||||||
| 	} | 	} | ||||||
|  | @ -478,3 +487,16 @@ func Test_mergeEnv(t *testing.T) { | ||||||
| 		t.Errorf("mergeEnv()\nactual = %v\nexpect = %v", actual, expected) | 		t.Errorf("mergeEnv()\nactual = %v\nexpect = %v", actual, expected) | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | func Test_Template(t *testing.T) { | ||||||
|  | 	var buffer bytes.Buffer | ||||||
|  | 	logger := NewLogger(&buffer, "debug") | ||||||
|  | 	helm := MockExecer(logger, "dev") | ||||||
|  | 	helm.TemplateRelease("path/to/chart", "--values", "file.yml") | ||||||
|  | 	expected := `exec: helm template path/to/chart --values file.yml --kube-context dev | ||||||
|  | exec: helm template path/to/chart --values file.yml --kube-context dev:  | ||||||
|  | ` | ||||||
|  | 	if buffer.String() != expected { | ||||||
|  | 		t.Errorf("helmexec.Template()\nactual = %v\nexpect = %v", buffer.String(), expected) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @ -25,7 +25,7 @@ type Runner interface { | ||||||
| type ShellRunner struct { | type ShellRunner struct { | ||||||
| 	Dir string | 	Dir string | ||||||
| 
 | 
 | ||||||
| 	logger *zap.SugaredLogger | 	Logger *zap.SugaredLogger | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // Execute a shell command
 | // Execute a shell command
 | ||||||
|  | @ -33,7 +33,7 @@ func (shell ShellRunner) Execute(cmd string, args []string, env map[string]strin | ||||||
| 	preparedCmd := exec.Command(cmd, args...) | 	preparedCmd := exec.Command(cmd, args...) | ||||||
| 	preparedCmd.Dir = shell.Dir | 	preparedCmd.Dir = shell.Dir | ||||||
| 	preparedCmd.Env = mergeEnv(os.Environ(), env) | 	preparedCmd.Env = mergeEnv(os.Environ(), env) | ||||||
| 	return combinedOutput(preparedCmd, shell.logger) | 	return combinedOutput(preparedCmd, shell.Logger) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func combinedOutput(c *exec.Cmd, logger *zap.SugaredLogger) ([]byte, error) { | func combinedOutput(c *exec.Cmd, logger *zap.SugaredLogger) ([]byte, error) { | ||||||
|  |  | ||||||
|  | @ -185,7 +185,9 @@ func (st *HelmState) loadEnvValues(name string, ctxEnv *environment.Environment, | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		if len(envSpec.Secrets) > 0 { | 		if len(envSpec.Secrets) > 0 { | ||||||
| 			helm := helmexec.New(st.logger, "") | 			helm := helmexec.New(st.logger, "", &helmexec.ShellRunner{ | ||||||
|  | 				Logger: st.logger, | ||||||
|  | 			}) | ||||||
| 
 | 
 | ||||||
| 			var envSecretFiles []string | 			var envSecretFiles []string | ||||||
| 			for _, urlOrPath := range envSpec.Secrets { | 			for _, urlOrPath := range envSpec.Secrets { | ||||||
|  |  | ||||||
|  | @ -1,8 +1,11 @@ | ||||||
| package state | package state | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
|  | 	"crypto/sha1" | ||||||
|  | 	"encoding/hex" | ||||||
| 	"errors" | 	"errors" | ||||||
| 	"fmt" | 	"fmt" | ||||||
|  | 	"io" | ||||||
| 	"io/ioutil" | 	"io/ioutil" | ||||||
| 	"os" | 	"os" | ||||||
| 	"path" | 	"path" | ||||||
|  | @ -534,7 +537,7 @@ func (st *HelmState) downloadCharts(helm helmexec.Interface, dir string, concurr | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // TemplateReleases wrapper for executing helm template on the releases
 | // TemplateReleases wrapper for executing helm template on the releases
 | ||||||
| func (st *HelmState) TemplateReleases(helm helmexec.Interface, additionalValues []string, args []string, workerLimit int) []error { | func (st *HelmState) TemplateReleases(helm helmexec.Interface, outputDir string, additionalValues []string, args []string, workerLimit int) []error { | ||||||
| 	// Reset the extra args if already set, not to break `helm fetch` by adding the args intended for `lint`
 | 	// Reset the extra args if already set, not to break `helm fetch` by adding the args intended for `lint`
 | ||||||
| 	helm.SetExtraArgs() | 	helm.SetExtraArgs() | ||||||
| 
 | 
 | ||||||
|  | @ -569,6 +572,7 @@ func (st *HelmState) TemplateReleases(helm helmexec.Interface, additionalValues | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			errs = append(errs, err) | 			errs = append(errs, err) | ||||||
| 		} | 		} | ||||||
|  | 
 | ||||||
| 		for _, value := range additionalValues { | 		for _, value := range additionalValues { | ||||||
| 			valfile, err := filepath.Abs(value) | 			valfile, err := filepath.Abs(value) | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
|  | @ -581,6 +585,17 @@ func (st *HelmState) TemplateReleases(helm helmexec.Interface, additionalValues | ||||||
| 			flags = append(flags, "--values", valfile) | 			flags = append(flags, "--values", valfile) | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
|  | 		if len(outputDir) > 0 { | ||||||
|  | 			releaseOutputDir, err := st.GenerateOutputDir(outputDir, release) | ||||||
|  | 			if err != nil { | ||||||
|  | 				errs = append(errs, err) | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			flags = append(flags, "--output-dir", releaseOutputDir) | ||||||
|  | 			st.logger.Debugf("Generating templates to : %s\n", releaseOutputDir) | ||||||
|  | 			os.Mkdir(releaseOutputDir, 0755) | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
| 		if len(errs) == 0 { | 		if len(errs) == 0 { | ||||||
| 			if err := helm.TemplateRelease(temp[release.Name], flags...); err != nil { | 			if err := helm.TemplateRelease(temp[release.Name], flags...); err != nil { | ||||||
| 				errs = append(errs, err) | 				errs = append(errs, err) | ||||||
|  | @ -1560,3 +1575,28 @@ func (hf *SubHelmfileSpec) UnmarshalYAML(unmarshal func(interface{}) error) erro | ||||||
| 	} | 	} | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | func (st *HelmState) GenerateOutputDir(outputDir string, release ReleaseSpec) (string, error) { | ||||||
|  | 	// get absolute path of state file to generate a hash
 | ||||||
|  | 	// use this hash to write helm output in a specific directory by state file and release name
 | ||||||
|  | 	// ie. in a directory named stateFileName-stateFileHash-releaseName
 | ||||||
|  | 	stateAbsPath, err := filepath.Abs(st.FilePath) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return stateAbsPath, err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	hasher := sha1.New() | ||||||
|  | 	io.WriteString(hasher, stateAbsPath) | ||||||
|  | 
 | ||||||
|  | 	var stateFileExtension = filepath.Ext(st.FilePath) | ||||||
|  | 	var stateFileName = st.FilePath[0 : len(st.FilePath)-len(stateFileExtension)] | ||||||
|  | 
 | ||||||
|  | 	var sb strings.Builder | ||||||
|  | 	sb.WriteString(stateFileName) | ||||||
|  | 	sb.WriteString("-") | ||||||
|  | 	sb.WriteString(hex.EncodeToString(hasher.Sum(nil))[:8]) | ||||||
|  | 	sb.WriteString("-") | ||||||
|  | 	sb.WriteString(release.Name) | ||||||
|  | 
 | ||||||
|  | 	return path.Join(outputDir, sb.String()), nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @ -524,7 +524,9 @@ func TestHelmState_flagsForUpgrade(t *testing.T) { | ||||||
| 				Releases:          []ReleaseSpec{*tt.release}, | 				Releases:          []ReleaseSpec{*tt.release}, | ||||||
| 				HelmDefaults:      tt.defaults, | 				HelmDefaults:      tt.defaults, | ||||||
| 			} | 			} | ||||||
| 			helm := helmexec.New(logger, "default") | 			helm := helmexec.New(logger, "default", &helmexec.ShellRunner{ | ||||||
|  | 				Logger: logger, | ||||||
|  | 			}) | ||||||
| 			args, err := state.flagsForUpgrade(helm, tt.release, 0) | 			args, err := state.flagsForUpgrade(helm, tt.release, 0) | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| 				t.Errorf("unexpected error flagsForUpgade: %v", err) | 				t.Errorf("unexpected error flagsForUpgade: %v", err) | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue