mirror of
https://github.com/helmfile/helmfile.git
synced 2026-09-30 14:01:46 +02:00
7cf4ff9a252255892bb57dd38e5e9cdd278a3f51
941
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
7cf4ff9a25 |
feat: add --skip-diff-validation-on-install CLI flag (#2728)
Signed-off-by: Richter <h.richter@sap.com> |
||
|
|
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>
|
||
|
|
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> |
||
|
|
7bebfab71a |
feat: add --repo-retries for helm repo and registry login commands (#2683)
* feat: add --repo-retries for retrying helm repo and registry login commands Add a configurable retry mechanism for chart repository operations to handle unstable networks (corporate proxies, slow internal registries). Closes #1894 - New --repo-retries N flag and HELMFILE_REPO_RETRIES env var (default 0 = opt-in, backward compatible) - Retry applies to helm repo add, helm repo update (incl. ACR), and helm registry login with exponential backoff (1s, 2s, 4s, ..., capped 30s) - Single retryRepoOp helper; per-attempt args/buffer are local to avoid state leaking across retries - Tests cover succeed-after-retry, exhausted-retries, disabled-by-default, and regression guards for password-buffer and args-accumulation Signed-off-by: yxxhero <aiopsclub@163.com> * fix: address PR review (overflow guard, cancellable sleep, flag-override, docs) Address Copilot review feedback on #2683: - Cap backoff shift exponent at 5 to prevent time.Duration overflow on large --repo-retries values - Make retry sleep context-aware (sleepCtx) so Ctrl+C aborts the retry loop promptly via the ShellRunner context - Log a concise exit status instead of the verbose ExitError dump, and clarify the retry-counter wording ('retry N/M') - Use -1 sentinel as the CLI default so --repo-retries=0 can explicitly disable retries even when HELMFILE_REPO_RETRIES is set - Align help text and docs: retry applies 'on failure' (not just transient errors), document the 0-disables behavior - Add tests for overflow guard, cancellable sleep, and flag-zero-disables Signed-off-by: yxxhero <aiopsclub@163.com> * fix: abort retries on canceled context, hide sentinel default, align comment Address follow-up Copilot review on #2683: - Fix tight-loop bug: sleepCtx now returns whether it completed vs was interrupted by context cancellation, and retryRepoOp aborts the retry loop on interruption so Ctrl+C no longer spins into rapid helm calls - Hide the -1 sentinel from --help by overriding the displayed default to 0 (pflag DefValue), matching the documented default while keeping the flag-override semantics - Correct HelmExecOptions.RepoRetry comment: 'on failure' not 'transient network errors', matching the actual retry behavior - Add Test_Retry_AbortsOnCanceledContext covering the no-tight-loop path Signed-off-by: yxxhero <aiopsclub@163.com> * fix: copy args per retry in RegistryLogin, make cancel test deterministic Address follow-up Copilot review on #2683: - RegistryLogin: pass a per-attempt copy of args to execStdIn so its internal append (for helm.extra) can't alias the shared slice across retries - Test_Retry_AbortsOnCanceledContext: cancel the context deterministically inside the op closure after the first attempt, replacing the flaky time.Sleep(20ms) goroutine Signed-off-by: yxxhero <aiopsclub@163.com> * fix: return error on unknown managed repo type instead of silent skip Address Copilot review on #2683: AddRepo logged an error for an unknown managed type but returned nil, silently succeeding while skipping the repo add. Now returns an error so misconfigurations fail loudly. Signed-off-by: yxxhero <aiopsclub@163.com> --------- Signed-off-by: yxxhero <aiopsclub@163.com> |
||
|
|
919aa29f15 |
feat: support disabling insecure template functions only (#2689)
Signed-off-by: fabiobaiao <53570695+fabiobaiao@users.noreply.github.com> |
||
|
|
e26c6fd13f |
feat: support disabling hooks (#2691)
Signed-off-by: fabiobaiao <53570695+fabiobaiao@users.noreply.github.com> |
||
|
|
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> |
||
|
|
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> |
||
|
|
30f529f529 |
chore: bump helm to v4.2.3 and v3.21.3 (#2699)
Signed-off-by: yxxhero <aiopsclub@163.com> |
||
|
|
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> |
||
|
|
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>
|
||
|
|
ad10814842 |
fix: replace YAML-based DeepCopy with recursive deep copy to fix #973 (#2675)
Environment.DeepCopy() used a YAML marshal/unmarshal round-trip to copy
values. When SOPS/KMS-encrypted secret files contained values with special
characters (colons, quotes, braces such as ~masked:ab#7i7!;{'".), the
YAML round-trip could mangle or silently drop adjacent keys, producing
the "map has no entry for key" error reported in #973.
Replace the YAML-based DeepCopy with maputil.DeepCopyMap(), a proper
recursive deep copy that:
- Preserves original Go types (string "true" stays string, not bool)
- Never loses data due to special characters in values
- Normalises map[any]any keys to strings (matching CastKeysToStrings)
Signed-off-by: yxxhero <aiopsclub@163.com>
|
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
9e66f55d23 |
fix: resolve symlinked plugin directories in GetPluginVersion (#2661)
In nix/devbox environments, helm plugin directories are typically symlinks into the Nix store. GetPluginVersion used entry.IsDir() which does not follow symlinks, causing the plugin to be reported as not installed. Follow symlinks with os.Stat before skipping non-directory entries. 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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
e12c875863 |
build(deps): bump helm from v4.2.1 to v4.2.2 (#2651)
* build(deps): bump helm from v4.2.1 to v4.2.2 Signed-off-by: yxxhero <aiopsclub@163.com> * build(deps): update helm v4.2.2 SHA256 checksums Signed-off-by: yxxhero <aiopsclub@163.com> --------- Signed-off-by: yxxhero <aiopsclub@163.com> |
||
|
|
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> |
||
|
|
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> |
||
|
|
6191f821eb |
fix: restore s3:: vhost-style remote source support (#2643) (#2644)
go-getter v2 (used since v1.4) removed its built-in S3 getter, so helmfile's own AWS-SDK-v2 S3Getter was added to compensate. However the routing only handled the s3://bucket/key form (Getter==normal, Scheme==s3); the go-getter forced-getter vhost form s3::https://bucket.s3.region.amazonaws.com/key fell through to go-getter v2, which can no longer download S3 at all, producing 'error downloading'. This restores 1.2.x behavior by: - routing u.Getter==s3 URLs to the built-in S3Getter - extending ParseS3Url to parse vhost/path-style amazonaws.com URLs (region/bucket/key), modeled on go-getter v1 - stripping the helmfile @<file> selector before deriving the S3 key - auto-decompressing archive objects (tar.gz/zip/...) via go-getter v2 decompressors so the @<file> selector resolves inside a tarball, as go-getter v1 did - cleaning up the cache dir on download/decompress failure (matching the GoGetter branch) and avoiding a nil-response panic in GetObject error handling Fixes #2643 Signed-off-by: yxxhero <aiopsclub@163.com> |
||
|
|
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> |
||
|
|
047fdcd17d |
build(deps): bump helm-diff to v3.15.9 (#2642)
Update helm-diff plugin version from v3.15.8 to v3.15.9 across Dockerfiles, recommended version constant, CI matrix, and integration test default. Signed-off-by: yxxhero <aiopsclub@163.com> |
||
|
|
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
|
||
|
|
6cf02956c2 |
Add App.WithFileSystem (#2638)
Signed-off-by: toyamagu-2021 <toyamagu2021@gmail.com> |
||
|
|
655399a03b |
Bump Helm support to 4.2.1 and 3.21.1 (#2635)
* chore: bump helm release pins * chore: align helm module metadata * chore: finalize helm patch bumps * fix: add --plain-http flag for Helm 3.21+ OCI push in tests Helm 3.21.1 introduced stricter security checks that reject HTTP scheme downgrades when pushing to OCI registries, with the error: "blob upload Location downgrades scheme from https" Previously only Helm 4 required --plain-http for HTTP-only OCI registries. Now Helm 3.21+ also requires this flag. Add a new requiresPlainHTTPForOCI() helper that returns true for both Helm 4.x and Helm 3.21+, and use it in execHelmPush() instead of isHelm4(). * fix: safe fallback in requiresPlainHTTPForOCI when version detection fails Default to true (require --plain-http) when helm version detection fails, since any Helm version that supports helm push also supports the --plain-http flag. This avoids the inconsistent HELMFILE_HELM4 env var fallback which only covered Helm 4. * fix: update snapshot tests for Helm 4.2.1 OCI pull output Helm 4.2.1 now outputs additional 'Pulled:' and 'Digest: sha256:...' lines after each OCI chart pull. The SHA256 digest is non-deterministic because helm packages include build timestamps, so normalize it with a regex placeholder. - Add ociDigestRegex to normalize non-deterministic OCI digest values - Create output-helm4.yaml for 5 tests that lacked Helm 4 snapshots - Update output-helm4.yaml for oci_need and postrenderer to include the new Pulled/Digest lines from Helm dependency pull operations * fix: update ociDigestRegex to match empty digest in Helm 4.2.1 OCI pull output Helm 4.2.1 outputs "Digest: sha256:" (empty hash) when pulling OCI charts. The regex required at least one hex char ([0-9a-f]+), so it did not match and the digest was not normalized to $DIGEST in snapshot tests. Also fix the replacement string: Go regex ReplaceAllString interprets $DIGEST as a capture group reference (resolving to empty). Use $$DIGEST to produce a literal $DIGEST in the output. 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> Co-authored-by: yxxhero <aiopsclub@163.com> |
||
|
|
94e14f3b4e |
fix: Fix broken trackLogs functionality in Kubedog tracker (#2630)
* fix: Fix broken `trackLogs` functionality in Kubedog tracker
When using helmfile with the following setup
```
releases:
- name: my-app
chart: ./mychart
trackMode: kubedog
trackLogs: true
trackFailOnError: true
```
We don't actually see any logs from the container being printed.
This is because when building the options for the Kubedog tracker, we never
specify `SaveLogsOnlyForNumberOfReplicas` which means this defaults to 0.
Looking at the logic in `pkg/tracker/deployment/tracker.go` we see
```
ignoreLogs := job.ignoreLogs || job.savingLogsReplicas >= job.SaveLogsOnlyForNumberOfReplicas
```
With job.SaveLogsOnlyForNumberOfReplicas always defaulting to 0, this will always ignore logs
This change sets it to a reasonable default of tracking logs from up to 10 pods.
Signed-off-by: Graeme Gillies <ggillies@gitlab.com>
* fix formatting
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Signed-off-by: Graeme Gillies <ggillies@gitlab.com>
* feat: Refactor out building tracker opts into it's own function, add tests
Signed-off-by: Graeme Gillies <ggillies@gitlab.com>
---------
Signed-off-by: Graeme Gillies <ggillies@gitlab.com>
Co-authored-by: Graeme Gillies <ggillies@gitlab.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
|
||
|
|
27382a27af |
Bump helm-diff to v3.15.8 across runtime defaults and execution environments (#2624)
chore: bump helm-diff to v3.15.8 Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> |
||
|
|
1fb47ec16d |
fix: remove naked return by returning expected values (#2617)
Signed-off-by: Niklas Ott <niklas.ott@unwired.at> |
||
|
|
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>
|
||
|
|
33eadc993e |
feat: support HELMFILE_* env vars for more global flags (#2606)
* feat: support more HELMFILE_* env vars as flag fallbacks
Adds env-var fallbacks for global flags, mirroring the existing
HELMFILE_ENVIRONMENT / HELMFILE_KUBE_CONTEXT pattern:
* --helm-binary -> HELMFILE_HELM_BINARY
* --kustomize-binary -> HELMFILE_KUSTOMIZE_BINARY
* --log-level -> HELMFILE_LOG_LEVEL
* --debug -> HELMFILE_DEBUG (expecting "true" lower case)
* --quiet -> HELMFILE_QUIET (expecting "true" lower case)
* --no-color -> HELMFILE_NO_COLOR (expecting "true" lower case),
additionally honors NO_COLOR per no-color.org
(any non-empty value disables color)
Flag values still take precedence; env vars are consulted only when the
flag is unset. The string-flag default values ("helm", "kustomize",
"info") move into the accessor methods so the env-var fallback can
actually trigger when no flag is passed.
Signed-off-by: Dominik Schmidt <dev@dominik-schmidt.de>
* docs: mention new HELMFILE_* env vars in cli.md and templating.md
Signed-off-by: Dominik Schmidt <dev@dominik-schmidt.de>
* fix: make Color/NoColor/env interaction consistent
Two issues with the env-aware NoColor() introduced together with
HELMFILE_NO_COLOR / NO_COLOR support:
1. Color() consulted the raw GlobalOptions.NoColor field instead of
NoColor(), so in a TTY with only the env set, Color() fell through
to terminal autodetect and ValidateConfig() spuriously errored with
"--color and --no-color cannot be specified at the same time".
2. NoColor() returned true via env even when --color was explicitly
passed, so `helmfile --color` with NO_COLOR (or HELMFILE_NO_COLOR=true)
in the environment hit the same ValidateConfig() error. A flag should
always win over an env var.
Fix both by routing Color() through NoColor() and giving NoColor() an
explicit --color short-circuit. Regression tests added for both paths.
Signed-off-by: Dominik Schmidt <dev@dominik-schmidt.de>
---------
Signed-off-by: Dominik Schmidt <dev@dominik-schmidt.de>
|
||
|
|
2a1574b383 |
feat: show diff preview when sync --interactive is used (#2603)
* feat: show diff preview when sync --interactive is used Signed-off-by: vomba <hani.harzallah@elastisys.com> |
||
|
|
27015e8d53 |
fix: restore kubedog status progress output during tracking (#2602)
* fix: restore kubedog status progress output during tracking
The refactor in commit
|
||
|
|
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> |
||
|
|
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> |
||
|
|
31ac918512 |
feat: support HELMFILE_NAMESPACE env var for default namespace (#2592)
* feat: support HELMFILE_NAMESPACE env var for default namespace Mirrors the existing HELMFILE_ENVIRONMENT pattern: the --namespace CLI flag takes precedence, falling back to HELMFILE_NAMESPACE when unset. Signed-off-by: Dominik Schmidt <dev@dominik-schmidt.de> * docs: mention HELMFILE_NAMESPACE in cli.md and templating.md Signed-off-by: Dominik Schmidt <dev@dominik-schmidt.de> --------- Signed-off-by: Dominik Schmidt <dev@dominik-schmidt.de> |
||
|
|
c15cbb096a |
feat: support HELMFILE_KUBE_CONTEXT env var for default kube context (#2593)
* feat: support HELMFILE_KUBE_CONTEXT env var for default kube context Mirrors the existing HELMFILE_ENVIRONMENT pattern: the --kube-context CLI flag takes precedence, falling back to HELMFILE_KUBE_CONTEXT when unset. Refs #1213. Signed-off-by: Dominik Schmidt <dev@dominik-schmidt.de> * docs: mention HELMFILE_KUBE_CONTEXT in cli.md and templating.md Signed-off-by: Dominik Schmidt <dev@dominik-schmidt.de> --------- Signed-off-by: Dominik Schmidt <dev@dominik-schmidt.de> |
||
|
|
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>
|
||
|
|
7ffd4a21ac |
Bump Helm support to 3.21.0 and 4.2.0 (#2588)
* chore: bump pinned helm versions Agent-Logs-Url: https://github.com/helmfile/helmfile/sessions/b1cfacaa-52d2-46c8-9fc7-67beaca43df0 Co-authored-by: yxxhero <11087727+yxxhero@users.noreply.github.com> * fix: align kubernetes module pins for helm bump Agent-Logs-Url: https://github.com/helmfile/helmfile/sessions/b1cfacaa-52d2-46c8-9fc7-67beaca43df0 Co-authored-by: yxxhero <11087727+yxxhero@users.noreply.github.com> * docs: clarify helm and k8s version pins Agent-Logs-Url: https://github.com/helmfile/helmfile/sessions/b1cfacaa-52d2-46c8-9fc7-67beaca43df0 Co-authored-by: yxxhero <11087727+yxxhero@users.noreply.github.com> * test: update helm 4 snapshot outputs Agent-Logs-Url: https://github.com/helmfile/helmfile/sessions/b430f041-d8fb-407f-af06-070f2d0e9293 Co-authored-by: yxxhero <11087727+yxxhero@users.noreply.github.com> * test: update helm 4 postrender integration fixture Agent-Logs-Url: https://github.com/helmfile/helmfile/sessions/fea792b4-b24c-43a9-a391-1fd52e59f843 Co-authored-by: yxxhero <11087727+yxxhero@users.noreply.github.com> * test: update helm 4 postrender template fixture Agent-Logs-Url: https://github.com/helmfile/helmfile/sessions/7ca16c9d-e398-46ce-849b-6299214b2b60 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> |
||
|
|
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> |
||
|
|
997051bba4 |
chore: Emit "cleanup" events later to match the behavior in "apply" (#2522)
This changes the behaviour of the cleanup event during sync to be triggered right before the function exits and matches the behaviour of apply Signed-off-by: Niklas Ott <niklas.ott@unwired.at> Co-authored-by: Raphael Luba <raphael@leanbyte.com> |
||
|
|
5027e6aa5e |
chore: Deduplicate preparation code of sync and apply (#2523)
This commit deduplicates the preparation logic for sync and apply by moving it to a common function. Signed-off-by: Niklas Ott <niklas.ott@unwired.at> Co-authored-by: Raphael Luba <raphael@leanbyte.com> |
||
|
|
897400d64f |
Expose internal apis (#2520)
* feat: expose WithPreparedCharts and SyncRun This exposes the previously internal functions withPreparedCharts and sync to be used by tooling built on top of helmfile Co-authored-by: Raphael Luba <raphael@leanbyte.com> Signed-off-by: Niklas Ott <niklas.ott@unwired.at> * feat: expose Helm interface and HelmState This adds a function to get the current HelmState Co-authored-by: Raphael Luba <raphael@leanbyte.com> Signed-off-by: Niklas Ott <niklas.ott@unwired.at> * feat: return an error instead of panicking on multiple calls on WithPreparedCharts Signed-off-by: Niklas Ott <niklas.ott@unwired.at> --------- Signed-off-by: Niklas Ott <niklas.ott@unwired.at> Co-authored-by: Raphael Luba <raphael@leanbyte.com> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |