Skip to content

mirror: extractVersion is unbounded below, can render --kube-version 1.25 #2340

Description

@yuanchen8911

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:

  1. Enumerate resolvable recipes and check which end up with K8s.server.version from base.yaml only.
  2. If none, this is a latent trap rather than a live bug — worth guarding anyway, since a future overlay could drop its own constraint.
  3. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions