feat(state): add missingFileHandlerConfig and related logic (#2105)

* feat(state): add missingFileHandlerConfig and related logic

Signed-off-by: yxxhero <aiopsclub@163.com>

* feat(state): add missingFileHandlerConfig and related logic

Signed-off-by: yxxhero <aiopsclub@163.com>

---------

Signed-off-by: yxxhero <aiopsclub@163.com>
This commit is contained in:
yxxhero 2025-07-22 07:15:51 +08:00 committed by GitHub
parent 6fd4048653
commit b0911ab1a2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 87 additions and 19 deletions

View File

@ -412,6 +412,10 @@ helmfiles:
# If set to "Error", return an error when a subhelmfile points to a # If set to "Error", return an error when a subhelmfile points to a
# non-existent path. The default behavior is to print a warning and continue. # non-existent path. The default behavior is to print a warning and continue.
missingFileHandler: Error missingFileHandler: Error
missingFileHandlerConfig:
# Ignores missing git branch error so that the Debug/Info/Warn handler can treat a missing branch as non-error.
# See https://github.com/helmfile/helmfile/issues/392
ignoreMissingGitBranch: true
# #
# Advanced Configuration: Environments # Advanced Configuration: Environments

View File

@ -224,6 +224,31 @@ func (c *StateCreator) loadBases(envValues, overrodeEnv *environment.Environment
return layers[0], nil return layers[0], nil
} }
// getEnvMissingFileHandlerConfig returns the first non-nil MissingFileHandlerConfig from the environment spec, state, or default.
func (st *HelmState) getEnvMissingFileHandlerConfig(es EnvironmentSpec) *MissingFileHandlerConfig {
switch {
case es.MissingFileHandlerConfig != nil:
return es.MissingFileHandlerConfig
case st.MissingFileHandlerConfig != nil:
return st.MissingFileHandlerConfig
default:
return nil
}
}
// getEnvMissingFileHandler returns the first non-nil MissingFileHandler from the environment spec, state, or default.
func (st *HelmState) getEnvMissingFileHandler(es EnvironmentSpec) *string {
defaultMissingFileHandler := "Error"
switch {
case es.MissingFileHandler != nil:
return es.MissingFileHandler
case st.MissingFileHandler != nil:
return st.MissingFileHandler
default:
return &defaultMissingFileHandler
}
}
// nolint: unparam // nolint: unparam
func (c *StateCreator) loadEnvValues(st *HelmState, name string, failOnMissingEnv bool, ctxEnv, overrode *environment.Environment) (*environment.Environment, error) { func (c *StateCreator) loadEnvValues(st *HelmState, name string, failOnMissingEnv bool, ctxEnv, overrode *environment.Environment) (*environment.Environment, error) {
secretVals := map[string]any{} secretVals := map[string]any{}
@ -240,7 +265,7 @@ func (c *StateCreator) loadEnvValues(st *HelmState, name string, failOnMissingEn
var envSecretFiles []string var envSecretFiles []string
if len(envSpec.Secrets) > 0 { if len(envSpec.Secrets) > 0 {
for _, urlOrPath := range envSpec.Secrets { for _, urlOrPath := range envSpec.Secrets {
resolved, skipped, err := st.storage().resolveFile(envSpec.MissingFileHandler, "environment values", urlOrPath, envSpec.MissingFileHandlerConfig.resolveFileOptions()...) resolved, skipped, err := st.storage().resolveFile(st.getEnvMissingFileHandler(envSpec), "environment values", urlOrPath, st.getEnvMissingFileHandlerConfig(envSpec).resolveFileOptions()...)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -14,5 +14,5 @@ type EnvironmentSpec struct {
// a message about the missing file at the log-level. // a message about the missing file at the log-level.
MissingFileHandler *string `yaml:"missingFileHandler,omitempty"` MissingFileHandler *string `yaml:"missingFileHandler,omitempty"`
// MissingFileHandlerConfig is composed of various settings for the MissingFileHandler // MissingFileHandlerConfig is composed of various settings for the MissingFileHandler
MissingFileHandlerConfig MissingFileHandlerConfig `yaml:"missingFileHandlerConfig,omitempty"` MissingFileHandlerConfig *MissingFileHandlerConfig `yaml:"missingFileHandlerConfig,omitempty"`
} }

View File

@ -350,7 +350,7 @@ func (st *HelmState) PrepareChartify(helm helmexec.Interface, release *ReleaseSp
jsonPatches := release.JSONPatches jsonPatches := release.JSONPatches
if len(jsonPatches) > 0 { if len(jsonPatches) > 0 {
generatedFiles, err := st.generateTemporaryReleaseValuesFiles(release, jsonPatches, release.MissingFileHandler) generatedFiles, err := st.generateTemporaryReleaseValuesFiles(release, jsonPatches)
if err != nil { if err != nil {
return nil, clean, err return nil, clean, err
} }
@ -364,7 +364,7 @@ func (st *HelmState) PrepareChartify(helm helmexec.Interface, release *ReleaseSp
strategicMergePatches := release.StrategicMergePatches strategicMergePatches := release.StrategicMergePatches
if len(strategicMergePatches) > 0 { if len(strategicMergePatches) > 0 {
generatedFiles, err := st.generateTemporaryReleaseValuesFiles(release, strategicMergePatches, release.MissingFileHandler) generatedFiles, err := st.generateTemporaryReleaseValuesFiles(release, strategicMergePatches)
if err != nil { if err != nil {
return nil, clean, err return nil, clean, err
} }
@ -378,7 +378,7 @@ func (st *HelmState) PrepareChartify(helm helmexec.Interface, release *ReleaseSp
transformers := release.Transformers transformers := release.Transformers
if len(transformers) > 0 { if len(transformers) > 0 {
generatedFiles, err := st.generateTemporaryReleaseValuesFiles(release, transformers, release.MissingFileHandler) generatedFiles, err := st.generateTemporaryReleaseValuesFiles(release, transformers)
if err != nil { if err != nil {
return nil, clean, err return nil, clean, err
} }

View File

@ -84,9 +84,9 @@ type ReleaseSetSpec struct {
// If set to "Error", return an error when a subhelmfile points to a // If set to "Error", return an error when a subhelmfile points to a
// non-existent path. The default behavior is to print a warning. Note the // non-existent path. The default behavior is to print a warning. Note the
// differing default compared to other MissingFileHandlers. // differing default compared to other MissingFileHandlers.
MissingFileHandler string `yaml:"missingFileHandler,omitempty"` MissingFileHandler *string `yaml:"missingFileHandler,omitempty"`
// MissingFileHandlerConfig is composed of various settings for the MissingFileHandler // MissingFileHandlerConfig is composed of various settings for the MissingFileHandler
MissingFileHandlerConfig MissingFileHandlerConfig `yaml:"missingFileHandlerConfig,omitempty"` MissingFileHandlerConfig *MissingFileHandlerConfig `yaml:"missingFileHandlerConfig,omitempty"`
LockFile string `yaml:"lockFilePath,omitempty"` LockFile string `yaml:"lockFilePath,omitempty"`
} }
@ -307,6 +307,10 @@ type ReleaseSpec struct {
// MissingFileHandler is set to either "Error" or "Warn". "Error" instructs helmfile to fail when unable to find a values or secrets file. When "Warn", it prints the file and continues. // MissingFileHandler is set to either "Error" or "Warn". "Error" instructs helmfile to fail when unable to find a values or secrets file. When "Warn", it prints the file and continues.
// The default value for MissingFileHandler is "Error". // The default value for MissingFileHandler is "Error".
MissingFileHandler *string `yaml:"missingFileHandler,omitempty"` MissingFileHandler *string `yaml:"missingFileHandler,omitempty"`
// MissingFileHandlerConfig is composed of various settings for the MissingFileHandler
MissingFileHandlerConfig *MissingFileHandlerConfig `yaml:"missingFileHandlerConfig,omitempty"`
// Needs is the [KUBECONTEXT/][NS/]NAME representations of releases that this release depends on. // Needs is the [KUBECONTEXT/][NS/]NAME representations of releases that this release depends on.
Needs []string `yaml:"needs,omitempty"` Needs []string `yaml:"needs,omitempty"`
@ -3194,7 +3198,7 @@ func (st *HelmState) ExpandedHelmfiles() ([]SubHelmfileSpec, error) {
} }
if len(matches) == 0 { if len(matches) == 0 {
err := fmt.Errorf("no matches for path: %s", hf.Path) err := fmt.Errorf("no matches for path: %s", hf.Path)
if st.MissingFileHandler == "Error" { if *st.MissingFileHandler == "Error" {
return nil, err return nil, err
} }
st.logger.Warnf("no matches for path: %s", hf.Path) st.logger.Warnf("no matches for path: %s", hf.Path)
@ -3241,19 +3245,54 @@ func (st *HelmState) removeFiles(files []string) {
} }
} }
func (c MissingFileHandlerConfig) resolveFileOptions() []resolveFileOption { func (c *MissingFileHandlerConfig) resolveFileOptions() []resolveFileOption {
if c == nil {
return []resolveFileOption{
ignoreMissingGitBranch(false),
}
}
return []resolveFileOption{ return []resolveFileOption{
ignoreMissingGitBranch(c.IgnoreMissingGitBranch), ignoreMissingGitBranch(c.IgnoreMissingGitBranch),
} }
} }
func (st *HelmState) generateTemporaryReleaseValuesFiles(release *ReleaseSpec, values []any, missingFileHandler *string) ([]string, error) { // getReleaseMissingFileHandlerConfig returns the first non-nil MissingFileHandlerConfig in the following order:
// - release.MissingFileHandlerConfig
// - st.MissingFileHandlerConfig
func (st *HelmState) getReleaseMissingFileHandlerConfig(release *ReleaseSpec) *MissingFileHandlerConfig {
switch {
case release.MissingFileHandlerConfig != nil:
return release.MissingFileHandlerConfig
case st.MissingFileHandlerConfig != nil:
return st.MissingFileHandlerConfig
default:
return nil
}
}
// getReleaseMissingFileHandler returns the first non-nil MissingFileHandler in the following order:
// - release.MissingFileHandler
// - st.MissingFileHandler
// - "Error"
func (st *HelmState) getReleaseMissingFileHandler(release *ReleaseSpec) *string {
defaultMissingFileHandler := "Error"
switch {
case release.MissingFileHandler != nil:
return release.MissingFileHandler
case st.MissingFileHandler != nil:
return st.MissingFileHandler
default:
return &defaultMissingFileHandler
}
}
func (st *HelmState) generateTemporaryReleaseValuesFiles(release *ReleaseSpec, values []any) ([]string, error) {
generatedFiles := []string{} generatedFiles := []string{}
for _, value := range values { for _, value := range values {
switch typedValue := value.(type) { switch typedValue := value.(type) {
case string: case string:
paths, skip, err := st.storage().resolveFile(missingFileHandler, "values", typedValue, st.MissingFileHandlerConfig.resolveFileOptions()...) paths, skip, err := st.storage().resolveFile(st.getReleaseMissingFileHandler(release), "values", typedValue, st.getReleaseMissingFileHandlerConfig(release).resolveFileOptions()...)
if err != nil { if err != nil {
return generatedFiles, err return generatedFiles, err
} }
@ -3334,7 +3373,7 @@ func (st *HelmState) generateVanillaValuesFiles(release *ReleaseSpec) ([]string,
return nil, fmt.Errorf("Failed to render values in %s for release %s: type %T isn't supported", st.FilePath, release.Name, valuesMapSecretsRendered["values"]) return nil, fmt.Errorf("Failed to render values in %s for release %s: type %T isn't supported", st.FilePath, release.Name, valuesMapSecretsRendered["values"])
} }
generatedFiles, err := st.generateTemporaryReleaseValuesFiles(release, valuesSecretsRendered, release.MissingFileHandler) generatedFiles, err := st.generateTemporaryReleaseValuesFiles(release, valuesSecretsRendered)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -3400,7 +3439,7 @@ func (st *HelmState) generateSecretValuesFiles(helm helmexec.Interface, release
generatedDecryptedFiles = append(generatedDecryptedFiles, valfile) generatedDecryptedFiles = append(generatedDecryptedFiles, valfile)
} }
generatedFiles, err := st.generateTemporaryReleaseValuesFiles(release, generatedDecryptedFiles, release.MissingFileHandler) generatedFiles, err := st.generateTemporaryReleaseValuesFiles(release, generatedDecryptedFiles)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -38,39 +38,39 @@ func TestGenerateID(t *testing.T) {
run(testcase{ run(testcase{
subject: "baseline", subject: "baseline",
release: ReleaseSpec{Name: "foo", Chart: "incubator/raw"}, release: ReleaseSpec{Name: "foo", Chart: "incubator/raw"},
want: "foo-values-57ff559cd5", want: "foo-values-7d454b9558",
}) })
run(testcase{ run(testcase{
subject: "different bytes content", subject: "different bytes content",
release: ReleaseSpec{Name: "foo", Chart: "incubator/raw"}, release: ReleaseSpec{Name: "foo", Chart: "incubator/raw"},
data: []byte(`{"k":"v"}`), data: []byte(`{"k":"v"}`),
want: "foo-values-75cd785b8b", want: "foo-values-59c86d55bf",
}) })
run(testcase{ run(testcase{
subject: "different map content", subject: "different map content",
release: ReleaseSpec{Name: "foo", Chart: "incubator/raw"}, release: ReleaseSpec{Name: "foo", Chart: "incubator/raw"},
data: map[string]any{"k": "v"}, data: map[string]any{"k": "v"},
want: "foo-values-5b44fdc768", want: "foo-values-6f87c5cd79",
}) })
run(testcase{ run(testcase{
subject: "different chart", subject: "different chart",
release: ReleaseSpec{Name: "foo", Chart: "stable/envoy"}, release: ReleaseSpec{Name: "foo", Chart: "stable/envoy"},
want: "foo-values-6ddb5db94d", want: "foo-values-5dfd748475",
}) })
run(testcase{ run(testcase{
subject: "different name", subject: "different name",
release: ReleaseSpec{Name: "bar", Chart: "incubator/raw"}, release: ReleaseSpec{Name: "bar", Chart: "incubator/raw"},
want: "bar-values-79bbb8df74", want: "bar-values-858b9c55cc",
}) })
run(testcase{ run(testcase{
subject: "specific ns", subject: "specific ns",
release: ReleaseSpec{Name: "foo", Chart: "incubator/raw", Namespace: "myns"}, release: ReleaseSpec{Name: "foo", Chart: "incubator/raw", Namespace: "myns"},
want: "myns-foo-values-c9457ccfb", want: "myns-foo-values-58dc9c6667",
}) })
for id, n := range ids { for id, n := range ids {