diff --git a/pkg/v1/platform.go b/pkg/v1/platform.go index b52f163bf..9ee91ee29 100644 --- a/pkg/v1/platform.go +++ b/pkg/v1/platform.go @@ -15,7 +15,9 @@ package v1 import ( + "fmt" "sort" + "strings" ) // Platform represents the target os/arch for an image. @@ -28,11 +30,59 @@ type Platform struct { Features []string `json:"features,omitempty"` } +func (p Platform) String() string { + if p.OS == "" { + return "" + } + var b strings.Builder + b.WriteString(p.OS) + if p.Architecture != "" { + b.WriteString("/") + b.WriteString(p.Architecture) + } + if p.Variant != "" { + b.WriteString("/") + b.WriteString(p.Variant) + } + if p.OSVersion != "" { + b.WriteString(":") + b.WriteString(p.OSVersion) + } + return b.String() +} + +// ParsePlatform parses a string representing a Platform, if possible. +func ParsePlatform(s string) (*Platform, error) { + var p Platform + parts := strings.Split(strings.TrimSpace(s), ":") + if len(parts) == 2 { + p.OSVersion = parts[1] + } + parts = strings.Split(parts[0], "/") + if len(parts) > 0 { + p.OS = parts[0] + } + if len(parts) > 1 { + p.Architecture = parts[1] + } + if len(parts) > 2 { + p.Variant = parts[2] + } + if len(parts) > 3 { + return nil, fmt.Errorf("too many slashes in platform spec: %s", s) + } + return &p, nil +} + // Equals returns true if the given platform is semantically equivalent to this one. // The order of Features and OSFeatures is not important. func (p Platform) Equals(o Platform) bool { - return p.OS == o.OS && p.Architecture == o.Architecture && p.Variant == o.Variant && p.OSVersion == o.OSVersion && - stringSliceEqualIgnoreOrder(p.OSFeatures, o.OSFeatures) && stringSliceEqualIgnoreOrder(p.Features, o.Features) + return p.OS == o.OS && + p.Architecture == o.Architecture && + p.Variant == o.Variant && + p.OSVersion == o.OSVersion && + stringSliceEqualIgnoreOrder(p.OSFeatures, o.OSFeatures) && + stringSliceEqualIgnoreOrder(p.Features, o.Features) } // stringSliceEqual compares 2 string slices and returns if their contents are identical. diff --git a/pkg/v1/platform_test.go b/pkg/v1/platform_test.go index 98c24150f..8b02f639a 100644 --- a/pkg/v1/platform_test.go +++ b/pkg/v1/platform_test.go @@ -21,30 +21,128 @@ import ( v1 "github.com/google/go-containerregistry/pkg/v1" ) +func TestPlatformString(t *testing.T) { + for _, c := range []struct { + plat v1.Platform + want string + }{{ + v1.Platform{}, + "", + }, { + v1.Platform{OS: "linux"}, + "linux", + }, { + v1.Platform{OS: "linux", Architecture: "amd64"}, + "linux/amd64", + }, { + v1.Platform{OS: "linux", Architecture: "amd64", Variant: "v7"}, + "linux/amd64/v7", + }, { + v1.Platform{OS: "linux", Architecture: "amd64", OSVersion: "1.2.3.4"}, + "linux/amd64:1.2.3.4", + }, { + v1.Platform{OS: "linux", Architecture: "amd64", OSVersion: "1.2.3.4", OSFeatures: []string{"a", "b"}, Features: []string{"c", "d"}}, + "linux/amd64:1.2.3.4", + }} { + if got := c.plat.String(); got != c.want { + t.Errorf("got %q, want %q", got, c.want) + } + + if len(c.plat.OSFeatures) > 0 || len(c.plat.Features) > 0 { + // If these values are set, roundtripping back to the + // Platform will be lossy, and we expect that. + continue + } + + back, err := v1.ParsePlatform(c.plat.String()) + if err != nil { + t.Errorf("ParsePlatform(%q): %v", c.plat, err) + } + if d := cmp.Diff(&c.plat, back); d != "" { + t.Errorf("ParsePlatform(%q) diff:\n%s", c.plat.String(), d) + } + } + + // Known bad examples. + for _, s := range []string{ + "linux/amd64/v7/s9", // too many slashes + } { + got, err := v1.ParsePlatform(s) + if err == nil { + t.Errorf("ParsePlatform(%q) wanted error; got %v", s, got) + } + } +} + func TestPlatformEquals(t *testing.T) { tests := []struct { - a v1.Platform - b v1.Platform + a, b v1.Platform equal bool - }{ - {v1.Platform{Architecture: "amd64", OS: "linux"}, v1.Platform{Architecture: "amd64", OS: "linux"}, true}, - {v1.Platform{Architecture: "amd64", OS: "linux"}, v1.Platform{Architecture: "arm64", OS: "linux"}, false}, - {v1.Platform{Architecture: "amd64", OS: "linux"}, v1.Platform{Architecture: "amd64", OS: "darwin"}, false}, - {v1.Platform{Architecture: "amd64", OS: "linux", OSVersion: "5.0"}, v1.Platform{Architecture: "amd64", OS: "linux"}, false}, - {v1.Platform{Architecture: "amd64", OS: "linux", OSVersion: "5.0"}, v1.Platform{Architecture: "amd64", OS: "linux", OSVersion: "3.6"}, false}, - {v1.Platform{Architecture: "amd64", OS: "linux", Variant: "pios"}, v1.Platform{Architecture: "amd64", OS: "linux"}, false}, - {v1.Platform{Architecture: "amd64", OS: "linux", Variant: "pios"}, v1.Platform{Architecture: "amd64", OS: "linux", Variant: "ubuntu"}, false}, - {v1.Platform{Architecture: "amd64", OS: "linux", Variant: "pios"}, v1.Platform{Architecture: "amd64", OS: "linux", Variant: "pios"}, true}, - {v1.Platform{Architecture: "amd64", OS: "linux", OSFeatures: []string{"a", "b"}}, v1.Platform{Architecture: "amd64", OS: "linux"}, false}, - {v1.Platform{Architecture: "amd64", OS: "linux", OSFeatures: []string{"a", "b"}}, v1.Platform{Architecture: "amd64", OS: "linux", OSFeatures: []string{"a", "b"}}, true}, - {v1.Platform{Architecture: "amd64", OS: "linux", OSFeatures: []string{"a", "b"}}, v1.Platform{Architecture: "amd64", OS: "linux", OSFeatures: []string{"ac", "bd"}}, false}, - {v1.Platform{Architecture: "amd64", OS: "linux", OSFeatures: []string{"a", "b"}}, v1.Platform{Architecture: "amd64", OS: "linux", OSFeatures: []string{"b", "a"}}, true}, - - {v1.Platform{Architecture: "amd64", OS: "linux", Features: []string{"a", "b"}}, v1.Platform{Architecture: "amd64", OS: "linux"}, false}, - {v1.Platform{Architecture: "amd64", OS: "linux", Features: []string{"a", "b"}}, v1.Platform{Architecture: "amd64", OS: "linux", Features: []string{"a", "b"}}, true}, - {v1.Platform{Architecture: "amd64", OS: "linux", Features: []string{"a", "b"}}, v1.Platform{Architecture: "amd64", OS: "linux", Features: []string{"ac", "bd"}}, false}, - {v1.Platform{Architecture: "amd64", OS: "linux", Features: []string{"a", "b"}}, v1.Platform{Architecture: "amd64", OS: "linux", Features: []string{"b", "a"}}, true}, - } + }{{ + v1.Platform{Architecture: "amd64", OS: "linux"}, + v1.Platform{Architecture: "amd64", OS: "linux"}, + true, + }, { + v1.Platform{Architecture: "amd64", OS: "linux"}, + v1.Platform{Architecture: "arm64", OS: "linux"}, + false, + }, { + v1.Platform{Architecture: "amd64", OS: "linux"}, + v1.Platform{Architecture: "amd64", OS: "darwin"}, + false, + }, { + v1.Platform{Architecture: "amd64", OS: "linux", OSVersion: "5.0"}, + v1.Platform{Architecture: "amd64", OS: "linux"}, + false, + }, { + v1.Platform{Architecture: "amd64", OS: "linux", OSVersion: "5.0"}, + v1.Platform{Architecture: "amd64", OS: "linux", OSVersion: "3.6"}, + false, + }, { + v1.Platform{Architecture: "amd64", OS: "linux", Variant: "pios"}, + v1.Platform{Architecture: "amd64", OS: "linux"}, + false, + }, { + v1.Platform{Architecture: "amd64", OS: "linux", Variant: "pios"}, + v1.Platform{Architecture: "amd64", OS: "linux", Variant: "ubuntu"}, + false, + }, { + v1.Platform{Architecture: "amd64", OS: "linux", Variant: "pios"}, + v1.Platform{Architecture: "amd64", OS: "linux", Variant: "pios"}, + true, + }, { + v1.Platform{Architecture: "amd64", OS: "linux", OSFeatures: []string{"a", "b"}}, + v1.Platform{Architecture: "amd64", OS: "linux"}, + false, + }, { + v1.Platform{Architecture: "amd64", OS: "linux", OSFeatures: []string{"a", "b"}}, + v1.Platform{Architecture: "amd64", OS: "linux", OSFeatures: []string{"a", "b"}}, + true, + }, { + v1.Platform{Architecture: "amd64", OS: "linux", OSFeatures: []string{"a", "b"}}, + v1.Platform{Architecture: "amd64", OS: "linux", OSFeatures: []string{"ac", "bd"}}, + false, + }, { + v1.Platform{Architecture: "amd64", OS: "linux", OSFeatures: []string{"a", "b"}}, + v1.Platform{Architecture: "amd64", OS: "linux", OSFeatures: []string{"b", "a"}}, + true, + }, { + v1.Platform{Architecture: "amd64", OS: "linux", Features: []string{"a", "b"}}, + v1.Platform{Architecture: "amd64", OS: "linux"}, + false, + }, { + v1.Platform{Architecture: "amd64", OS: "linux", Features: []string{"a", "b"}}, + v1.Platform{Architecture: "amd64", OS: "linux", Features: []string{"a", "b"}}, + true, + }, { + v1.Platform{Architecture: "amd64", OS: "linux", Features: []string{"a", "b"}}, + v1.Platform{Architecture: "amd64", OS: "linux", Features: []string{"ac", "bd"}}, + false, + }, { + v1.Platform{Architecture: "amd64", OS: "linux", Features: []string{"a", "b"}}, + v1.Platform{Architecture: "amd64", OS: "linux", Features: []string{"b", "a"}}, + true, + }} for i, tt := range tests { if equal := tt.a.Equals(tt.b); equal != tt.equal { t.Errorf("%d: mismatched was %v expected %v; original (-want +got) %s", i, equal, tt.equal, cmp.Diff(tt.a, tt.b))