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
This commit is contained in:
KUOKA Yusuke 2020-08-06 09:06:25 +09:00 committed by GitHub
parent 53c3fe9f37
commit b85243a6b4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 33 additions and 108 deletions

View File

@ -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)

View File

@ -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(),
}

View File

@ -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)

View File

@ -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

View File

@ -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