-
Notifications
You must be signed in to change notification settings - Fork 3.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add OSVersion while parsing ocispec.Platform #9609
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -20,14 +20,14 @@ import ( | |||||
"path" | ||||||
"reflect" | ||||||
"runtime" | ||||||
"strings" | ||||||
"testing" | ||||||
|
||||||
specs "github.com/opencontainers/image-spec/specs-go/v1" | ||||||
) | ||||||
|
||||||
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", | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This empty variant double quote idea is really weird and I don't think we can expect users to write something like this. (I don't understand where the need for double quote specifically is coming from as well). My recommendation would be to strictly define the cases where osversion is allowed and from there deduce the packing rules between variant and osversion so either or both can be present in practical cases. We already have many known values for arch and variants. I also think we should define normalization rules like they exist for variants in amd64, arm and arm64 so that empty osversion is equal to a specific value defined for windows architecture and setting that value would normalize it to empty osversion in string form. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Right now, empty osversion is a valid value for os==windows, and containers exist with that value in their config JSON. They're the ones most-similar to non-Windows containers, as they contain no OS components inside the container itself, and we need to be able to match/exclude such containers. So the string-form empty string is most naturally going to be the empty string when parsed. I'm not clear why There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
What does that match rule mean in that case? If we would chose osversion that gets normalized to empty, I would assume it would be the one that is most common and compatible with most systems (as There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's actually the other way 'round in this case. The only way a Windows container image with no OS components can be executed is in Host Process mode, aka most-privileged mode. It can also be used to signal a container image being used to distribute Windows binaries but that is not expected to be executed itself, e.g., the containerd system for distributing runtime shims in registries. Conversely, when you're specifying a platform you want to run, then However, it makes a poor default because The Windows OS ABIs are not fully forward/backward compatible, so there isn't really a good "Runs everywhere" OS Version value like Architecture Variants have for Process Isolation (the most common isolation case); so any most-common value chosen is only correct for a limited period of time, and then its usage drops off over time, and is eventually end-of-lifed. With Hyper-V Isolation, the most-compatible variant is Windows Server 2016, but that's end-of-lifed and well out-of-support, so is clearly a poor choice. In terms of Windows platform matching, the matcher needs to know what host environment (version and isolation mode) the container will be run in, so it can set the constraints on OSVersion correctly. This is easy when you're actually running the container, as that knowledge is there. So I think the matcher is a better place to do things like defaulting OSVersion to the host. (That's moby/buildkit#4315 (comment) again. Maybe we should fix that behaviour first... Or at least agree to fix it in some way, and then see what (if anything) that shakes out for this use-case.) However, things like Maybe this makes the Of course, non-Windows hosts aren't going to be able to produce a rational value for "Current host ABI" but in such a case, they can't run the container image anyway so that closes off a bunch of places we actually care about osversion, and for other cases, they probably need to be explicit anyway. As a default in this case, the Host Process value (whatever it is, currently There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think would be correct to not fill in the OSVersion altogether for windows as that would mean HPC. I think for the time being we can let that behavior be and extend platforms.Parser() and platforms.Format() to account for OSVersion if one was specified. We could maybe use the format of OS/architecture/variant/OSVersion ? Once the changes for image pull per runtime class goes in, we would need to support option of specifying the OSVersion for windows. containerd/platforms/defaults_windows.go Line 31 in 46bca49
containerd/platforms/defaults.go Line 20 in 46bca49
IMO, we should be fixing on a format that platforms.Format() takes and parses. It should honor OSVersion if one was specified and not fill in the OSVersion by default. variant for windows OS and amd64 architecture is "" so the format could be windows/amd64// . Thoughts? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
But There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I agree that when building an image, In the quoted text, I was talking about So in the end, I think filling in an unspeficied osversion is a BuildKit thing (or other All that said, using Maybe we should add a "NONE" value for osversion, and then the Also, if you specify an osversion in Does non-Windows have the equivalent of unrunnable images? Like, if you have an image that contains only data, no executables, does it need to have an os and arch value, or can we use "" there too? Late edit: I wrote the above before I saw that the main thread has shaken out towards |
||||||
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 { | ||||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should rethink some of this; Parse should really do just that parse what's in the string format, and return the results.
We could consider a functional argument to provide defaults, but it's problematic that we start to fill in missing information; doing so means that we make assumptions based on the local platform , which can be problematic. See the discussion below for more context;
moby/buildkit#4315 (comment)
We should also formalize the string format; "/variant" (optional) and "/os-version" (also optional) can be ambiguous, which means we won't be able to tell for sure what's what.
/cc @dmcgowan @tonistiigi
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also related; I once started a draft where I looked at how we currently handle some of this (but never found time to finish); here's a gist of some of the notes I drafted; https://gist.github.com/thaJeztah/a970f7b93f5f0a68b36b5dbb755c320d
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks like something done in the rest of the cases in the function now. Is this something that needs to be solved for this PR or more wholistically?