feat: Release Template (#439)
This feature is supposed to help advanced use-cases like Conventional Directory Structure explained in several issues like #428. Newly added configuration keys `templates`, `missingFileHandler`, and the ability to defer executing template expressions in `values`, `secrets`, `namespace`, and `chart` of releases allows you to abstract away repetitions into a reusable template: ```yaml templates: default: &default missingFileHandler: Warn namespace: "{{`{{ .Release.Name }}`}}" chart: stable/{{`{{ .Release.Name }}`}} values: - config/{{`{{ .Release.Name }}`}}/values.yaml - config/{{`{{ .Release.Name }}`}}/{{`{{ .Environment.Name }}`}}.yaml secrets: - config/{{`{{ .Release.Name }}`}}/secrets.yaml - config/{{`{{ .Release.Name }}`}}/{{`{{ .Environment.Name }}`}}-secrets.yaml releases: - name: envoy <<: *default ``` See the updated documentation for more details. Resolves #428
This commit is contained in:
		
							parent
							
								
									23178b398c
								
							
						
					
					
						commit
						f813ac2642
					
				|  | @ -179,7 +179,11 @@ helmfile apply | ||||||
| 
 | 
 | ||||||
| Congratulations! You now have your first Prometheus deployment running inside your cluster. | Congratulations! You now have your first Prometheus deployment running inside your cluster. | ||||||
| 
 | 
 | ||||||
| Iterate on the `helmfile.yaml` by referencing the [configuration syntax](#configuration-syntax) and the [cli reference](#cli-reference). | Iterate on the `helmfile.yaml` by referencing: | ||||||
|  | 
 | ||||||
|  | - [Configuration syntax](#configuration-syntax) | ||||||
|  | - [CLI reference](#cli-reference). | ||||||
|  | - [Helmfile Best Practices Guide](https://github.com/roboll/helmfile/blob/master/docs/writing-helmfile.md) | ||||||
| 
 | 
 | ||||||
| ## cli reference | ## cli reference | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -30,6 +30,65 @@ If you want a kind of default values that is used when a missing key was referen | ||||||
| 
 | 
 | ||||||
| Now, you get `1` when there is no `eventApi.replicas` defined in environment values. | Now, you get `1` when there is no `eventApi.replicas` defined in environment values. | ||||||
| 
 | 
 | ||||||
|  | ## Release Template / Conventional Directory Structure | ||||||
|  | 
 | ||||||
|  | Introducing helmfile into a large-scale project that involes dozens of releases often results in a lot of repetitions in `helmfile.yaml` files. | ||||||
|  | 
 | ||||||
|  | The example below shows repetitions in `namespace`, `chart`, `values`, and `secrets`: | ||||||
|  | 
 | ||||||
|  | ```yaml | ||||||
|  | releases: | ||||||
|  | # *snip* | ||||||
|  | - name: heapster | ||||||
|  |   namespace: kube-system | ||||||
|  |   chart: stable/heapster | ||||||
|  |   version: 0.3.2 | ||||||
|  |   values: | ||||||
|  |   - "./config/heapster/values.yaml" | ||||||
|  |   - "./config/heapster/{{ .Environment.Name }}.yaml" | ||||||
|  |   secrets: | ||||||
|  |   - "./config/heapster/secrets.yaml" | ||||||
|  |   - "./config/heapster/{{ .Environment.Name }}-secrets.yaml" | ||||||
|  | 
 | ||||||
|  |  - name: kubernetes-dashboard | ||||||
|  |   namespace: kube-system | ||||||
|  |   chart: stable/kubernetes-dashboard | ||||||
|  |   version: 0.10.0 | ||||||
|  |   values: | ||||||
|  |   - "./config/kubernetes-dashboard/values.yaml" | ||||||
|  |   - "./config/kubernetes-dashboard/{{ .Environment.Name }}.yaml" | ||||||
|  |   values: | ||||||
|  |   - "./config/kubernetes-dashboard/secrets.yaml" | ||||||
|  |   - "./config/kubernetes-dashboard/{{ .Environment.Name }}-secrets.yaml" | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | This is where Helmfile's advanced feature called Release Template comes handy. | ||||||
|  | 
 | ||||||
|  | It allows you to abstract away the repetitions in releases into a template, which is then included and executed by using YAML anchor/alias: | ||||||
|  | 
 | ||||||
|  | ```yaml | ||||||
|  | templates: | ||||||
|  |   default: &default | ||||||
|  |     chart: stable/{{`{{ .Release.Name }}`}} | ||||||
|  |     namespace: kube-system | ||||||
|  |     # This prevents helmfile exiting when it encounters a missing file | ||||||
|  |     missingFileHandler: Warn | ||||||
|  |     values: | ||||||
|  |     - config/{{`{{ .Release.Name }}`}}/values.yaml | ||||||
|  |     - config/{{`{{ .Release.Name }}`}}/{{`{{ .Environment.Name }}`}}.yaml | ||||||
|  |     secrets: | ||||||
|  |     - config/{{`{{ .Release.Name }}`}}/secrets.yaml | ||||||
|  |     - config/{{`{{ .Release.Name }}`}}/{{`{{ .Environment.Name }}`}}-secrets.yaml | ||||||
|  | 
 | ||||||
|  | releases: | ||||||
|  | - name: heapster | ||||||
|  |   <<: *default | ||||||
|  | - name: kubernetes-dashboard | ||||||
|  |   <<: *default | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | See the [issue 428](https://github.com/roboll/helmfile/issues/428) for more context on how this is supposed to work. | ||||||
|  | 
 | ||||||
| ## Layering | ## Layering | ||||||
| 
 | 
 | ||||||
| You may occasionally end up with many helmfiles that shares common parts like which repositories to use, and whichi release to be bundled by default. | You may occasionally end up with many helmfiles that shares common parts like which repositories to use, and whichi release to be bundled by default. | ||||||
|  |  | ||||||
							
								
								
									
										9
									
								
								main.go
								
								
								
								
							
							
						
						
									
										9
									
								
								main.go
								
								
								
								
							|  | @ -756,7 +756,8 @@ func (r *twoPassRenderer) renderTemplate(content []byte) (*bytes.Buffer, error) | ||||||
| 	// try a first pass render. This will always succeed, but can produce a limited env
 | 	// try a first pass render. This will always succeed, but can produce a limited env
 | ||||||
| 	firstPassEnv := r.renderEnvironment(content) | 	firstPassEnv := r.renderEnvironment(content) | ||||||
| 
 | 
 | ||||||
| 	secondPassRenderer := tmpl.NewFileRenderer(r.reader, filepath.Dir(r.filename), firstPassEnv, r.namespace) | 	tmplData := state.EnvironmentTemplateData{Environment: firstPassEnv, Namespace: r.namespace} | ||||||
|  | 	secondPassRenderer := tmpl.NewFileRenderer(r.reader, filepath.Dir(r.filename), tmplData) | ||||||
| 	yamlBuf, err := secondPassRenderer.RenderTemplateContentToBuffer(content) | 	yamlBuf, err := secondPassRenderer.RenderTemplateContentToBuffer(content) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		if r.logger != nil { | 		if r.logger != nil { | ||||||
|  | @ -840,6 +841,12 @@ func (a *app) VisitDesiredStates(fileOrDir string, converge func(*state.HelmStat | ||||||
| 			} | 			} | ||||||
| 			noMatchInHelmfiles = noMatchInHelmfiles && noMatchInSubHelmfiles | 			noMatchInHelmfiles = noMatchInHelmfiles && noMatchInSubHelmfiles | ||||||
| 		} else { | 		} else { | ||||||
|  | 			var err error | ||||||
|  | 			st, err = st.ExecuteTemplates() | ||||||
|  | 			if err != nil { | ||||||
|  | 				return fmt.Errorf("failed executing release templates in \"%s\": %v", fileOrDir, err) | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
| 			var processed bool | 			var processed bool | ||||||
| 			processed, errs = converge(st, helm) | 			processed, errs = converge(st, helm) | ||||||
| 			noMatchInHelmfiles = noMatchInHelmfiles && !processed | 			noMatchInHelmfiles = noMatchInHelmfiles && !processed | ||||||
|  |  | ||||||
|  | @ -6,7 +6,7 @@ import ( | ||||||
| 	"github.com/imdario/mergo" | 	"github.com/imdario/mergo" | ||||||
| 	"github.com/roboll/helmfile/environment" | 	"github.com/roboll/helmfile/environment" | ||||||
| 	"github.com/roboll/helmfile/helmexec" | 	"github.com/roboll/helmfile/helmexec" | ||||||
| 	"github.com/roboll/helmfile/valuesfile" | 	"github.com/roboll/helmfile/tmpl" | ||||||
| 	"go.uber.org/zap" | 	"go.uber.org/zap" | ||||||
| 	"gopkg.in/yaml.v2" | 	"gopkg.in/yaml.v2" | ||||||
| 	"io" | 	"io" | ||||||
|  | @ -118,13 +118,14 @@ func (c *creator) CreateFromYaml(content []byte, file string, env string) (*Helm | ||||||
| 	return &state, nil | 	return &state, nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (state *HelmState) loadEnv(name string, readFile func(string) ([]byte, error)) (*environment.Environment, error) { | func (st *HelmState) loadEnv(name string, readFile func(string) ([]byte, error)) (*environment.Environment, error) { | ||||||
| 	envVals := map[string]interface{}{} | 	envVals := map[string]interface{}{} | ||||||
| 	envSpec, ok := state.Environments[name] | 	envSpec, ok := st.Environments[name] | ||||||
| 	if ok { | 	if ok { | ||||||
| 		for _, envvalFile := range envSpec.Values { | 		for _, envvalFile := range envSpec.Values { | ||||||
| 			envvalFullPath := filepath.Join(state.basePath, envvalFile) | 			envvalFullPath := filepath.Join(st.basePath, envvalFile) | ||||||
| 			r := valuesfile.NewRenderer(readFile, filepath.Dir(envvalFullPath), environment.EmptyEnvironment) | 			tmplData := EnvironmentTemplateData{Environment: environment.EmptyEnvironment, Namespace: ""} | ||||||
|  | 			r := tmpl.NewFileRenderer(readFile, filepath.Dir(envvalFullPath), tmplData) | ||||||
| 			bytes, err := r.RenderToBytes(envvalFullPath) | 			bytes, err := r.RenderToBytes(envvalFullPath) | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| 				return nil, fmt.Errorf("failed to load environment values file \"%s\": %v", envvalFile, err) | 				return nil, fmt.Errorf("failed to load environment values file \"%s\": %v", envvalFile, err) | ||||||
|  | @ -139,9 +140,9 @@ func (state *HelmState) loadEnv(name string, readFile func(string) ([]byte, erro | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		if len(envSpec.Secrets) > 0 { | 		if len(envSpec.Secrets) > 0 { | ||||||
| 			helm := helmexec.New(state.logger, "") | 			helm := helmexec.New(st.logger, "") | ||||||
| 			for _, secFile := range envSpec.Secrets { | 			for _, secFile := range envSpec.Secrets { | ||||||
| 				path := filepath.Join(state.basePath, secFile) | 				path := filepath.Join(st.basePath, secFile) | ||||||
| 				if _, err := os.Stat(path); os.IsNotExist(err) { | 				if _, err := os.Stat(path); os.IsNotExist(err) { | ||||||
| 					return nil, err | 					return nil, err | ||||||
| 				} | 				} | ||||||
|  |  | ||||||
|  | @ -0,0 +1,69 @@ | ||||||
|  | package state | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"fmt" | ||||||
|  | 	"github.com/roboll/helmfile/tmpl" | ||||||
|  | 	"gopkg.in/yaml.v2" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | func (r ReleaseSpec) ExecuteTemplateExpressions(renderer *tmpl.FileRenderer) (*ReleaseSpec, error) { | ||||||
|  | 	var result *ReleaseSpec | ||||||
|  | 	var err error | ||||||
|  | 
 | ||||||
|  | 	result, err = r.Clone() | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, fmt.Errorf("failed executing template expressions in release \"%s\": %v", r.Name, err) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	{ | ||||||
|  | 		ts := result.Chart | ||||||
|  | 		result.Chart, err = renderer.RenderTemplateContentToString([]byte(ts)) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return nil, fmt.Errorf("failed executing template expressions in release \"%s\".chart = \"%s\": %v", r.Name, ts, err) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	{ | ||||||
|  | 		ts := result.Namespace | ||||||
|  | 		result.Namespace, err = renderer.RenderTemplateContentToString([]byte(ts)) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return nil, fmt.Errorf("failed executing template expressions in release \"%s\".namespace = \"%s\": %v", r.Name, ts, err) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	for i, t := range result.Values { | ||||||
|  | 		switch ts := t.(type) { | ||||||
|  | 		case string: | ||||||
|  | 			s, err := renderer.RenderTemplateContentToBuffer([]byte(ts)) | ||||||
|  | 			if err != nil { | ||||||
|  | 				return nil, fmt.Errorf("failed executing template expressions in release \"%s\".values[%d] = \"%s\": %v", r.Name, i, ts, err) | ||||||
|  | 			} | ||||||
|  | 			result.Values[i] = s.String() | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	for i, ts := range result.Secrets { | ||||||
|  | 		s, err := renderer.RenderTemplateContentToBuffer([]byte(ts)) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return nil, fmt.Errorf("failed executing template expressions in release \"%s\".secrets[%d] = \"%s\": %v", r.Name, i, ts, err) | ||||||
|  | 		} | ||||||
|  | 		result.Secrets[i] = s.String() | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return result, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (r ReleaseSpec) Clone() (*ReleaseSpec, error) { | ||||||
|  | 	serialized, err := yaml.Marshal(r) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, fmt.Errorf("failed cloning release \"%s\": %v", r.Name, err) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	var deserialized ReleaseSpec | ||||||
|  | 
 | ||||||
|  | 	if err := yaml.Unmarshal(serialized, &deserialized); err != nil { | ||||||
|  | 		return nil, fmt.Errorf("failed cloning release \"%s\": %v", r.Name, err) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return &deserialized, nil | ||||||
|  | } | ||||||
							
								
								
									
										241
									
								
								state/state.go
								
								
								
								
							
							
						
						
									
										241
									
								
								state/state.go
								
								
								
								
							|  | @ -21,7 +21,7 @@ import ( | ||||||
| 
 | 
 | ||||||
| 	"github.com/roboll/helmfile/environment" | 	"github.com/roboll/helmfile/environment" | ||||||
| 	"github.com/roboll/helmfile/event" | 	"github.com/roboll/helmfile/event" | ||||||
| 	"github.com/roboll/helmfile/valuesfile" | 	"github.com/roboll/helmfile/tmpl" | ||||||
| 	"go.uber.org/zap" | 	"go.uber.org/zap" | ||||||
| 	"gopkg.in/yaml.v2" | 	"gopkg.in/yaml.v2" | ||||||
| ) | ) | ||||||
|  | @ -39,6 +39,8 @@ type HelmState struct { | ||||||
| 	Repositories       []RepositorySpec `yaml:"repositories"` | 	Repositories       []RepositorySpec `yaml:"repositories"` | ||||||
| 	Releases           []ReleaseSpec    `yaml:"releases"` | 	Releases           []ReleaseSpec    `yaml:"releases"` | ||||||
| 
 | 
 | ||||||
|  | 	Templates map[string]TemplateSpec `yaml:"templates"` | ||||||
|  | 
 | ||||||
| 	Env environment.Environment | 	Env environment.Environment | ||||||
| 
 | 
 | ||||||
| 	logger *zap.SugaredLogger | 	logger *zap.SugaredLogger | ||||||
|  | @ -95,6 +97,10 @@ type ReleaseSpec struct { | ||||||
| 	// Installed, when set to true, `delete --purge` the release
 | 	// Installed, when set to true, `delete --purge` the release
 | ||||||
| 	Installed *bool `yaml:"installed"` | 	Installed *bool `yaml:"installed"` | ||||||
| 
 | 
 | ||||||
|  | 	// MissingFileHandler is set to either "Error" or "Warn". "Error" instructs helmfile to fail when unable to find a values or secrets file. When "Warn", it prints the file and continues.
 | ||||||
|  | 	// The default value for MissingFileHandler is "Error".
 | ||||||
|  | 	MissingFileHandler *string `yaml:"missingFileHandler"` | ||||||
|  | 
 | ||||||
| 	// Hooks is a list of extension points paired with operations, that are executed in specific points of the lifecycle of releases defined in helmfile
 | 	// Hooks is a list of extension points paired with operations, that are executed in specific points of the lifecycle of releases defined in helmfile
 | ||||||
| 	Hooks []event.Hook `yaml:"hooks"` | 	Hooks []event.Hook `yaml:"hooks"` | ||||||
| 
 | 
 | ||||||
|  | @ -125,9 +131,9 @@ type SetValue struct { | ||||||
| 
 | 
 | ||||||
| const DefaultEnv = "default" | const DefaultEnv = "default" | ||||||
| 
 | 
 | ||||||
| func (state *HelmState) applyDefaultsTo(spec *ReleaseSpec) { | func (st *HelmState) applyDefaultsTo(spec *ReleaseSpec) { | ||||||
| 	if state.Namespace != "" { | 	if st.Namespace != "" { | ||||||
| 		spec.Namespace = state.Namespace | 		spec.Namespace = st.Namespace | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -137,10 +143,10 @@ type RepoUpdater interface { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // SyncRepos will update the given helm releases
 | // SyncRepos will update the given helm releases
 | ||||||
| func (state *HelmState) SyncRepos(helm RepoUpdater) []error { | func (st *HelmState) SyncRepos(helm RepoUpdater) []error { | ||||||
| 	errs := []error{} | 	errs := []error{} | ||||||
| 
 | 
 | ||||||
| 	for _, repo := range state.Repositories { | 	for _, repo := range st.Repositories { | ||||||
| 		if err := helm.AddRepo(repo.Name, repo.URL, repo.CertFile, repo.KeyFile, repo.Username, repo.Password); err != nil { | 		if err := helm.AddRepo(repo.Name, repo.URL, repo.CertFile, repo.KeyFile, repo.Username, repo.Password); err != nil { | ||||||
| 			errs = append(errs, err) | 			errs = append(errs, err) | ||||||
| 		} | 		} | ||||||
|  | @ -176,8 +182,8 @@ type syncPrepareResult struct { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // SyncReleases wrapper for executing helm upgrade on the releases
 | // SyncReleases wrapper for executing helm upgrade on the releases
 | ||||||
| func (state *HelmState) prepareSyncReleases(helm helmexec.Interface, additionalValues []string, concurrency int) ([]syncPrepareResult, []error) { | func (st *HelmState) prepareSyncReleases(helm helmexec.Interface, additionalValues []string, concurrency int) ([]syncPrepareResult, []error) { | ||||||
| 	releases := state.Releases | 	releases := st.Releases | ||||||
| 	numReleases := len(releases) | 	numReleases := len(releases) | ||||||
| 	jobs := make(chan *ReleaseSpec, numReleases) | 	jobs := make(chan *ReleaseSpec, numReleases) | ||||||
| 	results := make(chan syncPrepareResult, numReleases) | 	results := make(chan syncPrepareResult, numReleases) | ||||||
|  | @ -193,9 +199,9 @@ func (state *HelmState) prepareSyncReleases(helm helmexec.Interface, additionalV | ||||||
| 	for w := 1; w <= concurrency; w++ { | 	for w := 1; w <= concurrency; w++ { | ||||||
| 		go func() { | 		go func() { | ||||||
| 			for release := range jobs { | 			for release := range jobs { | ||||||
| 				state.applyDefaultsTo(release) | 				st.applyDefaultsTo(release) | ||||||
| 
 | 
 | ||||||
| 				flags, flagsErr := state.flagsForUpgrade(helm, release) | 				flags, flagsErr := st.flagsForUpgrade(helm, release) | ||||||
| 				if flagsErr != nil { | 				if flagsErr != nil { | ||||||
| 					results <- syncPrepareResult{errors: []*ReleaseError{&ReleaseError{release, flagsErr}}} | 					results <- syncPrepareResult{errors: []*ReleaseError{&ReleaseError{release, flagsErr}}} | ||||||
| 					continue | 					continue | ||||||
|  | @ -248,10 +254,10 @@ func (state *HelmState) prepareSyncReleases(helm helmexec.Interface, additionalV | ||||||
| 	return res, errs | 	return res, errs | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (state *HelmState) DetectReleasesToBeDeleted(helm helmexec.Interface) ([]*ReleaseSpec, error) { | func (st *HelmState) DetectReleasesToBeDeleted(helm helmexec.Interface) ([]*ReleaseSpec, error) { | ||||||
| 	detected := []*ReleaseSpec{} | 	detected := []*ReleaseSpec{} | ||||||
| 	for i, _ := range state.Releases { | 	for i, _ := range st.Releases { | ||||||
| 		release := state.Releases[i] | 		release := st.Releases[i] | ||||||
| 		if release.Installed != nil && !*release.Installed { | 		if release.Installed != nil && !*release.Installed { | ||||||
| 			err := helm.ReleaseStatus(release.Name) | 			err := helm.ReleaseStatus(release.Name) | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
|  | @ -274,8 +280,8 @@ func (state *HelmState) DetectReleasesToBeDeleted(helm helmexec.Interface) ([]*R | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // SyncReleases wrapper for executing helm upgrade on the releases
 | // SyncReleases wrapper for executing helm upgrade on the releases
 | ||||||
| func (state *HelmState) SyncReleases(helm helmexec.Interface, additionalValues []string, workerLimit int) []error { | func (st *HelmState) SyncReleases(helm helmexec.Interface, additionalValues []string, workerLimit int) []error { | ||||||
| 	preps, prepErrs := state.prepareSyncReleases(helm, additionalValues, workerLimit) | 	preps, prepErrs := st.prepareSyncReleases(helm, additionalValues, workerLimit) | ||||||
| 	if len(prepErrs) > 0 { | 	if len(prepErrs) > 0 { | ||||||
| 		return prepErrs | 		return prepErrs | ||||||
| 	} | 	} | ||||||
|  | @ -298,7 +304,7 @@ func (state *HelmState) SyncReleases(helm helmexec.Interface, additionalValues [ | ||||||
| 			for prep := range jobQueue { | 			for prep := range jobQueue { | ||||||
| 				release := prep.release | 				release := prep.release | ||||||
| 				flags := prep.flags | 				flags := prep.flags | ||||||
| 				chart := normalizeChart(state.basePath, release.Chart) | 				chart := normalizeChart(st.basePath, release.Chart) | ||||||
| 				if release.Installed != nil && !*release.Installed { | 				if release.Installed != nil && !*release.Installed { | ||||||
| 					if err := helm.ReleaseStatus(release.Name); err == nil { | 					if err := helm.ReleaseStatus(release.Name); err == nil { | ||||||
| 						if err := helm.DeleteRelease(release.Name, "--purge"); err != nil { | 						if err := helm.DeleteRelease(release.Name, "--purge"); err != nil { | ||||||
|  | @ -313,8 +319,8 @@ func (state *HelmState) SyncReleases(helm helmexec.Interface, additionalValues [ | ||||||
| 					results <- syncResult{} | 					results <- syncResult{} | ||||||
| 				} | 				} | ||||||
| 
 | 
 | ||||||
| 				if _, err := state.triggerCleanupEvent(prep.release, "sync"); err != nil { | 				if _, err := st.triggerCleanupEvent(prep.release, "sync"); err != nil { | ||||||
| 					state.logger.Warnf("warn: %v\n", err) | 					st.logger.Warnf("warn: %v\n", err) | ||||||
| 				} | 				} | ||||||
| 			} | 			} | ||||||
| 			waitGroup.Done() | 			waitGroup.Done() | ||||||
|  | @ -349,8 +355,8 @@ func (state *HelmState) SyncReleases(helm helmexec.Interface, additionalValues [ | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // downloadCharts will download and untar charts for Lint and Template
 | // downloadCharts will download and untar charts for Lint and Template
 | ||||||
| func (state *HelmState) downloadCharts(helm helmexec.Interface, dir string, workerLimit int, helmfileCommand string) (map[string]string, []error) { | func (st *HelmState) downloadCharts(helm helmexec.Interface, dir string, workerLimit int, helmfileCommand string) (map[string]string, []error) { | ||||||
| 	temp := make(map[string]string, len(state.Releases)) | 	temp := make(map[string]string, len(st.Releases)) | ||||||
| 	type downloadResults struct { | 	type downloadResults struct { | ||||||
| 		releaseName string | 		releaseName string | ||||||
| 		chartPath   string | 		chartPath   string | ||||||
|  | @ -358,20 +364,20 @@ func (state *HelmState) downloadCharts(helm helmexec.Interface, dir string, work | ||||||
| 	errs := []error{} | 	errs := []error{} | ||||||
| 
 | 
 | ||||||
| 	var wgFetch sync.WaitGroup | 	var wgFetch sync.WaitGroup | ||||||
| 	jobQueue := make(chan *ReleaseSpec, len(state.Releases)) | 	jobQueue := make(chan *ReleaseSpec, len(st.Releases)) | ||||||
| 	results := make(chan *downloadResults, len(state.Releases)) | 	results := make(chan *downloadResults, len(st.Releases)) | ||||||
| 	wgFetch.Add(len(state.Releases)) | 	wgFetch.Add(len(st.Releases)) | ||||||
| 
 | 
 | ||||||
| 	if workerLimit < 1 { | 	if workerLimit < 1 { | ||||||
| 		workerLimit = len(state.Releases) | 		workerLimit = len(st.Releases) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	for w := 1; w <= workerLimit; w++ { | 	for w := 1; w <= workerLimit; w++ { | ||||||
| 		go func() { | 		go func() { | ||||||
| 			for release := range jobQueue { | 			for release := range jobQueue { | ||||||
| 				chartPath := "" | 				chartPath := "" | ||||||
| 				if pathExists(normalizeChart(state.basePath, release.Chart)) { | 				if pathExists(normalizeChart(st.basePath, release.Chart)) { | ||||||
| 					chartPath = normalizeChart(state.basePath, release.Chart) | 					chartPath = normalizeChart(st.basePath, release.Chart) | ||||||
| 				} else { | 				} else { | ||||||
| 					fetchFlags := []string{} | 					fetchFlags := []string{} | ||||||
| 					if release.Version != "" { | 					if release.Version != "" { | ||||||
|  | @ -400,12 +406,12 @@ func (state *HelmState) downloadCharts(helm helmexec.Interface, dir string, work | ||||||
| 			wgFetch.Done() | 			wgFetch.Done() | ||||||
| 		}() | 		}() | ||||||
| 	} | 	} | ||||||
| 	for i := 0; i < len(state.Releases); i++ { | 	for i := 0; i < len(st.Releases); i++ { | ||||||
| 		jobQueue <- &state.Releases[i] | 		jobQueue <- &st.Releases[i] | ||||||
| 	} | 	} | ||||||
| 	close(jobQueue) | 	close(jobQueue) | ||||||
| 
 | 
 | ||||||
| 	for i := 0; i < len(state.Releases); i++ { | 	for i := 0; i < len(st.Releases); i++ { | ||||||
| 		downloadRes := <-results | 		downloadRes := <-results | ||||||
| 		temp[downloadRes.releaseName] = downloadRes.chartPath | 		temp[downloadRes.releaseName] = downloadRes.chartPath | ||||||
| 	} | 	} | ||||||
|  | @ -419,7 +425,7 @@ func (state *HelmState) downloadCharts(helm helmexec.Interface, dir string, work | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // TemplateReleases wrapper for executing helm template on the releases
 | // TemplateReleases wrapper for executing helm template on the releases
 | ||||||
| func (state *HelmState) TemplateReleases(helm helmexec.Interface, additionalValues []string, args []string, workerLimit int) []error { | func (st *HelmState) TemplateReleases(helm helmexec.Interface, additionalValues []string, args []string, workerLimit int) []error { | ||||||
| 	errs := []error{} | 	errs := []error{} | ||||||
| 	// Create tmp directory and bail immediately if it fails
 | 	// Create tmp directory and bail immediately if it fails
 | ||||||
| 	dir, err := ioutil.TempDir("", "") | 	dir, err := ioutil.TempDir("", "") | ||||||
|  | @ -429,7 +435,7 @@ func (state *HelmState) TemplateReleases(helm helmexec.Interface, additionalValu | ||||||
| 	} | 	} | ||||||
| 	defer os.RemoveAll(dir) | 	defer os.RemoveAll(dir) | ||||||
| 
 | 
 | ||||||
| 	temp, errs := state.downloadCharts(helm, dir, workerLimit, "template") | 	temp, errs := st.downloadCharts(helm, dir, workerLimit, "template") | ||||||
| 
 | 
 | ||||||
| 	if errs != nil { | 	if errs != nil { | ||||||
| 		errs = append(errs, err) | 		errs = append(errs, err) | ||||||
|  | @ -440,8 +446,8 @@ func (state *HelmState) TemplateReleases(helm helmexec.Interface, additionalValu | ||||||
| 		helm.SetExtraArgs(args...) | 		helm.SetExtraArgs(args...) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	for _, release := range state.Releases { | 	for _, release := range st.Releases { | ||||||
| 		flags, err := state.flagsForTemplate(helm, &release) | 		flags, err := st.flagsForTemplate(helm, &release) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			errs = append(errs, err) | 			errs = append(errs, err) | ||||||
| 		} | 		} | ||||||
|  | @ -463,8 +469,8 @@ func (state *HelmState) TemplateReleases(helm helmexec.Interface, additionalValu | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		if _, err := state.triggerCleanupEvent(&release, "template"); err != nil { | 		if _, err := st.triggerCleanupEvent(&release, "template"); err != nil { | ||||||
| 			state.logger.Warnf("warn: %v\n", err) | 			st.logger.Warnf("warn: %v\n", err) | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | @ -476,7 +482,7 @@ func (state *HelmState) TemplateReleases(helm helmexec.Interface, additionalValu | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // LintReleases wrapper for executing helm lint on the releases
 | // LintReleases wrapper for executing helm lint on the releases
 | ||||||
| func (state *HelmState) LintReleases(helm helmexec.Interface, additionalValues []string, args []string, workerLimit int) []error { | func (st *HelmState) LintReleases(helm helmexec.Interface, additionalValues []string, args []string, workerLimit int) []error { | ||||||
| 	errs := []error{} | 	errs := []error{} | ||||||
| 	// Create tmp directory and bail immediately if it fails
 | 	// Create tmp directory and bail immediately if it fails
 | ||||||
| 	dir, err := ioutil.TempDir("", "") | 	dir, err := ioutil.TempDir("", "") | ||||||
|  | @ -486,7 +492,7 @@ func (state *HelmState) LintReleases(helm helmexec.Interface, additionalValues [ | ||||||
| 	} | 	} | ||||||
| 	defer os.RemoveAll(dir) | 	defer os.RemoveAll(dir) | ||||||
| 
 | 
 | ||||||
| 	temp, errs := state.downloadCharts(helm, dir, workerLimit, "lint") | 	temp, errs := st.downloadCharts(helm, dir, workerLimit, "lint") | ||||||
| 	if errs != nil { | 	if errs != nil { | ||||||
| 		errs = append(errs, err) | 		errs = append(errs, err) | ||||||
| 		return errs | 		return errs | ||||||
|  | @ -496,8 +502,8 @@ func (state *HelmState) LintReleases(helm helmexec.Interface, additionalValues [ | ||||||
| 		helm.SetExtraArgs(args...) | 		helm.SetExtraArgs(args...) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	for _, release := range state.Releases { | 	for _, release := range st.Releases { | ||||||
| 		flags, err := state.flagsForLint(helm, &release) | 		flags, err := st.flagsForLint(helm, &release) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			errs = append(errs, err) | 			errs = append(errs, err) | ||||||
| 		} | 		} | ||||||
|  | @ -519,8 +525,8 @@ func (state *HelmState) LintReleases(helm helmexec.Interface, additionalValues [ | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		if _, err := state.triggerCleanupEvent(&release, "lint"); err != nil { | 		if _, err := st.triggerCleanupEvent(&release, "lint"); err != nil { | ||||||
| 			state.logger.Warnf("warn: %v\n", err) | 			st.logger.Warnf("warn: %v\n", err) | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | @ -551,9 +557,9 @@ type diffPrepareResult struct { | ||||||
| 	errors  []*ReleaseError | 	errors  []*ReleaseError | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (state *HelmState) prepareDiffReleases(helm helmexec.Interface, additionalValues []string, concurrency int, detailedExitCode, suppressSecrets bool) ([]diffPrepareResult, []error) { | func (st *HelmState) prepareDiffReleases(helm helmexec.Interface, additionalValues []string, concurrency int, detailedExitCode, suppressSecrets bool) ([]diffPrepareResult, []error) { | ||||||
| 	releases := []ReleaseSpec{} | 	releases := []ReleaseSpec{} | ||||||
| 	for _, r := range state.Releases { | 	for _, r := range st.Releases { | ||||||
| 		if r.Installed == nil || *r.Installed { | 		if r.Installed == nil || *r.Installed { | ||||||
| 			releases = append(releases, r) | 			releases = append(releases, r) | ||||||
| 		} | 		} | ||||||
|  | @ -575,9 +581,9 @@ func (state *HelmState) prepareDiffReleases(helm helmexec.Interface, additionalV | ||||||
| 			for release := range jobs { | 			for release := range jobs { | ||||||
| 				errs := []error{} | 				errs := []error{} | ||||||
| 
 | 
 | ||||||
| 				state.applyDefaultsTo(release) | 				st.applyDefaultsTo(release) | ||||||
| 
 | 
 | ||||||
| 				flags, err := state.flagsForDiff(helm, release) | 				flags, err := st.flagsForDiff(helm, release) | ||||||
| 				if err != nil { | 				if err != nil { | ||||||
| 					errs = append(errs, err) | 					errs = append(errs, err) | ||||||
| 				} | 				} | ||||||
|  | @ -644,8 +650,8 @@ func (state *HelmState) prepareDiffReleases(helm helmexec.Interface, additionalV | ||||||
| 
 | 
 | ||||||
| // DiffReleases wrapper for executing helm diff on the releases
 | // DiffReleases wrapper for executing helm diff on the releases
 | ||||||
| // It returns releases that had any changes
 | // It returns releases that had any changes
 | ||||||
| func (state *HelmState) DiffReleases(helm helmexec.Interface, additionalValues []string, workerLimit int, detailedExitCode, suppressSecrets bool, triggerCleanupEvents bool) ([]*ReleaseSpec, []error) { | func (st *HelmState) DiffReleases(helm helmexec.Interface, additionalValues []string, workerLimit int, detailedExitCode, suppressSecrets bool, triggerCleanupEvents bool) ([]*ReleaseSpec, []error) { | ||||||
| 	preps, prepErrs := state.prepareDiffReleases(helm, additionalValues, workerLimit, detailedExitCode, suppressSecrets) | 	preps, prepErrs := st.prepareDiffReleases(helm, additionalValues, workerLimit, detailedExitCode, suppressSecrets) | ||||||
| 	if len(prepErrs) > 0 { | 	if len(prepErrs) > 0 { | ||||||
| 		return []*ReleaseSpec{}, prepErrs | 		return []*ReleaseSpec{}, prepErrs | ||||||
| 	} | 	} | ||||||
|  | @ -668,7 +674,7 @@ func (state *HelmState) DiffReleases(helm helmexec.Interface, additionalValues [ | ||||||
| 			for prep := range jobQueue { | 			for prep := range jobQueue { | ||||||
| 				flags := prep.flags | 				flags := prep.flags | ||||||
| 				release := prep.release | 				release := prep.release | ||||||
| 				if err := helm.DiffRelease(release.Name, normalizeChart(state.basePath, release.Chart), flags...); err != nil { | 				if err := helm.DiffRelease(release.Name, normalizeChart(st.basePath, release.Chart), flags...); err != nil { | ||||||
| 					switch e := err.(type) { | 					switch e := err.(type) { | ||||||
| 					case *exec.ExitError: | 					case *exec.ExitError: | ||||||
| 						// Propagate any non-zero exit status from the external command like `helm` that is failed under the hood
 | 						// Propagate any non-zero exit status from the external command like `helm` that is failed under the hood
 | ||||||
|  | @ -683,8 +689,8 @@ func (state *HelmState) DiffReleases(helm helmexec.Interface, additionalValues [ | ||||||
| 				} | 				} | ||||||
| 
 | 
 | ||||||
| 				if triggerCleanupEvents { | 				if triggerCleanupEvents { | ||||||
| 					if _, err := state.triggerCleanupEvent(prep.release, "diff"); err != nil { | 					if _, err := st.triggerCleanupEvent(prep.release, "diff"); err != nil { | ||||||
| 						state.logger.Warnf("warn: %v\n", err) | 						st.logger.Warnf("warn: %v\n", err) | ||||||
| 					} | 					} | ||||||
| 				} | 				} | ||||||
| 			} | 			} | ||||||
|  | @ -718,14 +724,14 @@ func (state *HelmState) DiffReleases(helm helmexec.Interface, additionalValues [ | ||||||
| 	return rs, errs | 	return rs, errs | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (state *HelmState) ReleaseStatuses(helm helmexec.Interface, workerLimit int) []error { | func (st *HelmState) ReleaseStatuses(helm helmexec.Interface, workerLimit int) []error { | ||||||
| 	var errs []error | 	var errs []error | ||||||
| 	jobQueue := make(chan ReleaseSpec) | 	jobQueue := make(chan ReleaseSpec) | ||||||
| 	doneQueue := make(chan bool) | 	doneQueue := make(chan bool) | ||||||
| 	errQueue := make(chan error) | 	errQueue := make(chan error) | ||||||
| 
 | 
 | ||||||
| 	if workerLimit < 1 { | 	if workerLimit < 1 { | ||||||
| 		workerLimit = len(state.Releases) | 		workerLimit = len(st.Releases) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// WaitGroup is required to wait until goroutine per job in job queue cleanly stops.
 | 	// WaitGroup is required to wait until goroutine per job in job queue cleanly stops.
 | ||||||
|  | @ -745,13 +751,13 @@ func (state *HelmState) ReleaseStatuses(helm helmexec.Interface, workerLimit int | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	go func() { | 	go func() { | ||||||
| 		for _, release := range state.Releases { | 		for _, release := range st.Releases { | ||||||
| 			jobQueue <- release | 			jobQueue <- release | ||||||
| 		} | 		} | ||||||
| 		close(jobQueue) | 		close(jobQueue) | ||||||
| 	}() | 	}() | ||||||
| 
 | 
 | ||||||
| 	for i := 0; i < len(state.Releases); { | 	for i := 0; i < len(st.Releases); { | ||||||
| 		select { | 		select { | ||||||
| 		case err := <-errQueue: | 		case err := <-errQueue: | ||||||
| 			errs = append(errs, err) | 			errs = append(errs, err) | ||||||
|  | @ -770,11 +776,11 @@ func (state *HelmState) ReleaseStatuses(helm helmexec.Interface, workerLimit int | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // DeleteReleases wrapper for executing helm delete on the releases
 | // DeleteReleases wrapper for executing helm delete on the releases
 | ||||||
| func (state *HelmState) DeleteReleases(helm helmexec.Interface, purge bool) []error { | func (st *HelmState) DeleteReleases(helm helmexec.Interface, purge bool) []error { | ||||||
| 	var wg sync.WaitGroup | 	var wg sync.WaitGroup | ||||||
| 	errs := []error{} | 	errs := []error{} | ||||||
| 
 | 
 | ||||||
| 	for _, release := range state.Releases { | 	for _, release := range st.Releases { | ||||||
| 		wg.Add(1) | 		wg.Add(1) | ||||||
| 		go func(wg *sync.WaitGroup, release ReleaseSpec) { | 		go func(wg *sync.WaitGroup, release ReleaseSpec) { | ||||||
| 			flags := []string{} | 			flags := []string{} | ||||||
|  | @ -797,11 +803,11 @@ func (state *HelmState) DeleteReleases(helm helmexec.Interface, purge bool) []er | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // TestReleases wrapper for executing helm test on the releases
 | // TestReleases wrapper for executing helm test on the releases
 | ||||||
| func (state *HelmState) TestReleases(helm helmexec.Interface, cleanup bool, timeout int) []error { | func (st *HelmState) TestReleases(helm helmexec.Interface, cleanup bool, timeout int) []error { | ||||||
| 	var wg sync.WaitGroup | 	var wg sync.WaitGroup | ||||||
| 	errs := []error{} | 	errs := []error{} | ||||||
| 
 | 
 | ||||||
| 	for _, release := range state.Releases { | 	for _, release := range st.Releases { | ||||||
| 		wg.Add(1) | 		wg.Add(1) | ||||||
| 		go func(wg *sync.WaitGroup, release ReleaseSpec) { | 		go func(wg *sync.WaitGroup, release ReleaseSpec) { | ||||||
| 			flags := []string{} | 			flags := []string{} | ||||||
|  | @ -825,10 +831,10 @@ func (state *HelmState) TestReleases(helm helmexec.Interface, cleanup bool, time | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // Clean will remove any generated secrets
 | // Clean will remove any generated secrets
 | ||||||
| func (state *HelmState) Clean() []error { | func (st *HelmState) Clean() []error { | ||||||
| 	errs := []error{} | 	errs := []error{} | ||||||
| 
 | 
 | ||||||
| 	for _, release := range state.Releases { | 	for _, release := range st.Releases { | ||||||
| 		for _, value := range release.generatedValues { | 		for _, value := range release.generatedValues { | ||||||
| 			err := os.Remove(value) | 			err := os.Remove(value) | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
|  | @ -845,7 +851,7 @@ func (state *HelmState) Clean() []error { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // FilterReleases allows for the execution of helm commands against a subset of the releases in the helmfile.
 | // FilterReleases allows for the execution of helm commands against a subset of the releases in the helmfile.
 | ||||||
| func (state *HelmState) FilterReleases(labels []string) error { | func (st *HelmState) FilterReleases(labels []string) error { | ||||||
| 	var filteredReleases []ReleaseSpec | 	var filteredReleases []ReleaseSpec | ||||||
| 	releaseSet := map[string][]ReleaseSpec{} | 	releaseSet := map[string][]ReleaseSpec{} | ||||||
| 	filters := []ReleaseFilter{} | 	filters := []ReleaseFilter{} | ||||||
|  | @ -856,7 +862,7 @@ func (state *HelmState) FilterReleases(labels []string) error { | ||||||
| 		} | 		} | ||||||
| 		filters = append(filters, f) | 		filters = append(filters, f) | ||||||
| 	} | 	} | ||||||
| 	for _, r := range state.Releases { | 	for _, r := range st.Releases { | ||||||
| 		if r.Labels == nil { | 		if r.Labels == nil { | ||||||
| 			r.Labels = map[string]string{} | 			r.Labels = map[string]string{} | ||||||
| 		} | 		} | ||||||
|  | @ -875,17 +881,17 @@ func (state *HelmState) FilterReleases(labels []string) error { | ||||||
| 	for _, r := range releaseSet { | 	for _, r := range releaseSet { | ||||||
| 		filteredReleases = append(filteredReleases, r...) | 		filteredReleases = append(filteredReleases, r...) | ||||||
| 	} | 	} | ||||||
| 	state.Releases = filteredReleases | 	st.Releases = filteredReleases | ||||||
| 	numFound := len(filteredReleases) | 	numFound := len(filteredReleases) | ||||||
| 	state.logger.Debugf("%d release(s) matching %s found in %s\n", numFound, strings.Join(labels, ","), state.FilePath) | 	st.logger.Debugf("%d release(s) matching %s found in %s\n", numFound, strings.Join(labels, ","), st.FilePath) | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (state *HelmState) PrepareRelease(helm helmexec.Interface, helmfileCommand string) []error { | func (st *HelmState) PrepareRelease(helm helmexec.Interface, helmfileCommand string) []error { | ||||||
| 	errs := []error{} | 	errs := []error{} | ||||||
| 
 | 
 | ||||||
| 	for _, release := range state.Releases { | 	for _, release := range st.Releases { | ||||||
| 		if _, err := state.triggerPrepareEvent(&release, helmfileCommand); err != nil { | 		if _, err := st.triggerPrepareEvent(&release, helmfileCommand); err != nil { | ||||||
| 			errs = append(errs, &ReleaseError{&release, err}) | 			errs = append(errs, &ReleaseError{&release, err}) | ||||||
| 			continue | 			continue | ||||||
| 		} | 		} | ||||||
|  | @ -896,23 +902,23 @@ func (state *HelmState) PrepareRelease(helm helmexec.Interface, helmfileCommand | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (state *HelmState) triggerPrepareEvent(r *ReleaseSpec, helmfileCommand string) (bool, error) { | func (st *HelmState) triggerPrepareEvent(r *ReleaseSpec, helmfileCommand string) (bool, error) { | ||||||
| 	return state.triggerReleaseEvent("prepare", r, helmfileCommand) | 	return st.triggerReleaseEvent("prepare", r, helmfileCommand) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (state *HelmState) triggerCleanupEvent(r *ReleaseSpec, helmfileCommand string) (bool, error) { | func (st *HelmState) triggerCleanupEvent(r *ReleaseSpec, helmfileCommand string) (bool, error) { | ||||||
| 	return state.triggerReleaseEvent("cleanup", r, helmfileCommand) | 	return st.triggerReleaseEvent("cleanup", r, helmfileCommand) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (state *HelmState) triggerReleaseEvent(evt string, r *ReleaseSpec, helmfileCmd string) (bool, error) { | func (st *HelmState) triggerReleaseEvent(evt string, r *ReleaseSpec, helmfileCmd string) (bool, error) { | ||||||
| 	bus := &event.Bus{ | 	bus := &event.Bus{ | ||||||
| 		Hooks:         r.Hooks, | 		Hooks:         r.Hooks, | ||||||
| 		StateFilePath: state.FilePath, | 		StateFilePath: st.FilePath, | ||||||
| 		BasePath:      state.basePath, | 		BasePath:      st.basePath, | ||||||
| 		Namespace:     state.Namespace, | 		Namespace:     st.Namespace, | ||||||
| 		Env:           state.Env, | 		Env:           st.Env, | ||||||
| 		Logger:        state.logger, | 		Logger:        st.logger, | ||||||
| 		ReadFile:      state.readFile, | 		ReadFile:      st.readFile, | ||||||
| 	} | 	} | ||||||
| 	data := map[string]interface{}{ | 	data := map[string]interface{}{ | ||||||
| 		"Release":         r, | 		"Release":         r, | ||||||
|  | @ -922,12 +928,12 @@ func (state *HelmState) triggerReleaseEvent(evt string, r *ReleaseSpec, helmfile | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // UpdateDeps wrapper for updating dependencies on the releases
 | // UpdateDeps wrapper for updating dependencies on the releases
 | ||||||
| func (state *HelmState) UpdateDeps(helm helmexec.Interface) []error { | func (st *HelmState) UpdateDeps(helm helmexec.Interface) []error { | ||||||
| 	errs := []error{} | 	errs := []error{} | ||||||
| 
 | 
 | ||||||
| 	for _, release := range state.Releases { | 	for _, release := range st.Releases { | ||||||
| 		if isLocalChart(release.Chart) { | 		if isLocalChart(release.Chart) { | ||||||
| 			if err := helm.UpdateDeps(normalizeChart(state.basePath, release.Chart)); err != nil { | 			if err := helm.UpdateDeps(normalizeChart(st.basePath, release.Chart)); err != nil { | ||||||
| 				errs = append(errs, err) | 				errs = append(errs, err) | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
|  | @ -939,16 +945,16 @@ func (state *HelmState) UpdateDeps(helm helmexec.Interface) []error { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // JoinBase returns an absolute path in the form basePath/relative
 | // JoinBase returns an absolute path in the form basePath/relative
 | ||||||
| func (state *HelmState) JoinBase(relPath string) string { | func (st *HelmState) JoinBase(relPath string) string { | ||||||
| 	return filepath.Join(state.basePath, relPath) | 	return filepath.Join(st.basePath, relPath) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // normalizes relative path to absolute one
 | // normalizes relative path to absolute one
 | ||||||
| func (state *HelmState) normalizePath(path string) string { | func (st *HelmState) normalizePath(path string) string { | ||||||
| 	if filepath.IsAbs(path) { | 	if filepath.IsAbs(path) { | ||||||
| 		return path | 		return path | ||||||
| 	} else { | 	} else { | ||||||
| 		return state.JoinBase(path) | 		return st.JoinBase(path) | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -1000,25 +1006,25 @@ func findChartDirectory(topLevelDir string) (string, error) { | ||||||
| 	return topLevelDir, errors.New("No Chart.yaml found") | 	return topLevelDir, errors.New("No Chart.yaml found") | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (state *HelmState) flagsForUpgrade(helm helmexec.Interface, release *ReleaseSpec) ([]string, error) { | func (st *HelmState) flagsForUpgrade(helm helmexec.Interface, release *ReleaseSpec) ([]string, error) { | ||||||
| 	flags := []string{} | 	flags := []string{} | ||||||
| 	if release.Version != "" { | 	if release.Version != "" { | ||||||
| 		flags = append(flags, "--version", release.Version) | 		flags = append(flags, "--version", release.Version) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if state.isDevelopment(release) { | 	if st.isDevelopment(release) { | ||||||
| 		flags = append(flags, "--devel") | 		flags = append(flags, "--devel") | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if release.Verify != nil && *release.Verify || state.HelmDefaults.Verify { | 	if release.Verify != nil && *release.Verify || st.HelmDefaults.Verify { | ||||||
| 		flags = append(flags, "--verify") | 		flags = append(flags, "--verify") | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if release.Wait != nil && *release.Wait || state.HelmDefaults.Wait { | 	if release.Wait != nil && *release.Wait || st.HelmDefaults.Wait { | ||||||
| 		flags = append(flags, "--wait") | 		flags = append(flags, "--wait") | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	timeout := state.HelmDefaults.Timeout | 	timeout := st.HelmDefaults.Timeout | ||||||
| 	if release.Timeout != nil { | 	if release.Timeout != nil { | ||||||
| 		timeout = *release.Timeout | 		timeout = *release.Timeout | ||||||
| 	} | 	} | ||||||
|  | @ -1026,51 +1032,51 @@ func (state *HelmState) flagsForUpgrade(helm helmexec.Interface, release *Releas | ||||||
| 		flags = append(flags, "--timeout", fmt.Sprintf("%d", timeout)) | 		flags = append(flags, "--timeout", fmt.Sprintf("%d", timeout)) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if release.Force != nil && *release.Force || state.HelmDefaults.Force { | 	if release.Force != nil && *release.Force || st.HelmDefaults.Force { | ||||||
| 		flags = append(flags, "--force") | 		flags = append(flags, "--force") | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if release.RecreatePods != nil && *release.RecreatePods || state.HelmDefaults.RecreatePods { | 	if release.RecreatePods != nil && *release.RecreatePods || st.HelmDefaults.RecreatePods { | ||||||
| 		flags = append(flags, "--recreate-pods") | 		flags = append(flags, "--recreate-pods") | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	common, err := state.namespaceAndValuesFlags(helm, release) | 	common, err := st.namespaceAndValuesFlags(helm, release) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
| 	return append(flags, common...), nil | 	return append(flags, common...), nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (state *HelmState) flagsForTemplate(helm helmexec.Interface, release *ReleaseSpec) ([]string, error) { | func (st *HelmState) flagsForTemplate(helm helmexec.Interface, release *ReleaseSpec) ([]string, error) { | ||||||
| 	flags := []string{ | 	flags := []string{ | ||||||
| 		"--name", release.Name, | 		"--name", release.Name, | ||||||
| 	} | 	} | ||||||
| 	common, err := state.namespaceAndValuesFlags(helm, release) | 	common, err := st.namespaceAndValuesFlags(helm, release) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
| 	return append(flags, common...), nil | 	return append(flags, common...), nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (state *HelmState) flagsForDiff(helm helmexec.Interface, release *ReleaseSpec) ([]string, error) { | func (st *HelmState) flagsForDiff(helm helmexec.Interface, release *ReleaseSpec) ([]string, error) { | ||||||
| 	flags := []string{} | 	flags := []string{} | ||||||
| 	if release.Version != "" { | 	if release.Version != "" { | ||||||
| 		flags = append(flags, "--version", release.Version) | 		flags = append(flags, "--version", release.Version) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if state.isDevelopment(release) { | 	if st.isDevelopment(release) { | ||||||
| 		flags = append(flags, "--devel") | 		flags = append(flags, "--devel") | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	common, err := state.namespaceAndValuesFlags(helm, release) | 	common, err := st.namespaceAndValuesFlags(helm, release) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
| 	return append(flags, common...), nil | 	return append(flags, common...), nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (state *HelmState) isDevelopment(release *ReleaseSpec) bool { | func (st *HelmState) isDevelopment(release *ReleaseSpec) bool { | ||||||
| 	result := state.HelmDefaults.Devel | 	result := st.HelmDefaults.Devel | ||||||
| 	if release.Devel != nil { | 	if release.Devel != nil { | ||||||
| 		result = *release.Devel | 		result = *release.Devel | ||||||
| 	} | 	} | ||||||
|  | @ -1078,16 +1084,16 @@ func (state *HelmState) isDevelopment(release *ReleaseSpec) bool { | ||||||
| 	return result | 	return result | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (state *HelmState) flagsForLint(helm helmexec.Interface, release *ReleaseSpec) ([]string, error) { | func (st *HelmState) flagsForLint(helm helmexec.Interface, release *ReleaseSpec) ([]string, error) { | ||||||
| 	return state.namespaceAndValuesFlags(helm, release) | 	return st.namespaceAndValuesFlags(helm, release) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (state *HelmState) RenderValuesFileToBytes(path string) ([]byte, error) { | func (st *HelmState) RenderValuesFileToBytes(path string) ([]byte, error) { | ||||||
| 	r := valuesfile.NewRenderer(state.readFile, filepath.Dir(path), state.Env) | 	r := tmpl.NewFileRenderer(st.readFile, filepath.Dir(path), st.envTemplateData()) | ||||||
| 	return r.RenderToBytes(path) | 	return r.RenderToBytes(path) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (state *HelmState) namespaceAndValuesFlags(helm helmexec.Interface, release *ReleaseSpec) ([]string, error) { | func (st *HelmState) namespaceAndValuesFlags(helm helmexec.Interface, release *ReleaseSpec) ([]string, error) { | ||||||
| 	flags := []string{} | 	flags := []string{} | ||||||
| 	if release.Namespace != "" { | 	if release.Namespace != "" { | ||||||
| 		flags = append(flags, "--namespace", release.Namespace) | 		flags = append(flags, "--namespace", release.Namespace) | ||||||
|  | @ -1095,13 +1101,18 @@ func (state *HelmState) namespaceAndValuesFlags(helm helmexec.Interface, release | ||||||
| 	for _, value := range release.Values { | 	for _, value := range release.Values { | ||||||
| 		switch typedValue := value.(type) { | 		switch typedValue := value.(type) { | ||||||
| 		case string: | 		case string: | ||||||
| 			path := state.normalizePath(release.ValuesPathPrefix + typedValue) | 			path := st.normalizePath(release.ValuesPathPrefix + typedValue) | ||||||
| 
 | 
 | ||||||
| 			if _, err := os.Stat(path); os.IsNotExist(err) { | 			if _, err := os.Stat(path); os.IsNotExist(err) { | ||||||
| 				return nil, err | 				if release.MissingFileHandler == nil && *release.MissingFileHandler == "Error" { | ||||||
|  | 					return nil, err | ||||||
|  | 				} else { | ||||||
|  | 					st.logger.Warnf("skipping missing values file \"%s\"", path) | ||||||
|  | 					continue | ||||||
|  | 				} | ||||||
| 			} | 			} | ||||||
| 
 | 
 | ||||||
| 			yamlBytes, err := state.RenderValuesFileToBytes(path) | 			yamlBytes, err := st.RenderValuesFileToBytes(path) | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| 				return nil, fmt.Errorf("failed to render values files \"%s\": %v", typedValue, err) | 				return nil, fmt.Errorf("failed to render values files \"%s\": %v", typedValue, err) | ||||||
| 			} | 			} | ||||||
|  | @ -1115,7 +1126,7 @@ func (state *HelmState) namespaceAndValuesFlags(helm helmexec.Interface, release | ||||||
| 			if _, err := valfile.Write(yamlBytes); err != nil { | 			if _, err := valfile.Write(yamlBytes); err != nil { | ||||||
| 				return nil, fmt.Errorf("failed to write %s: %v", valfile.Name(), err) | 				return nil, fmt.Errorf("failed to write %s: %v", valfile.Name(), err) | ||||||
| 			} | 			} | ||||||
| 			state.logger.Debugf("successfully generated the value file at %s. produced:\n%s", path, string(yamlBytes)) | 			st.logger.Debugf("successfully generated the value file at %s. produced:\n%s", path, string(yamlBytes)) | ||||||
| 			flags = append(flags, "--values", valfile.Name()) | 			flags = append(flags, "--values", valfile.Name()) | ||||||
| 
 | 
 | ||||||
| 		case map[interface{}]interface{}: | 		case map[interface{}]interface{}: | ||||||
|  | @ -1133,10 +1144,16 @@ func (state *HelmState) namespaceAndValuesFlags(helm helmexec.Interface, release | ||||||
| 			flags = append(flags, "--values", valfile.Name()) | 			flags = append(flags, "--values", valfile.Name()) | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  | 
 | ||||||
| 	for _, value := range release.Secrets { | 	for _, value := range release.Secrets { | ||||||
| 		path := state.normalizePath(release.ValuesPathPrefix + value) | 		path := st.normalizePath(release.ValuesPathPrefix + value) | ||||||
| 		if _, err := os.Stat(path); os.IsNotExist(err) { | 		if _, err := os.Stat(path); os.IsNotExist(err) { | ||||||
| 			return nil, err | 			if release.MissingFileHandler == nil && *release.MissingFileHandler == "Error" { | ||||||
|  | 				return nil, err | ||||||
|  | 			} else { | ||||||
|  | 				st.logger.Warnf("skipping missing secrets file \"%s\"", path) | ||||||
|  | 				continue | ||||||
|  | 			} | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		valfile, err := helm.DecryptSecret(path) | 		valfile, err := helm.DecryptSecret(path) | ||||||
|  | @ -1152,7 +1169,7 @@ func (state *HelmState) namespaceAndValuesFlags(helm helmexec.Interface, release | ||||||
| 			if set.Value != "" { | 			if set.Value != "" { | ||||||
| 				flags = append(flags, "--set", fmt.Sprintf("%s=%s", escape(set.Name), escape(set.Value))) | 				flags = append(flags, "--set", fmt.Sprintf("%s=%s", escape(set.Name), escape(set.Value))) | ||||||
| 			} else if set.File != "" { | 			} else if set.File != "" { | ||||||
| 				flags = append(flags, "--set-file", fmt.Sprintf("%s=%s", escape(set.Name), state.normalizePath(set.File))) | 				flags = append(flags, "--set-file", fmt.Sprintf("%s=%s", escape(set.Name), st.normalizePath(set.File))) | ||||||
| 			} else if len(set.Values) > 0 { | 			} else if len(set.Values) > 0 { | ||||||
| 				items := make([]string, len(set.Values)) | 				items := make([]string, len(set.Values)) | ||||||
| 				for i, raw := range set.Values { | 				for i, raw := range set.Values { | ||||||
|  |  | ||||||
|  | @ -0,0 +1,32 @@ | ||||||
|  | package state | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"fmt" | ||||||
|  | 	"github.com/roboll/helmfile/tmpl" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | func (st *HelmState) envTemplateData() EnvironmentTemplateData { | ||||||
|  | 	return EnvironmentTemplateData{ | ||||||
|  | 		st.Env, | ||||||
|  | 		st.Namespace, | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (st *HelmState) ExecuteTemplates() (*HelmState, error) { | ||||||
|  | 	r := *st | ||||||
|  | 
 | ||||||
|  | 	for i, rt := range st.Releases { | ||||||
|  | 		tmplData := ReleaseTemplateData{ | ||||||
|  | 			Environment: st.Env, | ||||||
|  | 			Release:     rt, | ||||||
|  | 		} | ||||||
|  | 		renderer := tmpl.NewFileRenderer(st.readFile, st.basePath, tmplData) | ||||||
|  | 		r, err := rt.ExecuteTemplateExpressions(renderer) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return nil, fmt.Errorf("failed executing templates in release \"%s\".\"%s\": %v", st.FilePath, rt.Name, err) | ||||||
|  | 		} | ||||||
|  | 		st.Releases[i] = *r | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return &r, nil | ||||||
|  | } | ||||||
|  | @ -0,0 +1,76 @@ | ||||||
|  | package state | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"reflect" | ||||||
|  | 	"testing" | ||||||
|  | 
 | ||||||
|  | 	"github.com/roboll/helmfile/environment" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | func TestHelmState_executeTemplates(t *testing.T) { | ||||||
|  | 	tests := []struct { | ||||||
|  | 		name  string | ||||||
|  | 		input ReleaseSpec | ||||||
|  | 		want  ReleaseSpec | ||||||
|  | 	}{ | ||||||
|  | 		{ | ||||||
|  | 			name: "Has template expressions in chart, values, and secrets", | ||||||
|  | 			input: ReleaseSpec{ | ||||||
|  | 				Chart:     "test-charts/{{ .Release.Name }}", | ||||||
|  | 				Version:   "0.1", | ||||||
|  | 				Verify:    nil, | ||||||
|  | 				Name:      "test-app", | ||||||
|  | 				Namespace: "test-namespace-{{ .Release.Name }}", | ||||||
|  | 				Values:    []interface{}{"config/{{ .Environment.Name }}/{{ .Release.Name }}/values.yaml"}, | ||||||
|  | 				Secrets:   []string{"config/{{ .Environment.Name }}/{{ .Release.Name }}/secrets.yaml"}, | ||||||
|  | 			}, | ||||||
|  | 			want: ReleaseSpec{ | ||||||
|  | 				Chart:     "test-charts/test-app", | ||||||
|  | 				Version:   "0.1", | ||||||
|  | 				Verify:    nil, | ||||||
|  | 				Name:      "test-app", | ||||||
|  | 				Namespace: "test-namespace-test-app", | ||||||
|  | 				Values:    []interface{}{"config/test_env/test-app/values.yaml"}, | ||||||
|  | 				Secrets:   []string{"config/test_env/test-app/secrets.yaml"}, | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	for _, tt := range tests { | ||||||
|  | 		t.Run(tt.name, func(t *testing.T) { | ||||||
|  | 			state := &HelmState{ | ||||||
|  | 				basePath: ".", | ||||||
|  | 				HelmDefaults: HelmSpec{ | ||||||
|  | 					KubeContext: "test_context", | ||||||
|  | 				}, | ||||||
|  | 				Env:          environment.Environment{Name: "test_env"}, | ||||||
|  | 				Namespace:    "test-namespace_", | ||||||
|  | 				Repositories: nil, | ||||||
|  | 				Releases: []ReleaseSpec{ | ||||||
|  | 					tt.input, | ||||||
|  | 				}, | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			r, err := state.ExecuteTemplates() | ||||||
|  | 			if err != nil { | ||||||
|  | 				t.Errorf("Unexpected error: %v", err) | ||||||
|  | 				t.FailNow() | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			actual := r.Releases[0] | ||||||
|  | 
 | ||||||
|  | 			if !reflect.DeepEqual(actual.Chart, tt.want.Chart) { | ||||||
|  | 				t.Errorf("expected %+v, got %+v", tt.want.Chart, actual.Chart) | ||||||
|  | 			} | ||||||
|  | 			if !reflect.DeepEqual(actual.Namespace, tt.want.Namespace) { | ||||||
|  | 				t.Errorf("expected %+v, got %+v", tt.want.Namespace, actual.Namespace) | ||||||
|  | 			} | ||||||
|  | 			if !reflect.DeepEqual(actual.Values, tt.want.Values) { | ||||||
|  | 				t.Errorf("expected %+v, got %+v", tt.want.Values, actual.Values) | ||||||
|  | 			} | ||||||
|  | 			if !reflect.DeepEqual(actual.Secrets, tt.want.Secrets) { | ||||||
|  | 				t.Errorf("expected %+v, got %+v", tt.want.Secrets, actual.Secrets) | ||||||
|  | 			} | ||||||
|  | 		}) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | @ -0,0 +1,24 @@ | ||||||
|  | package state | ||||||
|  | 
 | ||||||
|  | import "github.com/roboll/helmfile/environment" | ||||||
|  | 
 | ||||||
|  | // TemplateSpec defines the structure of a reusable and composable template for helm releases.
 | ||||||
|  | type TemplateSpec struct { | ||||||
|  | 	ReleaseSpec `yaml:",inline"` | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // EnvironmentTemplateData provides variables accessible while executing golang text/template expressions in helmfile and values YAML files
 | ||||||
|  | type EnvironmentTemplateData struct { | ||||||
|  | 	// Environment is accessible as `.Environment` from any template executed by the renderer
 | ||||||
|  | 	Environment environment.Environment | ||||||
|  | 	// Namespace is accessible as `.Namespace` from any non-values template executed by the renderer
 | ||||||
|  | 	Namespace string | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // ReleaseTemplateData provides variables accessible while executing golang text/template expressions in releases of a helmfile YAML file
 | ||||||
|  | type ReleaseTemplateData struct { | ||||||
|  | 	// Environment is accessible as `.Environment` from any template expression executed by the renderer
 | ||||||
|  | 	Environment environment.Environment | ||||||
|  | 	// Release is accessible as `.Release` from any template expression executed by the renderer
 | ||||||
|  | 	Release ReleaseSpec | ||||||
|  | } | ||||||
							
								
								
									
										66
									
								
								tmpl/file.go
								
								
								
								
							
							
						
						
									
										66
									
								
								tmpl/file.go
								
								
								
								
							|  | @ -1,66 +0,0 @@ | ||||||
| package tmpl |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
| 	"bytes" |  | ||||||
| 	"io/ioutil" |  | ||||||
| 
 |  | ||||||
| 	"github.com/roboll/helmfile/environment" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| type templateFileRenderer struct { |  | ||||||
| 	ReadFile func(string) ([]byte, error) |  | ||||||
| 	Context  *Context |  | ||||||
| 	Data     TemplateData |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| type TemplateData struct { |  | ||||||
| 	// Environment is accessible as `.Environment` from any template executed by the renderer
 |  | ||||||
| 	Environment environment.Environment |  | ||||||
| 	// Namespace is accessible as `.Namespace` from any non-values template executed by the renderer
 |  | ||||||
| 	Namespace string |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| type FileRenderer interface { |  | ||||||
| 	RenderTemplateFileToBuffer(file string) (*bytes.Buffer, error) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func NewFileRenderer(readFile func(filename string) ([]byte, error), basePath string, env environment.Environment, namespace string) *templateFileRenderer { |  | ||||||
| 	return &templateFileRenderer{ |  | ||||||
| 		ReadFile: readFile, |  | ||||||
| 		Context: &Context{ |  | ||||||
| 			basePath: basePath, |  | ||||||
| 			readFile: readFile, |  | ||||||
| 		}, |  | ||||||
| 		Data: TemplateData{ |  | ||||||
| 			Environment: env, |  | ||||||
| 			Namespace:   namespace, |  | ||||||
| 		}, |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func NewFirstPassRenderer(basePath string, env environment.Environment) *templateFileRenderer { |  | ||||||
| 	return &templateFileRenderer{ |  | ||||||
| 		ReadFile: ioutil.ReadFile, |  | ||||||
| 		Context: &Context{ |  | ||||||
| 			preRender: true, |  | ||||||
| 			basePath:  basePath, |  | ||||||
| 			readFile:  ioutil.ReadFile, |  | ||||||
| 		}, |  | ||||||
| 		Data: TemplateData{ |  | ||||||
| 			Environment: env, |  | ||||||
| 		}, |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (r *templateFileRenderer) RenderTemplateFileToBuffer(file string) (*bytes.Buffer, error) { |  | ||||||
| 	content, err := r.ReadFile(file) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return r.RenderTemplateContentToBuffer(content) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (r *templateFileRenderer) RenderTemplateContentToBuffer(content []byte) (*bytes.Buffer, error) { |  | ||||||
| 	return r.Context.RenderTemplateToBuffer(string(content), r.Data) |  | ||||||
| } |  | ||||||
|  | @ -0,0 +1,78 @@ | ||||||
|  | package tmpl | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"bytes" | ||||||
|  | 	"io/ioutil" | ||||||
|  | 
 | ||||||
|  | 	"fmt" | ||||||
|  | 	"strings" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | type FileRenderer struct { | ||||||
|  | 	ReadFile func(string) ([]byte, error) | ||||||
|  | 	Context  *Context | ||||||
|  | 	Data     interface{} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func NewFileRenderer(readFile func(filename string) ([]byte, error), basePath string, data interface{}) *FileRenderer { | ||||||
|  | 	return &FileRenderer{ | ||||||
|  | 		ReadFile: readFile, | ||||||
|  | 		Context: &Context{ | ||||||
|  | 			basePath: basePath, | ||||||
|  | 			readFile: readFile, | ||||||
|  | 		}, | ||||||
|  | 		Data: data, | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func NewFirstPassRenderer(basePath string, data interface{}) *FileRenderer { | ||||||
|  | 	return &FileRenderer{ | ||||||
|  | 		ReadFile: ioutil.ReadFile, | ||||||
|  | 		Context: &Context{ | ||||||
|  | 			preRender: true, | ||||||
|  | 			basePath:  basePath, | ||||||
|  | 			readFile:  ioutil.ReadFile, | ||||||
|  | 		}, | ||||||
|  | 		Data: data, | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (r *FileRenderer) RenderTemplateFileToBuffer(file string) (*bytes.Buffer, error) { | ||||||
|  | 	content, err := r.ReadFile(file) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return r.RenderTemplateContentToBuffer(content) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (r *FileRenderer) RenderToBytes(path string) ([]byte, error) { | ||||||
|  | 	var yamlBytes []byte | ||||||
|  | 	splits := strings.Split(path, ".") | ||||||
|  | 	if len(splits) > 0 && splits[len(splits)-1] == "gotmpl" { | ||||||
|  | 		yamlBuf, err := r.RenderTemplateFileToBuffer(path) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return nil, fmt.Errorf("failed to render [%s], because of %v", path, err) | ||||||
|  | 		} | ||||||
|  | 		yamlBytes = yamlBuf.Bytes() | ||||||
|  | 	} else { | ||||||
|  | 		var err error | ||||||
|  | 		yamlBytes, err = r.ReadFile(path) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return nil, fmt.Errorf("failed to load [%s]: %v", path, err) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return yamlBytes, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (r *FileRenderer) RenderTemplateContentToBuffer(content []byte) (*bytes.Buffer, error) { | ||||||
|  | 	return r.Context.RenderTemplateToBuffer(string(content), r.Data) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (r *FileRenderer) RenderTemplateContentToString(content []byte) (string, error) { | ||||||
|  | 	buf, err := r.Context.RenderTemplateToBuffer(string(content), r.Data) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return "", err | ||||||
|  | 	} | ||||||
|  | 	return buf.String(), nil | ||||||
|  | } | ||||||
|  | @ -1,4 +1,4 @@ | ||||||
| package valuesfile | package tmpl | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
| 	"fmt" | 	"fmt" | ||||||
|  | @ -7,6 +7,11 @@ import ( | ||||||
| 	"testing" | 	"testing" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
|  | var emptyEnvTmplData = map[string]interface{}{ | ||||||
|  | 	"Environment": environment.EmptyEnvironment, | ||||||
|  | 	"Namespace":   "", | ||||||
|  | } | ||||||
|  | 
 | ||||||
| func TestRenderToBytes_Gotmpl(t *testing.T) { | func TestRenderToBytes_Gotmpl(t *testing.T) { | ||||||
| 	valuesYamlTmplContent := `foo: | 	valuesYamlTmplContent := `foo: | ||||||
|   bar: '{{ readFile "data.txt" }}' |   bar: '{{ readFile "data.txt" }}' | ||||||
|  | @ -17,7 +22,7 @@ func TestRenderToBytes_Gotmpl(t *testing.T) { | ||||||
| ` | ` | ||||||
| 	dataFile := "data.txt" | 	dataFile := "data.txt" | ||||||
| 	valuesTmplFile := "values.yaml.gotmpl" | 	valuesTmplFile := "values.yaml.gotmpl" | ||||||
| 	r := NewRenderer(func(filename string) ([]byte, error) { | 	r := NewFileRenderer(func(filename string) ([]byte, error) { | ||||||
| 		switch filename { | 		switch filename { | ||||||
| 		case valuesTmplFile: | 		case valuesTmplFile: | ||||||
| 			return []byte(valuesYamlTmplContent), nil | 			return []byte(valuesYamlTmplContent), nil | ||||||
|  | @ -25,7 +30,7 @@ func TestRenderToBytes_Gotmpl(t *testing.T) { | ||||||
| 			return []byte(dataFileContent), nil | 			return []byte(dataFileContent), nil | ||||||
| 		} | 		} | ||||||
| 		return nil, fmt.Errorf("unexpected filename: expected=%v or %v, actual=%s", dataFile, valuesTmplFile, filename) | 		return nil, fmt.Errorf("unexpected filename: expected=%v or %v, actual=%s", dataFile, valuesTmplFile, filename) | ||||||
| 	}, "", environment.EmptyEnvironment) | 	}, "", emptyEnvTmplData) | ||||||
| 	buf, err := r.RenderToBytes(valuesTmplFile) | 	buf, err := r.RenderToBytes(valuesTmplFile) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		t.Errorf("unexpected error: %v", err) | 		t.Errorf("unexpected error: %v", err) | ||||||
|  | @ -44,13 +49,13 @@ func TestRenderToBytes_Yaml(t *testing.T) { | ||||||
|   bar: '{{ readFile "data.txt" }}' |   bar: '{{ readFile "data.txt" }}' | ||||||
| ` | ` | ||||||
| 	valuesFile := "values.yaml" | 	valuesFile := "values.yaml" | ||||||
| 	r := NewRenderer(func(filename string) ([]byte, error) { | 	r := NewFileRenderer(func(filename string) ([]byte, error) { | ||||||
| 		switch filename { | 		switch filename { | ||||||
| 		case valuesFile: | 		case valuesFile: | ||||||
| 			return []byte(valuesYamlContent), nil | 			return []byte(valuesYamlContent), nil | ||||||
| 		} | 		} | ||||||
| 		return nil, fmt.Errorf("unexpected filename: expected=%v, actual=%s", valuesFile, filename) | 		return nil, fmt.Errorf("unexpected filename: expected=%v, actual=%s", valuesFile, filename) | ||||||
| 	}, "", environment.EmptyEnvironment) | 	}, "", emptyEnvTmplData) | ||||||
| 	buf, err := r.RenderToBytes(valuesFile) | 	buf, err := r.RenderToBytes(valuesFile) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		t.Errorf("unexpected error: %v", err) | 		t.Errorf("unexpected error: %v", err) | ||||||
|  | @ -1,39 +0,0 @@ | ||||||
| package valuesfile |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
| 	"fmt" |  | ||||||
| 	"github.com/roboll/helmfile/environment" |  | ||||||
| 	"github.com/roboll/helmfile/tmpl" |  | ||||||
| 	"strings" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| type renderer struct { |  | ||||||
| 	readFile         func(string) ([]byte, error) |  | ||||||
| 	tmplFileRenderer tmpl.FileRenderer |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func NewRenderer(readFile func(filename string) ([]byte, error), basePath string, env environment.Environment) *renderer { |  | ||||||
| 	return &renderer{ |  | ||||||
| 		readFile:         readFile, |  | ||||||
| 		tmplFileRenderer: tmpl.NewFileRenderer(readFile, basePath, env, ""), |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (r *renderer) RenderToBytes(path string) ([]byte, error) { |  | ||||||
| 	var yamlBytes []byte |  | ||||||
| 	splits := strings.Split(path, ".") |  | ||||||
| 	if len(splits) > 0 && splits[len(splits)-1] == "gotmpl" { |  | ||||||
| 		yamlBuf, err := r.tmplFileRenderer.RenderTemplateFileToBuffer(path) |  | ||||||
| 		if err != nil { |  | ||||||
| 			return nil, fmt.Errorf("failed to render [%s], because of %v", path, err) |  | ||||||
| 		} |  | ||||||
| 		yamlBytes = yamlBuf.Bytes() |  | ||||||
| 	} else { |  | ||||||
| 		var err error |  | ||||||
| 		yamlBytes, err = r.readFile(path) |  | ||||||
| 		if err != nil { |  | ||||||
| 			return nil, fmt.Errorf("failed to load [%s]: %v", path, err) |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	return yamlBytes, nil |  | ||||||
| } |  | ||||||
		Loading…
	
		Reference in New Issue