feat: `helmfile template` (#284)
`helmfile template` runs `helm template` over releases within the helmfiles, and provide you a stream of generated yaml documents of Kubernetes resources via stdout. Resolves #283
This commit is contained in:
		
							parent
							
								
									8a90e5320c
								
							
						
					
					
						commit
						93c5d4c219
					
				|  | @ -6,9 +6,10 @@ import ( | |||
| 	"os" | ||||
| 	"strings" | ||||
| 
 | ||||
| 	"sync" | ||||
| 
 | ||||
| 	"go.uber.org/zap" | ||||
| 	"go.uber.org/zap/zapcore" | ||||
| 	"sync" | ||||
| ) | ||||
| 
 | ||||
| const ( | ||||
|  | @ -143,6 +144,12 @@ func (helm *execer) DecryptSecret(name string) (string, error) { | |||
| 	return tmpFile.Name(), err | ||||
| } | ||||
| 
 | ||||
| func (helm *execer) TemplateRelease(chart string, flags ...string) error { | ||||
| 	out, err := helm.exec(append([]string{"template", chart}, flags...)...) | ||||
| 	helm.write(out) | ||||
| 	return err | ||||
| } | ||||
| 
 | ||||
| func (helm *execer) DiffRelease(name, chart string, flags ...string) error { | ||||
| 	helm.logger.Infof("Comparing %v %v", name, chart) | ||||
| 	out, err := helm.exec(append([]string{"diff", "upgrade", "--allow-unreleased", name, chart}, flags...)...) | ||||
|  |  | |||
|  | @ -10,6 +10,7 @@ type Interface interface { | |||
| 	UpdateDeps(chart string) error | ||||
| 	SyncRelease(name, chart string, flags ...string) error | ||||
| 	DiffRelease(name, chart string, flags ...string) error | ||||
| 	TemplateRelease(chart string, flags ...string) error | ||||
| 	Fetch(chart string, flags ...string) error | ||||
| 	Lint(chart string, flags ...string) error | ||||
| 	ReleaseStatus(name string) error | ||||
|  |  | |||
							
								
								
									
										48
									
								
								main.go
								
								
								
								
							
							
						
						
									
										48
									
								
								main.go
								
								
								
								
							|  | @ -11,6 +11,8 @@ import ( | |||
| 
 | ||||
| 	"os/exec" | ||||
| 
 | ||||
| 	"io/ioutil" | ||||
| 
 | ||||
| 	"github.com/roboll/helmfile/args" | ||||
| 	"github.com/roboll/helmfile/environment" | ||||
| 	"github.com/roboll/helmfile/helmexec" | ||||
|  | @ -19,7 +21,6 @@ import ( | |||
| 	"github.com/urfave/cli" | ||||
| 	"go.uber.org/zap" | ||||
| 	"go.uber.org/zap/zapcore" | ||||
| 	"io/ioutil" | ||||
| ) | ||||
| 
 | ||||
| const ( | ||||
|  | @ -197,6 +198,31 @@ func main() { | |||
| 				}) | ||||
| 			}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			Name:  "template", | ||||
| 			Usage: "template releases from state file against env (helm template)", | ||||
| 			Flags: []cli.Flag{ | ||||
| 				cli.StringFlag{ | ||||
| 					Name:  "args", | ||||
| 					Value: "", | ||||
| 					Usage: "pass args to helm template", | ||||
| 				}, | ||||
| 				cli.StringSliceFlag{ | ||||
| 					Name:  "values", | ||||
| 					Usage: "additional value files to be merged into the command", | ||||
| 				}, | ||||
| 				cli.IntFlag{ | ||||
| 					Name:  "concurrency", | ||||
| 					Value: 0, | ||||
| 					Usage: "maximum number of concurrent helm processes to run, 0 is unlimited", | ||||
| 				}, | ||||
| 			}, | ||||
| 			Action: func(c *cli.Context) error { | ||||
| 				return findAndIterateOverDesiredStatesUsingFlags(c, func(state *state.HelmState, helm helmexec.Interface) []error { | ||||
| 					return executeTemplateCommand(c, state, helm) | ||||
| 				}) | ||||
| 			}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			Name:  "lint", | ||||
| 			Usage: "lint charts from state file (helm lint)", | ||||
|  | @ -449,6 +475,26 @@ func executeSyncCommand(c *cli.Context, state *state.HelmState, helm helmexec.In | |||
| 	return state.SyncReleases(helm, values, workers) | ||||
| } | ||||
| 
 | ||||
| func executeTemplateCommand(c *cli.Context, state *state.HelmState, helm helmexec.Interface) []error { | ||||
| 	if errs := state.SyncRepos(helm); errs != nil && len(errs) > 0 { | ||||
| 		return errs | ||||
| 	} | ||||
| 
 | ||||
| 	if errs := state.UpdateDeps(helm); errs != nil && len(errs) > 0 { | ||||
| 		return errs | ||||
| 	} | ||||
| 
 | ||||
| 	if c.GlobalString("helm-binary") != "" { | ||||
| 		helm.SetHelmBinary(c.GlobalString("helm-binary")) | ||||
| 	} | ||||
| 
 | ||||
| 	args := args.GetArgs(c.String("args"), state) | ||||
| 	values := c.StringSlice("values") | ||||
| 	workers := c.Int("concurrency") | ||||
| 
 | ||||
| 	return state.TemplateReleases(helm, values, workers, args) | ||||
| } | ||||
| 
 | ||||
| func executeDiffCommand(c *cli.Context, state *state.HelmState, helm helmexec.Interface, detailedExitCode, suppressSecrets bool) []error { | ||||
| 	args := args.GetArgs(c.String("args"), state) | ||||
| 	if len(args) > 0 { | ||||
|  |  | |||
							
								
								
									
										117
									
								
								state/state.go
								
								
								
								
							
							
						
						
									
										117
									
								
								state/state.go
								
								
								
								
							|  | @ -3,7 +3,6 @@ package state | |||
| import ( | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"github.com/roboll/helmfile/helmexec" | ||||
| 	"io/ioutil" | ||||
| 	"os" | ||||
| 	"path" | ||||
|  | @ -12,6 +11,8 @@ import ( | |||
| 	"strings" | ||||
| 	"sync" | ||||
| 
 | ||||
| 	"github.com/roboll/helmfile/helmexec" | ||||
| 
 | ||||
| 	"regexp" | ||||
| 
 | ||||
| 	"github.com/roboll/helmfile/environment" | ||||
|  | @ -270,6 +271,111 @@ func (state *HelmState) SyncReleases(helm helmexec.Interface, additionalValues [ | |||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| // TemplateReleases wrapper for executing helm template on the releases
 | ||||
| func (state *HelmState) TemplateReleases(helm helmexec.Interface, additionalValues []string, workerLimit int, args []string) []error { | ||||
| 	var wgRelease sync.WaitGroup | ||||
| 	var wgError sync.WaitGroup | ||||
| 	errs := []error{} | ||||
| 	jobQueue := make(chan *ReleaseSpec, len(state.Releases)) | ||||
| 	errQueue := make(chan error) | ||||
| 
 | ||||
| 	if workerLimit < 1 { | ||||
| 		workerLimit = len(state.Releases) | ||||
| 	} | ||||
| 
 | ||||
| 	wgRelease.Add(len(state.Releases)) | ||||
| 
 | ||||
| 	// Create tmp directory and bail immediately if it fails
 | ||||
| 	dir, err := ioutil.TempDir("", "") | ||||
| 	if err != nil { | ||||
| 		errs = append(errs, err) | ||||
| 		return errs | ||||
| 	} | ||||
| 	defer os.RemoveAll(dir) | ||||
| 
 | ||||
| 	for w := 1; w <= workerLimit; w++ { | ||||
| 		go func() { | ||||
| 			for release := range jobQueue { | ||||
| 				errs := []error{} | ||||
| 				flags, err := state.flagsForTemplate(helm, release) | ||||
| 				if err != nil { | ||||
| 					errs = append(errs, err) | ||||
| 				} | ||||
| 				for _, value := range additionalValues { | ||||
| 					valfile, err := filepath.Abs(value) | ||||
| 					if err != nil { | ||||
| 						errs = append(errs, err) | ||||
| 					} | ||||
| 
 | ||||
| 					if _, err := os.Stat(valfile); os.IsNotExist(err) { | ||||
| 						errs = append(errs, err) | ||||
| 					} | ||||
| 					flags = append(flags, "--values", valfile) | ||||
| 				} | ||||
| 
 | ||||
| 				chartPath := "" | ||||
| 				if pathExists(normalizeChart(state.basePath, release.Chart)) { | ||||
| 					chartPath = normalizeChart(state.basePath, release.Chart) | ||||
| 				} else { | ||||
| 					fetchFlags := []string{} | ||||
| 					if release.Version != "" { | ||||
| 						chartPath = path.Join(dir, release.Name, release.Version, release.Chart) | ||||
| 						fetchFlags = append(fetchFlags, "--version", release.Version) | ||||
| 					} else { | ||||
| 						chartPath = path.Join(dir, release.Name, "latest", release.Chart) | ||||
| 					} | ||||
| 
 | ||||
| 					// only fetch chart if it is not already fetched
 | ||||
| 					if _, err := os.Stat(chartPath); os.IsNotExist(err) { | ||||
| 						fetchFlags = append(fetchFlags, "--untar", "--untardir", chartPath) | ||||
| 						if err := helm.Fetch(release.Chart, fetchFlags...); err != nil { | ||||
| 							errs = append(errs, err) | ||||
| 						} | ||||
| 					} | ||||
| 					chartPath = path.Join(chartPath, chartNameWithoutRepository(release.Chart)) | ||||
| 				} | ||||
| 
 | ||||
| 				if len(args) > 0 { | ||||
| 					helm.SetExtraArgs(args...) | ||||
| 				} | ||||
| 
 | ||||
| 				if len(errs) == 0 { | ||||
| 					if err := helm.TemplateRelease(chartPath, flags...); err != nil { | ||||
| 						errs = append(errs, err) | ||||
| 					} | ||||
| 				} | ||||
| 				for _, err := range errs { | ||||
| 					errQueue <- err | ||||
| 				} | ||||
| 				wgRelease.Done() | ||||
| 			} | ||||
| 		}() | ||||
| 	} | ||||
| 	wgError.Add(1) | ||||
| 	go func() { | ||||
| 		for err := range errQueue { | ||||
| 			errs = append(errs, err) | ||||
| 		} | ||||
| 		wgError.Done() | ||||
| 	}() | ||||
| 
 | ||||
| 	for i := 0; i < len(state.Releases); i++ { | ||||
| 		jobQueue <- &state.Releases[i] | ||||
| 	} | ||||
| 
 | ||||
| 	close(jobQueue) | ||||
| 	wgRelease.Wait() | ||||
| 
 | ||||
| 	close(errQueue) | ||||
| 	wgError.Wait() | ||||
| 
 | ||||
| 	if len(errs) != 0 { | ||||
| 		return errs | ||||
| 	} | ||||
| 
 | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| // DiffReleases wrapper for executing helm diff on the releases
 | ||||
| func (state *HelmState) DiffReleases(helm helmexec.Interface, additionalValues []string, workerLimit int, detailedExitCode, suppressSecrets bool) []error { | ||||
| 	var wgRelease sync.WaitGroup | ||||
|  | @ -691,6 +797,15 @@ func (state *HelmState) flagsForUpgrade(helm helmexec.Interface, release *Releas | |||
| 	return append(flags, common...), nil | ||||
| } | ||||
| 
 | ||||
| func (state *HelmState) flagsForTemplate(helm helmexec.Interface, release *ReleaseSpec) ([]string, error) { | ||||
| 	flags := []string{} | ||||
| 	common, err := state.namespaceAndValuesFlags(helm, release) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	return append(flags, common...), nil | ||||
| } | ||||
| 
 | ||||
| func (state *HelmState) flagsForDiff(helm helmexec.Interface, release *ReleaseSpec) ([]string, error) { | ||||
| 	flags := []string{} | ||||
| 	if release.Version != "" { | ||||
|  |  | |||
|  | @ -6,8 +6,9 @@ import ( | |||
| 	"testing" | ||||
| 
 | ||||
| 	"errors" | ||||
| 	"github.com/roboll/helmfile/helmexec" | ||||
| 	"strings" | ||||
| 
 | ||||
| 	"github.com/roboll/helmfile/helmexec" | ||||
| ) | ||||
| 
 | ||||
| var logger = helmexec.NewLogger(os.Stdout, "warn") | ||||
|  | @ -537,7 +538,9 @@ func (helm *mockHelmExec) Fetch(chart string, flags ...string) error { | |||
| func (helm *mockHelmExec) Lint(chart string, flags ...string) error { | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| func (helm *mockHelmExec) TemplateRelease(chart string, flags ...string) error { | ||||
| 	return nil | ||||
| } | ||||
| func TestHelmState_SyncRepos(t *testing.T) { | ||||
| 	tests := []struct { | ||||
| 		name  string | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue