diff --git a/images/handlers.go b/images/handlers.go index b95251c57..f127eb27f 100644 --- a/images/handlers.go +++ b/images/handlers.go @@ -193,10 +193,11 @@ func FilterPlatform(platform string, f HandlerFunc) HandlerFunc { var descs []ocispec.Descriptor if platform != "" && isMultiPlatform(desc.MediaType) { - matcher, err := platforms.Parse(platform) + p, err := platforms.Parse(platform) if err != nil { return nil, err } + matcher := platforms.NewMatcher(p) for _, d := range children { if d.Platform == nil || matcher.Match(*d.Platform) { diff --git a/images/image.go b/images/image.go index cdcf0af34..e986fcb2f 100644 --- a/images/image.go +++ b/images/image.go @@ -131,13 +131,13 @@ func Manifest(ctx context.Context, provider content.Provider, image ocispec.Desc var ( matcher platforms.Matcher m *ocispec.Manifest - err error ) if platform != "" { - matcher, err = platforms.Parse(platform) + p, err := platforms.Parse(platform) if err != nil { return ocispec.Manifest{}, err } + matcher = platforms.NewMatcher(p) } if err := Walk(ctx, HandlerFunc(func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) { diff --git a/platforms/platforms.go b/platforms/platforms.go index 77b6d8410..1f91ed288 100644 --- a/platforms/platforms.go +++ b/platforms/platforms.go @@ -122,18 +122,27 @@ var ( // Matcher matches platforms specifications, provided by an image or runtime. type Matcher interface { - Spec() specs.Platform 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 { specs.Platform } -func (m *matcher) Spec() specs.Platform { - return m.Platform -} - func (m *matcher) Match(platform specs.Platform) bool { normalized := Normalize(platform) 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 // back to the known set of architectures. The missing component will be // inferred based on the local environment. -// -// Applications should opt to use `Match` over directly parsing specifiers. -func Parse(specifier string) (Matcher, error) { +func Parse(specifier string) (specs.Platform, error) { if strings.Contains(specifier, "*") { // 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, "/") for _, part := range parts { 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 if p.Architecture == "arm" { // 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], "") if isKnownArch(p.Architecture) { 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: // In this case, we treat as a regular os/arch pair. We don't care // about whether or not we know of the platform. p.OS = normalizeOS(parts[0]) p.Architecture, p.Variant = normalizeArch(parts[1], "") - return &matcher{p}, nil + return p, nil case 3: // we have a fully specified variant, this is rare p.OS = normalizeOS(parts[0]) 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. diff --git a/platforms/platforms_test.go b/platforms/platforms_test.go index c7c416dc1..1a1be3650 100644 --- a/platforms/platforms_test.go +++ b/platforms/platforms_test.go @@ -191,15 +191,17 @@ func TestParseSelector(t *testing.T) { if testcase.skip { t.Skip("this case is not yet supported") } - m, err := Parse(testcase.input) + p, err := Parse(testcase.input) if err != nil { t.Fatal(err) } - if !reflect.DeepEqual(m.Spec(), testcase.expected) { - t.Fatalf("platform did not match expected: %#v != %#v", m.Spec(), testcase.expected) + if !reflect.DeepEqual(p, 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. if ok := m.Match(testcase.expected); !ok { 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) } - formatted := Format(m.Spec()) + formatted := Format(p) if 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) } - if Format(reparsed.Spec()) != formatted { - t.Fatalf("normalized output did not survive the round trip: %v != %v", Format(reparsed.Spec()), formatted) + if Format(reparsed) != formatted { + t.Fatalf("normalized output did not survive the round trip: %v != %v", Format(reparsed), formatted) } }) }