Skip to content
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

Implement Platform.String and v1.ParsePlatform #1270

Merged
merged 5 commits into from
Jan 27, 2022
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 52 additions & 2 deletions pkg/v1/platform.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@
package v1

import (
"fmt"
"sort"
"strings"
)

// Platform represents the target os/arch for an image.
Expand All @@ -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.
Expand Down
140 changes: 119 additions & 21 deletions pkg/v1/platform_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
return
imjasonh marked this conversation as resolved.
Show resolved Hide resolved
}

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)
}
}

for _, s := range []string{
"linux/amd64 something else", // unknown other stuff
"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))
Expand Down