Skip to content

Commit

Permalink
[PRFix: modify func WithPlatformFilter and selectPlatform, fix typo]
Browse files Browse the repository at this point in the history
Signed-off-by: Zoey Li <zoeyli@microsoft.com>
  • Loading branch information
lizMSFT committed Jul 21, 2022
1 parent d72fc0a commit 6cbedba
Show file tree
Hide file tree
Showing 3 changed files with 183 additions and 66 deletions.
37 changes: 18 additions & 19 deletions copy.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,37 +57,36 @@ type CopyOptions struct {
}

// selectPlatform implements platform filter and returns the descriptor of
// the first matched manifest from the manifest list / image index
func (o *CopyOptions) selectPlatform(ctx context.Context, src content.Storage, root ocispec.Descriptor, p *ocispec.Platform) (ocispec.Descriptor, error) {
if root.MediaType == docker.MediaTypeManifestList || root.MediaType == ocispec.MediaTypeImageIndex {
manifests, err := content.Successors(ctx, src, root)
if err != nil {
return ocispec.Descriptor{}, err
}
// the first matched manifest from the manifest list / image index.
func selectPlatform(ctx context.Context, src content.Storage, root ocispec.Descriptor, p *ocispec.Platform) (ocispec.Descriptor, error) {
if root.MediaType != docker.MediaTypeManifestList && root.MediaType != ocispec.MediaTypeImageIndex {
return ocispec.Descriptor{}, errdef.ErrUnsupported
}

// platform filter
for _, m := range manifests {
matched := platform.MatchPlatform(m.Platform, p)
if matched {
return m, nil
}
manifests, err := content.Successors(ctx, src, root)
if err != nil {
return ocispec.Descriptor{}, err
}

// platform filter
for _, m := range manifests {
if platform.MatchPlatform(m.Platform, p) {
return m, nil
}
return ocispec.Descriptor{}, errdef.ErrNotFound
}
return ocispec.Descriptor{}, errdef.ErrNotFound
}

// AddPlatformFilter adds the selectPlatform func into the MapRoot func
func (o *CopyOptions) AddPlatformFilter(p *ocispec.Platform) {
// WithPlatformFilter adds the check on the platform attributes.
func (o *CopyOptions) WithPlatformFilter(p *ocispec.Platform) {
mapRoot := o.MapRoot
o.MapRoot = func(ctx context.Context, src content.Storage, root ocispec.Descriptor) (ocispec.Descriptor, error) {
var err error
o.MapRoot = func(ctx context.Context, src content.Storage, root ocispec.Descriptor) (desc ocispec.Descriptor, err error) {
if mapRoot != nil {
if root, err = mapRoot(ctx, src, root); err != nil {
return ocispec.Descriptor{}, err
}
}
return o.selectPlatform(ctx, src, root, p)
return selectPlatform(ctx, src, root, p)
}
}

Expand Down
198 changes: 158 additions & 40 deletions copy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -369,7 +369,7 @@ func TestCopy_WithOptions(t *testing.T) {
Size: int64(len(blob)),
})
}
appendManifest := func(arc, os, variant string, mediaType string, blob []byte) {
appendManifest := func(arc, os string, mediaType string, blob []byte) {
blobs = append(blobs, blob)
descs = append(descs, ocispec.Descriptor{
MediaType: mediaType,
Expand All @@ -378,11 +378,10 @@ func TestCopy_WithOptions(t *testing.T) {
Platform: &ocispec.Platform{
Architecture: arc,
OS: os,
Variant: variant,
},
})
}
generateManifest := func(arc, os, variant string, config ocispec.Descriptor, layers ...ocispec.Descriptor) {
generateManifest := func(arc, os string, config ocispec.Descriptor, layers ...ocispec.Descriptor) {
manifest := ocispec.Manifest{
Config: config,
Layers: layers,
Expand All @@ -391,7 +390,7 @@ func TestCopy_WithOptions(t *testing.T) {
if err != nil {
t.Fatal(err)
}
appendManifest(arc, os, variant, ocispec.MediaTypeImageManifest, manifestJSON)
appendManifest(arc, os, ocispec.MediaTypeImageManifest, manifestJSON)
}
generateIndex := func(manifests ...ocispec.Descriptor) {
index := ocispec.Index{
Expand All @@ -404,15 +403,13 @@ func TestCopy_WithOptions(t *testing.T) {
appendBlob(ocispec.MediaTypeImageIndex, indexJSON)
}

appendBlob(ocispec.MediaTypeImageConfig, []byte("config")) // Blob 0
appendBlob(ocispec.MediaTypeImageLayer, []byte("foo")) // Blob 1
appendBlob(ocispec.MediaTypeImageLayer, []byte("bar")) // Blob 2
generateManifest("test-arc-1", "test-os-1", "v1", descs[0], descs[1:3]...) // Blob 3
appendBlob(ocispec.MediaTypeImageLayer, []byte("hello1")) // Blob 4
generateManifest("test-arc-2", "test-os-2", "v1", descs[0], descs[4]) // Blob 5
appendBlob(ocispec.MediaTypeImageLayer, []byte("hello2")) // Blob 6
generateManifest("test-arc-1", "test-os-1", "v2", descs[0], descs[6]) // Blob 7
generateIndex(descs[3], descs[5], descs[7]) // Blob 8
appendBlob(ocispec.MediaTypeImageConfig, []byte("config")) // Blob 0
appendBlob(ocispec.MediaTypeImageLayer, []byte("foo")) // Blob 1
appendBlob(ocispec.MediaTypeImageLayer, []byte("bar")) // Blob 2
generateManifest("test-arc-1", "test-os-1", descs[0], descs[1:3]...) // Blob 3
appendBlob(ocispec.MediaTypeImageLayer, []byte("hello")) // Blob 4
generateManifest("test-arc-2", "test-os-2", descs[0], descs[4]) // Blob 5
generateIndex(descs[3], descs[5]) // Blob 6

ctx := context.Background()
for i := range blobs {
Expand All @@ -422,7 +419,7 @@ func TestCopy_WithOptions(t *testing.T) {
}
}

root := descs[8]
root := descs[6]
ref := "foobar"
err := src.Tag(ctx, root, ref)
if err != nil {
Expand Down Expand Up @@ -472,6 +469,20 @@ func TestCopy_WithOptions(t *testing.T) {
preCopyCount := int64(0)
postCopyCount := int64(0)
opts = oras.CopyOptions{
MapRoot: func(ctx context.Context, src content.Storage, root ocispec.Descriptor) (ocispec.Descriptor, error) {
manifests, err := content.Successors(ctx, src, root)
if err != nil {
return ocispec.Descriptor{}, errdef.ErrNotFound
}

// platform filter
for _, m := range manifests {
if m.Platform.Architecture == "test-arc-2" && m.Platform.OS == "test-os-2" {
return m, nil
}
}
return ocispec.Descriptor{}, errdef.ErrNotFound
},
CopyGraphOptions: oras.CopyGraphOptions{
PreCopy: func(ctx context.Context, desc ocispec.Descriptor) error {
atomic.AddInt64(&preCopyCount, 1)
Expand All @@ -483,11 +494,6 @@ func TestCopy_WithOptions(t *testing.T) {
},
},
}
targetPlatform := ocispec.Platform{
Architecture: "test-arc-2",
OS: "test-os-2",
}
opts.AddPlatformFilter(&targetPlatform)
wantDesc := descs[5]
gotDesc, err = oras.Copy(ctx, src, ref, dst, "", opts)
if err != nil {
Expand Down Expand Up @@ -525,6 +531,136 @@ func TestCopy_WithOptions(t *testing.T) {
t.Errorf("count(PostCopy()) = %v, want %v", got, want)
}

// test copy with root filter, but no matching node can be found
dst = memory.New()
opts = oras.CopyOptions{
MapRoot: func(ctx context.Context, src content.Storage, root ocispec.Descriptor) (ocispec.Descriptor, error) {
if root.MediaType == "test" {
return root, nil
} else {
return ocispec.Descriptor{}, errdef.ErrNotFound
}
},
CopyGraphOptions: oras.DefaultCopyGraphOptions,
}

_, err = oras.Copy(ctx, src, ref, dst, "", opts)
if !errors.Is(err, errdef.ErrNotFound) {
t.Fatalf("Copy() error = %v, wantErr %v", err, errdef.ErrNotFound)
}
}

func TestCopy_WithPlatformFilterOptions(t *testing.T) {
src := memory.New()

// generate test content
var blobs [][]byte
var descs []ocispec.Descriptor
appendBlob := func(mediaType string, blob []byte) {
blobs = append(blobs, blob)
descs = append(descs, ocispec.Descriptor{
MediaType: mediaType,
Digest: digest.FromBytes(blob),
Size: int64(len(blob)),
})
}
appendManifest := func(arc, os, variant string, mediaType string, blob []byte) {
blobs = append(blobs, blob)
descs = append(descs, ocispec.Descriptor{
MediaType: mediaType,
Digest: digest.FromBytes(blob),
Size: int64(len(blob)),
Platform: &ocispec.Platform{
Architecture: arc,
OS: os,
Variant: variant,
},
})
}
generateManifest := func(arc, os, variant string, config ocispec.Descriptor, layers ...ocispec.Descriptor) {
manifest := ocispec.Manifest{
Config: config,
Layers: layers,
}
manifestJSON, err := json.Marshal(manifest)
if err != nil {
t.Fatal(err)
}
appendManifest(arc, os, variant, ocispec.MediaTypeImageManifest, manifestJSON)
}
generateIndex := func(manifests ...ocispec.Descriptor) {
index := ocispec.Index{
Manifests: manifests,
}
indexJSON, err := json.Marshal(index)
if err != nil {
t.Fatal(err)
}
appendBlob(ocispec.MediaTypeImageIndex, indexJSON)
}

appendBlob(ocispec.MediaTypeImageConfig, []byte("config")) // Blob 0
appendBlob(ocispec.MediaTypeImageLayer, []byte("foo")) // Blob 1
appendBlob(ocispec.MediaTypeImageLayer, []byte("bar")) // Blob 2
generateManifest("test-arc-1", "test-os-1", "v1", descs[0], descs[1:3]...) // Blob 3
appendBlob(ocispec.MediaTypeImageLayer, []byte("hello1")) // Blob 4
generateManifest("test-arc-2", "test-os-2", "v1", descs[0], descs[4]) // Blob 5
appendBlob(ocispec.MediaTypeImageLayer, []byte("hello2")) // Blob 6
generateManifest("test-arc-1", "test-os-1", "v2", descs[0], descs[6]) // Blob 7
generateIndex(descs[3], descs[5], descs[7]) // Blob 8

ctx := context.Background()
for i := range blobs {
err := src.Push(ctx, descs[i], bytes.NewReader(blobs[i]))
if err != nil {
t.Fatalf("failed to push test content to src: %d: %v", i, err)
}
}

root := descs[8]
ref := "foobar"
err := src.Tag(ctx, root, ref)
if err != nil {
t.Fatal("fail to tag root node", err)
}

// test copy with platform filter
dst := memory.New()
opts := oras.CopyOptions{}
targetPlatform := ocispec.Platform{
Architecture: "test-arc-2",
OS: "test-os-2",
}
opts.WithPlatformFilter(&targetPlatform)
wantDesc := descs[5]
gotDesc, err := oras.Copy(ctx, src, ref, dst, "", opts)
if err != nil {
t.Fatalf("Copy() error = %v, wantErr %v", err, false)
}
if !reflect.DeepEqual(gotDesc, wantDesc) {
t.Errorf("Copy() = %v, want %v", gotDesc, wantDesc)
}

// verify contents
for i, desc := range append([]ocispec.Descriptor{descs[0]}, descs[4:6]...) {
exists, err := dst.Exists(ctx, desc)
if err != nil {
t.Fatalf("dst.Exists(%d) error = %v", i, err)
}
if !exists {
t.Errorf("dst.Exists(%d) = %v, want %v", i, exists, true)
}
}

// verify tag
gotDesc, err = dst.Resolve(ctx, ref)
if err != nil {
t.Fatal("dst.Resolve() error =", err)
}
if !reflect.DeepEqual(gotDesc, wantDesc) {
t.Errorf("dst.Resolve() = %v, want %v", gotDesc, wantDesc)
}

// test copy with platform filter, if the multiple manifests match the required platform,
// return the first matching entry
dst = memory.New()
Expand All @@ -533,7 +669,7 @@ func TestCopy_WithOptions(t *testing.T) {
OS: "test-os-1",
}
opts = oras.CopyOptions{}
opts.AddPlatformFilter(&targetPlatform)
opts.WithPlatformFilter(&targetPlatform)
wantDesc = descs[3]
gotDesc, err = oras.Copy(ctx, src, ref, dst, "", opts)
if err != nil {
Expand Down Expand Up @@ -563,7 +699,7 @@ func TestCopy_WithOptions(t *testing.T) {
t.Errorf("dst.Resolve() = %v, want %v", gotDesc, wantDesc)
}

// test copy with platform filter and exisiting MapRoot func, but no matching node can be found
// test copy with platform filter and existing MapRoot func, but no matching node can be found
dst = memory.New()
opts = oras.CopyOptions{
MapRoot: func(ctx context.Context, src content.Storage, root ocispec.Descriptor) (ocispec.Descriptor, error) {
Expand All @@ -578,25 +714,7 @@ func TestCopy_WithOptions(t *testing.T) {
Architecture: "test-arc-1",
OS: "test-os-3",
}
opts.AddPlatformFilter(&targetPlatform)

_, err = oras.Copy(ctx, src, ref, dst, "", opts)
if !errors.Is(err, errdef.ErrNotFound) {
t.Fatalf("Copy() error = %v, wantErr %v", err, errdef.ErrNotFound)
}

// test copy with root filter, but no matching node can be found
dst = memory.New()
opts = oras.CopyOptions{
MapRoot: func(ctx context.Context, src content.Storage, root ocispec.Descriptor) (ocispec.Descriptor, error) {
if root.MediaType == "test" {
return root, nil
} else {
return ocispec.Descriptor{}, errdef.ErrNotFound
}
},
CopyGraphOptions: oras.DefaultCopyGraphOptions,
}
opts.WithPlatformFilter(&targetPlatform)

_, err = oras.Copy(ctx, src, ref, dst, "", opts)
if !errors.Is(err, errdef.ErrNotFound) {
Expand Down
14 changes: 7 additions & 7 deletions internal/platform/platform.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,29 +27,29 @@ import (
// of the current platform.
// Note: Variant, OSVersion and OSFeatures are optional fields, will skip the
// comparison if the target platform does not provide specfic value.
func MatchPlatform(curr *ocispec.Platform, target *ocispec.Platform) bool {
if curr.Architecture != target.Architecture || curr.OS != target.OS {
func MatchPlatform(got *ocispec.Platform, want *ocispec.Platform) bool {
if got.Architecture != want.Architecture || got.OS != want.OS {
return false
}

if target.OSVersion != "" && curr.OSVersion != target.OSVersion {
if want.OSVersion != "" && got.OSVersion != want.OSVersion {
return false
}

if target.Variant != "" && curr.Variant != target.Variant {
if want.Variant != "" && got.Variant != want.Variant {
return false
}

if len(target.OSFeatures) != 0 && !isSubset(target.OSFeatures, curr.OSFeatures) {
if len(want.OSFeatures) != 0 && !isSubset(want.OSFeatures, got.OSFeatures) {
return false
}

return true
}

// isSubset returns true if all items in slice A are present in slice B
// isSubset returns true if all items in slice A are present in slice B.
func isSubset(a, b []string) bool {
set := make(map[string]bool)
set := make(map[string]bool, len(b))
for _, v := range b {
set[v] = true
}
Expand Down

0 comments on commit 6cbedba

Please sign in to comment.