platforms: define selectors for platforms
For supporting selection of images and runtimes in the containerized world, there is thin support for selecting objects by platform. This package defines a syntax to display to users that can express specific platforms in addition to wild cards for matching platforms. The plan is to extend this to provide support for parsing flag arguments and displaying platform types for images. This package will also provide a configurable matcher to allow match of platforms against arbitrary targets, invariant to the Go compilation. The internals are based the OCI Image Spec model. This changeset is being submitted for feedback on the approach before this gets larger. Specifically, review the unit tests and raise any concerns. Signed-off-by: Stephen J Day <stephen.day@docker.com>
This commit is contained in:
233
platforms/platforms_test.go
Normal file
233
platforms/platforms_test.go
Normal file
@@ -0,0 +1,233 @@
|
||||
package platforms
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"runtime"
|
||||
"testing"
|
||||
|
||||
specs "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
)
|
||||
|
||||
func TestParseSelector(t *testing.T) {
|
||||
var (
|
||||
defaultOS = runtime.GOOS
|
||||
defaultArch = runtime.GOARCH
|
||||
)
|
||||
|
||||
for _, testcase := range []struct {
|
||||
skip bool
|
||||
input string
|
||||
expected specs.Platform
|
||||
formatted string
|
||||
}{
|
||||
// While wildcards are a valid use case for platform selection,
|
||||
// addressing these cases is outside the initial scope for this
|
||||
// package. When we do add platform wildcards, we should add in these
|
||||
// testcases to ensure that they are correctly represented.
|
||||
{
|
||||
skip: true,
|
||||
input: "*",
|
||||
expected: specs.Platform{
|
||||
OS: "*",
|
||||
Architecture: "*",
|
||||
},
|
||||
formatted: "*/*",
|
||||
},
|
||||
{
|
||||
skip: true,
|
||||
input: "linux/*",
|
||||
expected: specs.Platform{
|
||||
OS: "linux",
|
||||
Architecture: "*",
|
||||
},
|
||||
formatted: "linux/*",
|
||||
},
|
||||
{
|
||||
skip: true,
|
||||
input: "*/arm64",
|
||||
expected: specs.Platform{
|
||||
OS: "*",
|
||||
Architecture: "arm64",
|
||||
},
|
||||
formatted: "*/arm64",
|
||||
},
|
||||
{
|
||||
// NOTE(stevvooe): In this case, the consumer can assume this is v7
|
||||
// but we leave the variant blank. This will represent the vast
|
||||
// majority of arm images.
|
||||
input: "linux/arm",
|
||||
expected: specs.Platform{
|
||||
OS: "linux",
|
||||
Architecture: "arm",
|
||||
},
|
||||
formatted: "linux/arm",
|
||||
},
|
||||
{
|
||||
input: "linux/arm/v6",
|
||||
expected: specs.Platform{
|
||||
OS: "linux",
|
||||
Architecture: "arm",
|
||||
Variant: "v6",
|
||||
},
|
||||
formatted: "linux/arm/v6",
|
||||
},
|
||||
{
|
||||
input: "linux/arm/v7",
|
||||
expected: specs.Platform{
|
||||
OS: "linux",
|
||||
Architecture: "arm",
|
||||
Variant: "v7",
|
||||
},
|
||||
formatted: "linux/arm/v7",
|
||||
},
|
||||
{
|
||||
input: "arm",
|
||||
expected: specs.Platform{
|
||||
OS: defaultOS,
|
||||
Architecture: "arm",
|
||||
},
|
||||
formatted: "linux/arm",
|
||||
},
|
||||
{
|
||||
input: "armel",
|
||||
expected: specs.Platform{
|
||||
OS: defaultOS,
|
||||
Architecture: "arm",
|
||||
Variant: "v6",
|
||||
},
|
||||
formatted: "linux/arm/v6",
|
||||
},
|
||||
{
|
||||
input: "armhf",
|
||||
expected: specs.Platform{
|
||||
OS: defaultOS,
|
||||
Architecture: "arm",
|
||||
},
|
||||
formatted: "linux/arm",
|
||||
},
|
||||
{
|
||||
input: "Aarch64",
|
||||
expected: specs.Platform{
|
||||
OS: defaultOS,
|
||||
Architecture: "arm64",
|
||||
},
|
||||
formatted: joinNotEmpty(defaultOS, "arm64"),
|
||||
},
|
||||
{
|
||||
input: "x86_64",
|
||||
expected: specs.Platform{
|
||||
OS: defaultOS,
|
||||
Architecture: "amd64",
|
||||
},
|
||||
formatted: joinNotEmpty(defaultOS, "amd64"),
|
||||
},
|
||||
{
|
||||
input: "Linux/x86_64",
|
||||
expected: specs.Platform{
|
||||
OS: "linux",
|
||||
Architecture: "amd64",
|
||||
},
|
||||
formatted: "linux/amd64",
|
||||
},
|
||||
{
|
||||
input: "i386",
|
||||
expected: specs.Platform{
|
||||
OS: defaultOS,
|
||||
Architecture: "386",
|
||||
},
|
||||
formatted: joinNotEmpty(defaultOS, "386"),
|
||||
},
|
||||
{
|
||||
input: "linux",
|
||||
expected: specs.Platform{
|
||||
OS: "linux",
|
||||
Architecture: defaultArch,
|
||||
},
|
||||
formatted: joinNotEmpty("linux", defaultArch),
|
||||
},
|
||||
{
|
||||
input: "s390x",
|
||||
expected: specs.Platform{
|
||||
OS: defaultOS,
|
||||
Architecture: "s390x",
|
||||
},
|
||||
formatted: joinNotEmpty(defaultOS, "s390x"),
|
||||
},
|
||||
{
|
||||
input: "linux/s390x",
|
||||
expected: specs.Platform{
|
||||
OS: "linux",
|
||||
Architecture: "s390x",
|
||||
},
|
||||
formatted: "linux/s390x",
|
||||
},
|
||||
{
|
||||
input: "macOS",
|
||||
expected: specs.Platform{
|
||||
OS: "darwin",
|
||||
Architecture: defaultArch,
|
||||
},
|
||||
formatted: joinNotEmpty("darwin", defaultArch),
|
||||
},
|
||||
} {
|
||||
t.Run(testcase.input, func(t *testing.T) {
|
||||
if testcase.skip {
|
||||
t.Skip("this case is not yet supported")
|
||||
}
|
||||
p, err := Parse(testcase.input)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(p, testcase.expected) {
|
||||
t.Fatalf("platform did not match expected: %#v != %#v", p, testcase.expected)
|
||||
}
|
||||
|
||||
formatted := Format(p)
|
||||
if formatted != testcase.formatted {
|
||||
t.Fatalf("unexpected format: %q != %q", formatted, testcase.formatted)
|
||||
}
|
||||
|
||||
// re-parse the formatted output and ensure we are stable
|
||||
reparsed, err := Parse(formatted)
|
||||
if err != nil {
|
||||
t.Fatalf("error parsing formatted output: %v", err)
|
||||
}
|
||||
|
||||
if Format(reparsed) != formatted {
|
||||
t.Fatalf("normalized output did not survive the round trip: %v != %v", Format(reparsed), formatted)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseSelectorInvalid(t *testing.T) {
|
||||
for _, testcase := range []struct {
|
||||
input string
|
||||
}{
|
||||
{
|
||||
input: "", // empty
|
||||
},
|
||||
{
|
||||
input: "/linux/arm", // leading slash
|
||||
},
|
||||
{
|
||||
input: "linux/arm/", // trailing slash
|
||||
},
|
||||
{
|
||||
input: "linux /arm", // spaces
|
||||
},
|
||||
{
|
||||
input: "linux/&arm", // invalid character
|
||||
},
|
||||
{
|
||||
input: "linux/arm/foo/bar", // too mayn components
|
||||
},
|
||||
} {
|
||||
t.Run(testcase.input, func(t *testing.T) {
|
||||
if _, err := Parse(testcase.input); err == nil {
|
||||
t.Fatalf("should have received an error")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user