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

fix: fix bug when copying recursively with certain platforms #818

Merged
merged 91 commits into from
Feb 23, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
91 commits
Select commit Hold shift + click to select a range
d17b0a4
add test and fix license
qweeah Jan 16, 2023
f4e7398
Remove focused
qweeah Jan 16, 2023
ee2436d
add fallback
qweeah Jan 16, 2023
8222fd0
code clean
qweeah Jan 18, 2023
f03a6aa
add mount data for fallback testing
qweeah Jan 18, 2023
db50edc
fix: deduplicate copy status logs
qweeah Jan 18, 2023
07eba1b
Merge branch 'fix-copy' into e2e-copy
qweeah Jan 18, 2023
e4b5dee
fix copying
qweeah Jan 18, 2023
6a5084b
fix post copy
qweeah Jan 18, 2023
b00dc9a
Merge branch 'fix-copy' into e2e-copy
qweeah Jan 18, 2023
bbe6b0c
bump e2e oras-go
qweeah Jan 18, 2023
f52682c
deduplicate based on digest
qweeah Jan 18, 2023
66eb21e
Merge branch 'fix-copy' into e2e-copy
qweeah Jan 18, 2023
41b7371
revert copy change
qweeah Jan 18, 2023
ae063cb
add test
qweeah Jan 18, 2023
f0aa94b
Merge remote-tracking branch 'origin_src/main' into e2e-copy
qweeah Jan 18, 2023
be1ffd9
fix e2e
qweeah Jan 18, 2023
a264e84
use billy's fork for testing
qweeah Jan 18, 2023
5aac978
correct version
qweeah Jan 18, 2023
cabd2b6
Merge remote-tracking branch 'origin_src/main' into e2e-copy
qweeah Jan 29, 2023
b5429cc
add test data for fallback registry
qweeah Jan 29, 2023
c962337
add test scripts for e2e
qweeah Feb 9, 2023
e90901b
Merge remote-tracking branch 'origin_src/main' into e2e-copy
qweeah Feb 9, 2023
bffc5b9
add logs
qweeah Feb 9, 2023
bbbf9a6
correct git action variables
qweeah Feb 9, 2023
4c6ad45
add action logs
qweeah Feb 9, 2023
755a436
rewrite host names
qweeah Feb 9, 2023
e1dec73
use shell script to run tests
qweeah Feb 9, 2023
ceb86de
fix typo
qweeah Feb 9, 2023
94f029f
correct ci path
qweeah Feb 9, 2023
543657d
resolve script errors
qweeah Feb 9, 2023
eadc5f6
correct test scripts
qweeah Feb 9, 2023
615bae1
update scripts
qweeah Feb 9, 2023
e8a920d
add sh
qweeah Feb 9, 2023
767a036
add license checker
qweeah Feb 9, 2023
88ce722
add cleanup
qweeah Feb 9, 2023
5590b9c
add docs
qweeah Feb 9, 2023
dcf3101
more docs
qweeah Feb 9, 2023
d625470
code clean
qweeah Feb 9, 2023
c919057
add execute permission to scripts
qweeah Feb 9, 2023
a0e2c18
rename script
qweeah Feb 9, 2023
bad0fc9
add container stopping logs
qweeah Feb 10, 2023
78d4c37
doc clean
qweeah Feb 10, 2023
58f7144
use pushd instead
qweeah Feb 10, 2023
1d1786a
run distribution as current user
qweeah Feb 10, 2023
2d7aff7
add exiting trap
qweeah Feb 10, 2023
8276913
add fallback registry to registry
qweeah Feb 10, 2023
ef06747
doc update
qweeah Feb 10, 2023
ca1b183
fix error in doc
qweeah Feb 12, 2023
a20d0c8
Merge remote-tracking branch 'origin_src/main' into e2e-copy
qweeah Feb 13, 2023
a62f7ee
fix typo
qweeah Feb 13, 2023
65004e6
test(e2e): add e2e specs for `oras attach`
qweeah Feb 13, 2023
1998210
code clean
qweeah Feb 13, 2023
d38af2b
Merge remote-tracking branch 'origin_src/main' into e2e-attach
qweeah Feb 15, 2023
7ec6ee7
add test data for cp
qweeah Feb 15, 2023
e978ca0
test: use dedicated package for test data
qweeah Feb 16, 2023
e4e4223
move digest constant
qweeah Feb 16, 2023
5e1f858
move constants to dedicated pkg
qweeah Feb 16, 2023
1837cad
update readme and consts
qweeah Feb 16, 2023
5d0199c
add e2e for recursive copying with platforms
qweeah Feb 16, 2023
f02cafb
add test cases
qweeah Feb 16, 2023
53f9b56
add e2e tests
qweeah Feb 16, 2023
809b2f4
Merge remote-tracking branch 'origin_src/main' into fix-copy-platform
qweeah Feb 16, 2023
dfcaac8
bump ginkgo
qweeah Feb 16, 2023
38fac21
code clean
qweeah Feb 16, 2023
89c2e35
revert attach changes
qweeah Feb 16, 2023
ff09a66
add import
qweeah Feb 16, 2023
c77839f
fix e2e
qweeah Feb 16, 2023
6f752df
fix e2e
qweeah Feb 16, 2023
f51d578
show correct source digest
qweeah Feb 16, 2023
7916770
fix e2e
qweeah Feb 16, 2023
695eca0
Merge remote-tracking branch 'origin_src/main' into fix-copy-platform
qweeah Feb 16, 2023
c7094d6
doc clean
qweeah Feb 16, 2023
cf0337f
code clean
qweeah Feb 17, 2023
8cd48fc
align oci layout and registry behavior
qweeah Feb 20, 2023
3ef9f77
add e2e
qweeah Feb 20, 2023
00a6867
Merge remote-tracking branch 'origin_src/main' into fix-copy-platform
qweeah Feb 21, 2023
eb1834c
Merge remote-tracking branch 'origin_src/main' into fix-copy-platform
qweeah Feb 22, 2023
8d68137
optimize performance for recursive copying
qweeah Feb 22, 2023
46379ae
select right referrers
qweeah Feb 22, 2023
8149582
use referrer predecessors
qweeah Feb 22, 2023
c932d50
Merge remote-tracking branch 'origin_src/main' into fix-copy-platform
qweeah Feb 22, 2023
c2d52b5
nit
qweeah Feb 22, 2023
9c46f68
add unit test for successors
qweeah Feb 22, 2023
df694e4
add unit test
qweeah Feb 22, 2023
37a2281
add comment
qweeah Feb 22, 2023
8b4efbc
remove explicit tagging
qweeah Feb 22, 2023
6bd7e0f
add copy via digest
qweeah Feb 22, 2023
1cde14c
add e2e tests
qweeah Feb 22, 2023
6412a96
Merge branch 'main' into fix-copy-platform
qweeah Feb 23, 2023
9e6375a
fix e2e
qweeah Feb 23, 2023
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
27 changes: 22 additions & 5 deletions cmd/oras/cp.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,13 @@ import (
"strings"
"sync"

"github.com/opencontainers/go-digest"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/spf13/cobra"
"oras.land/oras-go/v2"
"oras.land/oras/cmd/oras/internal/display"
"oras.land/oras/cmd/oras/internal/option"
"oras.land/oras/internal/graph"
)

