Summary
mirror.KubeVersionFromConstraints can return a Kubernetes version well below what bundled Helm charts require, so mirror/BOM discovery may render with --kube-version 1.25. That is lower than Helm's own compiled-in default (v1.27.0) and below the >=1.32.0-0 some charts declare.
Mechanism (confirmed)
KubeVersionFromConstraints (pkg/mirror/discover.go:539) returns extractVersion of the recipe's own K8s.server.version constraint, falling back to defaults.MirrorDefaultKubeVersion (1.33.0) only when no such constraint exists.
recipes/overlays/base.yaml declares K8s.server.version: ">= 1.25", so a recipe resolving with only the base constraint yields 1.25. Reproduced directly:
extractVersion(">= 1.25") = "1.25"
extractVersion(">= 1.32.4") = "1.32.4"
extractVersion(">= 1.34") = "1.34"
defaults.MirrorDefaultKubeVersion is pinned at 1.33.0 precisely to avoid rendering too low on the fallback path — but the constraint path bypasses that guard entirely. The constant's own doc comment explains why the floor matters: Helm's compiled-in v1.27.0 "is too old for charts that declare a kubeVersion constraint (e.g., >=1.32.0-0)".
Reachability (NOT confirmed — needs triage)
This has not been shown to affect a real recipe, and the issue should not be treated as a live defect until it is.
90 overlays under recipes/overlays/ declare their own K8s.server.version, which overrides the base floor. The bug only bites a recipe that resolves carrying the base constraint alone. Whether any resolvable recipe does is the open question.
Suggested triage:
- Enumerate resolvable recipes and check which end up with
K8s.server.version from base.yaml only.
- If none, this is a latent trap rather than a live bug — worth guarding anyway, since a future overlay could drop its own constraint.
- If some, it is a real rendering bug for those recipes.
Suggested fix
Floor the extracted value: return max(extractVersion(constraint), MirrorDefaultKubeVersion) rather than the raw constraint value. That makes the constant a genuine floor for both paths instead of only the fallback, matching what its comment already claims.
An alternative — raising base.yaml's constraint — would be wrong: that constraint is the recipe validation floor and legitimately differs from the render floor.
Origin
Found by @njhensley while reviewing #2336, which corrects the MirrorDefaultKubeVersion doc comment. Deliberately kept out of that PR (comment-only), which now documents the gap:
The invariant binds this constant only. The constraint path is unbounded below: KubeVersionFromConstraints returns the recipe's own value, so a recipe carrying nothing but base.yaml's ">= 1.25" renders with --kube-version 1.25.
Summary
mirror.KubeVersionFromConstraintscan return a Kubernetes version well below what bundled Helm charts require, so mirror/BOM discovery may render with--kube-version 1.25. That is lower than Helm's own compiled-in default (v1.27.0) and below the>=1.32.0-0some charts declare.Mechanism (confirmed)
KubeVersionFromConstraints(pkg/mirror/discover.go:539) returnsextractVersionof the recipe's ownK8s.server.versionconstraint, falling back todefaults.MirrorDefaultKubeVersion(1.33.0) only when no such constraint exists.recipes/overlays/base.yamldeclaresK8s.server.version: ">= 1.25", so a recipe resolving with only the base constraint yields1.25. Reproduced directly:defaults.MirrorDefaultKubeVersionis pinned at1.33.0precisely to avoid rendering too low on the fallback path — but the constraint path bypasses that guard entirely. The constant's own doc comment explains why the floor matters: Helm's compiled-inv1.27.0"is too old for charts that declare a kubeVersion constraint (e.g.,>=1.32.0-0)".Reachability (NOT confirmed — needs triage)
This has not been shown to affect a real recipe, and the issue should not be treated as a live defect until it is.
90 overlays under
recipes/overlays/declare their ownK8s.server.version, which overrides the base floor. The bug only bites a recipe that resolves carrying the base constraint alone. Whether any resolvable recipe does is the open question.Suggested triage:
K8s.server.versionfrombase.yamlonly.Suggested fix
Floor the extracted value: return
max(extractVersion(constraint), MirrorDefaultKubeVersion)rather than the raw constraint value. That makes the constant a genuine floor for both paths instead of only the fallback, matching what its comment already claims.An alternative — raising
base.yaml's constraint — would be wrong: that constraint is the recipe validation floor and legitimately differs from the render floor.Origin
Found by @njhensley while reviewing #2336, which corrects the
MirrorDefaultKubeVersiondoc comment. Deliberately kept out of that PR (comment-only), which now documents the gap: