Merge pull request #52382 from renovate-bot/renovate/github.com-containerd-platforms-1.x

Update module github.com/containerd/platforms to v1.0.0-rc.4
This commit is contained in:
Paweł Gronowski
2026-06-05 16:48:22 +02:00
committed by GitHub
9 changed files with 309 additions and 56 deletions

2
go.mod
View File

@@ -27,7 +27,7 @@ require (
github.com/containerd/fifo v1.1.0
github.com/containerd/log v0.1.0
github.com/containerd/nri v0.12.0
github.com/containerd/platforms v1.0.0-rc.2
github.com/containerd/platforms v1.0.0-rc.4
github.com/containerd/typeurl/v2 v2.3.0
github.com/coreos/go-systemd/v22 v22.7.0
github.com/cpuguy83/tar2go v0.3.1

4
go.sum
View File

@@ -182,8 +182,8 @@ github.com/containerd/nri v0.12.0 h1:RvZtyCM64XOB1UmMAFOlfReTTwCd+hE2IEQ5XEpkKA0
github.com/containerd/nri v0.12.0/go.mod h1:TGAfPLH4a+qwbv0PxsefPiR+PobYecDj2aXMtz7GQcg=
github.com/containerd/nydus-snapshotter v0.15.13 h1:z9yCiTPMxVBIZlHxOPinZXhly2MdcIqxk9VXPlHIOJY=
github.com/containerd/nydus-snapshotter v0.15.13/go.mod h1:t95dwCb4I0RE4n1iOk0sJCWosNoACA8daOXmU5A2VHI=
github.com/containerd/platforms v1.0.0-rc.2 h1:0SPgaNZPVWGEi4grZdV8VRYQn78y+nm6acgLGv/QzE4=
github.com/containerd/platforms v1.0.0-rc.2/go.mod h1:J71L7B+aiM5SdIEqmd9wp6THLVRzJGXfNuWCZCllLA4=
github.com/containerd/platforms v1.0.0-rc.4 h1:M42JrUT4zfZTqtkUwkr0GzmUWbfyO5VO0Q5b3op97T4=
github.com/containerd/platforms v1.0.0-rc.4/go.mod h1:lKlMXyLybmBedS/JJm11uDofzI8L2v0J2ZbYvNsbq1A=
github.com/containerd/plugin v1.0.0 h1:c8Kf1TNl6+e2TtMHZt+39yAPDbouRH9WAToRjex483Y=
github.com/containerd/plugin v1.0.0/go.mod h1:hQfJe5nmWfImiqT1q8Si3jLv3ynMUIBB47bQ+KexvO8=
github.com/containerd/stargz-snapshotter/estargz v0.18.2 h1:yXkZFYIzz3eoLwlTUZKz2iQ4MrckBxJjkmD16ynUTrw=

View File

@@ -1,32 +1,25 @@
version: "2"
linters:
enable:
- copyloopvar
- gofmt
- goimports
- dupword
- gosec
- ineffassign
- misspell
- nolintlint
- revive
- staticcheck
- tenv # Detects using os.Setenv instead of t.Setenv since Go 1.17
- unconvert
- unused
- govet
- dupword # Checks for duplicate words in the source code
disable:
- errcheck
run:
timeout: 5m
issues:
exclude-dirs:
- api
- cluster
- design
- docs
- docs/man
- releases
- reports
- test # e2e scripts
exclusions:
generated: lax
presets:
- comments
- common-false-positives
- legacy
- std-error-handling
formatters:
enable:
- gofmt
- goimports
exclusions:
generated: lax

View File

@@ -152,6 +152,88 @@ func Only(platform specs.Platform) MatchComparer {
return Ordered(platformVector(Normalize(platform))...)
}
// OnlyOS returns a match comparer that matches only platforms with the same
// OS, OS version, and OS features, regardless of architecture. When comparing,
// it always ranks the best architecture match highest using the default
// platform resolution logic.
func OnlyOS(platform specs.Platform) MatchComparer {
normalized := Normalize(platform)
return onlyOSComparer{
platform: normalized,
osvM: newOSVersionMatcher(normalized),
archOrder: orderedPlatformComparer{
matchers: []Matcher{NewMatcher(normalized)},
},
}
}
func newOSVersionMatcher(platform specs.Platform) osVerMatcher {
if platform.OS == "windows" {
return &windowsVersionMatcher{
windowsOSVersion: getWindowsOSVersion(platform.OSVersion),
}
}
return nil
}
type onlyOSComparer struct {
platform specs.Platform
osvM osVerMatcher
archOrder orderedPlatformComparer
}
func (c onlyOSComparer) matchOS(platform specs.Platform) bool {
normalized := Normalize(platform)
if c.platform.OS != normalized.OS {
return false
}
if c.osvM != nil {
if !c.osvM.Match(platform.OSVersion) {
return false
}
}
if len(normalized.OSFeatures) > 0 {
if len(c.platform.OSFeatures) < len(normalized.OSFeatures) {
return false
}
j := 0
for _, feature := range normalized.OSFeatures {
found := false
for ; j < len(c.platform.OSFeatures); j++ {
if feature == c.platform.OSFeatures[j] {
found = true
j++
break
}
if feature < c.platform.OSFeatures[j] {
return false
}
}
if !found {
return false
}
}
}
return true
}
func (c onlyOSComparer) Match(platform specs.Platform) bool {
return c.matchOS(platform)
}
func (c onlyOSComparer) Less(p1, p2 specs.Platform) bool {
p1m := c.matchOS(p1)
p2m := c.matchOS(p2)
if p1m && !p2m {
return true
}
if !p1m {
return false
}
// Both match — rank by architecture preference
return c.archOrder.Less(p1, p2)
}
// OnlyStrict returns a match comparer for a single platform.
//
// Unlike Only, OnlyStrict does not match sub platforms.
@@ -213,9 +295,20 @@ func (c orderedPlatformComparer) Less(p1 specs.Platform, p2 specs.Platform) bool
return true
}
if p1m || p2m {
if p1m && p2m {
// Prefer one with most matching features
if len(p1.OSFeatures) != len(p2.OSFeatures) {
return len(p1.OSFeatures) > len(p2.OSFeatures)
}
}
return false
}
}
if len(p1.OSFeatures) > 0 || len(p2.OSFeatures) > 0 {
p1.OSFeatures = nil
p2.OSFeatures = nil
return c.Less(p1, p2)
}
return false
}
@@ -242,9 +335,20 @@ func (c anyPlatformComparer) Less(p1, p2 specs.Platform) bool {
p2m = true
}
if p1m && p2m {
return false
if len(p1.OSFeatures) != len(p2.OSFeatures) {
return len(p1.OSFeatures) > len(p2.OSFeatures)
}
break
}
}
// If neither match and has features, strip features and compare
if !p1m && !p2m && (len(p1.OSFeatures) > 0 || len(p2.OSFeatures) > 0) {
p1.OSFeatures = nil
p2.OSFeatures = nil
return c.Less(p1, p2)
}
// If one matches, and the other does, sort match first
return p1m && !p2m
}

