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

add FindManifests #828

Merged
merged 1 commit into from
Nov 20, 2020
Merged
Show file tree
Hide file tree
Changes from all 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
85 changes: 85 additions & 0 deletions pkg/v1/partial/index.go
Original file line number Diff line number Diff line change
@@ -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
}
122 changes: 122 additions & 0 deletions pkg/v1/partial/index_test.go
Original file line number Diff line number Diff line change
@@ -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)
}
}
18 changes: 18 additions & 0 deletions pkg/v1/types/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
62 changes: 62 additions & 0 deletions pkg/v1/types/types_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
}