Commit Graph
509 Commits
Author SHA1 Message Date
yxxhero 6034a54e0d refactor: drop empty-render workaround now that chartify handles it natively (#2747)
Bump github.com/helmfile/chartify to v0.28.2, which fixes the empty-render
crash upstream (helmfile/chartify#206, fixed in helmfile/chartify#207):
when a chart renders zero resources, chartify now treats it as a no-op
success itself - removing the chart's content dirs, cleaning Chart.yaml
dependencies and the lock file, and skipping the kustomize step - instead
of failing with:

  assertion failed: unexpected dir entry "" it must be the abs path to the output directory

That makes the helmfile-side string-matching workaround from #2724 dead
code: the error it matched can no longer be produced. Remove
isChartifyEmptyRenderOutputError and the special-cased no-op branch in
processChartification, so the empty-render case simply flows through the
regular chartify path.

Also bump github.com/helmfile/vals to v0.46.0 and refresh transitive
dependencies.

The regression test for #1757 is updated to assert the new direct
behavior: processChartification succeeds, returns a chartified chart
whose Chart.yaml no longer declares dependencies (so a subsequent
"helm dep build"/"helm template" cannot fail with "found in Chart.yaml,
but missing in charts/ directory"), renders empty output, and is cleaned
up by CleanupChartifyTempDirs.

Fixes #1757 (follow-up to #2724)

Signed-off-by: yxxhero <aiopsclub@163.com>
2026-08-16 10:47:19 +08:00
Thomas HanserandClaude Sonnet 5 2cb878d5a0 fix(#2741): prefetch shared remote charts instead of serializing sync (#2743)
* fix(state): prefetch shared remote charts instead of serializing sync/diff (#2741)

PR #2662 fixed a Windows chart-download race (#768) by wrapping the entire
helm upgrade/diff operation in a per-chart+version lock, not just the
download. Since sync/apply/diff never set ForceDownload, releases sharing a
remote chart end up fully serialized even at high --concurrency.

Add ChartPrepareOptions.PrefetchSharedRemoteCharts: PrepareCharts groups
selected releases by chart+version, and force-downloads (once) any chart
used by 2+ releases that also resolve to identical acquisition flags
(--verify/--keyring/--plain-http/--insecure-skip-tls-verify/--devel) and to
a configured repository (or OCI ref) - not a bare \"dir/chart\"-shaped local
path. That materializes release.ChartPath, which lets withChartOperationLock's
existing ChartPath != \"\" guard skip the lock, restoring concurrency without
touching the #768 protection for charts that aren't prefetched.

Also add chartFetchFlags to give forcedDownloadChart's \`helm fetch\` the same
verify/keyring/TLS flags flagsForUpgrade already applies, closing a parity
gap that predates this change (affects lint/unittest/pull too).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Signed-off-by: Thomas Hanser <gh@toms.place>

* fix(state): exclude verify-enabled charts from shared-chart prefetch, use NUL-delimited flag signature

Copilot review on #2741's PR flagged two issues in the shared-chart prefetch
added there:

1. forcedDownloadChart untars a shared chart into a local directory, but
   flagsForUpgrade unconditionally re-adds --verify for the later `helm
   upgrade` regardless of ChartPath. Helm's VerifyChart only accepts a
   packaged .tgz/provenance pair, not an unpacked directory, so upgrading a
   prefetched chart with verify enabled would fail. Exclude --verify from
   prefetch eligibility entirely rather than trying to suppress the later
   flag - those releases just keep the pre-existing serialized-lock
   behavior, unaffected by this feature.

2. The per-key flag-agreement signature joined flags with a space, which
   isn't injective: a keyring path containing a space and a flag-like token
   could collide with a different keyring plus a real flag, silently
   deduplicating releases with different acquisition settings. Join with
   NUL instead, which can't appear in an OS argument.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Signed-off-by: Thomas Hanser <gh@toms.place>

* fix(state): apply -chart override before shared-chart grouping

Copilot flagged that PrepareCharts grouped releases by release.Chart
before prepareChartForRelease applied st.OverrideChart (the -chart CLI
flag), so distinct original charts that all resolve to the same
overridden chart were never recognized as shared and missed the
prefetch. Apply the override once upfront, before the grouping loop
reads release.Chart.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Signed-off-by: Thomas Hanser <gh@toms.place>

---------

Signed-off-by: Thomas Hanser <gh@toms.place>
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-15 08:57:58 +08:00
henrichter-sap 7cf4ff9a25 feat: add --skip-diff-validation-on-install CLI flag (#2728)
Signed-off-by: Richter <h.richter@sap.com>
2026-08-03 21:11:45 +08:00
Ankit 1341fd8659 fix: skip chartification when chart renders zero resources (#2724)
* fix: skip chartification when chart renders zero resources

When a chart's templates render zero resources (e.g. everything is
gated behind a falsy `{{- if .Values.enabled }}`) and the release has
transformers, jsonPatches, or strategicMergePatches configured,
helmfile crashed with:

  assertion failed: unexpected dir entry "" it must be the abs path
  to the output directory

Root cause: chartify's replace.go runs `helm template --output-dir`
and expects exactly one directory entry under that output dir (the
rendered chart). When helm renders no resources, the output dir is
empty, so chartOutputDir stays "" and chartify's own assertion on it
being an absolute path fails. chartify (v0.28.0) doesn't expose a
typed/sentinel error for this, only the assertion text.

Since there's nothing to chartify when a release has no rendered
resources, treat this specific chartify failure as a no-op: keep
using the chart as-is and let helm template it normally (producing
the same empty output helm would have produced without chartify).
Any other chartify error is still surfaced unchanged.

Verified manually end-to-end with a real helm+kustomize:
- a chart with `enabled: false` + a transformer now runs without
  error instead of crashing
- a chart with `enabled: true` + the same transformer still gets
  transformed correctly (annotations applied), confirming the normal
  chartify path is untouched

Added unit tests for the new error-matching helper in
pkg/state/issue_1757_test.go.

Fixes #1757

Signed-off-by: ankit090701 <ankitanku090701@gmail.com>

* fix: return original chart path from empty-render no-op, not the deps-rewrite temp copy

Review feedback on the original fix (#2724) found a real bug: the
empty-render no-op path returned chartPath after it may have already
been reassigned to the temp copy created by rewriteChartDependencies
(for charts with relative file:// deps). That temp dir is removed by
a deferred cleanupTempChart() as soon as processChartification
returns, so the caller was handed a path to a directory that no
longer existed, breaking any subsequent helm command with "chart not
found" - narrow (only local charts with relative file:// deps that
also render zero resources) but real and reproducible.

Capture originalChartPath before the rewrite and return that instead.
Also flatten the nested `if err != nil { if isChartifyEmptyRenderOutputError...`
into two sequential checks per review, and link the upstream tracking
issue (helmfile/chartify#206, opened by a maintainer during review) in
the error-matching constant's doc comment.

Added TestProcessChartification_EmptyRenderReturnsSurvivingPath, an
end-to-end test exercising the real processChartification ->
chartify.Chartify wiring (not just the isChartifyEmptyRenderOutputError
helper) with a chart that has a relative file:// dependency and renders
zero resources - the exact conditions that trigger the bug. Verified
passing against real helm+kustomize in a Linux container; skipped on
Windows due to an unrelated, pre-existing Windows path-handling issue
in chartify's dependency resolution (a drive letter embedded in a
file:// URL gets mis-joined), independent of the code path under test.

Signed-off-by: ankit090701 <ankitanku090701@gmail.com>

* fix: narrow chartify empty-render error match to avoid false positives

Per Copilot's automated review on this PR: the previous substring,
"it must be the abs path to the output directory", matches chartify's
assertion regardless of what chartOutputDir actually is. In the real
empty-render case chartOutputDir is "" (formatted via %q as `""`), but
the same assertion (chartify replace.go:151) would also fire if
chartOutputDir were ever a non-empty-but-still-relative path - a
different, genuine bug that should be surfaced as an error, not
silently treated as an empty-render no-op.

Narrow the match to include the `unexpected dir entry ""` prefix, so
it can only match the exact empty-string case. Verified against the
actual chartify v0.28.0 source (fmt.Errorf with %q on chartOutputDir)
that this is precisely what the empty-render case produces.

Added a test case asserting the same assertion text with a non-empty
dir entry is correctly NOT treated as the empty-render case. Re-ran
the full test suite (including the real helm+kustomize end-to-end
integration test) in a Linux container to confirm the narrower match
still catches the actual reported bug.

Signed-off-by: ankit090701 <ankitanku090701@gmail.com>

---------

Signed-off-by: ankit090701 <ankitanku090701@gmail.com>
2026-08-02 07:28:57 +08:00
yxxhero 7f748fec1b feat: support Helm 4 --rollback-on-failure alongside deprecated --atomic (#2722)
* feat: support Helm 4 --rollback-on-failure alongside deprecated --atomic (#2712)

Helm 4 renamed the `--atomic` flag to `--rollback-on-failure`
(helm/helm#13629). The old flag still works under Helm 4 but is
deprecated (prints a warning) and slated for removal in Helm 5.

Add a `rollbackOnFailure` key to both `helmDefaults` (HelmSpec) and
`releases[]` (ReleaseSpec) that emits `--rollback-on-failure`. It
requires Helm 4+ (errors otherwise) and is mutually exclusive with
`atomic`.

Additionally, when the resolved Helm binary is v4+, an existing
`atomic: true` now emits `--rollback-on-failure` instead of `--atomic`,
so users are migrated off the deprecated flag automatically without any
config change. On older Helm, `atomic: true` continues to emit
`--atomic`.

Updated the spew-based values-ID hashes in temp_test.go that change
whenever ReleaseSpec gains a field (same approach as the --force-conflicts
change in #2480).

Closes #2712.

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

* test: add integration test for rollback-on-failure / atomic migration (#2712)

Covers the end-to-end plumbing that unit tests cannot (real helm version
detection + cluster deploy) via test/integration/run.sh:

  1. atomic: true parses, deploys a ConfigMap, and emits the version-correct
     flag: --rollback-on-failure on Helm 4 (auto-migration of the deprecated
     --atomic) and --atomic on Helm 3.
  2. rollbackOnFailure: true emits --rollback-on-failure on Helm 4 and is
     rejected with a clear Helm-4-required error on Helm 3.

Flag assertions grep the `exec: helm upgrade --install` lines logged under
--debug, matching flags as standalone tokens so the release name
"issue-2712-atomic" cannot be confused with the "--atomic" flag.

Verified locally against Helm 4.2.3: both atomic:true and
rollbackOnFailure:true emit --rollback-on-failure with no --atomic.

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

---------

Signed-off-by: yxxhero <aiopsclub@163.com>
2026-07-31 22:22:31 +08:00
yxxhero 43aafeaad1 fix: ensure OCI registry login when SkipRepos is set (#2701)
* fix: ensure OCI registry login when SkipRepos is set (#1847)

Commands like build, status, list, and show-dag set SkipRepos: true to
avoid slow helm repo add/update for classic repos. However, this also
skipped helm registry login for OCI registries, causing 401 Unauthorized
errors when pulling OCI charts.

Add a variadic SyncOption parameter (backward compatible) with
WithOCIOnly() that limits repo processing to OCI registries only.
When skipRepos is true, callers now pass WithOCIOnly() so that OCI
authentication still happens before chart pulls.

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

* fix: reword OCI login comments per review feedback

RegistryLogin is a no-op when credentials are not configured, so the
word 'always' was misleading. Clarify that login is only needed when
credentials are present.

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

* fix: skip OCI login for commands that don't pull charts

Commands like 'list' and 'write-values' skip chart preparation entirely,
so OCI registry login is unnecessary for them. Extract the skip-command
list into a shared variable and use it to gate OCI-only login in
WithPreparedCharts.

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

* docs: clarify commandsSkipChartPrep comment per review feedback

Clarify that these commands only skip OCI login when skipRepos is true;
when skipRepos is false, SyncReposOnce still runs normally for all repos.

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

---------

Signed-off-by: yxxhero <aiopsclub@163.com>
2026-07-12 09:51:24 +08:00
yxxhero a9e4a8518a feat: center section divider to match table width (#2698)
The '========== Updated Releases ==========' header was a fixed 38
chars regardless of the table width below it. Now the divider line
is extended to match the table's visual width, with the title text
centered within the '=' borders.

- Add TableVisualWidth() to measure table width via runewidth
- Add HeaderDividerCentered() and HeaderDividerCenteredStyled()
  for centered dividers with optional bold+blue ANSI styling
- Refactor DisplayAffectedReleases to build the table first, then
  compute its width before logging the header
- Update all test snapshots and integration test output files

Signed-off-by: yxxhero <aiopsclub@163.com>
2026-07-10 21:17:35 +08:00
Arthur Garreauandyxxhero cc85562625 feat: Add ConditionTemplate support in releaseSpec (#2669)
* feat: Add ConditionTemplate support in releaseSpec

Signed-off-by: Arthur Garreau <arthur.garreau98@gmail.com>

* feat: improve testing, clarify doc

Signed-off-by: Arthur Garreau <arthur.garreau98@gmail.com>

* feat: update CHANGELOG for ConditionTemplate support and fix test cases for ID generation

Signed-off-by: Arthur Garreau <arthur.garreau98@gmail.com>

* Update pkg/state/state_exec_tmpl.go

Co-authored-by: yxxhero <11087727+yxxhero@users.noreply.github.com>
Signed-off-by: Arthur Garreau <arthur.garreau98@gmail.com>

* Update pkg/state/state_exec_tmpl.go

Co-authored-by: yxxhero <11087727+yxxhero@users.noreply.github.com>
Signed-off-by: Arthur Garreau <arthur.garreau98@gmail.com>

* Update docs/configuration.md

Co-authored-by: yxxhero <11087727+yxxhero@users.noreply.github.com>
Signed-off-by: Arthur Garreau <arthur.garreau98@gmail.com>

* refactor: improve comments for condition checks and clean up whitespace

Signed-off-by: Arthur Garreau <arthur.garreau98@gmail.com>

---------

Signed-off-by: Arthur Garreau <arthur.garreau98@gmail.com>
Co-authored-by: yxxhero <11087727+yxxhero@users.noreply.github.com>
2026-07-01 18:44:43 +08:00
yxxhero afdb2487a6 feat: add inherits: for sub-helmfile config inheritance (#2680)
* feat: add `inherits:` for sub-helmfile config inheritance

Add an opt-in `inherits:` field to `helmfiles:` entries so a sub-helmfile
can inherit specific configuration categories from its parent:

  helmfiles:
  - path: myapp.yaml
    inherits: [repositories, environments]

Allowed values: repositories, helmDefaults, commonLabels, apiVersions,
kubeVersion, templates, environments. Child values win; parent fills gaps
(consistent with `bases:`). This directly fixes #1495, where a repository
declared in the parent was unavailable to sub-helmfiles, producing a
confusing "repo not found" error.

Implementation notes:
- The 6 pure fields (repositories, helmDefaults, commonLabels, apiVersions,
  kubeVersion, templates) are merged post-load via MergeInherited; verified
  all are consumed post-load (ExecuteTemplates/converge), never at parse.
- environments is injected pre-load as ctxEnv, because RenderedValues is
  baked at load time; the parent's resolved values become the base and the
  child's own environments: block overrides per key.
- helmDefaults uses a *HelmSpec pointer (value type is non-comparable) with
  a no-override mergo merge, so a child that omits helmDefaults inherits the
  parent's fully.
- A footgun warning (WarnUninheritedRepos) suggests
  `inherits: [repositories]` when a release references a repo the parent
  declares but the child lacks.
- Unknown inherits keys are rejected at parse time with the allowed set.

Inheritance is opt-in and fully backward compatible: empty (the default)
preserves the historical independent-sub-helmfile behavior.

Fixes #1495

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

* fix: address review — deep-copy inherited config and fix bases: doc link

- BuildInheritedConfig now deep-copies the pure fields via a YAML round-trip
  (Env via environment.DeepCopy) so the returned config never aliases the
  parent state's slices/maps, matching its doc comment. Now returns an error
  to surface round-trip failures; the call site in processNestedHelmfiles is
  updated. Added TestBuildInheritedConfig_PureFieldsAreDeepCopied to lock
  in the no-aliasing guarantee.
- Fix the broken `bases:` anchor (#) in shared-configuration-across-teams.md
  to point to writing-helmfile.md#layering-state-files.

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

* fix: address review — reject inherits without path and document helmDefaults caveat

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

* fix: address review — make AllowedInherits immutable and clarify effective-repo wording

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

---------

Signed-off-by: yxxhero <aiopsclub@163.com>
2026-07-01 14:44:16 +08:00
yxxhero bbf925f154 test: add integration test for issue #1880 transformers with file:// deps (#2673)
* test: add integration test for issue #1880 transformers with file:// deps

Add an integration test reproducing the exact scenario from issue #1880:
a local chart with a relative file:// dependency (file://../library) used
together with kustomize transformers.

Before the fix (rewriteChartDependencies in PR #2334), chartify copied
the chart to a temp directory, breaking the relative file:// path and
causing helm dependency up to fail with:
  Error: directory /tmp/chartify.../monitoring/library not found

Also fix test artifact leak in issue923_test.go where OCI chart downloads
wrote to CWD because OutputDirTemplate lacked {{ .OutputDir }}.

Closes #1880

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

* fix(test): guard exit-code captures against set -e in integration tests

Under set -e (enabled in run.sh), a failing command exits the shell
before 'var=$?' can execute, defeating diagnostic cat+fail blocks and
breaking helm diff tests that expect exit code 2.

Replaced 'cmd; var=$?' with 'var=0; cmd || var=$?' across 12 test
files (29 sites), matching the pattern already used in oci-parallel-pull.sh.

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

---------

Signed-off-by: yxxhero <aiopsclub@163.com>
2026-06-29 08:49:51 +08:00
yxxhero 8f1bb404d6 fix: support go-getter URLs in ad-hoc dependencies to fix #821 (#2670)
* fix: support go-getter URLs in ad-hoc dependencies to fix #821

Ad-hoc release dependencies (release.dependencies[].chart) that used
go-getter URLs like "git::https://host/repo.git@path?ref=tag" were passed
to chartify as-is. chartify then tried to resolve them via `helm repo
list`, which fails with "no helm list entry found for repository
\"git::https:\". please `helm repo add` it!".

The primary chart already fetched such URLs via downloadChartWithGoGetter,
but the ad-hoc dependency path in PrepareChartify only handled local
directories and OCI rewrites, so go-getter URLs fell through.

This adds a branch that detects remote go-getter URLs (remote.IsRemote)
and fetches them to a local cache directory via a new
downloadAdhocDepChartWithGoGetter helper, mirroring the primary-chart
fetch path. chartify then sees a local chart and takes its file:// branch.

Fixes #821

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

* test: add integration test for go-getter ad-hoc dependencies (#821)

Adds an integration test that commits a chart to a throwaway local git repo
and references it via a "git::file://..." go-getter URL as a release
ad-hoc dependency, then asserts `helmfile template` renders both the main
chart and the fetched dependency.

Using file:// (rather than https://) keeps the test deterministic and
network-free while exercising the exact fix path (remote.IsRemote +
downloadAdhocDepChartWithGoGetter in PrepareChartify). Verified to fail on
the unfixed code with the original "no helm list entry found for repository
\"git::file:\"" error and pass on the fixed code.

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

* test: surface helmfile error in issue #821 integration test

The integration runner (run.sh) enables `set -e`, so a non-zero helmfile
exit aborted the script before the captured output could be printed,
hiding the real failure in CI. Disable `set -e` around the helmfile
invocation and report the exit code plus full output on failure.

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

* test: fix issue-821 integration test chart path and errexit handling

Two issues caused the integration test to fail in CI (while passing in my
local smoke test, which used an absolute path):

1. The main chart path was relative to the integration CWD, but the
   helmfile.yaml is generated in a temp directory and helmfile resolves
   `chart:` relative to that directory (its basePath). The relative path
   was interpreted as a named-repo chart and failed instantly with
   `Error: repo test not found`. Resolve the case dir to an absolute path
   with `$(cd ... && pwd)`.

2. run.sh runs under `set -e`, so the unguarded helmfile invocation exited
   the whole script on failure before the captured output could be printed,
   hiding the real error. Capture the exit code via `cmd || rc=$?` so
   failures are reported.

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

---------

Signed-off-by: yxxhero <aiopsclub@163.com>
2026-06-28 15:28:05 +08:00
yxxhero e3f757d5ed feat: add --template-args flag to template/apply/sync for helm lookup() support (#2666)
* feat: add --template-args to enable helm lookup() during template/apply/sync (#1833)

Add a --template-args flag to the template, apply, and sync subcommands so
extra args (most notably --dry-run=server) can be passed to the helm template
invocation, enabling Helm's lookup() function to resolve live cluster values.

- template: --template-args reaches both chartify's pre-render helm template
  and the final helm template output (flagsForTemplate).
- apply/sync: --template-args reaches chartify's pre-render helm template.
  apply/sync already inject --dry-run=server automatically for cluster
  operations; the flag is an explicit opt-in for the template subcommand or
  for passing additional flags.
- When --dry-run is present in template args, kube-context/kubeconfig are also
  injected into chartify so lookup() can actually reach the cluster.
- Resolves the long-stale PR #1833 rebased onto current main, which already
  contains the cluster-connectivity infrastructure (issues #2271, #2309,
  #2355, #2444).
- Includes integration test (lookup.sh) covering both chartify and
  non-chartify scenarios.

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

* test: make lookup template nil-safe to fix integration CI

The lookup() function returns an empty map when the chart is rendered
without a cluster connection (notably the helm-diff phase of `helmfile
apply`). The original fixture chained `index` over the lookup result,
panicking with "index of untyped nil" during apply's diff rendering.

Guard every index with `default dict` so the template falls back to
"overwritten" when lookup is empty, while still resolving to the live
value ("init") when cluster access is available (--dry-run=server via
--template-args, or a real helm upgrade).

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

* feat: enable lookup() during apply/diff via --template-args in helm-diff

Thread --template-args into the helm-diff rendering path so that
`helmfile apply`/`diff --template-args="--dry-run=server"` resolves
Helm's lookup() function during the diff phase too. helm-diff supports
`--dry-run=server`, which explicitly "enables the cluster access ...
and the lookup template function".

Previously --template-args only reached chartify's pre-render (which is a
no-op for plain charts due to chartify's early-return when there is no
forceNamespace/patches/injections) and the final `helm template` of the
`template` subcommand. As a result `helmfile apply` on a lookup chart
rendered client-side during the diff phase.

Changes:
- pkg/state: add TemplateArgs to DiffOpts; append it in appendExtraDiffFlags
  (reaches every helm-diff invocation: apply, standalone diff, interactive
  sync), mirroring the existing flagsForTemplate handling.
- pkg/config + cmd: add --template-args to the diff/doctor commands and to
  DiffConfigProvider, so lookup works for `helmfile diff` as well.
- pkg/app: populate DiffOpts.TemplateArgs from apply/diff/sync-interactive.
- docs/cli.md: correct the previous overpromising wording and document diff
  support plus the nil-safe lookup guidance.
- tests: unit-test the TemplateArgs handling in appendExtraDiffFlags and
  flagsForTemplate; integration lookup.sh now exercises apply with
  --template-args="--dry-run=server".

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

* refactor: de-duplicate chartify template-args logic, add helmDefaults.templateArgs

Address review feedback on #2666:

1. Eliminate stale duplicated test helpers (issue_2444_test.go, issue_2355_test.go).
   Both files intentionally copied the processChartification flag-building logic
   with explicit SYNC WARNING comments, then drifted out of sync when #2666
   refactored the production code (needsKubeConnection gate, user-args merge).
   Extract the real logic into pure, unit-tested helpers
   (buildChartifyTemplateArgs, commandRequiresCluster) and delete the copies.

2. Add unit coverage for the new chartify merge path: template +
   --template-args=--dry-run=server now triggers kubeconfig/kube-context
   injection (TestTemplateArgsDryRunTriggersKubeInjection,
   TestTemplateArgsMergedBeforeInjection) — previously only covered by the
   cluster-dependent integration test.

3. Add a negative integration case (lookup.sh assert_template_fallback)
   verifying lookup() falls back to the default value WITHOUT --template-args,
   guarding against a regression that silently always connects to the cluster.

4. Add helmDefaults.templateArgs for parity with diffArgs/syncArgs, so users
   can enable lookup() support permanently instead of passing the flag on every
   invocation. CLI --template-args overrides (does not merge with) the default.
   Resolved via effectiveTemplateArgs, wired into the chartify, flagsForTemplate,
   and appendExtraDiffFlags paths.

5. Minor: capitalize --template-args help text to match surrounding flags;
   document helmDefaults.templateArgs precedence in docs/cli.md.

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

* test: cover helmDefaults->chartify composition; fix helm helm-diff typo

Address remaining review nits on #2666:

- Add TestHelmDefaultsTemplateArgsReachesChartify, a belt-and-suspenders test
  for the processChartification composition (effectiveTemplateArgs ->
  buildChartifyTemplateArgs), closing the last unit-level coverage gap for
  helmDefaults.templateArgs reaching the chartify path.

- Fix pre-existing typo in cmd/bind_diff_flags.go: 'pass args to helm helm-diff'
  -> 'Pass args to helm-diff' (doubled 'helm', lowercase).

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

* fix: correct 'helm helm-diff' typo in apply --diff-args help text

Sibling of the bind_diff_flags.go fix; the same doubled-'helm' typo and
lowercase help existed in cmd/apply.go's --diff-args registration, leaving
the apply and diff/doctor help strings inconsistent.

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

* docs: add helmDefaults.templateArgs to configuration reference

The complete helmfile.yaml schema in docs/configuration.md documents
diffArgs and syncArgs under helmDefaults but was missing the new
templateArgs field added in #2666. Add it beside syncArgs for
discoverability, noting the --template-args CLI override.

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

---------

Signed-off-by: yxxhero <aiopsclub@163.com>
2026-06-28 12:19:33 +08:00
yxxhero 0cf330611e fix: clean up chartify temp directories after helm operations (#2668)
fix: clean up chartify temp directories after helm operations (#1799)

Chartify creates temporary output directories (e.g. /tmp/chartify<random>/)
during chart preparation for commands like build, template, and diff. These
directories were never tracked for cleanup, causing disk space to accumulate
over time with thousands of orphaned chartify* folders.

The existing clean() closure in PrepareChartify only removed generated values
files, not the chartify output directory itself. The chartified chart must
survive until all helm operations complete, so it could not be removed during
chart preparation.

This change:
- Adds chartifyTempDirTracker to HelmState (pointer-based to avoid copy-lock
  issues from HelmState being copied in several places)
- Tracks chartify output dirs via addChartifyTempDir() after c.Chartify()
  succeeds in processChartification
- Cleans them up via CleanupChartifyTempDirs() in WithPreparedCharts after
  all helm operations complete
- Also removes empty parent temp directories (e.g. /tmp/chartify<random>/)

Fixes #1799

Signed-off-by: yxxhero <aiopsclub@163.com>
2026-06-28 11:55:31 +08:00
yxxhero 76613bd0bb fix: serialize concurrent helm operations for same chart to fix #768 (#2662)
When multiple releases in a helmfile use the same remote chart, concurrent
helm upgrade/diff calls race on helm's internal repository cache file rename,
causing intermittent 'cannot rename: Access is denied' errors on Windows.

This fix introduces two layers of serialization:

1. withChartOperationLock (operation-level): wraps SyncRelease/DiffRelease
   calls with a per-chart+version mutex. Only applies to remote charts
   (release.ChartPath is empty); local/pre-fetched/OCI charts bypass the
   lock entirely. Different charts remain fully parallel.

2. Per-chart+version download mutex in forcedDownloadChart/getOCIChart
   (download-level): uses double-check locking to ensure only one
   helm fetch runs per unique chart+version within a process.

The fix does NOT change chart paths passed to helm, preserving backward
compatibility with all existing behavior and tests.

Trade-off: same-chart releases are fully serialized (the entire helm
operation including deployment, not just download). This is unavoidable
without pre-fetching because helm downloads and deploys atomically.
Releases with different charts are unaffected.

Fixes #768

Signed-off-by: yxxhero <aiopsclub@163.com>
2026-06-25 14:37:04 +08:00
yxxhero 08d70bc9a7 fix: ensure OCI charts are prepared for needed releases with --include-needs (#2663)
fix: ensure OCI charts are prepared for needed releases with --include-needs (#923)

When using --include-needs with a selector, releases included via needs
must have their charts prepared (pulled/exported) before diff/sync/apply
can process them. The core fix (ChartPrepareOptions.IncludeTransitiveNeeds
= c.IncludeNeeds()) was already in place, but ForEachState calls in
Diff/Template/Lint/Unittest/Sync/Apply still passed c.IncludeTransitiveNeeds()
instead of c.IncludeNeeds(), creating an inconsistency that would resurface
if SetFilter(true) were ever added.

Changes:
- Use c.IncludeNeeds() in ForEachState for all commands supporting
  --include-needs (Diff, Template, Lint, Unittest, Sync, Apply, Doctor)
- Doctor is the only command with SetFilter(true), so this fixes a real
  bug: helmfile doctor --include-needs was silently ignored for filtering
- Add explanatory doc comment on ForEachState parameter semantics
- Enhance exectest.Helm.ChartPull to create minimal chart files and track
  pulls, enabling OCI chart testing
- Add resetChartCacheForTest() for test isolation from global chart cache
- Add regression tests for issue #923

Signed-off-by: yxxhero <11087727+yxxhero@users.noreply.github.com>
Signed-off-by: yxxhero <aiopsclub@163.com>
2026-06-25 14:36:50 +08:00
yxxhero 9b943adc9e feat: add helmfile doctor command for AI-assisted diff analysis (#2660)
* feat: add `helmfile doctor` command for AI-assisted diff analysis

`helmfile doctor` runs `helmfile diff` and asks an OpenAI-compatible LLM to
summarize the changes and flag risks (data loss, security exposure, breaking
changes, downtime, performance, best-practice issues).

Key design decisions:
- When no LLM is configured, doctor is equivalent to `helmfile diff` with
  one exception: --show-secrets is always forced off (secrets never reach
  stdout, even without an LLM).
- Secrets are ALWAYS redacted via two layers: (1) ShowSecrets() forced to
  false so helm-diff emits <REDACTED> placeholders; (2) a defense-in-depth
  text redactor strips residual secret-looking content (Secret YAML blocks,
  sensitive key/value lines, base64 blobs, JWT tokens) before LLM transmission.
- LLM configuration precedence: env (HELMFILE_LLM_*) < helmfile.yaml (llm:)
  < CLI flags (--llm-*).
- Supports any OpenAI-compatible backend (OpenAI, Azure, One-API, LiteLLM,
  Ollama, etc.) with automatic response_format fallback for backends that
  don't support JSON mode.
- Prompt injection defense: release names and environment values are
  JSON-encoded before insertion into the LLM prompt.
- Exit codes: 0 (success/low-risk), 2 (high-risk gate, bypass with --force),
  1 (other errors). Helm-diff's 'detected changes' exit-2 is swallowed.

New packages:
- pkg/agent/llm: OpenAI-compatible client with JSON response parsing, mock
  client for testing, prompt builder with injection defense.
- pkg/agent/doctor: secret redactor (state machine + regex), report renderer
  (markdown + JSON), config resolver (env < yaml < flag merge).

Testing: 70+ unit tests covering redaction patterns, prompt injection,
response_format fallback, JSON parsing, yaml roundtrip, concurrency safety,
panic recovery, and error propagation. go test -race passes.

Documentation: full doctor section in docs/cli.md, llm: block reference in
docs/configuration.md, updated skills/helmfile for AI agents.

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

* docs: fix doctor equivalence wording per PR review

Per review feedback (PR #2660): the docs claimed doctor is 'equivalent to
helmfile diff — same flags, same output, same exit codes' in the unconfigured
path, but this over-promises because:

  1. doctor --output is the report format (not helm-diff's output format)
  2. helm-diff's --output is exposed as --diff-output in doctor
  3. --show-secrets is silently ignored

Updated all three locations (cli.md, cmd/doctor.go Long + godoc, pkg/app/doctor.go
godoc) to say 'falls back to helmfile diff with --show-secrets forced off' and
explicitly note the --output / --diff-output flag difference.

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

---------

Signed-off-by: yxxhero <aiopsclub@163.com>
2026-06-22 16:52:35 +08:00
yxxheroandRoman Mykhailiuk 9d3a9ad6c7 feat: parallel kubedog tracking with progress printer and safety valves (#2654)
* feat: parallel kubedog tracking with progress printer and safety valves

Rework kubedog integration so resource tracking runs in parallel with
helm upgrade/install, giving live progress output and recovering from
known helm/kubedog wedge conditions.

Core:
- kubedogTrackingHandle runs tracking in a background goroutine alongside
  the helm subprocess (startBackgroundKubedogTracking); helm output is
  buffered and replayed as a single block so it no longer interleaves
  with progress ticks.
- Capture UID+generation baselines before handing off to helm so each
  tracker waits until the resource actually changes (freshness gate).

Progress printer (pkg/kubedog/printer.go):
- Styled, auto-sized progress table with a heartbeat flusher, child (pod)
  status roll-up, pre-ready pod-phase handling, multi-namespace support,
  and optional color. PreviewBreakdown summarizes kept/filtered resources.

Safety valves (verify cluster state via the live API):
- Tracker-race valve (always on): when helm succeeds but a dyntracker
  goroutine is wedged, poll the API and cancel the tracker so wait()
  returns success instead of blocking until --track-timeout.
- Helm-stuck killer (opt-in via helmStuckGrace): if the cluster stays
  converged while helm v4's hook waiter is wedged, SIGINT the helm
  subprocess to recover.
- Failure watchdog (pkg/kubedog/watchdog.go): surface failing pods that
  never made it into dyntracker's resource graph.

Options: trackFailedLogs, helmStuckGrace, trackTimeout, color
(Color/NoColor), and resource filtering (trackKinds/skipKinds/
trackResources). PersistentVolumeClaim support in resource classification.

Signed-off-by: Roman Mykhailiuk <romanm@cybellum.com>

* test: add unit tests for kubedog tracking

Cover the progress printer, resource classification, the failure
watchdog, helm-output trimming/dedup, the release hard-timeout helper,
and the color/track option plumbing.

Signed-off-by: Roman Mykhailiuk <romanm@cybellum.com>

* test: update golden logs, e2e snapshots, and values-id fixtures

Refresh pkg/app testapply/testdestroy golden logs, e2e template
snapshots, and the TestGenerateID values-id golden hashes for the new
kubedog progress output and the merged release struct layout.

Signed-off-by: Roman Mykhailiuk <romanm@cybellum.com>

* refactor: remove dead kubedog display code and dedupe tracker setup

Deep-review pass on the parallel kubedog tracking changes:

- Remove pkg/kubedog/display.go (308 lines) and display_test.go (453 lines).
  These rendered progress for the legacy per-kind trackers that this PR
  replaces; in the merged tree every function is unreferenced outside its
  own tests. The new progressPrinter (printer.go) supersedes them.
  TestMain (color.ForceColor for deterministic ANSI in tests) is preserved
  in a new main_test.go.
- Dedupe trackWithKubedog: the post-helm fallback rebuilt the exact same
  tracker options as buildReleaseTracker. Reuse buildReleaseTracker instead,
  dropping ~40 lines of duplicated timeout/log/filter/tracker construction.

No behavior change; build, go vet, golangci-lint, and the kubedog/state
unit tests all pass.

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

* fix: stop waitForFreshness busy-looping the API after helm finishes

Once upstreamDoneCh closes it is always ready, so the select in
waitForFreshness stopped blocking on the ticker and re-ran probe() (a
live GET) as fast as the round-trip allowed for the whole 3s grace
window — hammering the API server once per tracked resource.

Track a local view of the channel and nil it out on first delivery so
the first hit records the timestamp (one fast retry, as intended) and
all subsequent polls are ticker-throttled. Functional behavior is
unchanged: return nil when fresh, errUpstreamDoneNoChange after grace.

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

* test: drop trailing blank line from helm4 OCI pull snapshots

The trailing-newline trim in helmexec.info() removes the blank line helm
prints after the OCI chart "Digest:" line. The helm3 (output.yaml)
snapshots never captured that line, but the helm4 (output-helm4.yaml)
snapshots for oci_chart_pull{,_direct,_once,_once2} and
issue_473_oci_chart_url_fetch still expected it, so they failed under
helm 4. Remove the blank line so the snapshots match the trimmed output.

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

* test: refresh diff-args integration goldens for styled release headers

DisplayAffectedReleases now emits a styled "========== Updated Releases
==========" header (matching the app/e2e goldens already updated by this
PR) and helmexec.info() trims the trailing blank after helm's install
status. Update the diff-args apply-stderr{,-helm4} and apply-live-
stderr{,-helm4} goldens accordingly so they match the actual stderr.

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

* test: drop trimmed blank line from v1-subhelmfile template golden

The trailing-newline trim in helmexec.info() removes the blank line helm
prints after '"incubator" has been added to your repositories'. Update
the v1-subhelmfile-multi-bases-with-array-values result and result-live
goldens so the template stdout comparison matches.

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

---------

Signed-off-by: Roman Mykhailiuk <romanm@cybellum.com>
Signed-off-by: yxxhero <aiopsclub@163.com>
Co-authored-by: Roman Mykhailiuk <romanm@cybellum.com>
2026-06-21 13:43:17 +08:00
yxxhero c5eebc24bd fix: helmfile deps broken for OCI charts with underscores in path (#2648)
fix: helmfile deps broken for OCI charts with underscores in path (#954)

For OCI charts with multi-segment paths (e.g., myrepo/path_with_underscores/example),
helmfile was putting the full path as the dependency name in the generated Chart.yaml.
Helm then reconstructed the OCI reference using this name, and underscores in the
path caused issues with helm's OCI reference handling during dependency update.

Fix: move the chart path prefix into the repository URL and use only the chart
basename as the dependency name, matching Helm's recommended Chart.yaml format
for OCI dependencies:

  # Before (broken):
  dependencies:
  - name: path_with_underscores/example
    repository: oci://harbor.custom.com

  # After (fixed):
  dependencies:
  - name: example
    repository: oci://harbor.custom.com/path_with_underscores

The resulting OCI reference is identical, but the dependency name is now clean.

Includes backward-compatibility fallback for old lock files that used the full
path as the dependency name.

Signed-off-by: yxxhero <aiopsclub@163.com>
2026-06-18 22:51:44 +08:00
yxxhero bbceb05b31 feat: add helm 4 --server-side flag support for diff and bump helm-diff to v3.15.10 (#2645)
Add appendServerSideFlagsForDiff to validate helm-diff plugin version
(v3.15.10+) before passing the --server-side flag to helm diff upgrade.
Extract resolveServerSideValue helper to share precedence logic between
upgrade and diff paths. Bump helm-diff recommended version to v3.15.10
across Dockerfiles, CI, and integration scripts.

Signed-off-by: yxxhero <aiopsclub@163.com>
2026-06-17 09:07:44 +08:00
yxxhero a94fdf4194 feat: add support for helm 4 --server-side upgrade flag (#2641)
* feat: add support for helm 4 --server-side upgrade flag

Add support for the helm 4 upgrade flag --server-side which accepts
"true", "false", or "auto" (default "auto"). This allows users to
explicitly control server-side apply behavior, which is needed for
releases originally installed with Helm 3 and being managed with Helm 4.

The flag can be configured via:
- CLI: --server-side flag on sync, apply, and diff commands
- helmDefaults.serverSide in helmfile.yaml
- releases[].serverSide per-release override

Precedence: release-level > CLI flag > helmDefaults.
Errors are returned when serverSide is set but running Helm 3, or when
an invalid value is provided.

Closes #2640

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

* test: update TestGenerateID expected hashes for new ServerSide field

Adding ServerSide *string to ReleaseSpec changes spew's %#v output and
shifts the FNV hash used by generateValuesID. Update the hard-coded want
values to the new deterministic hashes.

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

---------

Signed-off-by: yxxhero <aiopsclub@163.com>
2026-06-16 08:24:47 +08:00
yxxheroandcopilot-swe-agent[bot] 38e27ee439 fix: include release values in .Values for patch template rendering (#2556)
* fix: include release values in .Values for jsonPatches/strategicMergePatches/transformers gotmpl rendering

Before this fix, .Values in patch template files only contained environment
values, not the release's own values. This meant references like
{{ .Values.ingress.enabled }} would fail when ingress.enabled was set in
the release's values: file rather than environment values.

Now patch gotmpl files see .Values as merged(environment values, release values),
matching user expectations that values defined in the release should be
accessible in conditional patches.

Fixes #1904

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

* test: add more tests for resolveReleaseValues, renderValuesFileToBytesWithData, and generateTemporaryReleaseValuesFilesWithData

Agent-Logs-Url: https://github.com/helmfile/helmfile/sessions/5da5c9d8-7464-4146-84b5-1433ed6193f3

Co-authored-by: yxxhero <11087727+yxxhero@users.noreply.github.com>

* test: simplify newTestHelmStateWithFiles by removing empty cleanup func

Agent-Logs-Url: https://github.com/helmfile/helmfile/sessions/5da5c9d8-7464-4146-84b5-1433ed6193f3

Co-authored-by: yxxhero <11087727+yxxhero@users.noreply.github.com>

* fix: remove always-constant basePath param from newTestHelmStateWithFiles to fix unparam lint error

Agent-Logs-Url: https://github.com/helmfile/helmfile/sessions/b4a669cb-692c-4ca6-a68b-1b04a062b989

Co-authored-by: yxxhero <11087727+yxxhero@users.noreply.github.com>

* fix: address c66017c review comments - error messages, defer-in-loop, map normalization, test cleanup

Agent-Logs-Url: https://github.com/helmfile/helmfile/sessions/44988dd8-1c67-465b-995c-80525a24eb93

Co-authored-by: yxxhero <11087727+yxxhero@users.noreply.github.com>

* refactor: extract generateTemporaryReleaseValuesFilesCore to eliminate duplication; fix temp dir leaks in tests

Agent-Logs-Url: https://github.com/helmfile/helmfile/sessions/b254ddda-aa95-4e2f-8dd9-1ce4c40eedb6

Co-authored-by: yxxhero <11087727+yxxhero@users.noreply.github.com>

* fix: remove rendered content from debug log; extract prepareReleaseValuesEntries shared helper

Agent-Logs-Url: https://github.com/helmfile/helmfile/sessions/321c6ba5-f835-4afd-be5e-ee790bc6b4a5

Co-authored-by: yxxhero <11087727+yxxhero@users.noreply.github.com>

* fix: compute mergedReleaseTemplateData lazily in PrepareChartify

Agent-Logs-Url: https://github.com/helmfile/helmfile/sessions/100b7974-3268-4dc8-be21-12bd82aa2dbb

Co-authored-by: yxxhero <11087727+yxxhero@users.noreply.github.com>

* fix: normalize nested YAML keys in resolveReleaseValues via CastKeysToStrings

Agent-Logs-Url: https://github.com/helmfile/helmfile/sessions/d0129d85-9c7d-4a31-966e-fc0b05b74867

Co-authored-by: yxxhero <11087727+yxxhero@users.noreply.github.com>

* fix: use %w for error wrapping in release values resolution

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

---------

Signed-off-by: yxxhero <aiopsclub@163.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
2026-06-15 16:31:46 +08:00
yxxhero 7391453cbe fix: support array of maps in set/setTemplate values (#2615)
Changed SetValue.Values type from []string to []any to allow passing
maps (not just strings) in the values field of set/setTemplate.

Previously, YAML like:
  setTemplate:
    - name: source.helm.parameters
      values:
        - name: demo
        - version: v2

would fail with 'cannot unmarshal !!map into string'. Map values are
now serialized to JSON when generating --set flags.

Fixes #1021

Signed-off-by: yxxhero <aiopsclub@163.com>
2026-05-31 09:57:35 +08:00
yxxheroandcopilot-swe-agent[bot] 781d28a47a feat: add defaultInherit for automatic release template inheritance (#2600)
* feat: add defaultInherit for automatic release template inheritance

Add a top-level defaultInherit field to helmfile.yaml that automatically
applies template inheritance to all releases without requiring explicit
inherit on each release.

The field accepts a single template name as a string or a list of
template names. Releases that already explicitly inherit from the same
template are not duplicated.

Fixes #2599

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

* style: fix gci formatting in app_template_test.go

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

* fix: correct relative chart path in integration test

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

* fix: use absolute chart path in bad-helmfile test

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

* fix: use clean chart path in bad-helmfile test

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

* fix: use dir variable for chart path

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

* test: fix flaky defaultInherit integration assertions

Agent-Logs-Url: https://github.com/helmfile/helmfile/sessions/d0884e8e-8b1b-456d-8250-dec1566b8a37

Co-authored-by: yxxhero <11087727+yxxhero@users.noreply.github.com>

* test: tighten defaultInherit integration assertions

Agent-Logs-Url: https://github.com/helmfile/helmfile/sessions/d0884e8e-8b1b-456d-8250-dec1566b8a37

Co-authored-by: yxxhero <11087727+yxxhero@users.noreply.github.com>

* test: harden release block parsing in issue-2599 case

Agent-Logs-Url: https://github.com/helmfile/helmfile/sessions/d0884e8e-8b1b-456d-8250-dec1566b8a37

Co-authored-by: yxxhero <11087727+yxxhero@users.noreply.github.com>

* test: make issue-2599 assertions format-tolerant

Agent-Logs-Url: https://github.com/helmfile/helmfile/sessions/d0884e8e-8b1b-456d-8250-dec1566b8a37

Co-authored-by: yxxhero <11087727+yxxhero@users.noreply.github.com>

* test: fix section extraction and regex matching in issue-2599 case

Agent-Logs-Url: https://github.com/helmfile/helmfile/sessions/d0884e8e-8b1b-456d-8250-dec1566b8a37

Co-authored-by: yxxhero <11087727+yxxhero@users.noreply.github.com>

* fix: sanitize defaultInherit values and dedupe applied templates

Agent-Logs-Url: https://github.com/helmfile/helmfile/sessions/85a8e815-3701-4b48-a28d-6bb2d50a3b40

Co-authored-by: yxxhero <11087727+yxxhero@users.noreply.github.com>

* chore: address validation feedback on defaultInherit fixes

Agent-Logs-Url: https://github.com/helmfile/helmfile/sessions/85a8e815-3701-4b48-a28d-6bb2d50a3b40

Co-authored-by: yxxhero <11087727+yxxhero@users.noreply.github.com>

* fix: sanitize releaseInherit entries in applyDefaultInherit; add cleanup trap and quote vars in integration test

Agent-Logs-Url: https://github.com/helmfile/helmfile/sessions/1fbf62d5-7ce2-42e5-898b-30151c0c1ef9

Co-authored-by: yxxhero <11087727+yxxhero@users.noreply.github.com>

* refactor: combine releaseInherit loops in applyDefaultInherit to avoid double TrimSpace; clarify test comment

Agent-Logs-Url: https://github.com/helmfile/helmfile/sessions/1fbf62d5-7ce2-42e5-898b-30151c0c1ef9

Co-authored-by: yxxhero <11087727+yxxhero@users.noreply.github.com>

* test: align default inherit tests with yaml wrapper and assertions

Agent-Logs-Url: https://github.com/helmfile/helmfile/sessions/3ea9b8e4-633f-43c4-899f-e063ec576486

Co-authored-by: yxxhero <11087727+yxxhero@users.noreply.github.com>

* test: address review feedback on defaultInherit tests

Agent-Logs-Url: https://github.com/helmfile/helmfile/sessions/3ea9b8e4-633f-43c4-899f-e063ec576486

Co-authored-by: yxxhero <11087727+yxxhero@users.noreply.github.com>

* test: fix issue-2599 integration script helmfile invocation

Agent-Logs-Url: https://github.com/helmfile/helmfile/sessions/9452bb65-7086-459f-b5ae-0b00c1e021eb

Co-authored-by: yxxhero <11087727+yxxhero@users.noreply.github.com>

---------

Signed-off-by: yxxhero <aiopsclub@163.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
2026-05-20 18:21:03 +08:00
yxxhero 81ab674017 fix: normalize dependency chart path before DirectoryExistsAt check (#2598)
When helmfile.d contains multiple release files and one release has a
local chart dependency (e.g. chart: ../chart), the dependency path was
passed to DirectoryExistsAt without normalizing against basePath. This
caused the path to be resolved against CWD instead of the helmfile
directory, so the local chart was not detected and helmfile tried to
resolve it as a remote repo, failing with:
  'failed reading adhoc dependencies: no helm list entry found for repository'

Fixes #2596

Signed-off-by: yxxhero <aiopsclub@163.com>
2026-05-20 08:44:14 +08:00
23802e7a95 fix: refresh Chart.lock after rewriting file:// dependencies (#2587)
* fix: refresh Chart.lock after rewriting file:// dependencies

`rewriteChartDependencies` rewrites relative `file://` repository URLs in
Chart.yaml to absolute paths so chartify can resolve them from a temp
directory. That mutates the Chart.yaml dependencies block, which
invalidates the Chart.lock digest (helm computes it as
`sha256(json.Marshal([2][]Dependency{req, lock}))` over the dependencies).

Once the lock is out of sync, downstream `helm dependency build` errors
with "the lock file (Chart.lock) is out of sync with the dependencies
file (Chart.yaml)" and chartify falls back to `helm dependency update`.
`dep update` then re-resolves Chart.yaml's version constraints against
the chart repo, so any constraint that admits newer versions
(e.g. `version: "*"`, `~1.0`) silently picks up a newer dependency on
every render — even though Chart.lock pins a specific version.

Repro:
  - Chart.yaml has `version: "*"` for some-dep, Chart.lock pins 4.1.0,
    upstream now publishes 4.2.0.
  - `helm template .` honors the cached `charts/some-dep-4.1.0.tgz`.
  - `helmfile template` produces 4.2.0, because it triggered chartify
    (via jsonPatches/strategic-merge/kustomize/etc), which copied the
    chart, ran `dep build` against an out-of-sync lock, fell back to
    `dep up`, and re-resolved the wildcard.

This commit refreshes Chart.lock alongside Chart.yaml in the temp copy:

- Mirror the rewritten file:// repository URLs onto matching entries in
  Chart.lock's dependencies. Without this, `helm dep build` would resolve
  the lock's relative `file://` paths against the temp chart directory
  and fail with "directory ... not found".
- Recompute the digest using helm's resolver.HashReq algorithm
  (`sha256(json.Marshal([2][]chart.Dependency{req, lock}))`). The
  algorithm is small and stable; resolver.HashReq itself lives in an
  internal package, so it's inlined here.
- Locked versions are preserved verbatim — only the repository URL is
  updated and the digest recomputed. Chart.lock remains the source of
  truth for which versions get installed.
- The original Chart.lock on disk is never modified; only the temp copy
  is rewritten.

Adds TestRewriteChartDependencies_RefreshesChartLock covering digest
recomputation, file:// URL mirroring, version preservation, untouched
non-file:// deps, and original-on-disk integrity.


Signed-off-by: Shane Starcher <shane.starcher@gmail.com>

* fix: address Copilot review issues for Chart.lock refresh

- Map all helm Dependency fields (alias, condition, tags, import-values,
  enabled) when building the request slice for digest computation, not
  just name/version/repository. This ensures the recomputed digest
  matches Helm's resolver.HashReq for all dependency shapes.
- Match lock entries by Name + Alias (not Name alone) to correctly
  handle charts with duplicate dependency names distinguished by alias.
- Log a warning when reading Chart.lock fails with a non-NotExist error,
  while still treating a missing Chart.lock as expected.
- Add test case exercising dependencies with alias, condition, tags, and
  import-values fields, including same-name deps disambiguated by alias.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

Signed-off-by: Shane Starcher <shane.starcher@gmail.com>

* build(deps): bump github.com/helmfile/chartify to v0.26.4

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

Signed-off-by: Shane Starcher <shane.starcher@gmail.com>

* fix: normalize import-values for JSON marshaling and improve test coverage

- Normalize import-values using maputil.RecursivelyStringifyMapKey before
  assigning to helmchart.Dependency.ImportValues. When go-yaml v2 decodes
  nested maps (e.g. import-values entries with child/parent keys), they
  become map[interface{}]interface{} which json.Marshal cannot encode.
  This would silently prevent Chart.lock rewriting. The normalization
  converts all map keys to strings, making the value JSON-safe.
- Improve TestRewriteChartDependencies_RefreshesChartLockWithExtraFields
  to prove that extra fields (condition, tags, import-values) actually
  affect the computed digest by comparing digests with and without those
  fields and asserting they differ.

Signed-off-by: Shane Starcher <shane.starcher@gmail.com>

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: normalize lock ImportValues and fix digest test isolation

- Normalize lock.Dependencies ImportValues via RecursivelyStringifyMapKey
  before json.Marshal, preventing failures when go-yaml v2 decodes nested
  maps as map[interface{}]interface{}.
- Fix TestRewriteChartDependencies_RefreshesChartLockWithExtraFields to use
  a shared root directory so both chart variants resolve file:// paths to
  the same absolute location, isolating digest differences to field content.
- Add TestRewriteChartDependencies_GoYamlV2ImportValues exercising the
  HELMFILE_GO_YAML_V3=false path with import-values containing nested maps.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Shane Starcher <shane.starcher@gmail.com>

* fix: add exact digest verification test against Helm's HashReq

Add TestRewriteChartDependencies_DigestMatchesHelmHashReq which computes
the expected digest independently using the same algorithm as Helm's
resolver.HashReq and asserts the rewritten Chart.lock matches exactly.
This guards against producing a digest that is "different" yet still
rejected by `helm dependency build`.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Shane Starcher <shane.starcher@gmail.com>

---------

Signed-off-by: Shane Starcher <shane.starcher@gmail.com>
Co-authored-by: Shane Starcher <shane.starcher@gmail.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-19 05:41:41 +08:00
yxxhero c82c61e061 fix: template helmDefaults.postRendererArgs with release data (#2583)
PR #1839 introduced template rendering for postRendererArgs, but PR #2510
reverted it while fixing a separate regression. This left helmDefaults-level
postRendererArgs containing template expressions (e.g. {{ .Release.Name }})
passed to helm as literal strings instead of being resolved per-release.

Add renderPostRendererArgs() that templates helmDefaults.postRendererArgs
at flag-generation time using the release's template data, reusing the
existing createReleaseTemplateData() helper. Release-level args are already
templated by ExecuteTemplateExpressions and CLI args are static, so only
the helmDefaults path needs rendering.

Fixes #2580

Signed-off-by: opencode <opencode@users.noreply.github.com>
Signed-off-by: yxxhero <aiopsclub@163.com>
2026-05-11 09:05:06 +08:00
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
Dominik Schmidt c6d0310029 fix(state): resolve OCI repo prefix in ad-hoc release dependencies (#2579)
When a release `dependencies[].chart` is given as `<repoName>/<chart>`
and the matching `repositories:` entry has `oci: true`, helmfile now
rewrites it to `oci://<repoURL>/<chart>` before passing it to chartify.
Without this, chartify's lookup falls into its `helm repo list` branch,
which never finds OCI repos because helm 3+ does not register OCI
registries as named repos (they live in the `helm registry login`
state instead). The user-visible failure was:

  failed reading adhoc dependencies: no helm list entry found for
  repository "<name>". please `helm repo add` it!

Explicit `oci://` URLs already worked through chartify's OCI branch;
this change makes the `<repoName>/<chart>` form behave the same way.
Non-OCI repo prefixes, unknown prefixes, single-segment names, and
explicit `oci://` URLs all pass through unchanged. A debug log records
each rewrite at the call site for easier troubleshooting.

Fixes #1756.

Signed-off-by: Dominik Schmidt <dev@dominik-schmidt.de>
2026-05-07 17:07:20 +08:00
yxxheroandcopilot-swe-agent[bot] 420cc3ba9c fix: add trackFailOnError option to control kubedog exit code (#2576)
* fix: add trackFailOnError option to control kubedog exit code behavior

When kubedog release tracking fails (e.g. pod ImagePullBackOff), helmfile
exits with code 0 instead of a non-zero exit code. Add a trackFailOnError
configuration option (default: false) that when set to true, propagates
kubedog tracking failures to the exit code.

The option is available as:
- Per-release YAML: trackFailOnError: true
- CLI flag: --track-fail-on-error (sync and apply commands)

Extract trackReleaseIfEnabled helper to consolidate kubedog tracking logic
from two duplicated call sites into a single maintainable method.

Fixes #2507

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

* fix: add //go:build ignore to server.go to fix go test CI failure

The test/integration/test-cases/issue-2103/input/server.go is a
package main helper binary used by the issue-2103 integration test.
When go test -coverprofile runs on this package, it fails with
"go: no such tool covdata" in the CI environment.

Adding //go:build ignore excludes the file from go list ./... (and
therefore from PKGS in the Makefile), while still allowing the
integration test to build it explicitly via file path:
  go build -o server ./path/to/server.go

Agent-Logs-Url: https://github.com/helmfile/helmfile/sessions/8a7000af-72b7-48f8-8a82-24813b5df341

Co-authored-by: yxxhero <11087727+yxxhero@users.noreply.github.com>

* fix: update TestGenerateID expected hashes after adding TrackFailOnError field

Adding TrackFailOnError *bool to ReleaseSpec changed the spew
serialization of the struct, which changed the FNV-32a hash values
produced by generateValuesID. Update temp_test.go with the new
expected hash strings.

Agent-Logs-Url: https://github.com/helmfile/helmfile/sessions/caa86cd9-73d1-4894-b745-fd70c0811fd6

Co-authored-by: yxxhero <11087727+yxxhero@users.noreply.github.com>

---------

Signed-off-by: yxxhero <aiopsclub@163.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
2026-05-04 14:20:03 +08:00
yxxheroandcopilot-swe-agent[bot] a8e8b67086 fix: use --post-renderer-args=VALUE format to prevent Helm flag parsing failure (#2570)
* fix: use --post-renderer-args=VALUE format to prevent Helm flag parsing failure

When postRendererArgs contains values like short flags (e.g. -v), passing
--post-renderer-args and the value as separate arguments causes Helm to
interpret the value as its own flag. Using the --post-renderer-args=VALUE
format unambiguously binds the value to the flag.

Fixes #2563

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

* fix: update hasFlagWithValue doc/errors and add -v short-flag test cases

- Update hasFlagWithValue doc comment to describe both '--flag value' and
  '--flag=value' forms
- Update t.Errorf messages in app_test.go to reflect both accepted formats
- Add 'post-renderer-args-short-flag-value' test case (-v) to both
  TestHelmState_flagsForUpgrade and TestHelmState_flagsForTemplate to
  verify --post-renderer-args=-v emission (core regression from #2563)

Agent-Logs-Url: https://github.com/helmfile/helmfile/sessions/dd95f046-358b-4867-9069-9432c1b5318e

Co-authored-by: yxxhero <11087727+yxxhero@users.noreply.github.com>

---------

Signed-off-by: yxxhero <aiopsclub@163.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
2026-05-02 10:04:28 +08:00
yxxhero dac42105dd fix: deduplicate chart dependencies in helmfile.lock (#2567)
When multiple releases reference the same chart with the same name,
repository, and version, helmfile deps would write duplicate entries
to helmfile.lock. This adds deduplication of resolved dependencies
after sorting and before writing the lock file.

Fixes #2562

Signed-off-by: yxxhero <aiopsclub@163.com>
2026-05-01 20:29:24 +08:00
Copilotandyxxhero 10deabb142 Honor skipSchemaValidation during chartification when forceNamespace is set (#2550)
* fix(state): honor skipSchemaValidation in chartify template args

Agent-Logs-Url: https://github.com/helmfile/helmfile/sessions/a695cbff-c37a-403a-9658-09f4fdaa65d0

Co-authored-by: yxxhero <11087727+yxxhero@users.noreply.github.com>

* test(state): harden chartify skip-schema flag detection

Agent-Logs-Url: https://github.com/helmfile/helmfile/sessions/a695cbff-c37a-403a-9658-09f4fdaa65d0

Co-authored-by: yxxhero <11087727+yxxhero@users.noreply.github.com>

* fix(state): propagate cli skip-schema-validation to chartify

Agent-Logs-Url: https://github.com/helmfile/helmfile/sessions/70ebf027-0ab5-4bdb-a4b4-5a77c822ee95

Co-authored-by: yxxhero <11087727+yxxhero@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: yxxhero <11087727+yxxhero@users.noreply.github.com>
2026-04-30 12:30:41 -04:00
yxxheroandcopilot-swe-agent[bot] 5368ab8d95 fix: skip subhelmfiles when selectors conflict with CLI selectors (#2545)
* fix: skip subhelmfiles when selectors conflict with CLI selectors (#2544)

When CLI selectors are provided (e.g. -l name=b), subhelmfiles whose
explicit selectors are provably incompatible are now skipped entirely,
avoiding unnecessary YAML loading and template rendering.

Two selector sets are incompatible when every pair has a positive label
conflict: same key with different values (e.g. name=b vs name=a).
Negative labels are not compared.

Fixes #2544

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

* fix: address PR review comments - use CLI selectors, fix doc comment, add malformed selector test

Agent-Logs-Url: https://github.com/helmfile/helmfile/sessions/1f1c33ce-e50d-4781-85b8-d606b5d4ca54

Co-authored-by: yxxhero <11087727+yxxhero@users.noreply.github.com>

* fix: add debug logging, unit tests, docs, and fix integration test for subhelmfile selector skip

- Add debug log when skipping subhelmfile due to selector conflict
- Add TestSubhelmfileSelectorsConflict with 11 cases for direct unit coverage
- Document the selector-based subhelmfile skip optimization in docs/index.md
- Fix integration test: use 'app' label key instead of reserved 'name' key
  (GetReleasesWithLabels overwrites labels["name"] with the release name)

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

* refactor: avoid map allocation in positiveLabelsCompatibleWith

Compare positive label slices directly instead of allocating a map per
comparison, as label counts are typically small (1-3 entries).

Addresses Copilot review comment on PR #2545.

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

* docs: clarify subhelmfile selector docs per Copilot review feedback

Reword the first two bullets to avoid the contradiction between
'CLI selectors are ignored' and the new skip optimization.

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

* fix: address Copilot review comments round 3

- Log parse errors from SelectorsAreCompatible at debug level instead of
  silently discarding them
- Hoist regex compilation to package-level vars in ParseLabels to avoid
  repeated compilation per selector
- Replace EXIT traps with explicit cleanup calls in integration test to
  avoid interfering with the parent runner's trap

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

---------

Signed-off-by: yxxhero <aiopsclub@163.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
2026-04-25 17:18:39 +08:00
yxxhero fc31dbfc5e fix: eliminate race condition in rewriteChartDependencies (#2541)
* fix: eliminate race condition in rewriteChartDependencies by copying chart before modifying

Instead of modifying the original Chart.yaml in-place (which causes race
conditions when multiple releases reference the same local chart), copy the
chart to a temporary directory and rewrite the copy's dependencies. This
eliminates the need for per-chart mutex locks and prevents file corruption
when concurrent goroutines process releases sharing the same local chart.

Fixes #2502

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

* fix: address PR review comments for rewriteChartDependencies

- Handle non-NotExist errors from st.fs.Stat to surface permission/IO failures
- Reword function doc to clarify temp copy is conditional on rewrite being needed
- Assert rewrittenPath vs tempDir based on expectModified in test table

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

* test: add integration test for issue #2502 race condition with shared local chart

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

* fix: separate environments and releases with --- in helmfile.yaml

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

* fix: correct file:// path and remove --skip-deps for dependency build

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

* fix: correct file:// dependency path (5 levels up to test/integration/)

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

* fix: remove output validation from race condition test

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

* fix: assert WriteFile/MkdirTemp/RemoveAll/CopyDir in DefaultFileSystem test

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

* fix: add strategicMergePatches to trigger chartify in race condition test

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

* fix: scope test values under raw subchart and align ConfigMap name with strategic merge patches

The race condition test values.yaml had templates at the top level instead
of scoped under the raw subchart key, causing helm template to produce no
output and chartify's ReplaceWithRendered to fail with an empty
helmx.1.rendered directory. Also align the ConfigMap name to match the
strategicMergePatches target.

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

---------

Signed-off-by: yxxhero <aiopsclub@163.com>
2026-04-20 10:15:47 +08:00
Diliz 7a60255a9b enabledns flags available on template command (#2511)
* enabledns flags available on template command

Enable dns flag was not available in helmfile template command

Signed-off-by: yxxhero <aiopsclub@163.com>
2026-04-16 15:59:48 +08:00
yxxheroandcopilot-swe-agent[bot] c584c0e07f fix: helmDefaults.postRendererArgs not passed to helm commands (#2510)
* fix: helmDefaults.postRendererArgs not passed to helm commands (#2508)

The commit b5eb8793 (#1839) added template support for postRendererArgs
by copying HelmDefaults.PostRendererArgs to each release and then
niling out HelmDefaults.PostRendererArgs. However, the nil-out
prevented the fallback in appendPostRenderArgsFlags from ever being
reached, causing helmDefaults.postRendererArgs to be silently ignored
when no release-level postRendererArgs was set.

Remove the nil-out line so that HelmDefaults.PostRendererArgs remains
available as a fallback while still supporting template expressions
via the copy-to-release mechanism.

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

* fix: use helm4-compatible postRenderer value in app tests

Agent-Logs-Url: https://github.com/helmfile/helmfile/sessions/17a1a3c2-e104-49c5-a607-1e81a7b9de06

Co-authored-by: yxxhero <11087727+yxxhero@users.noreply.github.com>

* fix: remove postRendererArgs copy loop so CLI flags can override helmDefaults

Agent-Logs-Url: https://github.com/helmfile/helmfile/sessions/c85f0436-2346-402e-8ad6-e08a4fff7413

Co-authored-by: yxxhero <11087727+yxxhero@users.noreply.github.com>

* test: add missing CLI>helmDefault and release>CLI postRendererArgs priority tests

Agent-Logs-Url: https://github.com/helmfile/helmfile/sessions/37cf3613-a4e1-4eac-b6bc-002761256d31

Co-authored-by: yxxhero <11087727+yxxhero@users.noreply.github.com>

* fix: clarify comment wording in app_test.go

Agent-Logs-Url: https://github.com/helmfile/helmfile/sessions/37cf3613-a4e1-4eac-b6bc-002761256d31

Co-authored-by: yxxhero <11087727+yxxhero@users.noreply.github.com>

* refactor: extract newPostRendererTestApp and hasFlagWithValue helpers in app_test.go

Agent-Logs-Url: https://github.com/helmfile/helmfile/sessions/faf50bca-33b2-4eb4-8ef1-49f470dfa5b7

Co-authored-by: yxxhero <11087727+yxxhero@users.noreply.github.com>

---------

Signed-off-by: yxxhero <aiopsclub@163.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
2026-04-16 11:08:15 +08:00
Vojta Polak 6faa477290 fix: update state values files handling to replace arrays instead of merging (#2537)
* feat: update state values handling to replace arrays instead of merging

Signed-off-by: Vojta Polak <vojta.polak@gmail.com>

* test: non-shallow copy of OverrideCLISetValues in DeepCopy + test

Signed-off-by: Vojta Polak <vojta.polak@gmail.com>

* fix: update error message for empty CalleePath in Load function

Signed-off-by: Vojta Polak <vojta.polak@gmail.com>

---------

Signed-off-by: Vojta Polak <vojta.polak@gmail.com>
2026-04-13 19:46:15 +08:00
yxxheroandcopilot-swe-agent[bot] acb7ce36fc fix: add mutex lock for concurrent rewriteChartDependencies access (#2509)
* fix: add mutex lock for concurrent rewriteChartDependencies access

Prevent race conditions when multiple goroutines concurrently access
the same Chart.yaml by introducing a per-chart-path mutex via sync.Map.

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

* fix: unlock mutex on all error paths and improve race condition test validation

Agent-Logs-Url: https://github.com/helmfile/helmfile/sessions/135e06b8-99e8-42cb-859d-524b2d2b1907

Co-authored-by: yxxhero <11087727+yxxhero@users.noreply.github.com>

* fix: remove duplicate mutex, reuse getNamedRWMutex, restore on write failure, add test barrier

Agent-Logs-Url: https://github.com/helmfile/helmfile/sessions/f93746bf-82fa-46b1-b8f0-b34bc9aa749c

Co-authored-by: yxxhero <11087727+yxxhero@users.noreply.github.com>

* fix: early unlock when unmodified, assert exact restore in race test

Agent-Logs-Url: https://github.com/helmfile/helmfile/sessions/5981aea4-2799-4560-9272-315d28d4b7d7

Co-authored-by: yxxhero <11087727+yxxhero@users.noreply.github.com>

* fix: improve comment wording and strengthen race test start barrier

Agent-Logs-Url: https://github.com/helmfile/helmfile/sessions/9e4d6f4f-cdc1-4e9e-bdc6-81061ebc1dcc

Co-authored-by: yxxhero <11087727+yxxhero@users.noreply.github.com>

---------

Signed-off-by: yxxhero <yxxhero@users.noreply.github.com>
Signed-off-by: yxxhero <aiopsclub@163.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
2026-04-13 11:59:52 +08:00
yxxheroandcopilot-swe-agent[bot] b9378b17f6 fix: boolean false overrides dropped in multi-document helmfiles (#2527) (#2532)
* fix: environment values pollution causing boolean false overrides to be dropped (#2527)

PR #2367 introduced envCopy.Values = values in NewEnvironmentTemplateData,
where values = GetMergedValues() = Defaults + Values + CLIOverrides. This
caused .Environment.Values to include Defaults, so when multi-part helmfiles
re-assigned environment values via {{ toYaml .Environment.Values }}, Defaults
values (e.g. helmDefaults.atomic: true) were written into the environment
Values field. Later, GetMergedValues() applied Values over Defaults, causing
the stale atomic: true to win over the correct atomic: false override.

Fix: set .Environment.Values to Values + CLIOverrides only (excluding Defaults),
so re-assignment patterns don't pollute the Values layer with Defaults.

Signed-off-by: yxx <yxx@users.noreply.github.com>
Signed-off-by: yxxhero <aiopsclub@163.com>

* fix: rename test to correctly reflect Values override Defaults precedence

Agent-Logs-Url: https://github.com/helmfile/helmfile/sessions/1b251877-7050-404b-8cc7-abd6aa3ec36b

Co-authored-by: yxxhero <11087727+yxxhero@users.noreply.github.com>

* test: flip regression test fixture to exercise false override (issue #2527)

Agent-Logs-Url: https://github.com/helmfile/helmfile/sessions/c428fd46-b698-4e88-bff2-4c9ac72d2deb

Co-authored-by: yxxhero <11087727+yxxhero@users.noreply.github.com>

---------

Signed-off-by: yxx <yxx@users.noreply.github.com>
Signed-off-by: yxxhero <aiopsclub@163.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
2026-04-12 09:44:49 +08:00
Etienne Champetier 4bdb6f097c fix: keep all chart dependencies key / values (#2501)
* feat: Refactor TestRewriteChartDependencies

Signed-off-by: Etienne Champetier <e.champetier@ateme.com>

* fix: keep all chart dependencies key / values

In rewriteChartDependencies we were only parsing name / repository / version,
thus dropping keys like condition / import-values.

This at least fixes the use of condition.

Signed-off-by: Etienne Champetier <e.champetier@ateme.com>

---------

Signed-off-by: Etienne Champetier <e.champetier@ateme.com>
2026-03-26 13:16:54 +08:00
yxxhero 732e4ad913 fix: helmfile fetch fails for kustomization directories (#2504)
* fix: helmfile fetch fails for kustomization directories

Fixes #2503

When running `helmfile fetch` on a release that points to a local
kustomization directory (without Chart.yaml), the command failed with
"Chart.yaml is missing".

The issue was that the condition `helmfileCommand != "pull"` in
prepareChartForRelease skipped chartification for ALL cases during
fetch, including local kustomization directories that NEED chartify
to convert them to Helm charts.

Solution:
- Added `NeedsChartifyForLocalDir` field to the Chartify struct to
  track when chartification is needed because the local directory
  is not a Helm chart (no Chart.yaml)
- Modified the condition to skip chartification for "pull" ONLY when
  it's not a local directory without Chart.yaml

This preserves the original fix (commit 1f134d93) for remote charts
with transformers while fixing local kustomization directories.

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

* test: add integration test for helmfile fetch with kustomization

Add test case for issue #2503 to verify helmfile fetch works correctly
with local kustomization directories (without Chart.yaml).

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

---------

Signed-off-by: yxxhero <aiopsclub@163.com>
2026-03-26 09:44:28 +08:00
Jinyu c70b20ad7a feat: add an arg that passing description to helm upgrade command (#2497)
* feat: add an arg that passing description to `helm upgrade` command

fix: github actions

Signed-off-by: swimablefish <swimablefish@gmail.com>

* fix: lint and test failed

Signed-off-by: swimablefish <swimablefish@gmail.com>

* feat: encapsulation

Signed-off-by: swimablefish <swimablefish@gmail.com>

* feat: add version gate

Signed-off-by: swimablefish <swimablefish@gmail.com>

* feat: rephrase

Signed-off-by: swimablefish <swimablefish@gmail.com>

---------

Signed-off-by: swimablefish <swimablefish@gmail.com>
2026-03-24 21:01:44 +08:00
yxxhero 43b426892e fix: cleanup hooks not receiving error signal (#2475)
* fix: cleanup hooks not receiving error signal

Closes #1041

Signed-off-by: yxxhero <11087727+yxxhero@users.noreply.github.com>
Signed-off-by: yxxhero <aiopsclub@163.com>

* Add tests for cleanup hooks error propagation

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

* fix tests

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

* fix tests

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

* fix tests

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

* fix tests

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

---------

Signed-off-by: yxxhero <11087727+yxxhero@users.noreply.github.com>
Signed-off-by: yxxhero <aiopsclub@163.com>
2026-03-21 14:29:32 +08:00
yxxhero df01afbbeb fix: helmfile list now reflects version from helmfile.lock (#2486)
* fix: helmfile list now reflects version from helmfile.lock

The list command now resolves locked dependencies before returning
release information, ensuring the version field reflects the pinned
version from helmfile.lock when present.

Fixes #1953

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

* fix: address PR review comments

- Remove redundant maps.Copy in list() - labels already merged by GetReleasesWithLabels()
- Fix default lockfile path to use basePath for multi-file mode
- Update test to expect basePath-joined lockfile path
- Add multi-file test for lockfile resolution in helmfile.d directory

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

* fix more test

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

* fix tests

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

* fix: propagate errors instead of panic in list()

When skipCharts=false, errors from list() now properly propagate instead
of causing a crash. Uses a closure variable to capture the error and
propagates it after withPreparedCharts completes.

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

* fix tests

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

* fix tests

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

* fix tests

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

---------

Signed-off-by: yxxhero <aiopsclub@163.com>
2026-03-19 14:25:03 +08:00
yxxhero d613c5484c feat: add --force-conflicts flag support for Helm 4 (#2480)
* feat: add --force-conflicts flag support for Helm 4

Add support for Helm 4's --force-conflicts flag which forces server-side
apply changes against conflicts. This flag is mutually exclusive with
--force/--force-replace and only available in Helm 4.

Fixes #2429

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

* fix: address review comments on force-conflicts feature

- Fix comment grammar: 'forces' instead of 'force'
- Improve error messages to indicate both sources (releases[] and helmDefaults)
- Add test case for helmDefaults.forceConflicts with Helm 3 (should error)
- Update TestGenerateID expected hashes after adding ForceConflicts field to structs

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

---------

Signed-off-by: yxxhero <aiopsclub@163.com>
2026-03-14 08:44:44 +08:00
yxxhero 607225c34d fix: use --force-replace flag for Helm 4 instead of deprecated --force (#2477)
* fix: use --force-replace flag for Helm 4 instead of deprecated --force

Helm 4 deprecated the --force flag in favor of --force-replace.
This fix detects the Helm version and uses the appropriate flag:
- Helm 4: --force-replace
- Helm 3: --force

Also fixed a nil pointer panic in appendHideNotesFlags when called
with nil SyncOpts.

Fixes #2476

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

* fix(ci): pin semver to v2.12.0 for Go 1.25 compatibility

semver@latest requires Go 1.26.1 but the project uses Go 1.25.4.
Pinning to v2.12.0 which is compatible with Go 1.25.

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

* test: add test cases for force flag from defaults with nil release

Add test cases to cover the scenario where release.Force is nil and
HelmDefaults.Force enables force for both Helm 3 and Helm 4.

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

* test: add nil ops test and rename misleading test names

- Add test case for appendHideNotesFlags with ops=nil to prevent
  regression
- Rename force-from-default-nil-release-* to
  force-from-default-nil-force-* for clarity (release.Force is nil,
  not the release itself)

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

* refactor: add explicit parentheses for force condition

Add explicit parentheses around the two disjuncts in the force
condition to make the intended grouping unambiguous and easier
to read.

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

* refactor: check ops nil before Helm version in appendHideNotesFlags

- Swap the order to check ops == nil first to avoid unnecessary
  IsVersionAtLeast call
- Restore the "see Helm release" comment for consistency with other
  flag helpers

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

---------

Signed-off-by: yxxhero <aiopsclub@163.com>
2026-03-11 21:23:51 +08:00
Aditya Menon c375b48550 fix: nested helmfile values should replace arrays, not merge element-by-element (#2458)
PR #2367 introduced CLIOverrides to give --state-values-set element-by-element
array merge semantics. However, nested helmfile values (helmfiles[].values:)
were also routed into CLIOverrides, causing their arrays to merge instead of
replace. This broke the pre-v1.3.0 behavior where passing an array via
helmfiles[].values: would fully replace the child's default array.

Add OverrideValuesAreCLI flag to SubhelmfileEnvironmentSpec so the loader can
distinguish CLI flags from nested helmfile values. CLI values continue using
CLIOverrides (element-by-element merge); nested helmfile values now use Values
(Sparse merge strategy → full array replacement).

Fixes #2451

Signed-off-by: Aditya Menon <amenon@canarytechnologies.com>
2026-03-09 18:31:21 +08:00
yxxheroandCopilot c6e7249eb9 feat: add helm-legacy track mode for Helm v4 compatibility (#2466)
Add support for trackMode: helm-legacy to use Helm v4's --wait=legacy flag,
which maintains compatibility with Helm v3's wait behavior during migration.

Helm v4 changed the default --wait behavior from polling to a watcher-based
approach. This can cause issues with charts that have broken livenessProbe
configurations without startupProbe. The --wait=legacy flag preserves the
Helm v3 polling behavior for smoother migration.

Changes:
- Add TrackModeHelmLegacy constant in pkg/kubedog/options.go
- Use kubedog.TrackMode constants instead of raw strings in helmx.go
- Enhance appendWaitFlags to use --wait=legacy for Helm v4 when trackMode
  is helm-legacy
- Add nil check for logger before logging warning
- Add version check with warning when helm-legacy is used with Helm v3
- Update validation in pkg/config to accept helm-legacy track mode
- Update command-line flags in cmd/apply.go and cmd/sync.go
- Add comprehensive documentation in docs/advanced-features.md
- Add thorough test coverage including warning message verification

Behavior:
- Helm v4 + helm-legacy: Uses --wait=legacy
- Helm v3 + helm-legacy: Falls back to --wait with warning
- Helm v4 + helm: Uses --wait (watcher mode)
- Any + kubedog: Skips --wait flag

Fixes #2464

Signed-off-by: yxxhero <aiopsclub@163.com>
Co-authored-by: Copilot <copilot@github.com>
2026-03-08 11:51:14 +08:00
yxxhero 615e8132ee fix: pass --kubeconfig to chartify's helm template call (#2449)
When using jsonPatches or kustomize patches with helmfile, chartify runs
"helm template" internally to render the chart before applying patches.
The lookup() helm function requires cluster access (--dry-run=server).

Previously, --kubeconfig was passed to helm diff and helm upgrade commands,
but not to chartify's internal helm template call. This caused failures
when users specified --kubeconfig flag with a non-default kubeconfig location.

This fix ensures --kubeconfig is passed to chartify's TemplateArgs for
cluster-requiring commands (sync, apply, diff, etc.), alongside the existing
--kube-context and --dry-run=server flags.

Fixes #2444

Signed-off-by: yxxhero <aiopsclub@163.com>
2026-03-03 20:56:46 +08:00
yxxhero ce09f560d9 fix: configure kubedog rate limiter to prevent context cancellation (#2446) 2026-03-03 19:24:28 +08:00