View File

@@ -45,7 +45,6 @@ func getMachineArch() (string, error) {
// So we don't need to access the ARM registers to detect platform information
// by ourselves. We can just parse these information from /proc/cpuinfo
func getCPUInfo(pattern string) (info string, err error) {
cpuinfo, err := os.Open("/proc/cpuinfo")
if err != nil {
return "", err
@@ -75,7 +74,6 @@ func getCPUInfo(pattern string) (info string, err error) {
// getCPUVariantFromArch get CPU variant from arch through a system call
func getCPUVariantFromArch(arch string) (string, error) {
var variant string
arch = strings.ToLower(arch)

View File

@@ -24,10 +24,10 @@ import (
)
func getCPUVariant() (string, error) {
var variant string
if runtime.GOOS == "windows" || runtime.GOOS == "darwin" {
switch runtime.GOOS {
case "windows", "darwin":
// Windows/Darwin only supports v7 for ARM32 and v8 for ARM64 and so we can use
// runtime.GOARCH to determine the variants
switch runtime.GOARCH {
@@ -38,7 +38,7 @@ func getCPUVariant() (string, error) {
default:
variant = "unknown"
}
} else if runtime.GOOS == "freebsd" {
case "freebsd":
// FreeBSD supports ARMv6 and ARMv7 as well as ARMv4 and ARMv5 (though deprecated)
// detecting those variants is currently unimplemented
switch runtime.GOARCH {
@@ -47,7 +47,7 @@ func getCPUVariant() (string, error) {
default:
variant = "unknown"
}
} else {
default:
return "", fmt.Errorf("getCPUVariant for OS %s: %v", runtime.GOOS, errNotImplemented)
}

View File

@@ -17,6 +17,7 @@
package platforms
import (
"slices"
"strconv"
"strings"
@@ -162,3 +163,14 @@ func (c *windowsMatchComparer) Less(p1, p2 specs.Platform) bool {
}
return m1 && !m2
}
type windowsStripFeaturesMatcher struct {
Matcher
}
func (m windowsStripFeaturesMatcher) Match(p specs.Platform) bool {
if i := slices.Index(p.OSFeatures, "win32k"); i >= 0 {
p.OSFeatures = slices.Delete(slices.Clone(p.OSFeatures), i, i+1)
}
return m.Matcher.Match(p)
}

View File

@@ -111,9 +111,11 @@ package platforms
import (
"fmt"
"net/url"
"path"
"regexp"
"runtime"
"slices"
"strconv"
"strings"
@@ -121,12 +123,10 @@ import (
)
var (
specifierRe = regexp.MustCompile(`^[A-Za-z0-9_.-]+$`)
osAndVersionRe = regexp.MustCompile(`^([A-Za-z0-9_-]+)(?:\(([A-Za-z0-9_.-]*)\))?$`)
specifierRe = regexp.MustCompile(`^[A-Za-z0-9_.-]+$`)
osRe = regexp.MustCompile(`^([A-Za-z0-9_-]+)(?:\(([A-Za-z0-9_.%-]*)((?:\+[A-Za-z0-9_.%-]+)*)\))?$`)
)
const osAndVersionFormat = "%s(%s)"
// Platform is a type alias for convenience, so there is no need to import image-spec package everywhere.
type Platform = specs.Platform
@@ -143,6 +143,10 @@ type Matcher interface {
// functionality.
//
// Applications should opt to use `Match` over directly parsing specifiers.
//
// For OSFeatures, this matcher will match if the platform to match has
// OSFeatures which are a subset of the OSFeatures of the platform
// provided to NewMatcher.
func NewMatcher(platform specs.Platform) Matcher {
m := &matcher{
Platform: Normalize(platform),
@@ -152,6 +156,11 @@ func NewMatcher(platform specs.Platform) Matcher {
m.osvM = &windowsVersionMatcher{
windowsOSVersion: getWindowsOSVersion(platform.OSVersion),
}
// In prior versions, the win32k os feature was not considered for matching,
// strip out the win32k feature for comparison
var stripped Matcher = windowsStripFeaturesMatcher{m}
// In prior versions, on windows, the returned matcher implements a
// MatchComprarer interface.
// This preserves that behavior for backwards compatibility.
@@ -161,8 +170,9 @@ func NewMatcher(platform specs.Platform) Matcher {
// It was likely intended to be used in `Ordered` but it is not since
// `Less` that is implemented here ends up getting masked due to wrapping.
if runtime.GOOS == "windows" {
return &windowsMatchComparer{m}
return &windowsMatchComparer{stripped}
}
return stripped
}
return m
}
@@ -178,10 +188,39 @@ type matcher struct {
func (m *matcher) Match(platform specs.Platform) bool {
normalized := Normalize(platform)
return m.OS == normalized.OS &&
if m.OS == normalized.OS &&
m.Architecture == normalized.Architecture &&
m.Variant == normalized.Variant &&
m.matchOSVersion(platform)
m.matchOSVersion(platform) {
if len(normalized.OSFeatures) == 0 {
return true
}
if len(m.OSFeatures) >= len(normalized.OSFeatures) {
// Ensure that normalized.OSFeatures is a subset of
// m.OSFeatures
j := 0
for _, feature := range normalized.OSFeatures {
found := false
for ; j < len(m.OSFeatures); j++ {
if feature == m.OSFeatures[j] {
found = true
j++
break
}
// Since both lists are ordered, if the feature is less
// than what is seen, it is not in the list
if feature < m.OSFeatures[j] {
return false
}
}
if !found {
return false
}
}
return true
}
}
return false
}
func (m *matcher) matchOSVersion(platform specs.Platform) bool {
@@ -210,11 +249,14 @@ func ParseAll(specifiers []string) ([]specs.Platform, error) {
// Parse parses the platform specifier syntax into a platform declaration.
//
// Platform specifiers are in the format `<os>[(<OSVersion>)]|<arch>|<os>[(<OSVersion>)]/<arch>[/<variant>]`.
// Platform specifiers are in the format `<os>[(<os options>)]|<arch>|<os>[(<os options>)]/<arch>[/<variant>]`.
// The minimum required information for a platform specifier is the operating
// system or architecture. The OSVersion can be part of the OS like `windows(10.0.17763)`
// When an OSVersion is specified, then specs.Platform.OSVersion is populated with that value,
// and an empty string otherwise.
// system or architecture. The "os options" may be OSVersion which can be part of the OS
// like `windows(10.0.17763)`. When an OSVersion is specified, then specs.Platform.OSVersion is
// populated with that value, and an empty string otherwise. The "os options" may also include an
// array of OSFeatures, each feature prefixed with '+', without any other separator, and provided
// after the OSVersion when the OSVersion is specified. An "os options" with version and features
// is like `windows(10.0.17763+win32k)`.
// If there is only a single string (no slashes), the
// value will be matched against the known set of operating systems, then fall
// back to the known set of architectures. The missing component will be
@@ -231,14 +273,24 @@ func Parse(specifier string) (specs.Platform, error) {
var p specs.Platform
for i, part := range parts {
if i == 0 {
// First element is <os>[(<OSVersion>)]
osVer := osAndVersionRe.FindStringSubmatch(part)
if osVer == nil {
return specs.Platform{}, fmt.Errorf("%q is an invalid OS component of %q: OSAndVersion specifier component must match %q: %w", part, specifier, osAndVersionRe.String(), errInvalidArgument)
// First element is <os>[(<OSVersion>[+<OSFeature>]*)]
osOptions := osRe.FindStringSubmatch(part)
if osOptions == nil {
return specs.Platform{}, fmt.Errorf("%q is an invalid OS component of %q: OSAndVersion specifier component must match %q: %w", part, specifier, osRe.String(), errInvalidArgument)
}
p.OS = normalizeOS(osVer[1])
p.OSVersion = osVer[2]
p.OS = normalizeOS(osOptions[1])
osVersion, err := decodeOSOption(osOptions[2])
if err != nil {
return specs.Platform{}, fmt.Errorf("%q has an invalid OS version %q: %w", specifier, osOptions[2], err)
}
p.OSVersion = osVersion
if osOptions[3] != "" {
p.OSFeatures, err = parseOSFeatures(osOptions[3][1:])
if err != nil {
return specs.Platform{}, fmt.Errorf("%q has invalid OS features: %w", specifier, err)
}
}
} else {
if !specifierRe.MatchString(part) {
return specs.Platform{}, fmt.Errorf("%q is an invalid component of %q: platform specifier component must match %q: %w", part, specifier, specifierRe.String(), errInvalidArgument)
@@ -296,6 +348,30 @@ func Parse(specifier string) (specs.Platform, error) {
return specs.Platform{}, fmt.Errorf("%q: cannot parse platform specifier: %w", specifier, errInvalidArgument)
}
func parseOSFeatures(s string) ([]string, error) {
if s == "" {
return nil, nil
}
var features []string
for raw := range strings.SplitSeq(s, "+") {
raw = strings.TrimSpace(raw)
if raw == "" {
return nil, fmt.Errorf("empty os feature: %w", errInvalidArgument)
}
feature, err := decodeOSOption(raw)
if err != nil {
return nil, fmt.Errorf("invalid os feature %q: %w", raw, err)
}
if feature == "" {
continue
}
features = append(features, feature)
}
return features, nil
}
// MustParse is like Parses but panics if the specifier cannot be parsed.
// Simplifies initialization of global variables.
func MustParse(specifier string) specs.Platform {
@@ -321,12 +397,77 @@ func FormatAll(platform specs.Platform) string {
if platform.OS == "" {
return "unknown"
}
if platform.OSVersion != "" {
OSAndVersion := fmt.Sprintf(osAndVersionFormat, platform.OS, platform.OSVersion)
return path.Join(OSAndVersion, platform.Architecture, platform.Variant)
if platform.OSVersion == "" && len(platform.OSFeatures) == 0 {
return path.Join(platform.OS, platform.Architecture, platform.Variant)
}
return path.Join(platform.OS, platform.Architecture, platform.Variant)
var b strings.Builder
b.WriteString(platform.OS)
osv := encodeOSOption(platform.OSVersion)
formatted := formatOSFeatures(platform.OSFeatures)
if osv != "" || formatted != "" {
b.Grow(len(osv) + len(formatted) + 3) // parens + maybe '+'
b.WriteByte('(')
if osv != "" {
b.WriteString(osv)
}
if formatted != "" {
b.WriteByte('+')
b.WriteString(formatted)
}
b.WriteByte(')')
}
return path.Join(b.String(), platform.Architecture, platform.Variant)
}
func formatOSFeatures(features []string) string {
if len(features) == 0 {
return ""
}
if !slices.IsSorted(features) {
features = slices.Clone(features)
slices.Sort(features)
}
var b strings.Builder
var wrote bool
var prev string
for _, f := range features {
if f == "" || f == prev {
// skip empty and duplicate values
continue
}
prev = f
if wrote {
b.WriteByte('+')
}
b.WriteString(encodeOSOption(f))
wrote = true
}
return b.String()
}
// osOptionReplacer encodes characters in OS option values (version and
// features) that are ambiguous with the format syntax. The percent sign
// must be replaced first to avoid double-encoding.
var osOptionReplacer = strings.NewReplacer(
"%", "%25",
"+", "%2B",
"(", "%28",
")", "%29",
"/", "%2F",
)
func encodeOSOption(v string) string {
return osOptionReplacer.Replace(v)
}
func decodeOSOption(v string) (string, error) {
if strings.Contains(v, "%") {
return url.PathUnescape(v)
}
return v, nil
}
// Normalize validates and translate the platform to the canonical value.
@@ -336,6 +477,11 @@ func FormatAll(platform specs.Platform) string {
func Normalize(platform specs.Platform) specs.Platform {
platform.OS = normalizeOS(platform.OS)
platform.Architecture, platform.Variant = normalizeArch(platform.Architecture, platform.Variant)
if len(platform.OSFeatures) > 0 {
platform.OSFeatures = slices.Clone(platform.OSFeatures)
slices.Sort(platform.OSFeatures)
platform.OSFeatures = slices.Compact(platform.OSFeatures)
}
return platform
}

4
vendor/modules.txt vendored
View File

@@ -529,8 +529,8 @@ github.com/containerd/nri/plugins/default-validator/builtin
github.com/containerd/nydus-snapshotter/pkg/converter
github.com/containerd/nydus-snapshotter/pkg/converter/tool
github.com/containerd/nydus-snapshotter/pkg/label
# github.com/containerd/platforms v1.0.0-rc.2
## explicit; go 1.20
# github.com/containerd/platforms v1.0.0-rc.4
## explicit; go 1.24
github.com/containerd/platforms
# github.com/containerd/plugin v1.0.0
## explicit; go 1.20