Skip to content

Commit 5b124ef

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 005d370 commit 5b124ef

2 files changed

Lines changed: 60 additions & 16 deletions

File tree

platforms.go

Lines changed: 24 additions & 16 deletions
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

@@ -210,11 +208,14 @@ func ParseAll(specifiers []string) ([]specs.Platform, error) {
210208

211209
// Parse parses the platform specifier syntax into a platform declaration.
212210
//
213-
// Platform specifiers are in the format `<os>[(<OSVersion>)]|<arch>|<os>[(<OSVersion>)]/<arch>[/<variant>]`.
211+
// Platform specifiers are in the format `<os>[(<os options>)]|<arch>|<os>[(<os options>)]/<arch>[/<variant>]`.
214212
// The minimum required information for a platform specifier is the operating
215-
// system or architecture. The OSVersion can be part of the OS like `windows(10.0.17763)`
216-
// When an OSVersion is specified, then specs.Platform.OSVersion is populated with that value,
217-
// and an empty string otherwise.
213+
// system or architecture. The "os options" may be OSVersion which can be part of the OS
214+
// like `windows(10.0.17763)`. When an OSVersion is specified, then specs.Platform.OSVersion is
215+
// populated with that value, and an empty string otherwise. The "os options" may also include an
216+
// array of OSFeatures, each feature prefixed with '+', without any other separator, and provided
217+
// after the OSVersion when the OSVersion is specified. An "os options" with version and features
218+
// is like `windows(10.0.17763+win32k)`.
218219
// If there is only a single string (no slashes), the
219220
// value will be matched against the known set of operating systems, then fall
220221
// back to the known set of architectures. The missing component will be
@@ -231,14 +232,17 @@ func Parse(specifier string) (specs.Platform, error) {
231232
var p specs.Platform
232233
for i, part := range parts {
233234
if i == 0 {
234-
// First element is <os>[(<OSVersion>)]
235-
osVer := osAndVersionRe.FindStringSubmatch(part)
236-
if osVer == nil {
237-
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)
235+
// First element is <os>[(<OSVersion>[+<OSFeature>]*)]
236+
osOptions := osRe.FindStringSubmatch(part)
237+
if osOptions == nil {
238+
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)
238239
}
239240

240-
p.OS = normalizeOS(osVer[1])
241-
p.OSVersion = osVer[2]
241+
p.OS = normalizeOS(osOptions[1])
242+
p.OSVersion = osOptions[2]
243+
if osOptions[3] != "" {
244+
p.OSFeatures = strings.Split(osOptions[3][1:], "+")
245+
}
242246
} else {
243247
if !specifierRe.MatchString(part) {
244248
return specs.Platform{}, fmt.Errorf("%q is an invalid component of %q: platform specifier component must match %q: %w", part, specifier, specifierRe.String(), errInvalidArgument)
@@ -322,8 +326,12 @@ func FormatAll(platform specs.Platform) string {
322326
return "unknown"
323327
}
324328

325-
if platform.OSVersion != "" {
326-
OSAndVersion := fmt.Sprintf(osAndVersionFormat, platform.OS, platform.OSVersion)
329+
osOptions := platform.OSVersion
330+
for _, feature := range platform.OSFeatures {
331+
osOptions += "+" + feature
332+
}
333+
if osOptions != "" {
334+
OSAndVersion := fmt.Sprintf("%s(%s)", platform.OS, osOptions)
327335
return path.Join(OSAndVersion, platform.Architecture, platform.Variant)
328336
}
329337
return path.Join(platform.OS, platform.Architecture, platform.Variant)

platforms_test.go

Lines changed: 36 additions & 0 deletions
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)