From b85243a6b4a117ca253a877bcd7eb4e0f892f3ea Mon Sep 17 00:00:00 2001 From: KUOKA Yusuke Date: Thu, 6 Aug 2020 09:06:25 +0900 Subject: [PATCH] Fix various issues in chart preparation (#1400) In #1172, we accidentally changed the meaning of prepare hook that is intended to be called BEFORE the pathExists check. It broke the scenario where one used a prepare hook for generating the local chart dynamically. This fixes Helmfile not to fetch local chart generated by prepare hook. In addition to that, this patch results in the following fixes: - Fix an issue that `helmfile template` without `--skip-deps` fails while trying to run `helm dep build` on `helm fetch`ed chart, when the remote chart has outdated dependencies in the Chart.lock file. It should be up to the chart maintainer to update Chart.lock and the user should not be blocked due to that. So, after this patch `helm dep build` is run only on the local chart, not on fetched remote chart. - Skip fetching chart on `helmfile template` when using Helm v3. `helm template` in helm v3 does support rendering remote charts so we do not need to fetch beforehand. Fixes #1328 May relate to #1341 --- pkg/app/app.go | 32 ++----------- pkg/app/run.go | 20 +-------- pkg/state/chart_dependency.go | 2 +- pkg/state/state.go | 85 +++++++++++------------------------ pkg/testhelper/testfs.go | 2 +- 5 files changed, 33 insertions(+), 108 deletions(-) diff --git a/pkg/app/app.go b/pkg/app/app.go index 5b0e6290..fcc74386 100644 --- a/pkg/app/app.go +++ b/pkg/app/app.go @@ -216,7 +216,9 @@ func (a *App) Diff(c DiffConfigProvider) error { func (a *App) Template(c TemplateConfigProvider) error { return a.ForEachState(func(run *Run) (ok bool, errs []error) { - prepErr := run.withPreparedCharts(true, c.SkipDeps(), "template", func() { + // `helm template` in helm v2 does not support local chart. + // So, we set forceDownload=true for helm v2 only + prepErr := run.withPreparedCharts(!run.helm.IsHelm3(), c.SkipDeps(), "template", func() { ok, errs = a.template(run, c) }) @@ -230,6 +232,7 @@ func (a *App) Template(c TemplateConfigProvider) error { func (a *App) Lint(c LintConfigProvider) error { return a.ForEachState(func(run *Run) (_ bool, errs []error) { + // `helm lint` on helm v2 and v3 does not support remote charts, that we need to set `forceDownload=true` here prepErr := run.withPreparedCharts(true, c.SkipDeps(), "lint", func() { errs = run.Lint(c) }) @@ -970,15 +973,6 @@ func (a *App) apply(r *Run, c ApplyConfigProvider) (bool, bool, []error) { // on running various helm commands on unnecessary releases st.Releases = toApply - if !c.SkipDeps() { - if errs := st.BuildDeps(helm); errs != nil && len(errs) > 0 { - return false, false, errs - } - } - if errs := st.PrepareReleases(helm, "apply"); errs != nil && len(errs) > 0 { - return false, false, errs - } - // helm must be 2.11+ and helm-diff should be provided `--detailed-exitcode` in order for `helmfile apply` to work properly detailedExitCode := true @@ -1157,15 +1151,6 @@ func (a *App) sync(r *Run, c SyncConfigProvider) (bool, []error) { // on running various helm commands on unnecessary releases st.Releases = toSync - if !c.SkipDeps() { - if errs := st.BuildDeps(helm); errs != nil && len(errs) > 0 { - return false, errs - } - } - if errs := st.PrepareReleases(helm, "sync"); errs != nil && len(errs) > 0 { - return false, errs - } - toDelete, err := st.DetectReleasesToBeDeletedForSync(helm, toSync) if err != nil { return false, []error{err} @@ -1279,15 +1264,6 @@ func (a *App) template(r *Run, c TemplateConfigProvider) (bool, []error) { // on running various helm commands on unnecessary releases st.Releases = toRender - if !c.SkipDeps() { - if errs := st.BuildDeps(helm); errs != nil && len(errs) > 0 { - return false, errs - } - } - if errs := st.PrepareReleases(helm, "template"); errs != nil && len(errs) > 0 { - return false, errs - } - releasesToRender := map[string]state.ReleaseSpec{} for _, r := range toRender { id := state.ReleaseToID(&r) diff --git a/pkg/app/run.go b/pkg/app/run.go index 113bc25c..ae01649c 100644 --- a/pkg/app/run.go +++ b/pkg/app/run.go @@ -59,7 +59,7 @@ func (r *Run) withPreparedCharts(forceDownload, skipRepos bool, helmfileCommand return err } - releaseToChart, errs := r.state.PrepareCharts(r.helm, dir, 2, helmfileCommand, forceDownload) + releaseToChart, errs := r.state.PrepareCharts(r.helm, dir, 2, helmfileCommand, forceDownload, skipRepos) if len(errs) > 0 { return fmt.Errorf("%v", errs) @@ -114,7 +114,6 @@ func (r *Run) Status(c StatusesConfigProvider) []error { func (a *App) diff(r *Run, c DiffConfigProvider) (*string, bool, bool, []error) { st := r.state - helm := r.helm allReleases := st.GetReleasesWithOverrides() @@ -131,15 +130,6 @@ func (a *App) diff(r *Run, c DiffConfigProvider) (*string, bool, bool, []error) // on running various helm commands on unnecessary releases st.Releases = toDiff - if !c.SkipDeps() { - if errs := st.BuildDeps(helm); errs != nil && len(errs) > 0 { - return nil, false, false, errs - } - } - if errs := st.PrepareReleases(helm, "diff"); errs != nil && len(errs) > 0 { - return nil, false, false, errs - } - r.helm.SetExtraArgs(argparser.GetArgs(c.Args(), r.state)...) opts := &state.DiffOpts{ @@ -188,14 +178,6 @@ func (r *Run) Lint(c LintConfigProvider) []error { values := c.Values() args := argparser.GetArgs(c.Args(), st) workers := c.Concurrency() - if !c.SkipDeps() { - if errs := st.BuildDeps(helm); errs != nil && len(errs) > 0 { - return errs - } - } - if errs := st.PrepareReleases(helm, "lint"); errs != nil && len(errs) > 0 { - return errs - } opts := &state.LintOpts{ Set: c.Set(), } diff --git a/pkg/state/chart_dependency.go b/pkg/state/chart_dependency.go index b91e09e3..f7e7ea2e 100644 --- a/pkg/state/chart_dependency.go +++ b/pkg/state/chart_dependency.go @@ -254,7 +254,7 @@ func updateDependencies(st *HelmState, shell helmexec.DependencyUpdater, unresol _, err := depMan.Update(shell, wd, unresolved) if err != nil { - return nil, fmt.Errorf("unable to resolve %d deps: %v", len(unresolved.deps), err) + return nil, fmt.Errorf("unable to update %d deps: %v", len(unresolved.deps), err) } return resolveDependencies(st, depMan, unresolved) diff --git a/pkg/state/state.go b/pkg/state/state.go index 3984c83a..96856143 100644 --- a/pkg/state/state.go +++ b/pkg/state/state.go @@ -805,12 +805,13 @@ func releasesNeedCharts(releases []ReleaseSpec) []ReleaseSpec { // (2) temporary local chart generated from kustomization or manifests // (3) remote chart // -// When running `helmfile lint` or `helmfile template`, PrepareCharts will download and untar charts for linting and templating. +// When running `helmfile template` on helm v2, or `helmfile lint` on both helm v2 and v3, +// PrepareCharts will download and untar charts for linting and templating. // // Otheriwse, if a chart is not a helm chart, it will call "chartify" to turn it into a chart. // // If exists, it will also patch resources by json patches, strategic-merge patches, and injectors. -func (st *HelmState) PrepareCharts(helm helmexec.Interface, dir string, concurrency int, helmfileCommand string, forceDownload bool) (map[string]string, []error) { +func (st *HelmState) PrepareCharts(helm helmexec.Interface, dir string, concurrency int, helmfileCommand string, forceDownload, skipDeps bool) (map[string]string, []error) { releases := releasesNeedCharts(st.Releases) temp := make(map[string]string, len(releases)) @@ -831,6 +832,12 @@ func (st *HelmState) PrepareCharts(helm helmexec.Interface, dir string, concurre helm3 = helm.IsHelm3() } + updated, err := st.ResolveDeps() + if err != nil { + return nil, []error{err} + } + *st = *updated + st.scatterGather( concurrency, len(releases), @@ -842,6 +849,16 @@ func (st *HelmState) PrepareCharts(helm helmexec.Interface, dir string, concurre }, func(workerIndex int) { for release := range jobQueue { + // Call user-defined `prepare` hooks to create/modify local charts to be used by + // the later process. + // + // If it wasn't called here, Helmfile can end up an issue like + // https://github.com/roboll/helmfile/issues/1328 + if _, err := st.triggerPrepareEvent(release, helmfileCommand); err != nil { + results <- &downloadResults{err: err} + return + } + var chartPath string chartification, clean, err := st.PrepareChartify(helm, release, workerIndex) @@ -869,6 +886,13 @@ func (st *HelmState) PrepareCharts(helm helmexec.Interface, dir string, concurre chartPath = release.Chart } else if pathExists(normalizeChart(st.basePath, release.Chart)) { chartPath = normalizeChart(st.basePath, release.Chart) + + if !skipDeps { + if err := helm.BuildDeps(release.Name, chartPath); err != nil { + results <- &downloadResults{err: err} + return + } + } } else { fetchFlags := []string{} @@ -1540,35 +1564,6 @@ func (st *HelmState) FilterReleases() error { return nil } -func (st *HelmState) PrepareReleases(helm helmexec.Interface, helmfileCommand string) []error { - errs := []error{} - - for i := range st.Releases { - release := st.Releases[i] - - if release.Installed != nil && !*release.Installed { - continue - } - - if _, err := st.triggerPrepareEvent(&release, helmfileCommand); err != nil { - errs = append(errs, newReleaseFailedError(&release, err)) - continue - } - } - if len(errs) != 0 { - return errs - } - - updated, err := st.ResolveDeps() - if err != nil { - return []error{err} - } - - *st = *updated - - return nil -} - func (st *HelmState) TriggerGlobalPrepareEvent(helmfileCommand string) (bool, error) { return st.triggerGlobalReleaseEvent("prepare", nil, helmfileCommand) } @@ -1660,34 +1655,6 @@ func (st *HelmState) UpdateDeps(helm helmexec.Interface) []error { return nil } -// BuildDeps wrapper for building dependencies on the releases -func (st *HelmState) BuildDeps(helm helmexec.Interface) []error { - errs := []error{} - - releases := releasesNeedCharts(st.Releases) - - for _, release := range releases { - if len(release.Chart) == 0 { - errs = append(errs, errors.New("chart is required for: "+release.Name)) - continue - } - - if release.Installed != nil && !*release.Installed { - continue - } - - if isLocalChart(release.Chart) { - if err := helm.BuildDeps(release.Name, normalizeChart(st.basePath, release.Chart)); err != nil { - errs = append(errs, err) - } - } - } - if len(errs) != 0 { - return errs - } - return nil -} - func pathExists(chart string) bool { _, err := os.Stat(chart) return err == nil diff --git a/pkg/testhelper/testfs.go b/pkg/testhelper/testfs.go index bc99fdbf..f28d818a 100644 --- a/pkg/testhelper/testfs.go +++ b/pkg/testhelper/testfs.go @@ -70,7 +70,7 @@ func (f *TestFs) ReadFile(filename string) ([]byte, error) { str, ok = f.files[filepath.Join(f.Cwd, filename)] } if !ok { - return []byte(nil), fmt.Errorf("no registered file found: %s", filename) + return []byte(nil), os.ErrNotExist } f.fileReaderCalls += 1