Skip to content

Commit

Permalink
fix!: Tag() and TagN() return a descriptor (#396)
Browse files Browse the repository at this point in the history
Fix #395

Signed-off-by: Shiwei Zhang <shizh@microsoft.com>
  • Loading branch information
shizhMSFT authored Jan 6, 2023
1 parent 258bfba commit 31299c3
Show file tree
Hide file tree
Showing 3 changed files with 104 additions and 69 deletions.
95 changes: 47 additions & 48 deletions content.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,10 +67,10 @@ type TagNOptions struct {
}

// TagN tags the descriptor identified by srcReference with dstReferences.
func TagN(ctx context.Context, target Target, srcReference string, dstReferences []string, opts TagNOptions) error {
func TagN(ctx context.Context, target Target, srcReference string, dstReferences []string, opts TagNOptions) (ocispec.Descriptor, error) {
switch len(dstReferences) {
case 0:
return fmt.Errorf("dstReferences cannot be empty: %w", errdef.ErrMissingReference)
return ocispec.Descriptor{}, fmt.Errorf("dstReferences cannot be empty: %w", errdef.ErrMissingReference)
case 1:
return Tag(ctx, target, srcReference, dstReferences[0])
}
Expand All @@ -82,60 +82,43 @@ func TagN(ctx context.Context, target Target, srcReference string, dstReferences
opts.MaxMetadataBytes = defaultTagNMaxMetadataBytes
}

refFetcher, okFetch := target.(registry.ReferenceFetcher)
refPusher, okPush := target.(registry.ReferencePusher)
if okFetch && okPush {
_, isRefFetcher := target.(registry.ReferenceFetcher)
_, isRefPusher := target.(registry.ReferencePusher)
if isRefFetcher && isRefPusher {
if repo, ok := target.(interfaces.ReferenceParser); ok {
// add scope hints to minimize the number of auth requests
ref, err := repo.ParseReference(srcReference)
if err != nil {
return err
return ocispec.Descriptor{}, err
}
ctx = registryutil.WithScopeHint(ctx, ref, auth.ActionPull, auth.ActionPush)
}

desc, contentBytes, err := func() (ocispec.Descriptor, []byte, error) {
desc, rc, err := refFetcher.FetchReference(ctx, srcReference)
if err != nil {
return ocispec.Descriptor{}, nil, err
}
defer rc.Close()

if desc.Size > opts.MaxMetadataBytes {
return ocispec.Descriptor{}, nil, fmt.Errorf(
desc, contentBytes, err := FetchBytes(ctx, target, srcReference, FetchBytesOptions{
MaxBytes: opts.MaxMetadataBytes,
})
if err != nil {
if errors.Is(err, errdef.ErrSizeExceedsLimit) {
err = fmt.Errorf(
"content size %v exceeds MaxMetadataBytes %v: %w",
desc.Size,
opts.MaxMetadataBytes,
errdef.ErrSizeExceedsLimit)
}
contentBytes, err := content.ReadAll(rc, desc)
if err != nil {
return ocispec.Descriptor{}, nil, err
}
return desc, contentBytes, nil
}()
if err != nil {
return err
return ocispec.Descriptor{}, err
}

eg, egCtx := syncutil.LimitGroup(ctx, opts.Concurrency)
for _, dstRef := range dstReferences {
eg.Go(func(dst string) func() error {
return func() error {
r := bytes.NewReader(contentBytes)
if err := refPusher.PushReference(egCtx, desc, r, dst); err != nil && !errors.Is(err, errdef.ErrAlreadyExists) {
return fmt.Errorf("failed to tag %s as %s: %w", srcReference, dst, err)
}
return nil
}
}(dstRef))
if err := tagBytesN(ctx, target, desc, contentBytes, dstReferences, TagBytesNOptions{
Concurrency: opts.Concurrency,
}); err != nil {
return ocispec.Descriptor{}, err
}
return eg.Wait()
return desc, nil
}

desc, err := target.Resolve(ctx, srcReference)
if err != nil {
return err
return ocispec.Descriptor{}, err
}
eg, egCtx := syncutil.LimitGroup(ctx, opts.Concurrency)
for _, dstRef := range dstReferences {
Expand All @@ -149,35 +132,44 @@ func TagN(ctx context.Context, target Target, srcReference string, dstReferences
}(dstRef))
}

return eg.Wait()
if err := eg.Wait(); err != nil {
return ocispec.Descriptor{}, err
}
return desc, nil
}

// Tag tags the descriptor identified by src with dst.
func Tag(ctx context.Context, target Target, src, dst string) error {
func Tag(ctx context.Context, target Target, src, dst string) (ocispec.Descriptor, error) {
refFetcher, okFetch := target.(registry.ReferenceFetcher)
refPusher, okPush := target.(registry.ReferencePusher)
if okFetch && okPush {
if repo, ok := target.(interfaces.ReferenceParser); ok {
// add scope hints to minimize the number of auth requests
ref, err := repo.ParseReference(src)
if err != nil {
return err
return ocispec.Descriptor{}, err
}
ctx = registryutil.WithScopeHint(ctx, ref, auth.ActionPull, auth.ActionPush)
}
desc, rc, err := refFetcher.FetchReference(ctx, src)
if err != nil {
return err
return ocispec.Descriptor{}, err
}
defer rc.Close()
return refPusher.PushReference(ctx, desc, rc, dst)
if err := refPusher.PushReference(ctx, desc, rc, dst); err != nil {
return ocispec.Descriptor{}, err
}
return desc, nil
}

desc, err := target.Resolve(ctx, src)
if err != nil {
return err
return ocispec.Descriptor{}, err
}
return target.Tag(ctx, desc, dst)
if err := target.Tag(ctx, desc, dst); err != nil {
return ocispec.Descriptor{}, err
}
return desc, nil
}

// DefaultResolveOptions provides the default ResolveOptions.
Expand Down Expand Up @@ -369,6 +361,16 @@ func TagBytesN(ctx context.Context, target Target, mediaType string, contentByte
if opts.Concurrency <= 0 {
opts.Concurrency = defaultTagConcurrency
}

if err := tagBytesN(ctx, target, desc, contentBytes, references, opts); err != nil {
return ocispec.Descriptor{}, err
}
return desc, nil
}

// tagBytesN pushes the contentBytes using the given desc, and tag it with the
// given references.
func tagBytesN(ctx context.Context, target Target, desc ocispec.Descriptor, contentBytes []byte, references []string, opts TagBytesNOptions) error {
eg, egCtx := syncutil.LimitGroup(ctx, opts.Concurrency)
if refPusher, ok := target.(registry.ReferencePusher); ok {
for _, reference := range references {
Expand All @@ -385,7 +387,7 @@ func TagBytesN(ctx context.Context, target Target, mediaType string, contentByte
} else {
r := bytes.NewReader(contentBytes)
if err := target.Push(ctx, desc, r); err != nil && !errors.Is(err, errdef.ErrAlreadyExists) {
return ocispec.Descriptor{}, fmt.Errorf("failed to push content: %w", err)
return fmt.Errorf("failed to push content: %w", err)
}
for _, reference := range references {
eg.Go(func(ref string) func() error {
Expand All @@ -399,10 +401,7 @@ func TagBytesN(ctx context.Context, target Target, mediaType string, contentByte
}
}

if err := eg.Wait(); err != nil {
return ocispec.Descriptor{}, err
}
return desc, nil
return eg.Wait()
}

// TagBytes describes the contentBytes using the given mediaType, pushes it,
Expand Down
72 changes: 54 additions & 18 deletions content_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,13 +85,16 @@ func TestTag_Memory(t *testing.T) {
}

// test Tag
err = oras.Tag(ctx, target, ref, "myTestingTag")
gotDesc, err := oras.Tag(ctx, target, ref, "myTestingTag")
if err != nil {
t.Fatalf("failed to retag using oras.Tag with err: %v", err)
}
if !reflect.DeepEqual(gotDesc, manifestDesc) {
t.Errorf("oras.Tag() = %v, want %v", gotDesc, manifestDesc)
}

// verify tag
gotDesc, err := target.Resolve(ctx, "myTestingTag")
gotDesc, err = target.Resolve(ctx, "myTestingTag")
if err != nil {
t.Fatal("target.Resolve() error =", err)
}
Expand Down Expand Up @@ -155,42 +158,54 @@ func TestTag_Repository(t *testing.T) {
ctx := context.Background()

// test with manifest tag
err = oras.Tag(ctx, repo, src, dst)
gotDesc, err := oras.Tag(ctx, repo, src, dst)
if err != nil {
t.Fatalf("Repository.TagReference() error = %v", err)
}
if !bytes.Equal(gotIndex, index) {
t.Errorf("Repository.TagReference() = %v, want %v", gotIndex, index)
}
if !reflect.DeepEqual(gotDesc, indexDesc) {
t.Errorf("oras.Tag() = %v, want %v", gotDesc, indexDesc)
}

// test with manifest digest
err = oras.Tag(ctx, repo, indexDesc.Digest.String(), dst)
gotDesc, err = oras.Tag(ctx, repo, indexDesc.Digest.String(), dst)
if err != nil {
t.Fatalf("Repository.TagReference() error = %v", err)
}
if !bytes.Equal(gotIndex, index) {
t.Errorf("Repository.TagReference() = %v, want %v", gotIndex, index)
}
if !reflect.DeepEqual(gotDesc, indexDesc) {
t.Errorf("oras.Tag() = %v, want %v", gotDesc, indexDesc)
}

// test with manifest tag@digest
tagDigestRef := src + "@" + indexDesc.Digest.String()
err = oras.Tag(ctx, repo, tagDigestRef, dst)
gotDesc, err = oras.Tag(ctx, repo, tagDigestRef, dst)
if err != nil {
t.Fatalf("Repository.TagReference() error = %v", err)
}
if !bytes.Equal(gotIndex, index) {
t.Errorf("Repository.TagReference() = %v, want %v", gotIndex, index)
}
if !reflect.DeepEqual(gotDesc, indexDesc) {
t.Errorf("oras.Tag() = %v, want %v", gotDesc, indexDesc)
}

// test with manifest FQDN
fqdnRef := repoName + ":" + tagDigestRef
err = oras.Tag(ctx, repo, fqdnRef, dst)
gotDesc, err = oras.Tag(ctx, repo, fqdnRef, dst)
if err != nil {
t.Fatalf("Repository.TagReference() error = %v", err)
}
if !bytes.Equal(gotIndex, index) {
t.Errorf("Repository.TagReference() = %v, want %v", gotIndex, index)
}
if !reflect.DeepEqual(gotDesc, indexDesc) {
t.Errorf("oras.Tag() = %v, want %v", gotDesc, indexDesc)
}
}

func TestTagN_Memory(t *testing.T) {
Expand Down Expand Up @@ -239,20 +254,23 @@ func TestTagN_Memory(t *testing.T) {
}

// test TagN with empty dstReferences
err = oras.TagN(ctx, target, srcRef, nil, oras.DefaultTagNOptions)
_, err = oras.TagN(ctx, target, srcRef, nil, oras.DefaultTagNOptions)
if !errors.Is(err, errdef.ErrMissingReference) {
t.Fatalf("oras.TagN() error = %v, wantErr %v", err, errdef.ErrMissingReference)
}

// test TagN with single dstReferences
dstRef := "single"
err = oras.TagN(ctx, target, srcRef, []string{dstRef}, oras.DefaultTagNOptions)
gotDesc, err := oras.TagN(ctx, target, srcRef, []string{dstRef}, oras.DefaultTagNOptions)
if err != nil {
t.Fatalf("failed to retag using oras.Tag with err: %v", err)
}
if !reflect.DeepEqual(gotDesc, manifestDesc) {
t.Errorf("oras.TagN() = %v, want %v", gotDesc, manifestDesc)
}

// verify tag
gotDesc, err := target.Resolve(ctx, dstRef)
gotDesc, err = target.Resolve(ctx, dstRef)
if err != nil {
t.Fatal("target.Resolve() error =", err)
}
Expand All @@ -266,10 +284,13 @@ func TestTagN_Memory(t *testing.T) {
opts := oras.TagNOptions{
MaxMetadataBytes: 1,
}
err = oras.TagN(ctx, target, srcRef, []string{dstRef}, opts)
gotDesc, err = oras.TagN(ctx, target, srcRef, []string{dstRef}, opts)
if err != nil {
t.Fatalf("failed to retag using oras.Tag with err: %v", err)
}
if !reflect.DeepEqual(gotDesc, manifestDesc) {
t.Errorf("oras.TagN() = %v, want %v", gotDesc, manifestDesc)
}

// verify tag
gotDesc, err = target.Resolve(ctx, dstRef)
Expand All @@ -282,10 +303,13 @@ func TestTagN_Memory(t *testing.T) {

// test TagN with multiple references
dstRefs := []string{"foo", "bar", "baz"}
err = oras.TagN(ctx, target, srcRef, dstRefs, oras.DefaultTagNOptions)
gotDesc, err = oras.TagN(ctx, target, srcRef, dstRefs, oras.DefaultTagNOptions)
if err != nil {
t.Fatal("oras.TagN() error =", err)
}
if !reflect.DeepEqual(gotDesc, manifestDesc) {
t.Errorf("oras.TagN() = %v, want %v", gotDesc, manifestDesc)
}

// verify multiple references
for _, ref := range dstRefs {
Expand All @@ -304,10 +328,13 @@ func TestTagN_Memory(t *testing.T) {
opts = oras.TagNOptions{
MaxMetadataBytes: 1,
}
err = oras.TagN(ctx, target, srcRef, dstRefs, opts)
gotDesc, err = oras.TagN(ctx, target, srcRef, dstRefs, opts)
if err != nil {
t.Fatal("oras.TagN() error =", err)
}
if !reflect.DeepEqual(gotDesc, manifestDesc) {
t.Errorf("oras.TagN() = %v, want %v", gotDesc, manifestDesc)
}

// verify multiple references
for _, ref := range dstRefs {
Expand Down Expand Up @@ -396,19 +423,22 @@ func TestTagN_Repository(t *testing.T) {
ctx := context.Background()

// test TagN with empty dstReferences
err = oras.TagN(ctx, repo, srcRef, nil, oras.DefaultTagNOptions)
_, err = oras.TagN(ctx, repo, srcRef, nil, oras.DefaultTagNOptions)
if !errors.Is(err, errdef.ErrMissingReference) {
t.Fatalf("oras.TagN() error = %v, wantErr %v", err, errdef.ErrMissingReference)
}

// test TagN with single dstReferences
err = oras.TagN(ctx, repo, srcRef, []string{refTag1}, oras.DefaultTagNOptions)
gotDesc, err := oras.TagN(ctx, repo, srcRef, []string{refTag1}, oras.DefaultTagNOptions)
if err != nil {
t.Fatalf("failed to retag using oras.Tag with err: %v", err)
}
if !reflect.DeepEqual(gotDesc, indexDesc) {
t.Errorf("oras.TagN() = %v, want %v", gotDesc, indexDesc)
}

// verify tag
gotDesc, err := repo.Resolve(ctx, refTag1)
gotDesc, err = repo.Resolve(ctx, refTag1)
if err != nil {
t.Fatal("target.Resolve() error =", err)
}
Expand All @@ -421,10 +451,13 @@ func TestTagN_Repository(t *testing.T) {
opts := oras.TagNOptions{
MaxMetadataBytes: 1,
}
err = oras.TagN(ctx, repo, srcRef, []string{refTag2}, opts)
gotDesc, err = oras.TagN(ctx, repo, srcRef, []string{refTag2}, opts)
if err != nil {
t.Fatalf("failed to retag using oras.Tag with err: %v", err)
}
if !reflect.DeepEqual(gotDesc, indexDesc) {
t.Errorf("oras.TagN() = %v, want %v", gotDesc, indexDesc)
}

// verify tag
gotDesc, err = repo.Resolve(ctx, refTag2)
Expand All @@ -436,10 +469,13 @@ func TestTagN_Repository(t *testing.T) {
}

// test TagN with multiple references
err = oras.TagN(ctx, repo, srcRef, dstRefs, oras.DefaultTagNOptions)
gotDesc, err = oras.TagN(ctx, repo, srcRef, dstRefs, oras.DefaultTagNOptions)
if err != nil {
t.Fatal("oras.TagN() error =", err)
}
if !reflect.DeepEqual(gotDesc, indexDesc) {
t.Errorf("oras.TagN() = %v, want %v", gotDesc, indexDesc)
}

// verify multiple references
for _, ref := range dstRefs {
Expand All @@ -458,7 +494,7 @@ func TestTagN_Repository(t *testing.T) {
opts = oras.TagNOptions{
MaxMetadataBytes: 1,
}
err = oras.TagN(ctx, repo, srcRef, dstRefs, opts)
_, err = oras.TagN(ctx, repo, srcRef, dstRefs, opts)
if !errors.Is(err, errdef.ErrSizeExceedsLimit) {
t.Fatalf("oras.TagN() error = %v, wantErr %v", err, errdef.ErrSizeExceedsLimit)
}
Expand Down
Loading

0 comments on commit 31299c3

Please sign in to comment.