type copyOptions struct {
Expand Down Expand Up @@ -117,6 +119,7 @@ func runCopy(opts copyOptions) error {
committed := &sync.Map{}
extendedCopyOptions := oras.DefaultExtendedCopyOptions
extendedCopyOptions.Concurrency = opts.concurrency
extendedCopyOptions.FindPredecessors = graph.FindReferrerPredecessors
extendedCopyOptions.PreCopy = display.StatusPrinter("Copying", opts.Verbose)
extendedCopyOptions.PostCopy = func(ctx context.Context, desc ocispec.Descriptor) error {
committed.Store(desc.Digest.String(), desc.Annotations[ocispec.AnnotationTitle])
Expand All @@ -131,9 +134,10 @@ func runCopy(opts copyOptions) error {
}

var desc ocispec.Descriptor
if ref := opts.To.Reference; ref == "" {
// push to the destination with digest only if no tag specified
desc, err = src.Resolve(ctx, opts.From.Reference)
rOpts := oras.DefaultResolveOptions
rOpts.TargetPlatform = opts.Platform.Platform
if dstRef := opts.To.Reference; dstRef == "" {
desc, err = oras.Resolve(ctx, src, opts.From.Reference, rOpts)
if err != nil {
return err
}
Expand All @@ -144,21 +148,34 @@ func runCopy(opts copyOptions) error {
}
} else {
if opts.recursive {
desc, err = oras.ExtendedCopy(ctx, src, opts.From.Reference, dst, opts.To.Reference, extendedCopyOptions)
srcRef := opts.From.Reference
if rOpts.TargetPlatform != nil {
// resolve source reference to specified platform
desc, err := oras.Resolve(ctx, src, opts.From.Reference, rOpts)
if err != nil {
return err
}
srcRef = desc.Digest.String()
}
desc, err = oras.ExtendedCopy(ctx, src, srcRef, dst, dstRef, extendedCopyOptions)
} else {
copyOptions := oras.CopyOptions{
CopyGraphOptions: extendedCopyOptions.CopyGraphOptions,
}
if opts.Platform.Platform != nil {
copyOptions.WithTargetPlatform(opts.Platform.Platform)
}
desc, err = oras.Copy(ctx, src, opts.From.Reference, dst, opts.To.Reference, copyOptions)
desc, err = oras.Copy(ctx, src, opts.From.Reference, dst, dstRef, copyOptions)
}
}
if err != nil {
return err
}

if from, err := digest.Parse(opts.From.Reference); err == nil && from != desc.Digest {
// correct source digest
opts.From.RawReference = fmt.Sprintf("%s@%s", opts.From.Path, desc.Digest.String())
}
fmt.Println("Copied", opts.From.AnnotatedReference(), "=>", opts.To.AnnotatedReference())

if len(opts.extraRefs) != 0 {
Expand Down
30 changes: 28 additions & 2 deletions internal/graph/graph.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ import (
"encoding/json"

ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"oras.land/oras-go/v2"
"oras.land/oras-go/v2/content"
"oras.land/oras-go/v2/registry"
"oras.land/oras/internal/docker"
Expand Down Expand Up @@ -63,7 +62,7 @@ func Successors(ctx context.Context, fetcher content.Fetcher, node ocispec.Descr
}

// Referrers returns referrer nodes of desc in target.
func Referrers(ctx context.Context, target oras.ReadOnlyGraphTarget, desc ocispec.Descriptor, artifactType string) ([]ocispec.Descriptor, error) {
func Referrers(ctx context.Context, target content.ReadOnlyGraphStorage, desc ocispec.Descriptor, artifactType string) ([]ocispec.Descriptor, error) {
var results []ocispec.Descriptor
if repo, ok := target.(registry.ReferrerLister); ok {
// get referrers directly
Expand Down Expand Up @@ -122,6 +121,33 @@ func Referrers(ctx context.Context, target oras.ReadOnlyGraphTarget, desc ocispe
return results, nil
}

// FindReferrerPredecessors returns referrer nodes of desc in target.
func FindReferrerPredecessors(ctx context.Context, src content.ReadOnlyGraphStorage, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) {
var results []ocispec.Descriptor
if repo, ok := src.(registry.ReferrerLister); ok {
// get referrers directly
err := repo.Referrers(ctx, desc, "", func(referrers []ocispec.Descriptor) error {
results = append(results, referrers...)
return nil
})
if err != nil {
return nil, err
}
return results, nil
}
predecessors, err := src.Predecessors(ctx, desc)
if err != nil {
return nil, err
}
for _, node := range predecessors {
switch node.MediaType {
case ocispec.MediaTypeArtifactManifest, ocispec.MediaTypeImageManifest:
results = append(results, node)
}
}
return results, nil
}

func fetchBytes(ctx context.Context, fetcher content.Fetcher, desc ocispec.Descriptor) ([]byte, error) {
rc, err := fetcher.Fetch(ctx, desc)
if err != nil {
Expand Down
231 changes: 231 additions & 0 deletions internal/graph/graph_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,9 @@ import (
"github.com/opencontainers/go-digest"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"oras.land/oras-go/v2"
"oras.land/oras-go/v2/content"
"oras.land/oras-go/v2/content/memory"
"oras.land/oras/internal/docker"
)

type errLister struct {
Expand All @@ -37,6 +39,14 @@ func (e *errLister) Referrers(ctx context.Context, desc ocispec.Descriptor, arti
return errors.New("")
}

type errFinder struct {
oras.ReadOnlyGraphTarget
}

func (e *errFinder) Predecessors(ctx context.Context, node ocispec.Descriptor) ([]ocispec.Descriptor, error) {
return nil, errors.New("")
}

type refLister struct {
referrers []ocispec.Descriptor
oras.ReadOnlyGraphTarget
Expand All @@ -50,6 +60,10 @@ type predecessorFinder struct {
*memory.Store
}

type fetcher struct {
content.Fetcher
}

func TestReferrers(t *testing.T) {
ctx := context.Background()
var blobs [][]byte
Expand Down Expand Up @@ -172,3 +186,220 @@ func TestReferrers(t *testing.T) {
}
})
}

func TestSuccessors(t *testing.T) {
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)),
})
}
generateImage := func(subject *ocispec.Descriptor, mediaType string, config ocispec.Descriptor, layers ...ocispec.Descriptor) {
manifest := ocispec.Manifest{
MediaType: mediaType,
Subject: subject,
Config: config,
Layers: layers,
}
manifestJSON, err := json.Marshal(manifest)
if err != nil {
t.Fatal(err)
}
appendBlob(mediaType, manifestJSON)
}
generateArtifact := func(artifactType string, subject *ocispec.Descriptor, blobs ...ocispec.Descriptor) {
manifest := ocispec.Artifact{
MediaType: ocispec.MediaTypeArtifactManifest,
Subject: subject,
Blobs: blobs,
}
manifestJSON, err := json.Marshal(manifest)
if err != nil {
t.Fatal(err)
}
appendBlob(ocispec.MediaTypeArtifactManifest, manifestJSON)
}
generateIndex := func(manifests ...ocispec.Descriptor) {
index := ocispec.Index{
Manifests: manifests,
}
manifestJSON, err := json.Marshal(index)
if err != nil {
t.Fatal(err)
}
appendBlob(ocispec.MediaTypeImageIndex, manifestJSON)
}
const (
subject = iota
config
ociImage
dockerImage
artifact
index
)
appendBlob(ocispec.MediaTypeArtifactManifest, []byte("subject content"))
imageType := "test.image"
appendBlob(imageType, []byte("config content"))
generateImage(&descs[subject], ocispec.MediaTypeImageManifest, descs[config])
generateImage(&descs[subject], docker.MediaTypeManifest, descs[config])
artifactType := "test.artifact"
generateArtifact(artifactType, &descs[subject])
generateIndex(descs[subject])
memory := memory.New()
ctx := context.Background()
for i := range descs {
memory.Push(ctx, descs[i], bytes.NewReader(blobs[i]))
}
fetcher := &fetcher{Fetcher: memory}

type args struct {
ctx context.Context
fetcher content.Fetcher
node ocispec.Descriptor
}
tests := []struct {
name string
args args
wantNodes []ocispec.Descriptor
wantSubject *ocispec.Descriptor
wantConfig *ocispec.Descriptor
wantErr bool
}{
{"should failed to get non-existent artifact", args{ctx, fetcher, ocispec.Descriptor{MediaType: ocispec.MediaTypeArtifactManifest}}, nil, nil, nil, true},
{"should failed to get non-existent OCI image", args{ctx, fetcher, ocispec.Descriptor{MediaType: ocispec.MediaTypeImageManifest}}, nil, nil, nil, true},
{"should failed to get non-existent docker image", args{ctx, fetcher, ocispec.Descriptor{MediaType: docker.MediaTypeManifest}}, nil, nil, nil, true},
{"should get success of a docker image", args{ctx, fetcher, descs[dockerImage]}, nil, &descs[subject], &descs[config], false},
{"should get success of an OCI image", args{ctx, fetcher, descs[ociImage]}, nil, &descs[subject], &descs[config], false},
{"should get success of an artifact", args{ctx, fetcher, descs[artifact]}, nil, &descs[subject], nil, false},
{"should get success of an index", args{ctx, fetcher, descs[index]}, []ocispec.Descriptor{descs[subject]}, nil, nil, false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
gotNodes, gotSubject, gotConfig, err := Successors(tt.args.ctx, tt.args.fetcher, tt.args.node)
if (err != nil) != tt.wantErr {
t.Errorf("Successors() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(gotNodes, tt.wantNodes) {
t.Errorf("Successors() gotNodes = %v, want %v", gotNodes, tt.wantNodes)
}
if !reflect.DeepEqual(gotSubject, tt.wantSubject) {
t.Errorf("Successors() gotSubject = %v, want %v", gotSubject, tt.wantSubject)
}
if !reflect.DeepEqual(gotConfig, tt.wantConfig) {
t.Errorf("Successors() gotConfig = %v, want %v", gotConfig, tt.wantConfig)
}
})
}
}

func TestFindReferrerPredecessors(t *testing.T) {
ctx := context.Background()
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)),
})
}
generateImage := func(subject *ocispec.Descriptor, annotations map[string]string, config ocispec.Descriptor, layers ...ocispec.Descriptor) {
manifest := ocispec.Manifest{
Subject: subject,
Config: config,
Layers: layers,
Annotations: annotations,
}
manifestJSON, err := json.Marshal(manifest)
if err != nil {
t.Fatal(err)
}
appendBlob(ocispec.MediaTypeImageManifest, manifestJSON)
}
generateArtifact := func(artifactType string, subject *ocispec.Descriptor, annotations map[string]string, blobs ...ocispec.Descriptor) {
manifest := ocispec.Artifact{
Subject: subject,
Blobs: blobs,
Annotations: annotations,
ArtifactType: artifactType,
}
manifestJSON, err := json.Marshal(manifest)
if err != nil {
t.Fatal(err)
}
appendBlob(ocispec.MediaTypeArtifactManifest, manifestJSON)
}
generateIndex := func(manifests ...ocispec.Descriptor) {
index := ocispec.Index{
Manifests: manifests,
}
manifestJSON, err := json.Marshal(index)
if err != nil {
t.Fatal(err)
}
appendBlob(ocispec.MediaTypeImageIndex, manifestJSON)
}
const (
subject = iota
imgConfig
image
artifact
)
var anno map[string]string
appendBlob(ocispec.MediaTypeArtifactManifest, []byte("subject content"))
imageType := "test.image"
appendBlob(imageType, []byte("config content"))
generateImage(&descs[subject], anno, descs[imgConfig])
imageDesc := descs[image]
imageDesc.Annotations = anno
imageDesc.ArtifactType = imageType
artifactType := "test.artifact"
generateArtifact(artifactType, &descs[subject], anno)
generateIndex(descs[subject])
artifactDesc := descs[artifact]
artifactDesc.Annotations = anno
artifactDesc.ArtifactType = artifactType

referrers := []ocispec.Descriptor{descs[image], descs[image]}
memory := memory.New()
for i := range descs {
memory.Push(ctx, descs[i], bytes.NewReader(blobs[i]))
}
finder := &predecessorFinder{Store: memory}
type args struct {
ctx context.Context
src content.ReadOnlyGraphStorage
desc ocispec.Descriptor
}
tests := []struct {
name string
args args
want []ocispec.Descriptor
wantErr bool
}{

{"should failed to get referrers", args{ctx, &errLister{}, ocispec.Descriptor{}}, nil, true},
{"should failed to get predecessor", args{ctx, &errFinder{}, ocispec.Descriptor{}}, nil, true},
{"should return referrers when target is a referrer lister", args{ctx, &refLister{referrers: referrers}, ocispec.Descriptor{}}, referrers, false},
{"should return image for config node", args{ctx, finder, descs[imgConfig]}, []ocispec.Descriptor{descs[image]}, false},
{"should return image and artifact for subject node", args{ctx, finder, descs[subject]}, []ocispec.Descriptor{descs[image], descs[artifact]}, false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := FindReferrerPredecessors(tt.args.ctx, tt.args.src, tt.args.desc)
if (err != nil) != tt.wantErr {
t.Errorf("FindReferrerPredecessors() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("FindReferrerPredecessors() = %v, want %v", got, tt.want)
}
})
}
}
9 changes: 9 additions & 0 deletions test/e2e/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,15 @@ graph TD;
D1["test.sbom.file(image)"] -- subject --> C1
D2["test.signature.file(image)"] -- subject --> D1
end
subgraph "file: artifacts_index.tar.gz"
direction TB
F0>tag: multi]-..->F1[oci index]
F1--linux/amd64-->F2[oci image]
F1--linux/arm64-->F3[oci image]
F1--linux/arm/v7-->F4[oci image]
G1["referrer.index(image)"] -- subject --> F1
G2["referrer.image(image)"] -- subject --> F2
end
end
```

Expand Down
Loading