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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -345,11 +345,12 @@ func (a *App) Lint(c LintConfigProvider) error {
|
|||
func (a *App) Fetch(c FetchConfigProvider) error {
|
||||
return a.ForEachState(func(run *Run) (ok bool, errs []error) {
|
||||
prepErr := run.withPreparedCharts("pull", state.ChartPrepareOptions{
|
||||
ForceDownload: true,
|
||||
SkipRepos: c.SkipDeps(),
|
||||
SkipDeps: c.SkipDeps(),
|
||||
OutputDir: c.OutputDir(),
|
||||
Concurrency: c.Concurrency(),
|
||||
ForceDownload: true,
|
||||
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