Skip to content

Implement generic variant handling - #35

Merged
dmcgowan merged 1 commit into
containerd:mainfrom
tianon-sso:variants
Nov 6, 2026
Merged

dmcgowan merged 1 commit into
containerd:mainfrom
tianon-sso:variants

Conversation

@tianon

@tianon tianon commented Aug 25, 2026

Copy link
Copy Markdown
Member

My old variant handling was creating explicit vector arrays, which doesn't scale well (algorithmically or maintainably).

This adjusts the logic to do more direct comparisons and handle "satisfiability" separate from "preferred order" which cleans up a lot of the comparisons.

This also makes it trivial to write a generic means of comparing variants that works for v4 vs v3 the same as power10 vs power8 and rva23u64 vs rva20u64.

As part of this work, it also folds in all the Windows-specific logic into a single matcher (because that's just standard platform matching logic that's naturally keyed off os, not a completely separate matcher).

It also genericizes the arm64 variant matching to the same vX.Y -> vX-1.Y+5 logic that Go uses.

See also https://github.com/opencontainers/image-spec/blob/v1.1.1/image-index.md#platform-variants

Assisted-By: "claude my eyes right out"

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR refactors platform variant compatibility and ordering to use generic parsing/comparison logic (instead of generating explicit variant vectors), adds generic “natural” ordering for variants/OS versions, and consolidates Windows OS version handling into the main matching path.

Changes:

  • Added a generic variant/version parser and comparison utilities, plus architecture-specific matching helpers (amd64/arm floors, arm64 v8↔v9 offset, generic “prefix+number+suffix” schemes).
  • Reworked Only to compute compatibility directly (Match) and sort independently (Less), avoiding unbounded vector generation for some variant schemes.
  • Folded Windows OS version matching into shared helpers and simplified OS feature subset logic (including win32k feature stripping behavior).

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
variant.go Introduces generic parsing/comparison utilities and matching helpers for versioned variants across architectures.
variant_test.go Adds unit tests for variant parsing, matching helpers, and natural ordering.
platforms.go Updates matcher behavior: factors OS version + OS feature subset logic into shared helpers and adjusts Windows matcher wrapping.
platform_windows_compat.go Refactors Windows OS version parsing/matching to reuse the new generic parser.
defaults_test.go Updates Windows MatchComparer test to explicitly wrap NewMatcher now that it returns only Matcher.
compare.go Reimplements Only matching/sorting with direct compatibility checks and a natural sort-based ranking model.
compare_test.go Expands test coverage for new arm64 arithmetic matching, generic variants (ppc64le/riscv64), and Only-specific ordering differences.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread platforms.go Resolved

@harryzcy harryzcy left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the great change overall

Comment thread compare.go Resolved
Comment thread compare.go Resolved
Comment thread compare.go Resolved
Comment thread compare.go Resolved
Comment thread variant.go
// sameShapeNumbers parses hostVariant and imageVariant and, if they share
// the same prefix and suffix (differing only in their numbers), returns
// both their numeric components for comparison.
func sameShapeNumbers(hostVariant, imageVariant string) (hostNumbers, imageNumbers []int, ok bool) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the behavior of variant with and without v prefix? are 8.3 and v8.3 treated differently? or should they get treated the same? I can see possibilities of user input error here.

