From 5224d4573faaa1140b181657958f4067f343483e 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 Co-authored-by: jonjohnsonjr Signed-off-by: Avi Deitcher --- pkg/v1/partial/index.go | 85 ++++++++++++++++++++++++ pkg/v1/partial/index_test.go | 122 +++++++++++++++++++++++++++++++++++ pkg/v1/types/types.go | 18 ++++++ pkg/v1/types/types_test.go | 62 ++++++++++++++++++ 4 files changed, 287 insertions(+) create mode 100644 pkg/v1/partial/index.go create mode 100644 pkg/v1/partial/index_test.go diff --git a/pkg/v1/partial/index.go b/pkg/v1/partial/index.go new file mode 100644 index 000000000..9c7a92485 --- /dev/null +++ b/pkg/v1/partial/index.go @@ -0,0 +1,85 @@ +// Copyright 2020 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 ( + "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 { + // if it is not an image, ignore it + if !desc.MediaType.IsImage() { + continue + } + img, err := index.Image(desc.Digest) + 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 { + if !desc.MediaType.IsIndex() { + continue + } + // if it is not an index, ignore it + idx, err := index.ImageIndex(desc.Digest) + if err != nil { + return nil, err + } + matches = append(matches, idx) + } + return matches, nil +} diff --git a/pkg/v1/partial/index_test.go b/pkg/v1/partial/index_test.go new file mode 100644 index 000000000..9dcb27acd --- /dev/null +++ b/pkg/v1/partial/index_test.go @@ -0,0 +1,122 @@ +// Copyright 2020 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_test + +import ( + "testing" + + v1 "github.com/google/go-containerregistry/pkg/v1" + "github.com/google/go-containerregistry/pkg/v1/mutate" + "github.com/google/go-containerregistry/pkg/v1/partial" + "github.com/google/go-containerregistry/pkg/v1/random" + "github.com/google/go-containerregistry/pkg/v1/types" +) + +func TestFindManifests(t *testing.T) { + + ii, err := random.Index(100, 5, 6) // random image of 6 manifests, each having 5 layers of size 100 + if err != nil { + t.Fatal("could not create random index:", err) + } + m, _ := ii.IndexManifest() + digest := m.Manifests[0].Digest + + matcher := func(desc v1.Descriptor) bool { + return desc.Digest != digest + } + + descriptors, err := partial.FindManifests(ii, matcher) + expected := len(m.Manifests) - 1 + switch { + case err != nil: + t.Error("unexpected error:", err) + case len(descriptors) != expected: + t.Errorf("failed on manifests, actual %d, expected %d", len(descriptors), expected) + } +} + +func TestFindImages(t *testing.T) { + // create our imageindex with which to work + ii, err := random.Index(100, 5, 6) // random image of 6 manifests, each having 5 layers of size 100 + if err != nil { + t.Fatal("could not create random index:", err) + } + m, _ := ii.IndexManifest() + digest := m.Manifests[0].Digest + + matcher := func(desc v1.Descriptor) bool { + return desc.Digest != digest + } + images, err := partial.FindImages(ii, matcher) + expected := len(m.Manifests) - 1 + switch { + case err != nil: + t.Error("unexpected error:", err) + case len(images) != expected: + t.Errorf("failed on images, actual %d, expected %d", len(images), expected) + } +} + +func TestFindIndexes(t *testing.T) { + // there is no utility to generate an index of indexes, so we need to create one + // base index + var ( + indexCount = 5 + imageCount = 7 + ) + base, err := random.Index(0, 0, 0) + if err != nil { + t.Fatal("error creating random index:", err) + } + // we now have 5 indexes and 5 images, so wrap them into a single index + adds := []mutate.IndexAddendum{} + for i := 0; i < indexCount; i++ { + ii, err := random.Index(100, 5, 6) + if err != nil { + t.Fatalf("%d: unable to create random index: %v", i, err) + } + adds = append(adds, mutate.IndexAddendum{ + Add: ii, + Descriptor: v1.Descriptor{ + MediaType: types.OCIImageIndex, + }, + }) + } + for i := 0; i < imageCount; i++ { + img, err := random.Image(100, 5) + if err != nil { + t.Fatalf("%d: unable to create random image: %v", i, err) + } + adds = append(adds, mutate.IndexAddendum{ + Add: img, + Descriptor: v1.Descriptor{ + MediaType: types.OCIManifestSchema1, + }, + }) + } + + // just see if it finds all of the indexes + matcher := func(desc v1.Descriptor) bool { + return true + } + index := mutate.AppendManifests(base, adds...) + idxes, err := partial.FindIndexes(index, matcher) + switch { + case err != nil: + t.Error("unexpected error:", err) + case len(idxes) != indexCount: + t.Errorf("failed on index, actual %d, expected %d", len(idxes), indexCount) + } +} diff --git a/pkg/v1/types/types.go b/pkg/v1/types/types.go index 2b21ab0d9..21f223650 100644 --- a/pkg/v1/types/types.go +++ b/pkg/v1/types/types.go @@ -51,3 +51,21 @@ func (m MediaType) IsDistributable() bool { } return true } + +// IsImage returns true if the mediaType represents an image manifest, as opposed to something else, like an index. +func (m MediaType) IsImage() bool { + switch m { + case OCIManifestSchema1, DockerManifestSchema2: + return true + } + return false +} + +// IsIndex returns true if the mediaType represents an index, as opposed to something else, like an image. +func (m MediaType) IsIndex() bool { + switch m { + case OCIImageIndex, DockerManifestList: + return true + } + return false +} diff --git a/pkg/v1/types/types_test.go b/pkg/v1/types/types_test.go index 712ceb21f..291be00eb 100644 --- a/pkg/v1/types/types_test.go +++ b/pkg/v1/types/types_test.go @@ -34,3 +34,65 @@ func TestIsDistributable(t *testing.T) { } } } + +func TestIsImage(t *testing.T) { + for _, mt := range []MediaType{ + OCIManifestSchema1, DockerManifestSchema2, + } { + if !mt.IsImage() { + t.Errorf("%s: should be image", mt) + } + } + + for _, mt := range []MediaType{ + OCIContentDescriptor, + OCIImageIndex, + OCIConfigJSON, + OCILayer, + OCIRestrictedLayer, + OCIUncompressedLayer, + OCIUncompressedRestrictedLayer, + + DockerManifestList, + DockerLayer, + DockerConfigJSON, + DockerPluginConfig, + DockerForeignLayer, + DockerUncompressedLayer, + } { + if mt.IsImage() { + t.Errorf("%s: should not be image", mt) + } + } +} + +func TestIsIndex(t *testing.T) { + for _, mt := range []MediaType{ + OCIImageIndex, DockerManifestList, + } { + if !mt.IsIndex() { + t.Errorf("%s: should be index", mt) + } + } + + for _, mt := range []MediaType{ + OCIContentDescriptor, + OCIConfigJSON, + OCILayer, + OCIRestrictedLayer, + OCIUncompressedLayer, + OCIUncompressedRestrictedLayer, + OCIManifestSchema1, + + DockerManifestSchema2, + DockerLayer, + DockerConfigJSON, + DockerPluginConfig, + DockerForeignLayer, + DockerUncompressedLayer, + } { + if mt.IsIndex() { + t.Errorf("%s: should not be index", mt) + } + } +}