Add --output-dir-template flag to the fetch command (#456)
* Add --output-dir-template flag to the fetch command Signed-off-by: elchenberg <elchenberg@users.noreply.github.com>
This commit is contained in:
		
							parent
							
								
									712ee3a0a2
								
							
						
					
					
						commit
						8f59a1c18a
					
				|  | @ -5,6 +5,7 @@ import ( | |||
| 
 | ||||
| 	"github.com/helmfile/helmfile/pkg/app" | ||||
| 	"github.com/helmfile/helmfile/pkg/config" | ||||
| 	"github.com/helmfile/helmfile/pkg/state" | ||||
| ) | ||||
| 
 | ||||
| // NewFetchCmd returns fetch subcmd
 | ||||
|  | @ -34,6 +35,7 @@ func NewFetchCmd(globalCfg *config.GlobalImpl) *cobra.Command { | |||
| 	f.IntVar(&fetchOptions.Concurrency, "concurrency", 0, "maximum number of concurrent helm processes to run, 0 is unlimited") | ||||
| 	f.BoolVar(&fetchOptions.SkipDeps, "skip-deps", false, `skip running "helm repo update" and "helm dependency build"`) | ||||
| 	f.StringVar(&fetchOptions.OutputDir, "output-dir", "", "directory to store charts (default: temporary directory which is deleted when the command terminates)") | ||||
| 	f.StringVar(&fetchOptions.OutputDirTemplate, "output-dir-template", state.DefaultFetchOutputDirTemplate, "go text template for generating the output directory") | ||||
| 
 | ||||
| 	return cmd | ||||
| } | ||||
|  |  | |||
|  | @ -349,6 +349,7 @@ func (a *App) Fetch(c FetchConfigProvider) error { | |||
| 			SkipRepos:         c.SkipDeps(), | ||||
| 			SkipDeps:          c.SkipDeps(), | ||||
| 			OutputDir:         c.OutputDir(), | ||||
| 			OutputDirTemplate: c.OutputDirTemplate(), | ||||
| 			Concurrency:       c.Concurrency(), | ||||
| 		}, func() { | ||||
| 		}) | ||||
|  |  | |||
|  | @ -179,6 +179,7 @@ type LintConfigProvider interface { | |||
| type FetchConfigProvider interface { | ||||
| 	SkipDeps() bool | ||||
| 	OutputDir() string | ||||
| 	OutputDirTemplate() string | ||||
| 
 | ||||
| 	concurrencyConfig | ||||
| } | ||||
|  |  | |||
|  | @ -8,6 +8,8 @@ type FetchOptions struct { | |||
| 	SkipDeps bool | ||||
| 	// OutputDir is the output directory
 | ||||
| 	OutputDir string | ||||
| 	// OutputDirTemplate is the go template to generate the path of output directory
 | ||||
| 	OutputDirTemplate string | ||||
| } | ||||
| 
 | ||||
| // NewFetchOptions creates a new Apply
 | ||||
|  | @ -43,3 +45,8 @@ func (c *FetchImpl) SkipDeps() bool { | |||
| func (c *FetchImpl) OutputDir() string { | ||||
| 	return c.FetchOptions.OutputDir | ||||
| } | ||||
| 
 | ||||
| // OutputDirTemplate returns the go template to generate the path of output directory
 | ||||
| func (c *FetchImpl) OutputDirTemplate() string { | ||||
| 	return c.FetchOptions.OutputDirTemplate | ||||
| } | ||||
|  |  | |||
|  | @ -359,6 +359,16 @@ const MissingFileHandlerWarn = "Warn" | |||
| // MissingFileHandlerDebug is the debug returned when a file is missing
 | ||||
| const MissingFileHandlerDebug = "Debug" | ||||
| 
 | ||||
| var DefaultFetchOutputDirTemplate = path.Join( | ||||
| 	"{{ .OutputDir }}{{ if .Release.TillerNamespace }}", | ||||
| 	"{{ .Release.TillerNamespace }}{{ end }}{{ if .Release.Namespace }}", | ||||
| 	"{{ .Release.Namespace }}{{ end }}{{ if .Release.KubeContext }}", | ||||
| 	"{{ .Release.KubeContext }}{{ end }}", | ||||
| 	"{{ .Release.Name }}", | ||||
| 	"{{ .ChartName }}", | ||||
| 	"{{ or .Release.Version \"latest\" }}", | ||||
| ) | ||||
| 
 | ||||
| func (st *HelmState) ApplyOverrides(spec *ReleaseSpec) { | ||||
| 	if st.OverrideKubeContext != "" { | ||||
| 		spec.KubeContext = st.OverrideKubeContext | ||||
|  | @ -983,6 +993,7 @@ type ChartPrepareOptions struct { | |||
| 	Wait                   bool | ||||
| 	WaitForJobs            bool | ||||
| 	OutputDir              string | ||||
| 	OutputDirTemplate      string | ||||
| 	IncludeTransitiveNeeds bool | ||||
| 	Concurrency            int | ||||
| } | ||||
|  | @ -1211,31 +1222,12 @@ func (st *HelmState) PrepareCharts(helm helmexec.Interface, dir string, concurre | |||
| 					//    For helm 2, we `helm fetch` with the version flags and call `helm template`
 | ||||
| 					//    WITHOUT the version flags.
 | ||||
| 				} else { | ||||
| 					pathElems := []string{ | ||||
| 						dir, | ||||
| 					chartPath, err = generateChartPath(chartName, dir, release, opts.OutputDirTemplate) | ||||
| 					if err != nil { | ||||
| 						results <- &chartPrepareResult{err: err} | ||||
| 						return | ||||
| 					} | ||||
| 
 | ||||
| 					if release.TillerNamespace != "" { | ||||
| 						pathElems = append(pathElems, release.TillerNamespace) | ||||
| 					} | ||||
| 
 | ||||
| 					if release.Namespace != "" { | ||||
| 						pathElems = append(pathElems, release.Namespace) | ||||
| 					} | ||||
| 
 | ||||
| 					if release.KubeContext != "" { | ||||
| 						pathElems = append(pathElems, release.KubeContext) | ||||
| 					} | ||||
| 
 | ||||
| 					chartVersion := "latest" | ||||
| 					if release.Version != "" { | ||||
| 						chartVersion = release.Version | ||||
| 					} | ||||
| 
 | ||||
| 					pathElems = append(pathElems, release.Name, chartName, chartVersion) | ||||
| 
 | ||||
| 					chartPath = path.Join(pathElems...) | ||||
| 
 | ||||
| 					// only fetch chart if it is not already fetched
 | ||||
| 					if _, err := os.Stat(chartPath); os.IsNotExist(err) { | ||||
| 						fetchFlags := st.chartVersionFlags(release) | ||||
|  | @ -1244,6 +1236,8 @@ func (st *HelmState) PrepareCharts(helm helmexec.Interface, dir string, concurre | |||
| 							results <- &chartPrepareResult{err: err} | ||||
| 							return | ||||
| 						} | ||||
| 					} else { | ||||
| 						st.logger.Infof("\"%s\" has not been downloaded because the output directory \"%s\" already exists", chartName, chartPath) | ||||
| 					} | ||||
| 
 | ||||
| 					// Set chartPath to be the path containing Chart.yaml, if found
 | ||||
|  | @ -3181,6 +3175,36 @@ func (st *HelmState) GenerateOutputDir(outputDir string, release *ReleaseSpec, o | |||
| 	return buf.String(), nil | ||||
| } | ||||
| 
 | ||||
| // generateChartPath generates the path of the output directory of the `helmfile fetch` command.
 | ||||
| // It uses a go template with data from the chart name, output directory and release spec.
 | ||||
| // If no template was provided (via the `--output-dir-template` flag) it uses the DefaultFetchOutputDirTemplate.
 | ||||
| func generateChartPath(chartName string, outputDir string, release *ReleaseSpec, outputDirTemplate string) (string, error) { | ||||
| 	if outputDirTemplate == "" { | ||||
| 		outputDirTemplate = DefaultFetchOutputDirTemplate | ||||
| 	} | ||||
| 
 | ||||
| 	t, err := template.New("output-dir-template").Parse(outputDirTemplate) | ||||
| 	if err != nil { | ||||
| 		return "", fmt.Errorf("parsing output-dir-template template %q: %w", outputDirTemplate, err) | ||||
| 	} | ||||
| 
 | ||||
| 	buf := &bytes.Buffer{} | ||||
| 	data := struct { | ||||
| 		ChartName string | ||||
| 		OutputDir string | ||||
| 		Release   ReleaseSpec | ||||
| 	}{ | ||||
| 		ChartName: chartName, | ||||
| 		OutputDir: outputDir, | ||||
| 		Release:   *release, | ||||
| 	} | ||||
| 	if err := t.Execute(buf, data); err != nil { | ||||
| 		return "", fmt.Errorf("executing output-dir-template template: %w", err) | ||||
| 	} | ||||
| 
 | ||||
| 	return buf.String(), nil | ||||
| } | ||||
| 
 | ||||
| func (st *HelmState) GenerateOutputFilePath(release *ReleaseSpec, outputFileTemplate string) (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
 | ||||
|  |  | |||
|  | @ -2812,3 +2812,149 @@ func TestGetOCIQualifiedChartName(t *testing.T) { | |||
| 		}) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func TestGenerateChartPath(t *testing.T) { | ||||
| 	tests := []struct { | ||||
| 		testName          string | ||||
| 		chartName         string | ||||
| 		release           *ReleaseSpec | ||||
| 		outputDir         string | ||||
| 		outputDirTemplate string | ||||
| 		wantErr           bool | ||||
| 		expected          string | ||||
| 	}{ | ||||
| 		{ | ||||
| 			testName:  "PathGeneratedWithGivenOutputDirAndDefaultReleaseVersion", | ||||
| 			chartName: "chart-name", | ||||
| 			release:   &ReleaseSpec{Name: "release-name"}, | ||||
| 			outputDir: "/output-dir", | ||||
| 			wantErr:   false, | ||||
| 			expected:  "/output-dir/release-name/chart-name/latest", | ||||
| 		}, | ||||
| 		{ | ||||
| 			testName:  "PathGeneratedWithGivenOutputDirAndGivenReleaseVersion", | ||||
| 			chartName: "chart-name", | ||||
| 			release:   &ReleaseSpec{Name: "release-name", Version: "0.0.0"}, | ||||
| 			outputDir: "/output-dir", | ||||
| 			wantErr:   false, | ||||
| 			expected:  "/output-dir/release-name/chart-name/0.0.0", | ||||
| 		}, | ||||
| 		{ | ||||
| 			testName:  "PathGeneratedWithGivenOutputDirAndGivenReleaseTillerNamespace", | ||||
| 			chartName: "chart-name", | ||||
| 			release:   &ReleaseSpec{Name: "release-name", TillerNamespace: "tiller-namespace"}, | ||||
| 			outputDir: "/output-dir", | ||||
| 			wantErr:   false, | ||||
| 			expected:  "/output-dir/tiller-namespace/release-name/chart-name/latest", | ||||
| 		}, | ||||
| 		{ | ||||
| 			testName:  "PathGeneratedWithGivenOutputDirAndGivenReleaseNamespace", | ||||
| 			chartName: "chart-name", | ||||
| 			release:   &ReleaseSpec{Name: "release-name", Namespace: "release-namespace"}, | ||||
| 			outputDir: "/output-dir", | ||||
| 			wantErr:   false, | ||||
| 			expected:  "/output-dir/release-namespace/release-name/chart-name/latest", | ||||
| 		}, | ||||
| 		{ | ||||
| 			testName:  "PathGeneratedWithGivenOutputDirAndGivenReleaseTillerNamespaceAndGivenReleaseNamespace", | ||||
| 			chartName: "chart-name", | ||||
| 			release:   &ReleaseSpec{Name: "release-name", TillerNamespace: "tiller-namespace", Namespace: "release-namespace"}, | ||||
| 			outputDir: "/output-dir", | ||||
| 			wantErr:   false, | ||||
| 			expected:  "/output-dir/tiller-namespace/release-namespace/release-name/chart-name/latest", | ||||
| 		}, | ||||
| 		{ | ||||
| 			testName:  "PathGeneratedWithGivenOutputDirAndGivenReleaseKubeContext", | ||||
| 			chartName: "chart-name", | ||||
| 			release:   &ReleaseSpec{Name: "release-name", KubeContext: "kube-context"}, | ||||
| 			outputDir: "/output-dir", | ||||
| 			wantErr:   false, | ||||
| 			expected:  "/output-dir/kube-context/release-name/chart-name/latest", | ||||
| 		}, | ||||
| 		{ | ||||
| 			testName:  "PathGeneratedWithGivenOutputDirAndGivenReleaseNamespaceAndGivenReleaseKubeContext", | ||||
| 			chartName: "chart-name", | ||||
| 			release:   &ReleaseSpec{Name: "release-name", Namespace: "release-namespace", KubeContext: "kube-context"}, | ||||
| 			outputDir: "/output-dir", | ||||
| 			wantErr:   false, | ||||
| 			expected:  "/output-dir/release-namespace/kube-context/release-name/chart-name/latest", | ||||
| 		}, | ||||
| 		{ | ||||
| 			testName:  "PathGeneratedWithGivenOutputDirAndGivenReleaseTillerNamespaceAndGivenReleaseKubeContext", | ||||
| 			chartName: "chart-name", | ||||
| 			release:   &ReleaseSpec{Name: "release-name", TillerNamespace: "tiller-namespace", KubeContext: "kube-context"}, | ||||
| 			outputDir: "/output-dir", | ||||
| 			wantErr:   false, | ||||
| 			expected:  "/output-dir/tiller-namespace/kube-context/release-name/chart-name/latest", | ||||
| 		}, | ||||
| 		{ | ||||
| 			testName:  "PathGeneratedWithGivenOutputDirAndGivenReleaseTillerNamespaceAndGivenReleaseNamespaceAndGivenReleaseKubeContext", | ||||
| 			chartName: "chart-name", | ||||
| 			release:   &ReleaseSpec{Name: "release-name", TillerNamespace: "tiller-namespace", Namespace: "release-namespace", KubeContext: "kube-context"}, | ||||
| 			outputDir: "/output-dir", | ||||
| 			wantErr:   false, | ||||
| 			expected:  "/output-dir/tiller-namespace/release-namespace/kube-context/release-name/chart-name/latest", | ||||
| 		}, | ||||
| 		{ | ||||
| 			testName:          "PathGeneratedWithGivenOutputDirAndGivenOutputDirTemplateWithFieldNameOutputDir", | ||||
| 			chartName:         "chart-name", | ||||
| 			release:           &ReleaseSpec{Name: "release-name"}, | ||||
| 			outputDir:         "/output-dir", | ||||
| 			outputDirTemplate: "{{ .OutputDir }}", | ||||
| 			wantErr:           false, | ||||
| 			expected:          "/output-dir", | ||||
| 		}, | ||||
| 		{ | ||||
| 			testName:          "PathGeneratedWithGivenOutputDirAndGivenOutputDirTemplateWithFieldNamesOutputDirAndReleaseName", | ||||
| 			chartName:         "chart-name", | ||||
| 			release:           &ReleaseSpec{Name: "release-name"}, | ||||
| 			outputDir:         "/output-dir", | ||||
| 			outputDirTemplate: "{{ .OutputDir }}/{{ .Release.Name }}", | ||||
| 			wantErr:           false, | ||||
| 			expected:          "/output-dir/release-name", | ||||
| 		}, | ||||
| 		{ | ||||
| 			testName:          "PathGeneratedWithGivenOutputDirTemplateWithFieldNamesOutputDir", | ||||
| 			chartName:         "chart-name", | ||||
| 			release:           &ReleaseSpec{Name: "release-name"}, | ||||
| 			outputDirTemplate: "{{ .OutputDir }}", | ||||
| 			wantErr:           false, | ||||
| 			expected:          "", | ||||
| 		}, | ||||
| 		{ | ||||
| 			testName:          "PathGeneratedWithGivenOutputDirTemplateWithFieldNameReleaseName", | ||||
| 			chartName:         "chart-name", | ||||
| 			release:           &ReleaseSpec{Name: "release-name"}, | ||||
| 			outputDirTemplate: "{{ .Release.Name }}", | ||||
| 			wantErr:           false, | ||||
| 			expected:          "release-name", | ||||
| 		}, | ||||
| 		{ | ||||
| 			testName:          "PathGeneratedWithGivenOutputDirTemplateWithStringAndFieldNameReleaseName", | ||||
| 			chartName:         "chart-name", | ||||
| 			release:           &ReleaseSpec{Name: "release-name"}, | ||||
| 			outputDirTemplate: "./charts/{{ .Release.Name }}", | ||||
| 			wantErr:           false, | ||||
| 			expected:          "./charts/release-name", | ||||
| 		}, | ||||
| 		{ | ||||
| 			testName:          "ErrorReturnedWithGivenInvalidOutputDirTemplate", | ||||
| 			chartName:         "chart-name", | ||||
| 			release:           &ReleaseSpec{Name: "release-name"}, | ||||
| 			outputDirTemplate: "{{ .OutputDir }", | ||||
| 			wantErr:           true, | ||||
| 		}, | ||||
| 	} | ||||
| 	for _, tt := range tests { | ||||
| 		t.Run(tt.testName, func(t *testing.T) { | ||||
| 			got, err := generateChartPath(tt.chartName, tt.outputDir, tt.release, tt.outputDirTemplate) | ||||
| 
 | ||||
| 			if tt.wantErr { | ||||
| 				require.Errorf(t, err, "GenerateChartPath() error \"%v\", want error", err) | ||||
| 			} else { | ||||
| 				require.NoError(t, err, "GenerateChartPath() error \"%v\", want no error", err) | ||||
| 			} | ||||
| 			require.Equalf(t, tt.expected, got, "GenerateChartPath() got \"%v\", want \"%v\"", got, tt.expected) | ||||
| 		}) | ||||
| 	} | ||||
| } | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue