diff --git a/cmd/oras/cp.go b/cmd/oras/cp.go index 96a9e17be..606ecf5c2 100644 --- a/cmd/oras/cp.go +++ b/cmd/oras/cp.go @@ -181,7 +181,7 @@ func runCopy(opts copyOptions) error { if len(opts.extraRefs) != 0 { tagNOpts := oras.DefaultTagNOptions tagNOpts.Concurrency = opts.concurrency - if _, err = oras.TagN(ctx, display.NewTagManifestStatusPrinter(dst), opts.To.Reference, opts.extraRefs, tagNOpts); err != nil { + if _, err = oras.TagN(ctx, display.NewTagStatusPrinter(dst), opts.To.Reference, opts.extraRefs, tagNOpts); err != nil { return err } } diff --git a/cmd/oras/internal/display/print.go b/cmd/oras/internal/display/print.go index 675f4915e..35de4ec5a 100644 --- a/cmd/oras/internal/display/print.go +++ b/cmd/oras/internal/display/print.go @@ -75,8 +75,8 @@ func PrintSuccessorStatus(ctx context.Context, desc ocispec.Descriptor, status s return nil } -// NewTagManifestStatusPrinter creates a wrapper type for printing tag status. -func NewTagManifestStatusPrinter(target oras.Target) oras.Target { +// NewTagStatusPrinter creates a wrapper type for printing tag status. +func NewTagStatusPrinter(target oras.Target) oras.Target { if repo, ok := target.(registry.Repository); ok { return &tagManifestStatusForRepo{ Repository: repo, @@ -87,12 +87,38 @@ func NewTagManifestStatusPrinter(target oras.Target) oras.Target { } } +// NewTagStatusHintPrinter creates a wrapper type for printing +// tag status and hint. +func NewTagStatusHintPrinter(target oras.Target, refPrefix string) oras.Target { + var printHint sync.Once + if repo, ok := target.(registry.Repository); ok { + return &tagManifestStatusForRepo{ + Repository: repo, + printHint: &printHint, + refPrefix: refPrefix, + } + } + return &tagManifestStatusForTarget{ + Target: target, + printHint: &printHint, + refPrefix: refPrefix, + } +} + type tagManifestStatusForRepo struct { registry.Repository + printHint *sync.Once + refPrefix string } // PushReference overrides Repository.PushReference method to print off which tag(s) were added successfully. func (p *tagManifestStatusForRepo) PushReference(ctx context.Context, expected ocispec.Descriptor, content io.Reader, reference string) error { + if p.printHint != nil { + p.printHint.Do(func() { + ref := p.refPrefix + "@" + expected.Digest.String() + Print("Tagging", ref) + }) + } if err := p.Repository.PushReference(ctx, expected, content, reference); err != nil { return err } @@ -101,10 +127,18 @@ func (p *tagManifestStatusForRepo) PushReference(ctx context.Context, expected o type tagManifestStatusForTarget struct { oras.Target + printHint *sync.Once + refPrefix string } // Tag tags a descriptor with a reference string. func (p *tagManifestStatusForTarget) Tag(ctx context.Context, desc ocispec.Descriptor, reference string) error { + if p.printHint != nil { + p.printHint.Do(func() { + ref := p.refPrefix + "@" + desc.Digest.String() + Print("Tagging", ref) + }) + } if err := p.Target.Tag(ctx, desc, reference); err != nil { return err } diff --git a/cmd/oras/manifest/push.go b/cmd/oras/manifest/push.go index 4e37cc017..031eb7d93 100644 --- a/cmd/oras/manifest/push.go +++ b/cmd/oras/manifest/push.go @@ -178,7 +178,7 @@ func pushManifest(opts pushOptions) error { } display.Print("Pushed", opts.AnnotatedReference()) if len(opts.extraRefs) != 0 { - if _, err = oras.TagBytesN(ctx, display.NewTagManifestStatusPrinter(target), mediaType, contentBytes, opts.extraRefs, tagBytesNOpts); err != nil { + if _, err = oras.TagBytesN(ctx, display.NewTagStatusPrinter(target), mediaType, contentBytes, opts.extraRefs, tagBytesNOpts); err != nil { return err } } diff --git a/cmd/oras/push.go b/cmd/oras/push.go index 1eab00a8a..921681438 100644 --- a/cmd/oras/push.go +++ b/cmd/oras/push.go @@ -202,7 +202,7 @@ func runPush(opts pushOptions) error { } tagBytesNOpts := oras.DefaultTagBytesNOptions tagBytesNOpts.Concurrency = opts.concurrency - if _, err = oras.TagBytesN(ctx, display.NewTagManifestStatusPrinter(dst), root.MediaType, contentBytes, opts.extraRefs, tagBytesNOpts); err != nil { + if _, err = oras.TagBytesN(ctx, display.NewTagStatusPrinter(dst), root.MediaType, contentBytes, opts.extraRefs, tagBytesNOpts); err != nil { return err } } diff --git a/cmd/oras/tag/tag.go b/cmd/oras/tag/tag.go index a2486b0df..91044bd9e 100644 --- a/cmd/oras/tag/tag.go +++ b/cmd/oras/tag/tag.go @@ -16,6 +16,8 @@ limitations under the License. package tag import ( + "fmt" + "github.com/spf13/cobra" "oras.land/oras-go/v2" "oras.land/oras/cmd/oras/internal/display" @@ -82,6 +84,12 @@ func tagManifest(opts tagOptions) error { tagNOpts := oras.DefaultTagNOptions tagNOpts.Concurrency = opts.concurrency - _, err = oras.TagN(ctx, display.NewTagManifestStatusPrinter(target), opts.Reference, opts.targetRefs, tagNOpts) + _, err = oras.TagN( + ctx, + display.NewTagStatusHintPrinter(target, fmt.Sprintf("[%s] %s", opts.Type, opts.Path)), + opts.Reference, + opts.targetRefs, + tagNOpts, + ) return err } diff --git a/test/e2e/suite/command/tag.go b/test/e2e/suite/command/tag.go index 871ee9356..8dae5cf8e 100644 --- a/test/e2e/suite/command/tag.go +++ b/test/e2e/suite/command/tag.go @@ -16,7 +16,12 @@ limitations under the License. package command import ( + "fmt" + "regexp" + . "github.com/onsi/ginkgo/v2" + "github.com/onsi/gomega" + "github.com/onsi/gomega/gbytes" "oras.land/oras/test/e2e/internal/testdata/multi_arch" . "oras.land/oras/test/e2e/internal/utils" ) @@ -35,22 +40,25 @@ var _ = Describe("ORAS beginners:", func() { }) var _ = Describe("Common registry users:", func() { - var tagAndValidate = func(reg string, repo string, tagOrDigest string, tags ...string) { - ORAS(append([]string{"tag", Reference(reg, repo, tagOrDigest)}, tags...)...).MatchKeyWords(tags...).Exec() + var tagAndValidate = func(reg string, repo string, tagOrDigest string, digest string, tags ...string) { + out := ORAS(append([]string{"tag", Reference(reg, repo, tagOrDigest)}, tags...)...).MatchKeyWords(tags...).Exec().Out + hint := regexp.QuoteMeta(fmt.Sprintf("Tagging [registry] %s", Reference(reg, repo, digest))) + gomega.Expect(out).To(gbytes.Say(hint)) + gomega.Expect(out).NotTo(gbytes.Say(hint)) // should only say hint once ORAS("repo", "tags", Reference(reg, repo, "")).MatchKeyWords(tags...).Exec() } When("running `tag`", func() { It("should add a tag to an existent manifest when providing tag reference", func() { - tagAndValidate(Host, ImageRepo, multi_arch.Tag, "tag-via-tag") + tagAndValidate(Host, ImageRepo, multi_arch.Tag, multi_arch.Digest, "tag-via-tag") }) It("should add a tag to an existent manifest when providing digest reference", func() { - tagAndValidate(Host, ImageRepo, multi_arch.Digest, "tag-via-digest") + tagAndValidate(Host, ImageRepo, multi_arch.Digest, multi_arch.Digest, "tag-via-digest") }) It("should add multiple tags to an existent manifest when providing digest reference", func() { - tagAndValidate(Host, ImageRepo, multi_arch.Digest, "tag1-via-digest", "tag2-via-digest", "tag3-via-digest") + tagAndValidate(Host, ImageRepo, multi_arch.Digest, multi_arch.Digest, "tag1-via-digest", "tag2-via-digest", "tag3-via-digest") }) It("should add multiple tags to an existent manifest when providing tag reference", func() { - tagAndValidate(Host, ImageRepo, multi_arch.Tag, "tag1-via-tag", "tag1-via-tag", "tag1-via-tag") + tagAndValidate(Host, ImageRepo, multi_arch.Tag, multi_arch.Digest, "tag1-via-tag", "tag1-via-tag", "tag1-via-tag") }) }) })