mirror of
https://github.com/helmfile/helmfile.git
synced 2026-09-30 10:51:41 +02:00
* fix: resolve OCI version constraints before deriving the shared chart cache path When an OCI release uses a semver constraint (e.g. `~1`, `^2.0.0`, `*`), `getOCIChartPath` currently derives the on-disk cache directory from the raw constraint string via `safeVersionPath`, which substitutes constraint characters (`~`, `^`, `>`, `<`, `!`, `|`, `=`, ` `, `,`, `*`) with `_`. So `version: ~1` becomes `.../mychart/_1/` on disk. `acquireChartLock` then refuses to refresh anything under the shared cache dir to avoid race conditions between concurrent processes, so once the constraint is first resolved and written to `_1/`, every subsequent render on that machine (or that container replica) returns the pinned tarball regardless of newer matching tags being published. In multi-pod deployments like ArgoCD's argocd-repo-server this shows up as intermittent stale renders: different pods populate their caches at different moments and serve different snapshots of the same `~1` release forever. Fix: for OCI releases whose `version` looks like a constraint, run `helm show chart <ref> --version <constraint> [flags]` and use the returned metadata.Version as the effective version for all downstream cache-key and path derivation. Helm already resolves the constraint against the registry and returns the concrete matching Chart.yaml. Callers get a content-addressable cache path (`.../mychart/1.0.1/`) that naturally invalidates when the constraint resolves to a new version. Exact-version releases and non-OCI releases skip the extra call. Adds an opt-out `resolveOCIVersions` field on `helmDefaults` and `ReleaseSpec` (both default true). If the resolution call fails transiently, the resolver logs a warning and falls back to the pre-fix behavior so a network hiccup doesn't break rendering. Adds `ShowChartWithFlags` to helmexec.Interface so the existing `ShowChart` API stays backwards compatible. Resolves #2766 Signed-off-by: Samuel Archambault <samuel.archambault@getmaintainx.com> * refactor: move ShowChartWithFlags to a ChartInspector capability interface Address Copilot review feedback on PR #2768: adding a method to the exported helmexec.Interface is a source-breaking change for every third-party implementation and mock of that interface, even though ShowChart itself stayed backward-compatible. Move ShowChartWithFlags off Interface and onto a new capability interface, helmexec.ChartInspector, following the same pattern used by the existing DependencyUpdater capability interface. The concrete execer and the exectest.Helm test stub still satisfy it (they already have the method); the OCI resolver in state.HelmState now type-asserts and falls back to the pre-fix caching behavior when the capability is absent, so downstream callers with their own helmexec.Interface implementations keep compiling untouched. Adds TestResolveOCIConstraintVersion_ChartInspectorFallback that exercises the type-assertion path with a helm value that satisfies Interface but deliberately does not satisfy ChartInspector. Reverts ShowChartWithFlags additions from testutil.noCallHelmExec and app_test.mockHelmExec since Interface no longer requires them. Signed-off-by: Samuel Archambault <samuel.archambault@getmaintainx.com> * fix: detect wildcard-segment semver constraints (1.x, 1.X) as constraints Address Copilot review feedback on PR #2768: the previous isVersionConstraint implementation scanned the input for operator characters (~, ^, >, <, !, |, =, space, comma, *). Masterminds/semver also accepts wildcard-segment constraints like "1.x", "1.X", "1.x.x", and "1.2.X" that contain no operator characters. Those would slip past the classifier, bypass OCI constraint resolution, and remain cached forever under the raw ".../mychart/1.x/" path — the same stale-cache bug the PR is meant to fix. Replace the character scan with a semver-parser-based check: a value is a constraint iff Masterminds/semver rejects it as a NewVersion but accepts it as a NewConstraint. This correctly: - Recognizes wildcard forms (1.x, 1.X, 1.x.x, 1.2.x, v1.x). - Preserves exact versions where "x" appears in prerelease metadata ("1.0.0-alpha.x") or build metadata ("1.0.0+x", "1.0.0+build.x.1") without misclassifying them, which a naive "add x to the scanned charset" fix would have gotten wrong. - Continues to classify values that are neither a version nor a constraint (empty string, "latest", junk) as non-constraints; helm handles those elsewhere. Removes the now-unused versionConstraintChars string constant. Expands TestIsVersionConstraint with 8 wildcard cases and 3 prerelease /build metadata cases containing "x", plus 2 non-parseable inputs. Adds a "wildcard segment constraint resolves to concrete version" subtest to TestResolveOCIConstraintVersion so the end-to-end pipeline is exercised for a version string that has no operator characters. Signed-off-by: Samuel Archambault <samuel.archambault@getmaintainx.com> * test: add getOCIChart integration test proving cache-path/pull-flag wiring Address Copilot review feedback on PR #2768. The existing unit test exercised resolveOCIConstraintVersion in isolation but did not prove that its output was propagated into the downstream cache key, cache path, and `helm chart pull --version` flag. Add a targeted integration test that: 1. Calls getOCIChart with a constraint release (`~1`) and a helm mock whose ShowChartWithFlags returns Chart.yaml version 1.0.1. 2. Asserts helm chart pull receives `--version 1.0.1`, not `~1`. 3. Asserts the destination path passed to helm chart pull contains the resolved-version segment (`/1.0.1/`) and does NOT contain the raw-constraint segment (`/_1/`). 4. Reads back the on-disk Chart.yaml under the cache path to confirm resolved version, path, and flag agree end to end. Add a second test that runs the same release twice with different resolver outputs (1.0.1, then 1.0.2 — simulating a newly published matching tag) and asserts the two resolutions land in distinct cache directories. This is the promise of the fix: once the raw constraint is out of the path, a new matching tag stops silently reusing the previously-resolved cache entry. The integration test flushed out a real correctness gap in the initial fix: getOCIChart resolved release.Version and chartVersion but did NOT recompute the qualified OCI ref that getOCIQualifiedChartName built pre-resolution. Helm was therefore receiving `oci://<repo>/<chart>:<constraint>` alongside a `--version <resolved>` flag — at best redundant, at worst rejected by future Helm versions. Fixed by re-invoking getOCIQualifiedChartName on the mutated release copy so the embedded tag also carries the resolved value. Isolates the shared helmfile cache via `t.Setenv(HELMFILE_CACHE_HOME, t.TempDir())` so the OutputDirTemplate == "" code path (which writes into remote.CacheDir) does not touch the user's real `~/.cache/helmfile` during test runs. Signed-off-by: Samuel Archambault <samuel.archambault@getmaintainx.com> * refactor: flatten OCI constraint-resolution wiring in getOCIChart Address review feedback on PR #2768: - Extract the inline resolve/requalify block from getOCIChart into applyOCIConstraintResolution, keeping getOCIChart flat (guard-clause style) and making the resolution wiring independently testable. The helper returns the (possibly updated) release, qualified chart name, and chart version; every failure mode returns its inputs unchanged. - On a re-qualify failure after a successful resolution, fall back to the pre-fix behavior entirely (raw constraint in cache key, ref, AND --version flag) instead of the previous half-resolved mix (resolved version in the cache key, raw constraint in the path and flag), which could desynchronize the in-process cache key from the on-disk path. - Build the 'helm show chart' ref by reusing parseOCIChartRef instead of re-implementing its last-slash/last-colon tag-splitting inline. Same behavior for all realistic refs (registry ports preserved), and it also handles the digest suffix should one ever reach this point. - Drop --devel from the resolver flags: helm documents --devel as ignored whenever --version is set, and --version is always passed on this path. No behavior change intended beyond the requalify-failure fallback (which cannot realistically trigger) and the removal of the inert --devel flag. Signed-off-by: yxxhero <aiopsclub@163.com> * fix: classify partial semver versions (1, 1.2) as OCI constraints Address review feedback on PR #2768: Masterminds' lenient parser accepts partial versions like "1" or "1.2" as versions, so the previous classifier (NewVersion fails && NewConstraint succeeds) treated them as exact pins. But helm's OCI resolution — registry.GetTagMatchingVersionOrConstraint — honors a version string as an exact pin ONLY when a registry tag literally equals it; otherwise it parses the string as a constraint, and "1"/"1.2" float across 1.x.y/1.2.y tags. Caching those under their raw spelling reproduces the stale-cache bug of issue #2766, just with a narrower trigger. Replace the NewVersion probe with isFullSemver, which additionally requires the whole major.minor.patch triple to be spelled out (optional v prefix, prerelease, and build metadata all still count as exact when the core is fully qualified). When a registry does carry a literal tag equal to the version string, the resolver's metadata.Version == chartVersion path reports no change, so literal-tag pins keep today's behavior. TestIsVersionConstraint: "1"/"1.0" flip to constraints, joined by new v1.2/0/v1 cases and a 1.2.3 exact case. TestResolveOCIConstraintVersion gains a "partial version resolves" subtest. Docs updated to describe the parser-based classification instead of "constraint characters". Signed-off-by: yxxhero <aiopsclub@163.com> * fix: skip OCI constraint resolution under skipRefresh Address review feedback on PR #2768: the resolver ran even under --skip-refresh, so offline and cache-only workflows gained a 'helm show chart' registry attempt per constraint-versioned OCI release. It degraded gracefully (warn + fallback), but added registry-timeout latency and warning noise per release. skipOCIConstraintResolution now suppresses resolution when any of the skipRefresh levels is set — CLI --skip-refresh (forced), per-release skipRefresh, or helmDefaults.skipRefresh — with the same precedence the other skipRefresh consumers in prepareChartForRelease use. Skipped runs fall back to the constraint-keyed cache path, i.e. they reuse whatever a previous non-skipped run resolved, which is what 'skip checking for updates to cached charts' means for constraint versions. The existing issue #2766 integration tests flip their opts to SkipRefresh: false since they assert resolution happens. New coverage: TestSkipOCIConstraintResolution (tri-state precedence table) and TestGetOCIChart_SkipRefreshSkipsConstraintResolution (no inspector call, raw constraint in --version and cache path). Signed-off-by: yxxhero <aiopsclub@163.com> * perf: memoize OCI constraint resolution per chart+constraint Address review feedback on PR #2768: resolution ran before the in-process chart-cache fast path and was not memoized, so every constraint-versioned OCI release paid its own 'helm show chart' registry round-trip on every render — including N releases sharing the same chart+constraint, whose parallel workers could even resolve to different versions if the registry changed between their lookups. Memoize successful resolutions in resolvedOCIConstraints keyed by (chart ref, constraint), mirroring the downloadedCharts pattern: - Releases sharing a chart+constraint cost one round-trip per process and consistently use one resolved version per run. - Only successful resolutions are memoized; failures may be transient. - Flags are not part of the key: they govern TLS/verification/registry credentials, not which tag a constraint matches (--devel is already omitted as it is ignored whenever --version is set). - Concurrent misses may both hit the registry; last write wins, harmlessly. resetResolvedOCIConstraintsForTest is added alongside the existing resetChartCacheForTest and wired into the issue #2766 tests — notably ResolvesToDifferentVersionsPicksSeparateCachePaths, which reuses the same chart+constraint across its two runs and would otherwise be served the first resolution from the memo (which is exactly the intended per-process semantics). New coverage: TestResolveOCIConstraintVersion_Memoized (memo hit skips the registry, different constraint is a different key) and TestGetOCIChart_SharedConstraintResolvedOncePerProcess (two releases, one inspector call, one pull, same path). Signed-off-by: yxxhero <aiopsclub@163.com> * test: cover URL-embedded OCI constraint resolution Address review feedback on PR #2768: the existing integration tests only exercised the repo-aliased spelling (chart: myrepo/mychart, version: '~1') and the version-field spelling. The chart-URL spelling (chart: oci://<registry>/<chart>:~1) takes a different branch in getOCIQualifiedChartName — the URL version is deliberately NOT embedded into the qualified ref and flows through --version only — so its re-qualification after constraint resolution (release.Version mutated to the resolved value, versionInURL still the constraint) was untested. TestGetOCIChart_URLEmbeddedConstraintResolves asserts the resolver receives the URL-embedded constraint, helm chart pull receives the resolved version via --version with a tag-less ref, and the cache path carries the resolved version segment instead of the raw constraint. Also gofmt-aligns the test tables added in earlier commits and drops a redundant 1.2.3 test case that tripped goconst. Signed-off-by: yxxhero <aiopsclub@163.com> * docs: note empty-version OCI releases are unaffected by resolveOCIVersions Releases with no version: at all keep their pre-existing semantics: helm picks the latest tag at pull time and helmfile caches it under a version-less shared-cache path. Document the limitation alongside the other resolveOCIVersions scope notes. Signed-off-by: yxxhero <aiopsclub@163.com> --------- Signed-off-by: Samuel Archambault <samuel.archambault@getmaintainx.com> Signed-off-by: yxxhero <aiopsclub@163.com> Co-authored-by: Samuel Archambault <samuel.archambault@getmaintainx.com> Co-authored-by: yxxhero <aiopsclub@163.com>