feat: add --skip-charts flag to build command

Add a --skip-charts flag to the `helmfile build` command to allow users
to skip chart preparation, following the same pattern as the list and
destroy commands.

This addresses an issue where `helmfile build` would run `helm template`
on kustomize resources during chart preparation, even though build is
meant to be a read-only inspection command.

Changes:
- Add SkipCharts field to BuildOptions
- Add --skip-charts flag to build command CLI
- Update StateConfigProvider interface to include SkipCharts()
- Update PrintState() to conditionally skip withPreparedCharts when flag is set

By default, the flag is false (charts are prepared) to maintain backward
compatibility. Users can now run `helmfile build --skip-charts` to get
fast output without chart preparation.

Signed-off-by: Shane Starcher <shanestarcher@gmail.com>
This commit is contained in:
Shane Starcher 2025-10-04 14:43:23 -04:00 committed by Shane Starcher
parent 6673ebad84
commit d21e87b639
4 changed files with 34 additions and 18 deletions

View File

@ -32,6 +32,7 @@ func NewBuildCmd(globalCfg *config.GlobalImpl) *cobra.Command {
f := cmd.Flags()
f.BoolVar(&buildOptions.EmbedValues, "embed-values", false, "Read all the values files for every release and embed into the output helmfile.yaml")
f.BoolVar(&buildOptions.SkipCharts, "skip-charts", false, "don't prepare charts when building releases")
return cmd
}

View File

@ -521,11 +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{
SkipRepos: true,
SkipDeps: true,
Concurrency: 2,
}, func() {
printState := func() {
if c.EmbedValues() {
for i := range run.state.Releases {
r := run.state.Releases[i]
@ -562,13 +558,22 @@ func (a *App) PrintState(c StateConfigProvider) error {
fmt.Printf("---\n# Source: %s\n\n%+v", sourceFile, stateYaml)
errs = []error{}
})
if err != nil {
errs = append(errs, err)
}
return
if !c.SkipCharts() {
err := run.withPreparedCharts("build", state.ChartPrepareOptions{
SkipRepos: true,
SkipDeps: true,
Concurrency: 2,
}, printState)
if err != nil {
errs = append(errs, err)
}
} else {
printState()
}
return false, errs
}, false, SetFilter(true))
}
@ -592,20 +597,22 @@ func (a *App) ListReleases(c ListConfigProvider) error {
var stateReleases []*HelmRelease
var err error
listReleases := func() {
rel, err := a.list(run)
if err != nil {
panic(err)
}
stateReleases = rel
}
if !c.SkipCharts() {
err = run.withPreparedCharts("list", state.ChartPrepareOptions{
SkipRepos: true,
SkipDeps: true,
Concurrency: 2,
}, func() {
rel, err := a.list(run)
if err != nil {
panic(err)
}
stateReleases = rel
})
}, listReleases)
} else {
stateReleases, err = a.list(run)
listReleases()
}
if err != nil {

View File

@ -264,6 +264,7 @@ type StatusesConfigProvider interface {
type StateConfigProvider interface {
EmbedValues() bool
SkipCharts() bool
}
type DAGConfigProvider any

View File

@ -4,6 +4,8 @@ package config
type BuildOptions struct {
// EmbedValues is true if the values should be embedded
EmbedValues bool
// SkipCharts makes Build skip `withPreparedCharts`
SkipCharts bool
}
// NewBuildOptions creates a new Apply
@ -29,3 +31,8 @@ func NewBuildImpl(g *GlobalImpl, b *BuildOptions) *BuildImpl {
func (b *BuildImpl) EmbedValues() bool {
return b.BuildOptions.EmbedValues
}
// SkipCharts returns skipCharts flag
func (b *BuildImpl) SkipCharts() bool {
return b.BuildOptions.SkipCharts
}