From faa444107780e7e762070061cb677b711c044c02 Mon Sep 17 00:00:00 2001 From: Terry Howe Date: Sun, 7 Jul 2024 07:05:09 -0400 Subject: [PATCH] refactor: Create text copy handler Signed-off-by: Terry Howe --- cmd/oras/internal/display/handler.go | 7 ++-- cmd/oras/internal/display/status/interface.go | 9 ++++ cmd/oras/internal/display/status/text.go | 42 +++++++++++++++++++ cmd/oras/internal/display/status/utils.go | 9 ++++ cmd/oras/root/cp.go | 31 ++++---------- cmd/oras/root/cp_test.go | 12 ++++-- 6 files changed, 82 insertions(+), 28 deletions(-) diff --git a/cmd/oras/internal/display/handler.go b/cmd/oras/internal/display/handler.go index 22be8d5ad..d1c8e09fb 100644 --- a/cmd/oras/internal/display/handler.go +++ b/cmd/oras/internal/display/handler.go @@ -20,6 +20,7 @@ import ( "os" ocispec "github.com/opencontainers/image-spec/specs-go/v1" + fetcher "oras.land/oras-go/v2/content" "oras.land/oras/cmd/oras/internal/display/content" "oras.land/oras/cmd/oras/internal/display/metadata" @@ -173,7 +174,7 @@ func NewManifestPushHandler(printer *output.Printer) metadata.ManifestPushHandle return text.NewManifestPushHandler(printer) } -// NewCopyHandler returns a copy handler. -func NewCopyHandler(printer *output.Printer) metadata.CopyHandler { - return text.NewCopyHandler(printer) +// NewCopyHandler returns copy handlers. +func NewCopyHandler(printer *output.Printer, fetcher fetcher.Fetcher) (status.CopyHandler, metadata.CopyHandler) { + return status.NewTextCopyHandler(printer, fetcher), text.NewCopyHandler(printer) } diff --git a/cmd/oras/internal/display/status/interface.go b/cmd/oras/internal/display/status/interface.go index 4527bcc97..a254af38e 100644 --- a/cmd/oras/internal/display/status/interface.go +++ b/cmd/oras/internal/display/status/interface.go @@ -16,6 +16,7 @@ limitations under the License. package status import ( + "context" ocispec "github.com/opencontainers/image-spec/specs-go/v1" "oras.land/oras-go/v2" "oras.land/oras-go/v2/content" @@ -51,3 +52,11 @@ type PullHandler interface { // OnNodeSkipped is called when a node is skipped. OnNodeSkipped(desc ocispec.Descriptor) error } + +// CopyHandler handles status output for cp command. +type CopyHandler interface { + OnCopySkipped(ctx context.Context, desc ocispec.Descriptor) error + PreCopy(ctx context.Context, desc ocispec.Descriptor) error + PostCopy(ctx context.Context, desc ocispec.Descriptor) error + OnMounted(ctx context.Context, desc ocispec.Descriptor) error +} diff --git a/cmd/oras/internal/display/status/text.go b/cmd/oras/internal/display/status/text.go index 403155e9e..c2f35af62 100644 --- a/cmd/oras/internal/display/status/text.go +++ b/cmd/oras/internal/display/status/text.go @@ -118,3 +118,45 @@ func NewTextPullHandler(printer *output.Printer) PullHandler { printer: printer, } } + +// TextCopyHandler handles text status output for push events. +type TextCopyHandler struct { + printer *output.Printer + committed *sync.Map + fetcher content.Fetcher +} + +// NewTextCopyHandler returns a new handler for push command. +func NewTextCopyHandler(printer *output.Printer, fetcher content.Fetcher) CopyHandler { + return &TextCopyHandler{ + printer: printer, + fetcher: fetcher, + committed: &sync.Map{}, + } +} + +// OnCopySkipped is called when an object already exists. +func (ch *TextCopyHandler) OnCopySkipped(_ context.Context, desc ocispec.Descriptor) error { + ch.committed.Store(desc.Digest.String(), desc.Annotations[ocispec.AnnotationTitle]) + return ch.printer.PrintStatus(desc, copyPromptExists) +} + +// PreCopy implements PreCopy of CopyHandler. +func (ch *TextCopyHandler) PreCopy(_ context.Context, desc ocispec.Descriptor) error { + return ch.printer.PrintStatus(desc, copyPromptCopying) +} + +// PostCopy implements PostCopy of CopyHandler. +func (ch *TextCopyHandler) PostCopy(ctx context.Context, desc ocispec.Descriptor) error { + ch.committed.Store(desc.Digest.String(), desc.Annotations[ocispec.AnnotationTitle]) + if err := output.PrintSuccessorStatus(ctx, desc, ch.fetcher, ch.committed, ch.printer.StatusPrinter(copyPromptSkipped)); err != nil { + return err + } + return ch.printer.PrintStatus(desc, copyPromptCopied) +} + +// OnMounted implements OnMounted of CopyHandler. +func (ch *TextCopyHandler) OnMounted(_ context.Context, desc ocispec.Descriptor) error { + ch.committed.Store(desc.Digest.String(), desc.Annotations[ocispec.AnnotationTitle]) + return ch.printer.PrintStatus(desc, copyPromptMounted) +} diff --git a/cmd/oras/internal/display/status/utils.go b/cmd/oras/internal/display/status/utils.go index 40e857c42..0b7f244e1 100644 --- a/cmd/oras/internal/display/status/utils.go +++ b/cmd/oras/internal/display/status/utils.go @@ -32,3 +32,12 @@ const ( PushPromptSkipped = "Skipped " PushPromptExists = "Exists " ) + +// Prompts for cp events. +const ( + copyPromptExists = "Exists " + copyPromptCopying = "Copying" + copyPromptCopied = "Copied " + copyPromptSkipped = "Skipped" + copyPromptMounted = "Mounted" +) diff --git a/cmd/oras/root/cp.go b/cmd/oras/root/cp.go index 3a9ab2ef4..5f04adf67 100644 --- a/cmd/oras/root/cp.go +++ b/cmd/oras/root/cp.go @@ -19,6 +19,7 @@ import ( "context" "encoding/json" "fmt" + "oras.land/oras/cmd/oras/internal/display/status" "slices" "strings" "sync" @@ -127,8 +128,9 @@ func runCopy(cmd *cobra.Command, opts *copyOptions) error { return err } ctx = registryutil.WithScopeHint(ctx, dst, auth.ActionPull, auth.ActionPush) + copyHandler, handler := display.NewCopyHandler(opts.Printer, dst) - desc, err := doCopy(ctx, opts.Printer, src, dst, opts) + desc, err := doCopy(ctx, copyHandler, src, dst, opts) if err != nil { return err } @@ -142,7 +144,6 @@ func runCopy(cmd *cobra.Command, opts *copyOptions) error { if len(opts.extraRefs) != 0 { tagNOpts := oras.DefaultTagNOptions tagNOpts.Concurrency = opts.concurrency - handler := display.NewCopyHandler(opts.Printer) tagListener := listener.NewTaggedListener(dst, handler.OnTagged) if _, err = oras.TagN(ctx, tagListener, opts.To.Reference, opts.extraRefs, tagNOpts); err != nil { return err @@ -154,7 +155,7 @@ func runCopy(cmd *cobra.Command, opts *copyOptions) error { return nil } -func doCopy(ctx context.Context, printer *output.Printer, src oras.ReadOnlyGraphTarget, dst oras.GraphTarget, opts *copyOptions) (ocispec.Descriptor, error) { +func doCopy(ctx context.Context, copyHandler status.CopyHandler, src oras.ReadOnlyGraphTarget, dst oras.GraphTarget, opts *copyOptions) (ocispec.Descriptor, error) { // Prepare copy options committed := &sync.Map{} extendedCopyOptions := oras.DefaultExtendedCopyOptions @@ -178,25 +179,11 @@ func doCopy(ctx context.Context, printer *output.Printer, src oras.ReadOnlyGraph } } if opts.TTY == nil { - // none TTY output - extendedCopyOptions.OnCopySkipped = func(ctx context.Context, desc ocispec.Descriptor) error { - committed.Store(desc.Digest.String(), desc.Annotations[ocispec.AnnotationTitle]) - return printer.PrintStatus(desc, promptExists) - } - extendedCopyOptions.PreCopy = func(ctx context.Context, desc ocispec.Descriptor) error { - return printer.PrintStatus(desc, promptCopying) - } - extendedCopyOptions.PostCopy = func(ctx context.Context, desc ocispec.Descriptor) error { - committed.Store(desc.Digest.String(), desc.Annotations[ocispec.AnnotationTitle]) - if err := output.PrintSuccessorStatus(ctx, desc, dst, committed, printer.StatusPrinter(promptSkipped)); err != nil { - return err - } - return printer.PrintStatus(desc, promptCopied) - } - extendedCopyOptions.OnMounted = func(ctx context.Context, desc ocispec.Descriptor) error { - committed.Store(desc.Digest.String(), desc.Annotations[ocispec.AnnotationTitle]) - return printer.PrintStatus(desc, promptMounted) - } + // no TTY output + extendedCopyOptions.OnCopySkipped = copyHandler.OnCopySkipped + extendedCopyOptions.PreCopy = copyHandler.PreCopy + extendedCopyOptions.PostCopy = copyHandler.PostCopy + extendedCopyOptions.OnMounted = copyHandler.OnMounted } else { // TTY output tracked, err := track.NewTarget(dst, promptCopying, promptCopied, opts.TTY) diff --git a/cmd/oras/root/cp_test.go b/cmd/oras/root/cp_test.go index 16bb0010d..c347f05c9 100644 --- a/cmd/oras/root/cp_test.go +++ b/cmd/oras/root/cp_test.go @@ -24,6 +24,7 @@ import ( "net/http" "net/http/httptest" "net/url" + "oras.land/oras/cmd/oras/internal/display/status" "oras.land/oras/cmd/oras/internal/output" "os" "strings" @@ -133,8 +134,9 @@ func Test_doCopy(t *testing.T) { dst := memory.New() builder := &strings.Builder{} printer := output.NewPrinter(builder, os.Stderr, opts.Verbose) + handler := status.NewTextCopyHandler(printer, dst) // test - _, err = doCopy(context.Background(), printer, memStore, dst, &opts) + _, err = doCopy(context.Background(), handler, memStore, dst, &opts) if err != nil { t.Fatal(err) } @@ -157,8 +159,10 @@ func Test_doCopy_skipped(t *testing.T) { opts.From.Reference = memDesc.Digest.String() builder := &strings.Builder{} printer := output.NewPrinter(builder, os.Stderr, opts.Verbose) + handler := status.NewTextCopyHandler(printer, memStore) + // test - _, err = doCopy(context.Background(), printer, memStore, memStore, &opts) + _, err = doCopy(context.Background(), handler, memStore, memStore, &opts) if err != nil { t.Fatal(err) } @@ -192,8 +196,10 @@ func Test_doCopy_mounted(t *testing.T) { to.PlainHTTP = true builder := &strings.Builder{} printer := output.NewPrinter(builder, os.Stderr, opts.Verbose) + handler := status.NewTextCopyHandler(printer, to) + // test - _, err = doCopy(context.Background(), printer, from, to, &opts) + _, err = doCopy(context.Background(), handler, from, to, &opts) if err != nil { t.Fatal(err) }