diff --git a/platforms/platforms.go b/platforms/platforms.go index 43e4ad3d83a1..92696a0978aa 100644 --- a/platforms/platforms.go +++ b/platforms/platforms.go @@ -121,7 +121,7 @@ import ( ) var ( - specifierRe = regexp.MustCompile(`^[A-Za-z0-9_-]+$`) + specifierRe = regexp.MustCompile(`^[A-Za-z0-9_."-]+$`) ) // Platform is a type alias for convenience, so there is no need to import image-spec package everywhere. @@ -174,7 +174,7 @@ func ParseAll(specifiers []string) ([]specs.Platform, error) { // Parse parses the platform specifier syntax into a platform declaration. // -// Platform specifiers are in the format `||/[/]`. +// Platform specifiers are in the format `||/[/]|//[/]`. // The minimum required information for a platform specifier is the operating // system or architecture. If there is only a single string (no slashes), the // value will be matched against the known set of operating systems, then fall @@ -223,6 +223,9 @@ func Parse(specifier string) (specs.Platform, error) { } if isKnownArch(p.Architecture) { p.OS = runtime.GOOS + if p.OS == "windows" { + p.OSVersion = GetWindowsOsVersion() + } return p, nil } @@ -253,6 +256,21 @@ func Parse(specifier string) (specs.Platform, error) { p.OSVersion = GetWindowsOsVersion() } + return p, nil + case 4: + // we have a fully specified variant, this is rare + p.OS = normalizeOS(parts[0]) + variant := parts[2] + if variant == "\"\"" { // empty variant + variant = "" + } + p.Architecture, p.Variant = normalizeArch(parts[1], variant) + if p.Architecture == "arm64" && p.Variant == "" { + p.Variant = "v8" + } + + p.OSVersion = parts[3] + return p, nil } @@ -270,12 +288,22 @@ func MustParse(specifier string) specs.Platform { } // Format returns a string specifier from the provided platform specification. -func Format(platform specs.Platform) string { - if platform.OS == "" { +func Format(ocispecPlatform specs.Platform) string { + if ocispecPlatform.OS == "" { return "unknown" } - return path.Join(platform.OS, platform.Architecture, platform.Variant) + if ocispecPlatform.OSVersion != "" { + variant := ocispecPlatform.Variant + if variant == "" { + variant = "\"\"" + } + + platform := []string{ocispecPlatform.OS, ocispecPlatform.Architecture, variant, ocispecPlatform.OSVersion} + return strings.Join(platform, "/") + } + + return path.Join(ocispecPlatform.OS, ocispecPlatform.Architecture, ocispecPlatform.Variant) } // Normalize validates and translate the platform to the canonical value. diff --git a/platforms/platforms_test.go b/platforms/platforms_test.go index c2af02178beb..dee596f2e9a5 100644 --- a/platforms/platforms_test.go +++ b/platforms/platforms_test.go @@ -20,6 +20,7 @@ import ( "path" "reflect" "runtime" + "strings" "testing" specs "github.com/opencontainers/image-spec/specs-go/v1" @@ -27,7 +28,6 @@ import ( func TestParseSelector(t *testing.T) { var ( - defaultOS = runtime.GOOS defaultArch = runtime.GOARCH defaultVariant = "" ) @@ -200,45 +200,45 @@ func TestParseSelector(t *testing.T) { formatted: "linux/arm/v7", }, { - input: "arm", + input: "linux/arm", expected: specs.Platform{ - OS: defaultOS, + OS: "linux", Architecture: "arm", }, - formatted: path.Join(defaultOS, "arm"), + formatted: path.Join("linux", "arm"), }, { - input: "armel", + input: "linux/armel", expected: specs.Platform{ - OS: defaultOS, + OS: "linux", Architecture: "arm", Variant: "v6", }, - formatted: path.Join(defaultOS, "arm/v6"), + formatted: path.Join("linux", "arm/v6"), }, { - input: "armhf", + input: "linux/armhf", expected: specs.Platform{ - OS: defaultOS, + OS: "linux", Architecture: "arm", }, - formatted: path.Join(defaultOS, "arm"), + formatted: path.Join("linux", "arm"), }, { - input: "Aarch64", + input: "linux/Aarch64", expected: specs.Platform{ - OS: defaultOS, + OS: "linux", Architecture: "arm64", }, - formatted: path.Join(defaultOS, "arm64"), + formatted: path.Join("linux", "arm64"), }, { - input: "x86_64", + input: "linux/x86_64", expected: specs.Platform{ - OS: defaultOS, + OS: "linux", Architecture: "amd64", }, - formatted: path.Join(defaultOS, "amd64"), + formatted: path.Join("linux", "amd64"), }, { input: "Linux/x86_64", @@ -249,12 +249,12 @@ func TestParseSelector(t *testing.T) { formatted: "linux/amd64", }, { - input: "i386", + input: "linux/i386", expected: specs.Platform{ - OS: defaultOS, + OS: "linux", Architecture: "386", }, - formatted: path.Join(defaultOS, "386"), + formatted: path.Join("linux", "386"), }, { input: "linux", @@ -266,12 +266,12 @@ func TestParseSelector(t *testing.T) { formatted: path.Join("linux", defaultArch, defaultVariant), }, { - input: "s390x", + input: "linux/s390x", expected: specs.Platform{ - OS: defaultOS, + OS: "linux", Architecture: "s390x", }, - formatted: path.Join(defaultOS, "s390x"), + formatted: path.Join("linux", "s390x"), }, { input: "linux/s390x", @@ -290,12 +290,35 @@ func TestParseSelector(t *testing.T) { }, formatted: path.Join("darwin", defaultArch, defaultVariant), }, + { + input: "windows/amd64", + expected: specs.Platform{ + OS: "windows", + Architecture: "amd64", + Variant: "", + OSVersion: GetWindowsOsVersion(), + }, + formatted: strings.Join([]string{"windows", "amd64", "\"\"", GetWindowsOsVersion()}, "/"), + }, + + { + input: "windows/amd64/\"\"/10.0.20348", + expected: specs.Platform{ + OS: "windows", + Architecture: "amd64", + Variant: "", + OSVersion: "10.0.20348", + }, + formatted: strings.Join([]string{"windows", "amd64", "\"\"", "10.0.20348"}, "/"), + }, } { t.Run(testcase.input, func(t *testing.T) { if testcase.skip { t.Skip("this case is not yet supported") } + t.Logf("testcase.input %v", testcase.input) p, err := Parse(testcase.input) + t.Logf("parse the testcase.input %v", p) if err != nil { t.Fatal(err) } @@ -317,6 +340,7 @@ func TestParseSelector(t *testing.T) { } formatted := Format(p) + t.Logf("formatted string %v", formatted) if formatted != testcase.formatted { t.Fatalf("unexpected format: %q != %q", formatted, testcase.formatted) } @@ -353,9 +377,6 @@ func TestParseSelectorInvalid(t *testing.T) { { input: "linux/&arm", // invalid character }, - { - input: "linux/arm/foo/bar", // too many components - }, } { t.Run(testcase.input, func(t *testing.T) { if _, err := Parse(testcase.input); err == nil {