Skip to content

Commit 1dc1164

Browse files
committed
Add support for OS Features in the format
Updates the os part of the format to include features after the os version. The guarantees that the format may fully represent the platform structure. Signed-off-by: Derek McGowan <[email protected]>
1 parent 9b464c3 commit 1dc1164

File tree

2 files changed

+60
-16
lines changed

2 files changed

+60
-16
lines changed

platforms.go

+24-16
Original file line numberDiff line numberDiff line change
@@ -121,12 +121,10 @@ import (
121121
)
122122

123123
var (
124-
specifierRe = regexp.MustCompile(`^[A-Za-z0-9_.-]+$`)
125-
osAndVersionRe = regexp.MustCompile(`^([A-Za-z0-9_-]+)(?:\(([A-Za-z0-9_.-]*)\))?$`)
124+
specifierRe = regexp.MustCompile(`^[A-Za-z0-9_.-]+$`)
125+
osRe = regexp.MustCompile(`^([A-Za-z0-9_-]+)(?:\(([A-Za-z0-9_.-]*)((?:\+[A-Za-z0-9_.-]+)*)\))?$`)
126126
)
127127

128-
const osAndVersionFormat = "%s(%s)"
129-
130128
// Platform is a type alias for convenience, so there is no need to import image-spec package everywhere.
131129
type Platform = specs.Platform
132130

@@ -177,11 +175,14 @@ func ParseAll(specifiers []string) ([]specs.Platform, error) {
177175

178176
// Parse parses the platform specifier syntax into a platform declaration.
179177
//
180-
// Platform specifiers are in the format `<os>[(<OSVersion>)]|<arch>|<os>[(<OSVersion>)]/<arch>[/<variant>]`.
178+
// Platform specifiers are in the format `<os>[(<os options>)]|<arch>|<os>[(<os options>)]/<arch>[/<variant>]`.
181179
// The minimum required information for a platform specifier is the operating
182-
// system or architecture. The OSVersion can be part of the OS like `windows(10.0.17763)`
183-
// When an OSVersion is specified, then specs.Platform.OSVersion is populated with that value,
184-
// and an empty string otherwise.
180+
// system or architecture. The "os options" may be OSVersion which can be part of the OS
181+
// like `windows(10.0.17763)`. When an OSVersion is specified, then specs.Platform.OSVersion is
182+
// populated with that value, and an empty string otherwise. The "os options" may also include an
183+
// array of OSFeatures, each feature prefixed with '+', without any other separator, and provided
184+
// after the OSVersion when the OSVersion is specified. An "os options" with version and features
185+
// is like `windows(10.0.17763+win32k)`.
185186
// If there is only a single string (no slashes), the
186187
// value will be matched against the known set of operating systems, then fall
187188
// back to the known set of architectures. The missing component will be
@@ -198,14 +199,17 @@ func Parse(specifier string) (specs.Platform, error) {
198199
var p specs.Platform
199200
for i, part := range parts {
200201
if i == 0 {
201-
// First element is <os>[(<OSVersion>)]
202-
osVer := osAndVersionRe.FindStringSubmatch(part)
203-
if osVer == nil {
204-
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)
202+
// First element is <os>[(<OSVersion>[+<OSFeature>]*)]
203+
osOptions := osRe.FindStringSubmatch(part)
204+
if osOptions == nil {
205+
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)
205206
}
206207

207-
p.OS = normalizeOS(osVer[1])
208-
p.OSVersion = osVer[2]
208+
p.OS = normalizeOS(osOptions[1])
209+
p.OSVersion = osOptions[2]
210+
if osOptions[3] != "" {
211+
p.OSFeatures = strings.Split(osOptions[3][1:], "+")
212+
}
209213
} else {
210214
if !specifierRe.MatchString(part) {
211215
return specs.Platform{}, fmt.Errorf("%q is an invalid component of %q: platform specifier component must match %q: %w", part, specifier, specifierRe.String(), errInvalidArgument)
@@ -289,8 +293,12 @@ func FormatAll(platform specs.Platform) string {
289293
return "unknown"
290294
}
291295

292-
if platform.OSVersion != "" {
293-
OSAndVersion := fmt.Sprintf(osAndVersionFormat, platform.OS, platform.OSVersion)
296+
osOptions := platform.OSVersion
297+
for _, feature := range platform.OSFeatures {
298+
osOptions += "+" + feature
299+
}
300+
if osOptions != "" {
301+
OSAndVersion := fmt.Sprintf("%s(%s)", platform.OS, osOptions)
294302
return path.Join(OSAndVersion, platform.Architecture, platform.Variant)
295303
}
296304
return path.Join(platform.OS, platform.Architecture, platform.Variant)

platforms_test.go

+36
Original file line numberDiff line numberDiff line change
@@ -343,6 +343,42 @@ func TestParseSelector(t *testing.T) {
343343
formatted: path.Join("windows(10.0.17763)", defaultArch, defaultVariant),
344344
useV2Format: true,
345345
},
346+
{
347+
input: "windows(10.0.17763+win32k)",
348+
expected: specs.Platform{
349+
OS: "windows",
350+
OSVersion: "10.0.17763",
351+
OSFeatures: []string{"win32k"},
352+
Architecture: defaultArch,
353+
Variant: defaultVariant,
354+
},
355+
formatted: path.Join("windows(10.0.17763+win32k)", defaultArch, defaultVariant),
356+
useV2Format: true,
357+
},
358+
{
359+
input: "linux(+gpu)",
360+
expected: specs.Platform{
361+
OS: "linux",
362+
OSVersion: "",
363+
OSFeatures: []string{"gpu"},
364+
Architecture: defaultArch,
365+
Variant: defaultVariant,
366+
},
367+
formatted: path.Join("linux(+gpu)", defaultArch, defaultVariant),
368+
useV2Format: true,
369+
},
370+
{
371+
input: "linux(+gpu+simd)",
372+
expected: specs.Platform{
373+
OS: "linux",
374+
OSVersion: "",
375+
OSFeatures: []string{"gpu", "simd"},
376+
Architecture: defaultArch,
377+
Variant: defaultVariant,
378+
},
379+
formatted: path.Join("linux(+gpu+simd)", defaultArch, defaultVariant),
380+
useV2Format: true,
381+
},
346382
} {
347383
t.Run(testcase.input, func(t *testing.T) {
348384
if testcase.skip {

0 commit comments

Comments
 (0)