diff --git a/database.go b/database.go index 2e26fd3..7205046 100644 --- a/database.go +++ b/database.go @@ -17,6 +17,7 @@ package platforms import ( + "regexp" "runtime" "strings" ) @@ -59,17 +60,35 @@ func isKnownArch(arch string) bool { return false } -func normalizeOS(os string) string { - if os == "" { - return runtime.GOOS +func getWindowsOsVersion(OSAndVersion string) string { + parts := regexp.MustCompile("[()]").Split(OSAndVersion, -1) + if len(parts) > 1 { + if parts[1] != "" { + return parts[1] + } + } + return "" +} + +// The formart of OS part of the platform specifier is [()] +// OSVersion is optional only and is currently used only by windows OS. +// If OsVersion is not specified, empty string is returned. +func normalizeOS(OSAndVersion string) (string, string) { + if OSAndVersion == "" { + return runtime.GOOS, "" } - os = strings.ToLower(os) + os := strings.Split(strings.ToLower(OSAndVersion), "(")[0] switch os { case "macos": os = "darwin" } - return os + + // check for OS verion if OS version is windows. Otherwise, use the host OSVersion + if os == "windows" { + return "windows", getWindowsOsVersion(OSAndVersion) + } + return os, "" } // normalizeArch normalizes the architecture. diff --git a/platforms.go b/platforms.go index 43e4ad3..16a5fc2 100644 --- a/platforms.go +++ b/platforms.go @@ -121,7 +121,8 @@ import ( ) var ( - specifierRe = regexp.MustCompile(`^[A-Za-z0-9_-]+$`) + specifierRe = regexp.MustCompile(`^[()A-Za-z0-9_.-]+$`) + OSAndVersionFormat = "%s(%s)" ) // Platform is a type alias for convenience, so there is no need to import image-spec package everywhere. @@ -174,9 +175,13 @@ 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 +// system or architecture. The OSVersion can be part of the OS like windows(10.0.17763) +// Currently, the OS version is only used by windows. Therefore, if the OS is windows +// and an os version is specified, then specs.Platform.OSVersion is populated. If not it +// is left empty. +// 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 // inferred based on the local environment. @@ -197,12 +202,13 @@ func Parse(specifier string) (specs.Platform, error) { var p specs.Platform switch len(parts) { case 1: - // in this case, we will test that the value might be an OS, then look - // it up. If it is not known, we'll treat it as an architecture. Since + // in this case, we will test that the value might be an OS (with or + // without the optional osversion specified) and look it up. + // If it is not known, we'll treat it as an architecture. Since // we have very little information about the platform here, we are // going to be a little more strict if we don't know about the argument // value. - p.OS = normalizeOS(parts[0]) + p.OS, p.OSVersion = normalizeOS(parts[0]) if isKnownOS(p.OS) { // picks a default architecture p.Architecture = runtime.GOARCH @@ -210,10 +216,6 @@ func Parse(specifier string) (specs.Platform, error) { p.Variant = cpuVariant() } - if p.OS == "windows" { - p.OSVersion = GetWindowsOsVersion() - } - return p, nil } @@ -228,31 +230,23 @@ func Parse(specifier string) (specs.Platform, error) { return specs.Platform{}, fmt.Errorf("%q: unknown operating system or architecture: %w", specifier, errInvalidArgument) case 2: - // In this case, we treat as a regular os/arch pair. We don't care + // In this case, we treat as a regular os[(osversion)]/arch pair. We don't care // about whether or not we know of the platform. - p.OS = normalizeOS(parts[0]) + p.OS, p.OSVersion = normalizeOS(parts[0]) p.Architecture, p.Variant = normalizeArch(parts[1], "") if p.Architecture == "arm" && p.Variant == "v7" { p.Variant = "" } - if p.OS == "windows" { - p.OSVersion = GetWindowsOsVersion() - } - return p, nil case 3: // we have a fully specified variant, this is rare - p.OS = normalizeOS(parts[0]) + p.OS, p.OSVersion = normalizeOS(parts[0]) p.Architecture, p.Variant = normalizeArch(parts[1], parts[2]) if p.Architecture == "arm64" && p.Variant == "" { p.Variant = "v8" } - if p.OS == "windows" { - p.OSVersion = GetWindowsOsVersion() - } - return p, nil } @@ -275,6 +269,11 @@ func Format(platform specs.Platform) string { return "unknown" } + if strings.ToLower(platform.OS) == "windows" && platform.OSVersion != "" { + windowsOsVersion := fmt.Sprintf(OSAndVersionFormat, platform.OS, platform.OSVersion) + return path.Join(windowsOsVersion, platform.Architecture, platform.Variant) + } + return path.Join(platform.OS, platform.Architecture, platform.Variant) } @@ -283,7 +282,7 @@ func Format(platform specs.Platform) string { // For example, if "Aarch64" is encountered, we change it to "arm64" or if // "x86_64" is encountered, it becomes "amd64". func Normalize(platform specs.Platform) specs.Platform { - platform.OS = normalizeOS(platform.OS) + platform.OS, platform.OSVersion = normalizeOS(platform.OS) platform.Architecture, platform.Variant = normalizeArch(platform.Architecture, platform.Variant) return platform diff --git a/platforms_test.go b/platforms_test.go index c2af021..501bc0d 100644 --- a/platforms_test.go +++ b/platforms_test.go @@ -290,6 +290,36 @@ func TestParseSelector(t *testing.T) { }, formatted: path.Join("darwin", defaultArch, defaultVariant), }, + { + input: "windows", + expected: specs.Platform{ + OS: "windows", + OSVersion: "", + Architecture: defaultArch, + Variant: defaultVariant, + }, + formatted: path.Join("windows", defaultArch, defaultVariant), + }, + { + input: "windows()", + expected: specs.Platform{ + OS: "windows", + OSVersion: "", + Architecture: defaultArch, + Variant: defaultVariant, + }, + formatted: path.Join("windows", defaultArch, defaultVariant), + }, + { + input: "windows(10.0.17763)", + expected: specs.Platform{ + OS: "windows", + OSVersion: "10.0.17763", + Architecture: defaultArch, + Variant: defaultVariant, + }, + formatted: path.Join("windows(10.0.17763)", defaultArch, defaultVariant), + }, } { t.Run(testcase.input, func(t *testing.T) { if testcase.skip {