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/app"
|
||||||
"github.com/helmfile/helmfile/pkg/config"
|
"github.com/helmfile/helmfile/pkg/config"
|
||||||
|
"github.com/helmfile/helmfile/pkg/state"
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewFetchCmd returns fetch subcmd
|
// 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.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.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.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
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -349,6 +349,7 @@ func (a *App) Fetch(c FetchConfigProvider) error {
|
||||||
SkipRepos: c.SkipDeps(),
|
SkipRepos: c.SkipDeps(),
|
||||||
SkipDeps: c.SkipDeps(),
|
SkipDeps: c.SkipDeps(),
|
||||||
OutputDir: c.OutputDir(),
|
OutputDir: c.OutputDir(),
|
||||||
|
OutputDirTemplate: c.OutputDirTemplate(),
|
||||||
Concurrency: c.Concurrency(),
|
Concurrency: c.Concurrency(),
|
||||||
}, func() {
|
}, func() {
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -179,6 +179,7 @@ type LintConfigProvider interface {
|
||||||
type FetchConfigProvider interface {
|
type FetchConfigProvider interface {
|
||||||
SkipDeps() bool
|
SkipDeps() bool
|
||||||
OutputDir() string
|
OutputDir() string
|
||||||
|
OutputDirTemplate() string
|
||||||
|
|
||||||
concurrencyConfig
|
concurrencyConfig
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,8 @@ type FetchOptions struct {
|
||||||
SkipDeps bool
|
SkipDeps bool
|
||||||
// OutputDir is the output directory
|
// OutputDir is the output directory
|
||||||
OutputDir string
|
OutputDir string
|
||||||
|
// OutputDirTemplate is the go template to generate the path of output directory
|
||||||
|
OutputDirTemplate string
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewFetchOptions creates a new Apply
|
// NewFetchOptions creates a new Apply
|
||||||
|
|
@ -43,3 +45,8 @@ func (c *FetchImpl) SkipDeps() bool {
|
||||||
func (c *FetchImpl) OutputDir() string {
|
func (c *FetchImpl) OutputDir() string {
|
||||||
return c.FetchOptions.OutputDir
|
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
|
// MissingFileHandlerDebug is the debug returned when a file is missing
|
||||||
const MissingFileHandlerDebug = "Debug"
|
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) {
|
func (st *HelmState) ApplyOverrides(spec *ReleaseSpec) {
|
||||||
if st.OverrideKubeContext != "" {
|
if st.OverrideKubeContext != "" {
|
||||||
spec.KubeContext = st.OverrideKubeContext
|
spec.KubeContext = st.OverrideKubeContext
|
||||||
|
|
@ -983,6 +993,7 @@ type ChartPrepareOptions struct {
|
||||||
Wait bool
|
Wait bool
|
||||||
WaitForJobs bool
|
WaitForJobs bool
|
||||||
OutputDir string
|
OutputDir string
|
||||||
|
OutputDirTemplate string
|
||||||
IncludeTransitiveNeeds bool
|
IncludeTransitiveNeeds bool
|
||||||
Concurrency int
|
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`
|
// For helm 2, we `helm fetch` with the version flags and call `helm template`
|
||||||
// WITHOUT the version flags.
|
// WITHOUT the version flags.
|
||||||
} else {
|
} else {
|
||||||
pathElems := []string{
|
chartPath, err = generateChartPath(chartName, dir, release, opts.OutputDirTemplate)
|
||||||
dir,
|
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
|
// only fetch chart if it is not already fetched
|
||||||
if _, err := os.Stat(chartPath); os.IsNotExist(err) {
|
if _, err := os.Stat(chartPath); os.IsNotExist(err) {
|
||||||
fetchFlags := st.chartVersionFlags(release)
|
fetchFlags := st.chartVersionFlags(release)
|
||||||
|
|
@ -1244,6 +1236,8 @@ func (st *HelmState) PrepareCharts(helm helmexec.Interface, dir string, concurre
|
||||||
results <- &chartPrepareResult{err: err}
|
results <- &chartPrepareResult{err: err}
|
||||||
return
|
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
|
// 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
|
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) {
|
func (st *HelmState) GenerateOutputFilePath(release *ReleaseSpec, outputFileTemplate string) (string, error) {
|
||||||
// get absolute path of state file to generate a hash
|
// 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
|
// 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