helmfile/pkg/state
Dominik Schmidt 0139304d97
feat(state): add mergeStrategy: fallback for first-file-wins env values (#2578)
* feat(state): add mergeStrategy field to EnvironmentSpec

Introduces a per-environment mergeStrategy with valid values "override"
(default, current behavior) and "fallback". This commit only adds the
field, the constants, and a parse-time validator; the loader still
ignores the value, so behavior is unchanged.

Subsequent commits thread the value through the values loader and
implement the fallback semantics.

Signed-off-by: Dominik Schmidt <dev@dominik-schmidt.de>

* refactor(state): thread mergeStrategy through values loader

Adds a mergeStrategy string parameter to LoadEnvironmentValues,
loadValuesEntries, and mapMerge so the value can flow from
EnvironmentSpec down to the merge call site. Behavior is unchanged in
this commit; mapMerge ignores the strategy and the next commit
implements the fallback semantics.

Top-level state.DefaultValues and the --state-values-file/-set loaders
are passed an empty strategy ("") since they have no per-environment
spec to consult and stay on the default override behavior.

Signed-off-by: Dominik Schmidt <dev@dominik-schmidt.de>

* feat(state): implement fallback merge strategy

Adds a hand-rolled fallbackDeepMerge that, unlike mergo, preserves
keys present in the destination even when their value is the zero
value (false, 0, "", nil, empty list/map). mapMerge dispatches to it
when mergeStrategy == "fallback"; "override" and the empty default
keep using mergo with WithOverride so existing behaviour is unchanged.

Validation lives at the entry of LoadEnvironmentValues so a single
chokepoint guards the field. Invalid values produce an error naming
both the offending value and the valid options.

Tests cover: first-file-wins precedence, gap filling, deep nested
merge, three-file chains, explicit zero-value preservation (the case
naïve mergo gets wrong), explicit nil preservation, inline map
entries, override regression, default-equals-override equivalence,
and invalid-strategy errors.

Signed-off-by: Dominik Schmidt <dev@dominik-schmidt.de>

* feat(state): expose prior-file values in fallback template context

Under mergeStrategy: fallback, .gotmpl values files can now reference
values from earlier files in the same `values:` list via .Values
(e.g. `service.domain: "service.{{ .Values.cluster.domain }}"`).

The accumulated result is layered under env.GetMergedValues so env
defaults, env values, and CLI overrides still win on overlap. Override
mode keeps the historical template context — unchanged — so this is
strictly opt-in via the mergeStrategy field.

Together with the precedence flip from the previous commit, this lets
users replace the brittle two-stage `merged-values.yaml.gotmpl`
workaround with native helmfile syntax.

Tests cover the headline cross-file template reference case and pin
the override-mode contract that prior-file values stay invisible.

Signed-off-by: Dominik Schmidt <dev@dominik-schmidt.de>

* docs: document mergeStrategy and fallback semantics

Adds a new section to values-and-merging.md describing the override vs
fallback strategies, the explicit-zero-value preservation guarantee,
and the cross-file template reference behavior. Adds a brief pointer
to environments.md so users land on the new field from the
environment values discussion.

Signed-off-by: Dominik Schmidt <dev@dominik-schmidt.de>

* refactor(state): reuse maputil.MergeMaps for fallback merge

Replaces the hand-rolled fallbackDeepMerge with a single call to
maputil.MergeMaps, swapping its arguments so the accumulated dest wins
over the new src file. Same first-file-wins semantic, fewer lines, and
the fallback path now inherits the same slice merge strategies the
rest of helmfile already uses.

The one observable behavior shift is for explicit nil values: under
fallback, nil in an earlier file no longer 'wins' over a non-nil value
in a later file — instead it falls through (matching MergeMaps' rule
that nil from the override side only fills missing keys). This is
internally consistent: nil-overwrites is an mergo.WithOverride quirk
that lives only in the override path. The renamed test
NilFallsThroughToFallback pins the new behavior with a comment
referencing the contrast with override mode (Issue1154).

Signed-off-by: Dominik Schmidt <dev@dominik-schmidt.de>

---------

Signed-off-by: Dominik Schmidt <dev@dominik-schmidt.de>
2026-05-07 21:50:05 +08:00
..
testdata feat(state): add mergeStrategy: fallback for first-file-wins env values (#2578) 2026-05-07 21:50:05 +08:00
chart_dependencies_rewrite_test.go fix: eliminate race condition in rewriteChartDependencies (#2541) 2026-04-20 10:15:47 +08:00
chart_dependency.go fix: deduplicate chart dependencies in helmfile.lock (#2567) 2026-05-01 20:29:24 +08:00
chart_dependency_test.go fix: deduplicate chart dependencies in helmfile.lock (#2567) 2026-05-01 20:29:24 +08:00
create.go feat(state): add mergeStrategy: fallback for first-file-wins env values (#2578) 2026-05-07 21:50:05 +08:00
create_test.go feat(state): add mergeStrategy: fallback for first-file-wins env values (#2578) 2026-05-07 21:50:05 +08:00
diff_error_propagation_test.go feat: add Helm 4 support while maintaining Helm 3 compatibility (#2262) 2025-11-19 07:49:30 +08:00
environment.go feat(state): add mergeStrategy: fallback for first-file-wins env values (#2578) 2026-05-07 21:50:05 +08:00
envvals_loader.go feat(state): add mergeStrategy: fallback for first-file-wins env values (#2578) 2026-05-07 21:50:05 +08:00
envvals_loader_test.go feat(state): add mergeStrategy: fallback for first-file-wins env values (#2578) 2026-05-07 21:50:05 +08:00
helmx.go fix(state): resolve OCI repo prefix in ad-hoc release dependencies (#2579) 2026-05-07 17:07:20 +08:00
helmx_test.go fix: use --force-replace flag for Helm 4 instead of deprecated --force (#2477) 2026-03-11 21:23:51 +08:00
issue_2297_test.go fix: resolve issues #2295, #2296, and #2297 (#2298) 2025-11-27 22:13:03 +08:00
issue_2355_test.go fix: resolve --validate flag conflict with kustomize in Helm 4 (#2362) 2026-01-18 13:59:55 +08:00
issue_2444_test.go fix: pass --kubeconfig to chartify's helm template call (#2449) 2026-03-03 20:56:46 +08:00
issue_2549_test.go Honor `skipSchemaValidation` during chartification when `forceNamespace` is set (#2550) 2026-04-30 12:30:41 -04:00
oci_chart_lock_test.go fix: skip cache refresh for shared cache paths to prevent race conditions (#2396) 2026-02-14 10:46:05 +08:00
oci_chart_version_test.go fix: support OCI chart digest syntax (@sha256:...) (#2398) 2026-02-12 20:20:43 +08:00
oci_parse_test.go fix: support OCI chart digest syntax (@sha256:...) (#2398) 2026-02-12 20:20:43 +08:00
release.go feat: execute templates against postRendererHooks (#1839) 2025-01-28 09:34:59 -05:00
release_error.go Fix misleading `helmfile diff` output (#1174) 2020-04-04 17:39:20 +09:00
release_filters.go fix: skip subhelmfiles when selectors conflict with CLI selectors (#2545) 2026-04-25 17:18:39 +08:00
release_filters_test.go fix: skip subhelmfiles when selectors conflict with CLI selectors (#2545) 2026-04-25 17:18:39 +08:00
release_test.go refactor(yaml): upgrade from gopkg.in/yaml.v2 to v3 (#2039) 2025-05-15 10:21:37 -04:00
run_helm_dep_builds_skip_refresh_test.go fix: local chart with external dependencies error when repos configured (#2433) 2026-02-28 09:23:04 +08:00
selector_test.go feat: add labels for helm release (#1046) 2025-03-30 19:24:41 -04:00
skip_test.go Fix four critical bugs: array merging (#2281), AWS SDK logging (#2270), helmDefaults skip flags (#2269), and OCI chart versions (#2247) (#2288) 2025-11-22 09:27:51 +08:00
state.go feat(state): add mergeStrategy: fallback for first-file-wins env values (#2578) 2026-05-07 21:50:05 +08:00
state_exec_tmpl.go Expose release version as .Release.ChartVersion for templating (#2080) 2025-06-17 19:41:42 +08:00
state_exec_tmpl_test.go Expose release version as .Release.ChartVersion for templating (#2080) 2025-06-17 19:41:42 +08:00
state_gogetter_test.go Prevent excessive log in tests 2022-10-11 06:19:32 +09:00
state_kubeversion_test.go 🐛 Fix four critical issues: environment merging, kubeVersion detection, lookup() with kustomize, and Helm 4 color flags (#2276) 2025-11-21 08:32:54 +08:00
state_run.go Drop Helm v2 support (#613) 2023-01-17 09:24:47 +09:00
state_test.go fix(state): resolve OCI repo prefix in ad-hoc release dependencies (#2579) 2026-05-07 17:07:20 +08:00
storage.go fix: eliminate os.Chdir in sequential helmfiles to fix relative path resolution (#2410) 2026-02-22 09:21:46 +08:00
storage_test.go fix: array merge regression - layer arrays now replace defaults (#2367) 2026-01-18 14:04:54 +08:00
temp.go Replace interface{} by any (#901) 2023-06-16 10:49:05 +09:00
temp_test.go fix: add trackFailOnError option to control kubedog exit code (#2576) 2026-05-04 14:20:03 +08:00
types.go fix: boolean false overrides dropped in multi-document helmfiles (#2527) (#2532) 2026-04-12 09:44:49 +08:00
types_test.go fix: boolean false overrides dropped in multi-document helmfiles (#2527) (#2532) 2026-04-12 09:44:49 +08:00
util.go fix: version path issue (#1344) 2024-02-16 09:08:28 +09:00
util_test.go fix: version path issue (#1344) 2024-02-16 09:08:28 +09:00