It would also make sense to mandate certain pattern, like v is needed for arm64/* type. But the pattern may be different for different architecture types.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Per the Platform Variants table, arm64 values are always v-prefixed (v8, v8.1, ...) -- 8.3 isn't a spec-conformant value to begin with, so no normalization is done: arm64VariantMatch requires both sides to parse with prefix v, so an unprefixed variant only matches itself verbatim (via the early exact-string check) and fails everything else. Fails closed rather than guessing at intent.

@tianon

tianon commented Sep 1, 2026

Copy link
Copy Markdown
Member Author

Updated per review feedback 🙇 ❤️

Comment thread compare.go
Comment on lines +131 to +134
case "arm64":
return arm64VariantMatch(c.platform.Variant, normalized.Variant)
default:
return genericVariantMatch(c.platform.Variant, normalized.Variant)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it make sense to make the switch exhaustive; or at least for known arches? OCI refers to GOARCH as a well-defined list, which we could use for this switch; https://go.dev/doc/install/source#environment

And for unknown arches, don't apply any heuristics, other than exact match?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO we don't gain anything from an "exhaustive" switch here other than maintenance burden -- in my opinion, the generic matching is exactly the point of this PR, and I can't imagine a case where it's not the right answer for something we're not familiar with yet (for example, when s390x finally matures and we get a GOS390 or GOS390X variable, this should "just work" not require a PR to containerd to "fix").

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Honestly, even the v1 and v5 special cases above could just be removed -- they're not doing much more than genericVariantMatch anyways. The only reason arm64 has to be special is because it has special rules of compatibility that don't really have anything similar elsewhere (unless we wanted to somehow support the plethora of x86 features individually, but I think that'd be a mistake).

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If that level of simplification is interesting, you can see the change in b0a0a90, but my goal here wasn't actually to rock the boat that far unless maintainers think that's interesting / useful in order to simplify the code slightly more. 😄

(it's a very minor breaking change in that a linux/arm/v7 host would then consider running a linux/arm/v4 image, but that's not wrong -- just not something we've traditionally done, and I doubt there are very many <v5 images that even exist, let alone even v5)

Comment thread compare.go Resolved
@thaJeztah

Copy link
Copy Markdown
Member

@tianon @tianon-sso I opened a PR with my suggestion (for preparing for the modern slices APIs); opened a PR against your fork; let me know if you like that approach (happy to make changes); feel free to squash with your commit!

@tianon

tianon commented Sep 21, 2026

Copy link
Copy Markdown
Member Author

Merged Seb's PR/idea in now 🙇

@tianon

tianon commented Sep 21, 2026

Copy link
Copy Markdown
Member Author

(test coverage for the whole package before this change was ~78.5% and after this change it's up to ~83.7% 😄)

@thaJeztah thaJeztah left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@thaJeztah

Copy link
Copy Markdown
Member

@dmcgowan @AkihiroSuda this one good to go? After this we may have some follow ups

@AkihiroSuda
AkihiroSuda requested review from dmcgowan and a balanced review from Copilot October 16, 2026 02:49

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Windows revision-tail parsing can bypass compatibility checks, and the amd64 fallback ignores explicit 386 variant requirements.

3 open findings

🧠 Review effort: Balanced

Comment thread platform_windows_compat.go Resolved
Comment thread compare.go Resolved
@tianon-sso
tianon-sso force-pushed the variants branch 2 times, most recently from 940d627 to d1b9bf3 Compare October 19, 2026 18:52

@thaJeztah thaJeztah left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

still LGTM

@AkihiroSuda @dmcgowan PTAL 🤗

@thaJeztah

Copy link
Copy Markdown
Member

Also re-requested a review from CoPilot in case it has anything useful (but it may get into "nag" mode now 😂)

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

Broad changes to public matching and ordering behavior require maintainer validation of downstream compatibility.

1 open issue
2 resolved since last review

Review effort: Balanced

My old variant handling was creating explicit vector arrays, which doesn't scale well (algorithmically or maintainably).

This adjusts the logic to do more direct comparisons and handle "satisfiability" separate from "preferred order" which cleans up a lot of the comparisons.

This also makes it trivial to write a generic means of comparing variants that works for `v4` vs `v3` the same as `power10` vs `power8` and `rva23u64` vs `rva20u64`.

As part of this work, it also folds in all the Windows-specific logic into a single matcher (because that's just standard platform matching logic that's naturally keyed off `os`, not a completely separate matcher).

It also genericizes the arm64 variant matching to the same vX.Y -> vX-1.Y+5 logic that Go uses.

Additionally, this adds tests that covers more edge cases, like making sure that Windows is never preferred over Linux for a Linux host.

As a final touch, it uses a `compare`-style interface underneath (-1, 0, 1) so that the new stdlib `cmp` package's expectations can be satisfied without much work sometime in the future.

Assisted-By: "claude my eyes right out"
Signed-off-by: Tianon Gravi <tianon.gravi@docker.com>
Co-authored-by: Sebastiaan van Stijn <github@gone.nl>
@tianon

tianon commented Nov 5, 2026

Copy link
Copy Markdown
Member Author

(rebased)

@dmcgowan
dmcgowan merged commit a9e2baa into containerd:main Nov 6, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants