From 3c331097eae331d1f44afde0f92970750c2f87d2 Mon Sep 17 00:00:00 2001 From: Avi Deitcher Date: Fri, 13 Nov 2020 01:49:43 +0200 Subject: [PATCH] add FindManifests Signed-off-by: Avi Deitcher --- pkg/v1/errors.go | 43 ++++++++++++++++++ pkg/v1/index.go | 6 ++- pkg/v1/layout/index.go | 4 +- pkg/v1/partial/index.go | 88 +++++++++++++++++++++++++++++++++++++ pkg/v1/remote/descriptor.go | 2 +- 5 files changed, 138 insertions(+), 5 deletions(-) create mode 100644 pkg/v1/errors.go create mode 100644 pkg/v1/partial/index.go diff --git a/pkg/v1/errors.go b/pkg/v1/errors.go new file mode 100644 index 0000000000..042dfb884a --- /dev/null +++ b/pkg/v1/errors.go @@ -0,0 +1,43 @@ +// Copyright 2018 Google LLC All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package v1 + +// NotImageError tried to resolve a non-image manifest into an Image +type NotImageError struct { + err string +} + +func (e NotImageError) Error() string { + return e.err +} + +// ErrNotImage create a NotImageError with a given message +func ErrNotImage(err string) NotImageError { + return NotImageError{err} +} + +// NotIndexError tried to resolve a non-index manifest into an Index +type NotIndexError struct { + err string +} + +func (e NotIndexError) Error() string { + return e.err +} + +// ErrNotIndex create a NotIndexError with a given message +func ErrNotIndex(err string) NotIndexError { + return NotIndexError{err} +} diff --git a/pkg/v1/index.go b/pkg/v1/index.go index 8e7bc8ebb3..83ee109810 100644 --- a/pkg/v1/index.go +++ b/pkg/v1/index.go @@ -35,9 +35,11 @@ type ImageIndex interface { // RawManifest returns the serialized bytes of IndexManifest(). RawManifest() ([]byte, error) - // Image returns a v1.Image that this ImageIndex references. + // Image returns a v1.Image that this ImageIndex references. If it matches the Hash + // but the item is not an Image manifest, should return NotImageError. Image(Hash) (Image, error) - // ImageIndex returns a v1.ImageIndex that this ImageIndex references. + // ImageIndex returns a v1.ImageIndex that this ImageIndex references. If it matches the Hash + // but the item is not an Index, should return NotIndexError. ImageIndex(Hash) (ImageIndex, error) } diff --git a/pkg/v1/layout/index.go b/pkg/v1/layout/index.go index 6b7da2c221..38ad215a5c 100644 --- a/pkg/v1/layout/index.go +++ b/pkg/v1/layout/index.go @@ -89,7 +89,7 @@ func (i *layoutIndex) Image(h v1.Hash) (v1.Image, error) { } if !isExpectedMediaType(desc.MediaType, types.OCIManifestSchema1, types.DockerManifestSchema2) { - return nil, fmt.Errorf("unexpected media type for %v: %s", h, desc.MediaType) + return nil, v1.ErrNotImage(fmt.Sprintf("unexpected media type for %v: %s", h, desc.MediaType)) } img := &layoutImage{ @@ -107,7 +107,7 @@ func (i *layoutIndex) ImageIndex(h v1.Hash) (v1.ImageIndex, error) { } if !isExpectedMediaType(desc.MediaType, types.OCIImageIndex, types.DockerManifestList) { - return nil, fmt.Errorf("unexpected media type for %v: %s", h, desc.MediaType) + return nil, v1.ErrNotIndex(fmt.Sprintf("unexpected media type for %v: %s", h, desc.MediaType)) } rawIndex, err := i.path.Bytes(h) diff --git a/pkg/v1/partial/index.go b/pkg/v1/partial/index.go new file mode 100644 index 0000000000..01b0b10ef4 --- /dev/null +++ b/pkg/v1/partial/index.go @@ -0,0 +1,88 @@ +// Copyright 2018 Google LLC All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package partial + +import ( + "errors" + "fmt" + + v1 "github.com/google/go-containerregistry/pkg/v1" + "github.com/google/go-containerregistry/pkg/v1/match" +) + +// FindManifests given a v1.ImageIndex, find the manifests that fit the matcher +func FindManifests(index v1.ImageIndex, matcher match.Matcher) ([]v1.Descriptor, error) { + // get the actual manifest list + indexManifest, err := index.IndexManifest() + if err != nil { + return nil, fmt.Errorf("unable to get raw index: %v", err) + } + manifests := []v1.Descriptor{} + // try to get the root of our image + for _, manifest := range indexManifest.Manifests { + if matcher(manifest) { + manifests = append(manifests, manifest) + } + } + return manifests, nil +} + +// FindImages given a v1.ImageIndex, find the images that fit the matcher. If a Descriptor +// matches the provider Matcher, but the referenced item is not an Image, ignores it. +// Only returns those that match the Matcher and are images. +func FindImages(index v1.ImageIndex, matcher match.Matcher) ([]v1.Image, error) { + matches := []v1.Image{} + manifests, err := FindManifests(index, matcher) + if err != nil { + return nil, err + } + for _, desc := range manifests { + img, err := index.Image(desc.Digest) + // if it is not an image, ignore it + // we probably should differentiate between different error types + if err != nil && errors.Is(err, v1.NotImageError{}) { + continue + } + if err != nil { + return nil, err + } + matches = append(matches, img) + } + return matches, nil +} + +// FindIndexes given a v1.ImageIndex, find the indexes that fit the matcher. If a Descriptor +// matches the provider Matcher, but the referenced item is not an Index, ignores it. +// Only returns those that match the Matcher and are indexes. +func FindIndexes(index v1.ImageIndex, matcher match.Matcher) ([]v1.ImageIndex, error) { + matches := []v1.ImageIndex{} + manifests, err := FindManifests(index, matcher) + if err != nil { + return nil, err + } + for _, desc := range manifests { + idx, err := index.ImageIndex(desc.Digest) + // if it is not an index, ignore it + // we probably should differentiate between different error types + if err != nil && errors.Is(err, v1.NotIndexError{}) { + continue + } + if err != nil { + return nil, err + } + matches = append(matches, idx) + } + return matches, nil +} diff --git a/pkg/v1/remote/descriptor.go b/pkg/v1/remote/descriptor.go index 653c175dad..c9dd5f6a8a 100644 --- a/pkg/v1/remote/descriptor.go +++ b/pkg/v1/remote/descriptor.go @@ -181,7 +181,7 @@ func (d *Descriptor) ImageIndex() (v1.ImageIndex, error) { return nil, newErrSchema1(d.MediaType) case types.OCIManifestSchema1, types.DockerManifestSchema2: // We want an index but the registry has an image, nothing we can do. - return nil, fmt.Errorf("unexpected media type for ImageIndex(): %s; call Image() instead", d.MediaType) + return nil, v1.ErrNotIndex(fmt.Sprintf("unexpected media type for ImageIndex(): %s; call Image() instead", d.MediaType)) case types.OCIImageIndex, types.DockerManifestList: // These are expected. default: