diff --git a/go.mod b/go.mod index 412dd9e67..4555bd5fb 100644 --- a/go.mod +++ b/go.mod @@ -29,7 +29,7 @@ require ( go.uber.org/zap v1.27.0 go.yaml.in/yaml/v2 v2.4.3 go.yaml.in/yaml/v3 v3.0.4 - golang.org/x/sync v0.17.0 + golang.org/x/sync v0.18.0 golang.org/x/term v0.36.0 helm.sh/helm/v3 v3.19.0 k8s.io/apimachinery v0.34.1 diff --git a/go.sum b/go.sum index f00c7b0da..2f25fa6a0 100644 --- a/go.sum +++ b/go.sum @@ -810,8 +810,8 @@ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= -golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I= +golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= diff --git a/pkg/app/app.go b/pkg/app/app.go index 0da37450b..a151de5b8 100644 --- a/pkg/app/app.go +++ b/pkg/app/app.go @@ -154,7 +154,7 @@ func (a *App) Diff(c DiffConfigProvider) error { includeCRDs := !c.SkipCRDs() - prepErr := run.withPreparedCharts("diff", state.ChartPrepareOptions{ + prepErr := run.WithPreparedCharts("diff", state.ChartPrepareOptions{ SkipRepos: c.SkipRefresh() || c.SkipDeps(), SkipRefresh: c.SkipRefresh(), SkipDeps: c.SkipDeps(), @@ -224,7 +224,7 @@ func (a *App) Template(c TemplateConfigProvider) error { // https://github.com/helmfile/helmfile/issues/1749 run.helm.SetExtraArgs() - prepErr := run.withPreparedCharts("template", state.ChartPrepareOptions{ + prepErr := run.WithPreparedCharts("template", state.ChartPrepareOptions{ SkipRepos: c.SkipRefresh() || c.SkipDeps(), SkipRefresh: c.SkipRefresh(), SkipDeps: c.SkipDeps(), @@ -250,7 +250,7 @@ func (a *App) Template(c TemplateConfigProvider) error { func (a *App) WriteValues(c WriteValuesConfigProvider) error { return a.ForEachState(func(run *Run) (ok bool, errs []error) { - prepErr := run.withPreparedCharts("write-values", state.ChartPrepareOptions{ + prepErr := run.WithPreparedCharts("write-values", state.ChartPrepareOptions{ SkipRepos: c.SkipRefresh() || c.SkipDeps(), SkipRefresh: c.SkipRefresh(), SkipDeps: c.SkipDeps(), @@ -301,7 +301,7 @@ func (a *App) Lint(c LintConfigProvider) error { var lintErrs []error // `helm lint` on helm v2 and v3 does not support remote charts, that we need to set `forceDownload=true` here - prepErr := run.withPreparedCharts("lint", state.ChartPrepareOptions{ + prepErr := run.WithPreparedCharts("lint", state.ChartPrepareOptions{ ForceDownload: true, SkipRepos: c.SkipRefresh() || c.SkipDeps(), SkipRefresh: c.SkipRefresh(), @@ -337,7 +337,7 @@ 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{ + prepErr := run.WithPreparedCharts("pull", state.ChartPrepareOptions{ ForceDownload: true, SkipRefresh: c.SkipRefresh(), SkipRepos: c.SkipRefresh() || c.SkipDeps(), @@ -359,7 +359,7 @@ func (a *App) Sync(c SyncConfigProvider) error { return a.ForEachState(func(run *Run) (ok bool, errs []error) { includeCRDs := !c.SkipCRDs() - prepErr := run.withPreparedCharts("sync", state.ChartPrepareOptions{ + prepErr := run.WithPreparedCharts("sync", state.ChartPrepareOptions{ SkipRepos: c.SkipRefresh() || c.SkipDeps(), SkipRefresh: c.SkipRefresh(), SkipDeps: c.SkipDeps(), @@ -371,7 +371,7 @@ func (a *App) Sync(c SyncConfigProvider) error { Validate: c.Validate(), Concurrency: c.Concurrency(), }, func() { - ok, errs = a.sync(run, c) + ok, errs = a.SyncRun(run, c) }) if prepErr != nil { @@ -394,7 +394,7 @@ func (a *App) Apply(c ApplyConfigProvider) error { err := a.ForEachState(func(run *Run) (ok bool, errs []error) { includeCRDs := !c.SkipCRDs() - prepErr := run.withPreparedCharts("apply", state.ChartPrepareOptions{ + prepErr := run.WithPreparedCharts("apply", state.ChartPrepareOptions{ SkipRepos: c.SkipRefresh() || c.SkipDeps(), SkipRefresh: c.SkipRefresh(), SkipDeps: c.SkipDeps(), @@ -438,7 +438,7 @@ func (a *App) Apply(c ApplyConfigProvider) error { func (a *App) Status(c StatusesConfigProvider) error { return a.ForEachState(func(run *Run) (ok bool, errs []error) { - err := run.withPreparedCharts("status", state.ChartPrepareOptions{ + err := run.WithPreparedCharts("status", state.ChartPrepareOptions{ SkipRepos: true, SkipDeps: true, Concurrency: c.Concurrency(), @@ -457,7 +457,7 @@ func (a *App) Status(c StatusesConfigProvider) error { func (a *App) Destroy(c DestroyConfigProvider) error { return a.ForEachState(func(run *Run) (ok bool, errs []error) { if !c.SkipCharts() { - err := run.withPreparedCharts("destroy", state.ChartPrepareOptions{ + err := run.WithPreparedCharts("destroy", state.ChartPrepareOptions{ SkipRepos: c.SkipRefresh() || c.SkipDeps(), SkipRefresh: c.SkipRefresh(), SkipDeps: c.SkipDeps(), @@ -485,7 +485,7 @@ func (a *App) Test(c TestConfigProvider) error { "or set helm.sh/hook-delete-policy\n") } - err := run.withPreparedCharts("test", state.ChartPrepareOptions{ + err := run.WithPreparedCharts("test", state.ChartPrepareOptions{ SkipRepos: c.SkipRefresh() || c.SkipDeps(), SkipRefresh: c.SkipRefresh(), SkipDeps: c.SkipDeps(), @@ -505,7 +505,7 @@ func (a *App) Test(c TestConfigProvider) error { func (a *App) PrintDAGState(c DAGConfigProvider) error { var err error return a.ForEachState(func(run *Run) (ok bool, errs []error) { - err = run.withPreparedCharts("show-dag", state.ChartPrepareOptions{ + err = run.WithPreparedCharts("show-dag", state.ChartPrepareOptions{ SkipRepos: true, SkipDeps: true, Concurrency: 2, @@ -521,7 +521,7 @@ func (a *App) PrintDAGState(c DAGConfigProvider) error { func (a *App) PrintState(c StateConfigProvider) error { return a.ForEachState(func(run *Run) (_ bool, errs []error) { - err := run.withPreparedCharts("build", state.ChartPrepareOptions{ + err := run.WithPreparedCharts("build", state.ChartPrepareOptions{ SkipRepos: true, SkipDeps: true, Concurrency: 2, @@ -593,7 +593,7 @@ func (a *App) ListReleases(c ListConfigProvider) error { var err error if !c.SkipCharts() { - err = run.withPreparedCharts("list", state.ChartPrepareOptions{ + err = run.WithPreparedCharts("list", state.ChartPrepareOptions{ SkipRepos: true, SkipDeps: true, Concurrency: 2, @@ -1344,42 +1344,54 @@ func (a *App) getSelectedReleases(r *Run, includeTransitiveNeeds bool) ([]state. return selected, deduplicated, nil } -func (a *App) apply(r *Run, c ApplyConfigProvider) (bool, bool, []error) { - st := r.state - helm := r.helm - - helm.SetExtraArgs(GetArgs(c.Args(), r.state)...) - - selectedReleases, selectedAndNeededReleases, err := a.getSelectedReleases(r, c.IncludeTransitiveNeeds()) +func (a *App) GetPlannedAndSelectedReleasesWithNeeds(r *Run, skipNeeds bool, includeNeeds bool, includeTransitiveNeeds bool) ([]state.ReleaseSpec, []state.ReleaseSpec, error) { + selectedReleases, selectedAndNeededReleases, err := a.getSelectedReleases(r, includeTransitiveNeeds) if err != nil { - return false, false, []error{err} + return nil, nil, err } if len(selectedReleases) == 0 { - return false, false, nil + return nil, nil, nil } // This is required when you're trying to deduplicate releases by the selector. // Without this, `PlanReleases` conflates duplicates and return both in `batches`, // even if we provided `SelectedReleases: selectedReleases`. // See https://github.com/roboll/helmfile/issues/1818 for more context. - st.Releases = selectedAndNeededReleases + r.state.Releases = selectedAndNeededReleases - plan, err := st.PlanReleases(state.PlanOptions{Reverse: false, SelectedReleases: selectedReleases, SkipNeeds: c.SkipNeeds(), IncludeNeeds: c.IncludeNeeds(), IncludeTransitiveNeeds: c.IncludeTransitiveNeeds()}) + batches, err := r.state.PlanReleases(state.PlanOptions{Reverse: false, SelectedReleases: selectedReleases, SkipNeeds: skipNeeds, IncludeNeeds: includeNeeds, IncludeTransitiveNeeds: includeTransitiveNeeds}) if err != nil { - return false, false, []error{err} + return nil, nil, err } - var toApplyWithNeeds []state.ReleaseSpec + var releasesWithNeeds []state.ReleaseSpec - for _, rs := range plan { + for _, rs := range batches { for _, r := range rs { - toApplyWithNeeds = append(toApplyWithNeeds, r.ReleaseSpec) + releasesWithNeeds = append(releasesWithNeeds, r.ReleaseSpec) } } + return releasesWithNeeds, selectedAndNeededReleases, nil +} + +func (a *App) apply(r *Run, c ApplyConfigProvider) (bool, bool, []error) { + st := r.state + helm := r.helm + + helm.SetExtraArgs(GetArgs(c.Args(), r.state)...) + + releasesWithNeeds, selectedAndNeededReleases, err := a.GetPlannedAndSelectedReleasesWithNeeds(r, c.SkipNeeds(), c.IncludeNeeds(), c.IncludeTransitiveNeeds()) + if err != nil { + return false, false, []error{err} + } + if len(releasesWithNeeds) == 0 { + return false, false, nil + } + // Do build deps and prepare only on selected releases so that we won't waste time // on running various helm commands on unnecessary releases - st.Releases = toApplyWithNeeds + st.Releases = releasesWithNeeds // helm must be 2.11+ and helm-diff should be provided `--detailed-exitcode` in order for `helmfile apply` to work properly detailedExitCode := true @@ -1402,27 +1414,27 @@ func (a *App) apply(r *Run, c ApplyConfigProvider) (bool, bool, []error) { TakeOwnership: c.TakeOwnership(), } - infoMsg, releasesToBeUpdated, releasesToBeDeleted, errs := r.diff(false, detailedExitCode, c, diffOpts) - if len(errs) > 0 { - return false, false, errs + infoMsg, releasesToUpdate, releasesToDelete, diffErrs := r.diff(false, detailedExitCode, c, diffOpts) + if len(diffErrs) > 0 { + return false, false, diffErrs } var toDelete []state.ReleaseSpec - for _, r := range releasesToBeDeleted { + for _, r := range releasesToDelete { toDelete = append(toDelete, r) } var toUpdate []state.ReleaseSpec - for _, r := range releasesToBeUpdated { + for _, r := range releasesToUpdate { toUpdate = append(toUpdate, r) } releasesWithNoChange := map[string]state.ReleaseSpec{} - for _, r := range toApplyWithNeeds { + for _, r := range releasesWithNeeds { release := r id := state.ReleaseToID(&release) - _, uninstalled := releasesToBeDeleted[id] - _, updated := releasesToBeUpdated[id] + _, uninstalled := releasesToDelete[id] + _, updated := releasesToUpdate[id] if !uninstalled && !updated { releasesWithNoChange[id] = release } @@ -1444,15 +1456,15 @@ Do you really want to apply? a.Logger.Debug(infoMsgStr) } - var applyErrs []error - - affectedReleases := state.AffectedReleases{} + var errs []error // Traverse DAG of all the releases so that we don't suffer from false-positive missing dependencies st.Releases = selectedAndNeededReleases + affectedReleases := state.AffectedReleases{} + if !interactive || interactive && r.askForConfirmation(confMsg) { - if _, preapplyErrors := withDAG(st, helm, a.Logger, state.PlanOptions{Purpose: "invoking preapply hooks for", Reverse: true, SelectedReleases: toApplyWithNeeds, SkipNeeds: true}, a.WrapWithoutSelector(func(subst *state.HelmState, helm helmexec.Interface) []error { + if _, preapplyErrors := withDAG(st, helm, a.Logger, state.PlanOptions{Purpose: "invoking preapply hooks for", Reverse: true, SelectedReleases: releasesWithNeeds, SkipNeeds: true}, a.WrapWithoutSelector(func(subst *state.HelmState, helm helmexec.Interface) []error { for _, r := range subst.Releases { release := r if _, err := st.TriggerPreapplyEvent(&release, "apply"); err != nil { @@ -1466,13 +1478,13 @@ Do you really want to apply? } // We deleted releases by traversing the DAG in reverse order - if len(releasesToBeDeleted) > 0 { + if len(releasesToDelete) > 0 { _, deletionErrs := withDAG(st, helm, a.Logger, state.PlanOptions{Reverse: true, SelectedReleases: toDelete, SkipNeeds: true}, a.WrapWithoutSelector(func(subst *state.HelmState, helm helmexec.Interface) []error { var rs []state.ReleaseSpec for _, r := range subst.Releases { release := r - if r2, ok := releasesToBeDeleted[state.ReleaseToID(&release)]; ok { + if r2, ok := releasesToDelete[state.ReleaseToID(&release)]; ok { rs = append(rs, r2) } } @@ -1483,18 +1495,18 @@ Do you really want to apply? })) if len(deletionErrs) > 0 { - applyErrs = append(applyErrs, deletionErrs...) + errs = append(errs, deletionErrs...) } } // We upgrade releases by traversing the DAG - if len(releasesToBeUpdated) > 0 { - _, updateErrs := withDAG(st, helm, a.Logger, state.PlanOptions{SelectedReleases: toUpdate, Reverse: false, SkipNeeds: true, IncludeTransitiveNeeds: c.IncludeTransitiveNeeds()}, a.WrapWithoutSelector(func(subst *state.HelmState, helm helmexec.Interface) []error { + if len(releasesToUpdate) > 0 { + _, updateErrs := withDAG(st, helm, a.Logger, state.PlanOptions{SelectedReleases: toUpdate, SkipNeeds: true, IncludeTransitiveNeeds: c.IncludeTransitiveNeeds()}, a.WrapWithoutSelector(func(subst *state.HelmState, helm helmexec.Interface) []error { var rs []state.ReleaseSpec for _, r := range subst.Releases { release := r - if r2, ok := releasesToBeUpdated[state.ReleaseToID(&release)]; ok { + if r2, ok := releasesToUpdate[state.ReleaseToID(&release)]; ok { rs = append(rs, r2) } } @@ -1502,27 +1514,27 @@ Do you really want to apply? subst.Releases = rs syncOpts := &state.SyncOpts{ + HideNotes: c.HideNotes(), + PostRenderer: c.PostRenderer(), + PostRendererArgs: c.PostRendererArgs(), + ResetValues: c.ResetValues(), + ReuseValues: c.ReuseValues(), Set: c.Set(), SkipCleanup: c.SkipCleanup(), SkipCRDs: c.SkipCRDs(), - Wait: c.Wait(), - WaitRetries: c.WaitRetries(), - WaitForJobs: c.WaitForJobs(), - ReuseValues: c.ReuseValues(), - ResetValues: c.ResetValues(), - PostRenderer: c.PostRenderer(), - PostRendererArgs: c.PostRendererArgs(), SkipSchemaValidation: c.SkipSchemaValidation(), SyncArgs: c.SyncArgs(), - HideNotes: c.HideNotes(), - TakeOwnership: c.TakeOwnership(), SyncReleaseLabels: c.SyncReleaseLabels(), + TakeOwnership: c.TakeOwnership(), + Wait: c.Wait(), + WaitForJobs: c.WaitForJobs(), + WaitRetries: c.WaitRetries(), } return subst.SyncReleases(&affectedReleases, helm, c.Values(), c.Concurrency(), syncOpts) })) if len(updateErrs) > 0 { - applyErrs = append(applyErrs, updateErrs...) + errs = append(errs, updateErrs...) } } } @@ -1535,11 +1547,11 @@ Do you really want to apply? a.Logger.Warnf("warn: %v\n", err) } } - if releasesToBeDeleted == nil && releasesToBeUpdated == nil { + if releasesToDelete == nil && releasesToUpdate == nil { return true, false, nil } - return true, true, applyErrs + return true, true, errs } func (a *App) delete(r *Run, purge bool, c DestroyConfigProvider) (bool, []error) { @@ -1748,42 +1760,25 @@ func (a *App) status(r *Run, c StatusesConfigProvider) (bool, []error) { return true, errs } -func (a *App) sync(r *Run, c SyncConfigProvider) (bool, []error) { +func (a *App) SyncRun(r *Run, c SyncConfigProvider) (bool, []error) { st := r.state helm := r.helm - selectedReleases, selectedAndNeededReleases, err := a.getSelectedReleases(r, c.IncludeTransitiveNeeds()) - if err != nil { - return false, []error{err} - } - if len(selectedReleases) == 0 { - return false, nil - } - - // This is required when you're trying to deduplicate releases by the selector. - // Without this, `PlanReleases` conflates duplicates and return both in `batches`, - // even if we provided `SelectedReleases: selectedReleases`. - // See https://github.com/roboll/helmfile/issues/1818 for more context. - st.Releases = selectedAndNeededReleases + helm.SetExtraArgs(GetArgs(c.Args(), r.state)...) - batches, err := st.PlanReleases(state.PlanOptions{Reverse: false, SelectedReleases: selectedReleases, IncludeNeeds: c.IncludeNeeds(), IncludeTransitiveNeeds: c.IncludeTransitiveNeeds(), SkipNeeds: c.SkipNeeds()}) + releasesWithNeeds, selectedAndNeededReleases, err := a.GetPlannedAndSelectedReleasesWithNeeds(r, c.SkipNeeds(), c.IncludeNeeds(), c.IncludeTransitiveNeeds()) if err != nil { return false, []error{err} } - - var toSyncWithNeeds []state.ReleaseSpec - - for _, rs := range batches { - for _, r := range rs { - toSyncWithNeeds = append(toSyncWithNeeds, r.ReleaseSpec) - } + if len(releasesWithNeeds) == 0 { + return false, nil } // Do build deps and prepare only on selected releases so that we won't waste time // on running various helm commands on unnecessary releases - st.Releases = toSyncWithNeeds + st.Releases = releasesWithNeeds - toDelete, err := st.DetectReleasesToBeDeletedForSync(helm, toSyncWithNeeds) + toDelete, err := st.DetectReleasesToBeDeletedForSync(helm, releasesWithNeeds) if err != nil { return false, []error{err} } @@ -1796,7 +1791,7 @@ func (a *App) sync(r *Run, c SyncConfigProvider) (bool, []error) { } var toUpdate []state.ReleaseSpec - for _, r := range toSyncWithNeeds { + for _, r := range releasesWithNeeds { release := r if _, deleted := releasesToDelete[state.ReleaseToID(&release)]; !deleted { if r.Desired() { @@ -1816,7 +1811,7 @@ func (a *App) sync(r *Run, c SyncConfigProvider) (bool, []error) { } releasesWithNoChange := map[string]state.ReleaseSpec{} - for _, r := range toSyncWithNeeds { + for _, r := range releasesWithNeeds { release := r id := state.ReleaseToID(&release) _, uninstalled := releasesToDelete[id] @@ -1826,13 +1821,6 @@ func (a *App) sync(r *Run, c SyncConfigProvider) (bool, []error) { } } - for id := range releasesWithNoChange { - r := releasesWithNoChange[id] - if _, err := st.TriggerCleanupEvent(&r, "sync"); err != nil { - a.Logger.Warnf("warn: %v\n", err) - } - } - names := []string{} for _, r := range releasesToUpdate { names = append(names, fmt.Sprintf(" %s (%s) UPDATED", r.Name, r.Chart)) @@ -1860,8 +1848,6 @@ Do you really want to sync? var errs []error - r.helm.SetExtraArgs(GetArgs(c.Args(), r.state)...) - // Traverse DAG of all the releases so that we don't suffer from false-positive missing dependencies st.Releases = selectedAndNeededReleases @@ -1902,23 +1888,23 @@ Do you really want to sync? subst.Releases = rs - opts := &state.SyncOpts{ - Set: c.Set(), - SkipCRDs: c.SkipCRDs(), - Wait: c.Wait(), - WaitRetries: c.WaitRetries(), - WaitForJobs: c.WaitForJobs(), - ReuseValues: c.ReuseValues(), - ResetValues: c.ResetValues(), + syncOpts := &state.SyncOpts{ + HideNotes: c.HideNotes(), PostRenderer: c.PostRenderer(), PostRendererArgs: c.PostRendererArgs(), - SyncArgs: c.SyncArgs(), - HideNotes: c.HideNotes(), - TakeOwnership: c.TakeOwnership(), + ResetValues: c.ResetValues(), + ReuseValues: c.ReuseValues(), + Set: c.Set(), + SkipCRDs: c.SkipCRDs(), SkipSchemaValidation: c.SkipSchemaValidation(), + SyncArgs: c.SyncArgs(), SyncReleaseLabels: c.SyncReleaseLabels(), + TakeOwnership: c.TakeOwnership(), + Wait: c.Wait(), + WaitForJobs: c.WaitForJobs(), + WaitRetries: c.WaitRetries(), } - return subst.SyncReleases(&affectedReleases, helm, c.Values(), c.Concurrency(), opts) + return subst.SyncReleases(&affectedReleases, helm, c.Values(), c.Concurrency(), syncOpts) })) if len(syncErrs) > 0 { @@ -1926,7 +1912,16 @@ Do you really want to sync? } } } + affectedReleases.DisplayAffectedReleases(c.Logger()) + + for id := range releasesWithNoChange { + r := releasesWithNoChange[id] + if _, err := st.TriggerCleanupEvent(&r, "sync"); err != nil { + a.Logger.Warnf("warn: %v\n", err) + } + } + return true, errs } diff --git a/pkg/app/run.go b/pkg/app/run.go index 2beab11ae..b438e80a8 100644 --- a/pkg/app/run.go +++ b/pkg/app/run.go @@ -57,7 +57,7 @@ func (r *Run) prepareChartsIfNeeded(helmfileCommand string, dir string, concurre return releaseToChart, nil } -func (r *Run) withPreparedCharts(helmfileCommand string, opts state.ChartPrepareOptions, f func()) error { +func (r *Run) WithPreparedCharts(helmfileCommand string, opts state.ChartPrepareOptions, f func()) error { if r.ReleaseToChart != nil { panic("Run.PrepareCharts can be called only once") } @@ -222,3 +222,15 @@ func (r *Run) diff(triggerCleanupEvent bool, detailedExitCode bool, c DiffConfig return &infoMsg, releasesToBeUpdated, releasesToBeDeleted, nil } + +// Hack to get access to helmfile’s API +// -rluba, 2025-05-19 +func (r *Run) GetState() *state.HelmState { + return r.state +} + +// Hack to get access to helmfile’s API +// -rluba, 2025-05-19 +func (r *Run) GetHelm() helmexec.Interface { + return r.helm +}