update platforms Parse to return platform spec, decouple matcher

Signed-off-by: Jess Valarezo <valarezo.jessica@gmail.com>
This commit is contained in:
Jess Valarezo 2018-02-27 14:21:49 -08:00
parent fd2e1f6dec
commit ac8008437a
4 changed files with 36 additions and 26 deletions

View File

@ -193,10 +193,11 @@ func FilterPlatform(platform string, f HandlerFunc) HandlerFunc {
var descs []ocispec.Descriptor var descs []ocispec.Descriptor
if platform != "" && isMultiPlatform(desc.MediaType) { if platform != "" && isMultiPlatform(desc.MediaType) {
matcher, err := platforms.Parse(platform) p, err := platforms.Parse(platform)
if err != nil { if err != nil {
return nil, err return nil, err
} }
matcher := platforms.NewMatcher(p)
for _, d := range children { for _, d := range children {
if d.Platform == nil || matcher.Match(*d.Platform) { if d.Platform == nil || matcher.Match(*d.Platform) {

View File

@ -131,13 +131,13 @@ func Manifest(ctx context.Context, provider content.Provider, image ocispec.Desc
var ( var (
matcher platforms.Matcher matcher platforms.Matcher
m *ocispec.Manifest m *ocispec.Manifest
err error
) )
if platform != "" { if platform != "" {
matcher, err = platforms.Parse(platform) p, err := platforms.Parse(platform)
if err != nil { if err != nil {
return ocispec.Manifest{}, err return ocispec.Manifest{}, err
} }
matcher = platforms.NewMatcher(p)
} }
if err := Walk(ctx, HandlerFunc(func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) { if err := Walk(ctx, HandlerFunc(func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) {

View File

@ -122,18 +122,27 @@ var (
// Matcher matches platforms specifications, provided by an image or runtime. // Matcher matches platforms specifications, provided by an image or runtime.
type Matcher interface { type Matcher interface {
Spec() specs.Platform
Match(platform specs.Platform) bool Match(platform specs.Platform) bool
} }
// NewMatcher returns a simple matcher based on the provided platform
// specification. The returned matcher only looks for equality based on os,
// architecture and variant.
//
// One may implement their own matcher if this doesn't provide the the required
// functionality.
//
// Applications should opt to use `Match` over directly parsing specifiers.
func NewMatcher(platform specs.Platform) Matcher {
return &matcher{
Platform: platform,
}
}
type matcher struct { type matcher struct {
specs.Platform specs.Platform
} }
func (m *matcher) Spec() specs.Platform {
return m.Platform
}
func (m *matcher) Match(platform specs.Platform) bool { func (m *matcher) Match(platform specs.Platform) bool {
normalized := Normalize(platform) normalized := Normalize(platform)
return m.OS == normalized.OS && return m.OS == normalized.OS &&
@ -153,19 +162,17 @@ func (m *matcher) String() string {
// value will be matched against the known set of operating systems, then fall // 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 // back to the known set of architectures. The missing component will be
// inferred based on the local environment. // inferred based on the local environment.
// func Parse(specifier string) (specs.Platform, error) {
// Applications should opt to use `Match` over directly parsing specifiers.
func Parse(specifier string) (Matcher, error) {
if strings.Contains(specifier, "*") { if strings.Contains(specifier, "*") {
// TODO(stevvooe): need to work out exact wildcard handling // TODO(stevvooe): need to work out exact wildcard handling
return nil, errors.Wrapf(errdefs.ErrInvalidArgument, "%q: wildcards not yet supported", specifier) return specs.Platform{}, errors.Wrapf(errdefs.ErrInvalidArgument, "%q: wildcards not yet supported", specifier)
} }
parts := strings.Split(specifier, "/") parts := strings.Split(specifier, "/")
for _, part := range parts { for _, part := range parts {
if !specifierRe.MatchString(part) { if !specifierRe.MatchString(part) {
return nil, errors.Wrapf(errdefs.ErrInvalidArgument, "%q is an invalid component of %q: platform specifier component must match %q", part, specifier, specifierRe.String()) return specs.Platform{}, errors.Wrapf(errdefs.ErrInvalidArgument, "%q is an invalid component of %q: platform specifier component must match %q", part, specifier, specifierRe.String())
} }
} }
@ -183,35 +190,35 @@ func Parse(specifier string) (Matcher, error) {
p.Architecture = runtime.GOARCH p.Architecture = runtime.GOARCH
if p.Architecture == "arm" { if p.Architecture == "arm" {
// TODO(stevvooe): Resolve arm variant, if not v6 (default) // TODO(stevvooe): Resolve arm variant, if not v6 (default)
return nil, errors.Wrapf(errdefs.ErrNotImplemented, "arm support not fully implemented") return specs.Platform{}, errors.Wrapf(errdefs.ErrNotImplemented, "arm support not fully implemented")
} }
return &matcher{p}, nil return p, nil
} }
p.Architecture, p.Variant = normalizeArch(parts[0], "") p.Architecture, p.Variant = normalizeArch(parts[0], "")
if isKnownArch(p.Architecture) { if isKnownArch(p.Architecture) {
p.OS = runtime.GOOS p.OS = runtime.GOOS
return &matcher{p}, nil return p, nil
} }
return nil, errors.Wrapf(errdefs.ErrInvalidArgument, "%q: unknown operating system or architecture", specifier) return specs.Platform{}, errors.Wrapf(errdefs.ErrInvalidArgument, "%q: unknown operating system or architecture", specifier)
case 2: 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/arch pair. We don't care
// about whether or not we know of the platform. // about whether or not we know of the platform.
p.OS = normalizeOS(parts[0]) p.OS = normalizeOS(parts[0])
p.Architecture, p.Variant = normalizeArch(parts[1], "") p.Architecture, p.Variant = normalizeArch(parts[1], "")
return &matcher{p}, nil return p, nil
case 3: case 3:
// we have a fully specified variant, this is rare // we have a fully specified variant, this is rare
p.OS = normalizeOS(parts[0]) p.OS = normalizeOS(parts[0])
p.Architecture, p.Variant = normalizeArch(parts[1], parts[2]) p.Architecture, p.Variant = normalizeArch(parts[1], parts[2])
return &matcher{p}, nil return p, nil
} }
return nil, errors.Wrapf(errdefs.ErrInvalidArgument, "%q: cannot parse platform specifier", specifier) return specs.Platform{}, errors.Wrapf(errdefs.ErrInvalidArgument, "%q: cannot parse platform specifier", specifier)
} }
// Format returns a string specifier from the provided platform specification. // Format returns a string specifier from the provided platform specification.

View File

@ -191,15 +191,17 @@ func TestParseSelector(t *testing.T) {
if testcase.skip { if testcase.skip {
t.Skip("this case is not yet supported") t.Skip("this case is not yet supported")
} }
m, err := Parse(testcase.input) p, err := Parse(testcase.input)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
if !reflect.DeepEqual(m.Spec(), testcase.expected) { if !reflect.DeepEqual(p, testcase.expected) {
t.Fatalf("platform did not match expected: %#v != %#v", m.Spec(), testcase.expected) t.Fatalf("platform did not match expected: %#v != %#v", p, testcase.expected)
} }
m := NewMatcher(p)
// ensure that match works on the input to the output. // ensure that match works on the input to the output.
if ok := m.Match(testcase.expected); !ok { if ok := m.Match(testcase.expected); !ok {
t.Fatalf("expected specifier %q matches %v", testcase.input, testcase.expected) t.Fatalf("expected specifier %q matches %v", testcase.input, testcase.expected)
@ -209,7 +211,7 @@ func TestParseSelector(t *testing.T) {
t.Fatalf("unexpected matcher string: %q != %q", fmt.Sprint(m), testcase.formatted) t.Fatalf("unexpected matcher string: %q != %q", fmt.Sprint(m), testcase.formatted)
} }
formatted := Format(m.Spec()) formatted := Format(p)
if formatted != testcase.formatted { if formatted != testcase.formatted {
t.Fatalf("unexpected format: %q != %q", formatted, testcase.formatted) t.Fatalf("unexpected format: %q != %q", formatted, testcase.formatted)
} }
@ -220,8 +222,8 @@ func TestParseSelector(t *testing.T) {
t.Fatalf("error parsing formatted output: %v", err) t.Fatalf("error parsing formatted output: %v", err)
} }
if Format(reparsed.Spec()) != formatted { if Format(reparsed) != formatted {
t.Fatalf("normalized output did not survive the round trip: %v != %v", Format(reparsed.Spec()), formatted) t.Fatalf("normalized output did not survive the round trip: %v != %v", Format(reparsed), formatted)
} }
}) })
} }