From 63d4954cc688d951c6efb0c9261eaaa9a540ffa3 Mon Sep 17 00:00:00 2001 From: Terry Howe Date: Sun, 17 Mar 2024 09:45:16 -0600 Subject: [PATCH 1/3] Create printer object to manage output Signed-off-by: Terry Howe --- cmd/oras/internal/display/status/print.go | 26 ++++++++++++++++++----- cmd/oras/root/manifest/push.go | 3 ++- 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/cmd/oras/internal/display/status/print.go b/cmd/oras/internal/display/status/print.go index 85fcd21e9..9180d7549 100644 --- a/cmd/oras/internal/display/status/print.go +++ b/cmd/oras/internal/display/status/print.go @@ -27,17 +27,33 @@ import ( "oras.land/oras-go/v2/registry" ) -var printLock sync.Mutex +type Printer struct { + printLock sync.Mutex + verbose bool +} + +var printer Printer + +// NewPrinter creates a printer object +func NewPrinter(verbose bool) *Printer { + printer = Printer{verbose: verbose} + return &printer +} + +// Print objects to display concurrent-safely. +func (p *Printer) Print(a ...any) error { + p.printLock.Lock() + defer p.printLock.Unlock() + _, err := fmt.Println(a...) + return err +} // PrintFunc is the function type returned by StatusPrinter. type PrintFunc func(ocispec.Descriptor) error // Print objects to display concurrent-safely. func Print(a ...any) error { - printLock.Lock() - defer printLock.Unlock() - _, err := fmt.Println(a...) - return err + return printer.Print(a...) } // StatusPrinter returns a tracking function for transfer status. diff --git a/cmd/oras/root/manifest/push.go b/cmd/oras/root/manifest/push.go index 8afee2b0b..83ac051fc 100644 --- a/cmd/oras/root/manifest/push.go +++ b/cmd/oras/root/manifest/push.go @@ -152,6 +152,7 @@ func pushManifest(cmd *cobra.Command, opts pushOptions) error { return err } verbose := opts.Verbose && !opts.OutputDescriptor + printer := status.NewPrinter(verbose) if match { if err := status.PrintStatus(desc, "Exists", verbose); err != nil { return err @@ -184,7 +185,7 @@ func pushManifest(cmd *cobra.Command, opts pushOptions) error { } return opts.Output(os.Stdout, descJSON) } - status.Print("Pushed", opts.AnnotatedReference()) + printer.Print("Pushed", opts.AnnotatedReference()) if len(opts.extraRefs) != 0 { if _, err = oras.TagBytesN(ctx, status.NewTagStatusPrinter(target), mediaType, contentBytes, opts.extraRefs, tagBytesNOpts); err != nil { return err From df149c9b2fc6ba84c30fe5a7cfd5edf23d69f1f2 Mon Sep 17 00:00:00 2001 From: Terry Howe Date: Mon, 25 Mar 2024 09:52:51 -0600 Subject: [PATCH 2/3] Replace PrintVerbose and PrintStatus methods Signed-off-by: Terry Howe --- Makefile | 1 + cmd/oras/internal/display/handler.go | 8 ++--- .../display/metadata/model/descriptor.go | 24 ++++++++++++++ cmd/oras/internal/display/status/convert.go | 32 ------------------- cmd/oras/internal/display/status/text.go | 31 +++++++++--------- cmd/oras/root/attach.go | 4 ++- cmd/oras/root/blob/push.go | 21 ++++++------ cmd/oras/root/cp.go | 14 +++++--- cmd/oras/root/manifest/push.go | 13 +++----- cmd/oras/root/pull.go | 24 ++++++++------ cmd/oras/root/push.go | 3 +- 11 files changed, 87 insertions(+), 88 deletions(-) delete mode 100644 cmd/oras/internal/display/status/convert.go diff --git a/Makefile b/Makefile index ca72c5b4b..07e857e43 100644 --- a/Makefile +++ b/Makefile @@ -33,6 +33,7 @@ LDFLAGS += -X $(PROJECT_PKG)/internal/version.GitTreeState=${GIT_DIRTY} .PHONY: test test: tidy vendor check-encoding ## tidy and run tests + $(GO_EXE) build ./... $(GO_EXE) test -race -v -coverprofile=coverage.txt -covermode=atomic -coverpkg=./... ./... .PHONY: teste2e diff --git a/cmd/oras/internal/display/handler.go b/cmd/oras/internal/display/handler.go index 56e05ae63..9b186861d 100644 --- a/cmd/oras/internal/display/handler.go +++ b/cmd/oras/internal/display/handler.go @@ -26,12 +26,12 @@ import ( ) // NewPushHandler returns status and metadata handlers for push command. -func NewPushHandler(format string, tty *os.File, verbose bool) (status.PushHandler, metadata.PushHandler) { +func NewPushHandler(format string, tty *os.File, printer *status.Printer) (status.PushHandler, metadata.PushHandler) { var statusHandler status.PushHandler if tty != nil { statusHandler = status.NewTTYPushHandler(tty) } else if format == "" { - statusHandler = status.NewTextPushHandler(verbose) + statusHandler = status.NewTextPushHandler(printer) } else { statusHandler = status.NewDiscardHandler() } @@ -50,12 +50,12 @@ func NewPushHandler(format string, tty *os.File, verbose bool) (status.PushHandl } // NewAttachHandler returns status and metadata handlers for attach command. -func NewAttachHandler(format string, tty *os.File, verbose bool) (status.AttachHandler, metadata.AttachHandler) { +func NewAttachHandler(format string, tty *os.File, printer *status.Printer) (status.AttachHandler, metadata.AttachHandler) { var statusHandler status.AttachHandler if tty != nil { statusHandler = status.NewTTYAttachHandler(tty) } else if format == "" { - statusHandler = status.NewTextAttachHandler(verbose) + statusHandler = status.NewTextAttachHandler(printer) } else { statusHandler = status.NewDiscardHandler() } diff --git a/cmd/oras/internal/display/metadata/model/descriptor.go b/cmd/oras/internal/display/metadata/model/descriptor.go index 646293c75..c4f254054 100644 --- a/cmd/oras/internal/display/metadata/model/descriptor.go +++ b/cmd/oras/internal/display/metadata/model/descriptor.go @@ -18,6 +18,7 @@ package model import ( "github.com/opencontainers/go-digest" ocispec "github.com/opencontainers/image-spec/specs-go/v1" + "oras.land/oras/cmd/oras/internal/display/status" ) // DigestReference is a reference to an artifact with digest. @@ -70,3 +71,26 @@ func FromDescriptor(name string, desc ocispec.Descriptor) Descriptor { } return ret } + +// ShortDigest converts the digest of the descriptor to a short form for displaying. +func ShortDigest(desc ocispec.Descriptor) (digestString string) { + digestString = desc.Digest.String() + if err := desc.Digest.Validate(); err == nil { + if algo := desc.Digest.Algorithm(); algo == digest.SHA256 { + digestString = desc.Digest.Encoded()[:12] + } + } + return digestString +} + +// PrintDescriptor prints descriptor status and name. +func PrintDescriptor(printer *status.Printer, desc ocispec.Descriptor, status string) { + name, ok := desc.Annotations[ocispec.AnnotationTitle] + if !ok { + name = desc.MediaType + printer.PrintVerbose(status, ShortDigest(desc), name) + return + } + printer.Print(status, ShortDigest(desc), name) + return +} diff --git a/cmd/oras/internal/display/status/convert.go b/cmd/oras/internal/display/status/convert.go deleted file mode 100644 index b34ff383e..000000000 --- a/cmd/oras/internal/display/status/convert.go +++ /dev/null @@ -1,32 +0,0 @@ -/* -Copyright The ORAS Authors. -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - -http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package status - -import ( - "github.com/opencontainers/go-digest" - ocispec "github.com/opencontainers/image-spec/specs-go/v1" -) - -// ShortDigest converts the digest of the descriptor to a short form for displaying. -func ShortDigest(desc ocispec.Descriptor) (digestString string) { - digestString = desc.Digest.String() - if err := desc.Digest.Validate(); err == nil { - if algo := desc.Digest.Algorithm(); algo == digest.SHA256 { - digestString = desc.Digest.Encoded()[:12] - } - } - return digestString -} diff --git a/cmd/oras/internal/display/status/text.go b/cmd/oras/internal/display/status/text.go index a62c2a313..a7f90a7a2 100644 --- a/cmd/oras/internal/display/status/text.go +++ b/cmd/oras/internal/display/status/text.go @@ -18,6 +18,7 @@ package status import ( "context" "fmt" + "oras.land/oras/cmd/oras/internal/display/metadata/model" "sync" ocispec "github.com/opencontainers/image-spec/specs-go/v1" @@ -27,23 +28,20 @@ import ( // TextPushHandler handles text status output for push events. type TextPushHandler struct { - verbose bool + printer *Printer } // NewTextPushHandler returns a new handler for push command. -func NewTextPushHandler(verbose bool) PushHandler { +func NewTextPushHandler(printer *Printer) PushHandler { return &TextPushHandler{ - verbose: verbose, + printer: printer, } } // OnFileLoading is called when a file is being prepared for upload. func (ph *TextPushHandler) OnFileLoading(name string) error { - if !ph.verbose { - return nil - } - _, err := fmt.Println("Preparing", name) - return err + printer.PrintVerbose("Preparing", name) + return nil } // OnEmptyArtifact is called when an empty artifact is being uploaded. @@ -68,21 +66,22 @@ func (ph *TextPushHandler) UpdateCopyOptions(opts *oras.CopyGraphOptions, fetche committed := &sync.Map{} opts.OnCopySkipped = func(ctx context.Context, desc ocispec.Descriptor) error { committed.Store(desc.Digest.String(), desc.Annotations[ocispec.AnnotationTitle]) - return PrintStatus(desc, promptExists, ph.verbose) + model.PrintDescriptor(ph.printer, desc, promptExists) + return nil } opts.PreCopy = func(ctx context.Context, desc ocispec.Descriptor) error { - return PrintStatus(desc, promptUploading, ph.verbose) + model.PrintDescriptor(ph.printer, desc, promptUploading) + return nil } opts.PostCopy = func(ctx context.Context, desc ocispec.Descriptor) error { committed.Store(desc.Digest.String(), desc.Annotations[ocispec.AnnotationTitle]) - if err := PrintSuccessorStatus(ctx, desc, fetcher, committed, StatusPrinter(promptSkipped, ph.verbose)); err != nil { - return err - } - return PrintStatus(desc, promptUploaded, ph.verbose) + PrintSuccessorStatus(ctx, desc, fetcher, committed, promptSkipped, ph.printer) + model.PrintDescriptor(ph.printer, desc, promptUploaded) + return nil } } // NewTextAttachHandler returns a new handler for attach command. -func NewTextAttachHandler(verbose bool) AttachHandler { - return NewTextPushHandler(verbose) +func NewTextAttachHandler(printer *Printer) AttachHandler { + return NewTextPushHandler(printer) } diff --git a/cmd/oras/root/attach.go b/cmd/oras/root/attach.go index b1711e869..ce796cbac 100644 --- a/cmd/oras/root/attach.go +++ b/cmd/oras/root/attach.go @@ -19,6 +19,7 @@ import ( "context" "errors" "fmt" + "oras.land/oras/cmd/oras/internal/display/status" ocispec "github.com/opencontainers/image-spec/specs-go/v1" "github.com/spf13/cobra" @@ -113,7 +114,8 @@ func runAttach(cmd *cobra.Command, opts *attachOptions) error { Recommendation: `To attach to an existing artifact, please provide files via argument or annotations via flag "--annotation". Run "oras attach -h" for more options and examples`, } } - displayStatus, displayMetadata := display.NewAttachHandler(opts.Template, opts.TTY, opts.Verbose) + printer := status.NewPrinter(opts.Verbose) + displayStatus, displayMetadata := display.NewAttachHandler(opts.Template, opts.TTY, printer) // prepare manifest store, err := file.New("") diff --git a/cmd/oras/root/blob/push.go b/cmd/oras/root/blob/push.go index 7f5536e36..18195b79a 100644 --- a/cmd/oras/root/blob/push.go +++ b/cmd/oras/root/blob/push.go @@ -18,8 +18,8 @@ package blob import ( "context" "errors" - "fmt" "io" + "oras.land/oras/cmd/oras/internal/display/metadata/model" "os" ocispec "github.com/opencontainers/image-spec/specs-go/v1" @@ -119,11 +119,11 @@ func pushBlob(cmd *cobra.Command, opts *pushBlobOptions) (err error) { if err != nil { return err } - verbose := opts.Verbose && !opts.OutputDescriptor + printer := status.NewPrinter(opts.Verbose && !opts.OutputDescriptor) if exists { - err = status.PrintStatus(desc, "Exists", verbose) + printer.PrintVerbose(desc, "Exists") } else { - err = opts.doPush(ctx, target, desc, rc) + err = opts.doPush(ctx, target, desc, rc, printer) } if err != nil { return err @@ -137,21 +137,20 @@ func pushBlob(cmd *cobra.Command, opts *pushBlobOptions) (err error) { return opts.Output(os.Stdout, descJSON) } - fmt.Println("Pushed", opts.AnnotatedReference()) - fmt.Println("Digest:", desc.Digest) + printer.Print("Pushed", opts.AnnotatedReference()) + printer.Print("Digest:", desc.Digest) return nil } -func (opts *pushBlobOptions) doPush(ctx context.Context, t oras.Target, desc ocispec.Descriptor, r io.Reader) error { +func (opts *pushBlobOptions) doPush(ctx context.Context, t oras.Target, desc ocispec.Descriptor, r io.Reader, printer *status.Printer) error { if opts.TTY == nil { // none TTY output - if err := status.PrintStatus(desc, "Uploading", opts.Verbose); err != nil { - return err - } + model.PrintDescriptor(printer, desc, "Uploading") if err := t.Push(ctx, desc, r); err != nil { return err } - return status.PrintStatus(desc, "Uploaded ", opts.Verbose) + model.PrintDescriptor(printer, desc, "Uploaded ") + return nil } // TTY output diff --git a/cmd/oras/root/cp.go b/cmd/oras/root/cp.go index a08ce4695..088ca3c46 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/metadata/model" "slices" "strings" "sync" @@ -173,24 +174,29 @@ func doCopy(ctx context.Context, src oras.ReadOnlyGraphTarget, dst oras.GraphTar } } if opts.TTY == nil { + printer := status.NewPrinter(opts.Verbose) // none TTY output extendedCopyOptions.OnCopySkipped = func(ctx context.Context, desc ocispec.Descriptor) error { committed.Store(desc.Digest.String(), desc.Annotations[ocispec.AnnotationTitle]) - return status.PrintStatus(desc, promptExists, opts.Verbose) + model.PrintDescriptor(printer, desc, promptExists) + return nil } extendedCopyOptions.PreCopy = func(ctx context.Context, desc ocispec.Descriptor) error { - return status.PrintStatus(desc, promptCopying, opts.Verbose) + model.PrintDescriptor(printer, desc, promptCopying) + return nil } extendedCopyOptions.PostCopy = func(ctx context.Context, desc ocispec.Descriptor) error { committed.Store(desc.Digest.String(), desc.Annotations[ocispec.AnnotationTitle]) if err := status.PrintSuccessorStatus(ctx, desc, dst, committed, status.StatusPrinter(promptSkipped, opts.Verbose)); err != nil { return err } - return status.PrintStatus(desc, promptCopied, opts.Verbose) + model.PrintDescriptor(printer, desc, promptCopied) + return nil } extendedCopyOptions.OnMounted = func(ctx context.Context, desc ocispec.Descriptor) error { committed.Store(desc.Digest.String(), desc.Annotations[ocispec.AnnotationTitle]) - return status.PrintStatus(desc, promptMounted, opts.Verbose) + model.PrintDescriptor(printer, desc, promptMounted) + return nil } } else { // TTY output diff --git a/cmd/oras/root/manifest/push.go b/cmd/oras/root/manifest/push.go index 83ac051fc..cf9fc97b0 100644 --- a/cmd/oras/root/manifest/push.go +++ b/cmd/oras/root/manifest/push.go @@ -19,6 +19,7 @@ import ( "context" "errors" "fmt" + "oras.land/oras/cmd/oras/internal/display/metadata/model" "os" "strings" @@ -154,19 +155,13 @@ func pushManifest(cmd *cobra.Command, opts pushOptions) error { verbose := opts.Verbose && !opts.OutputDescriptor printer := status.NewPrinter(verbose) if match { - if err := status.PrintStatus(desc, "Exists", verbose); err != nil { - return err - } + model.PrintDescriptor(printer, desc, "Exists") } else { - if err = status.PrintStatus(desc, "Uploading", verbose); err != nil { - return err - } + model.PrintDescriptor(printer, desc, "Uploading") if _, err := oras.TagBytes(ctx, target, mediaType, contentBytes, ref); err != nil { return err } - if err = status.PrintStatus(desc, "Uploaded ", verbose); err != nil { - return err - } + model.PrintDescriptor(printer, desc, "Uploaded ") } tagBytesNOpts := oras.DefaultTagBytesNOptions diff --git a/cmd/oras/root/pull.go b/cmd/oras/root/pull.go index da2b99d6c..de911609a 100644 --- a/cmd/oras/root/pull.go +++ b/cmd/oras/root/pull.go @@ -20,6 +20,7 @@ import ( "errors" "fmt" "io" + "oras.land/oras/cmd/oras/internal/display/metadata/model" "os" "sync" "sync/atomic" @@ -172,6 +173,7 @@ func doPull(ctx context.Context, src oras.ReadOnlyTarget, dst oras.GraphTarget, promptDownloaded = "Downloaded " ) + printer := status.NewPrinter(po.Verbose) dst, err = getTrackedTarget(dst, po.TTY, "Downloading", "Pulled ") if err != nil { return ocispec.Descriptor{}, false, err @@ -189,9 +191,7 @@ func doPull(ctx context.Context, src oras.ReadOnlyTarget, dst oras.GraphTarget, } if po.TTY == nil { // none TTY, print status log for first-time fetching - if err := status.PrintStatus(target, promptDownloading, po.Verbose); err != nil { - return nil, err - } + model.PrintDescriptor(printer, target, promptDownloading) } rc, err := fetcher.Fetch(ctx, target) if err != nil { @@ -204,7 +204,8 @@ func doPull(ctx context.Context, src oras.ReadOnlyTarget, dst oras.GraphTarget, }() if po.TTY == nil { // none TTY, add logs for processing manifest - return rc, status.PrintStatus(target, promptProcessing, po.Verbose) + model.PrintDescriptor(printer, target, promptProcessing) + return rc, nil } return rc, nil }) @@ -247,7 +248,7 @@ func doPull(ctx context.Context, src oras.ReadOnlyTarget, dst oras.GraphTarget, } if len(ss) == 0 { // skip s if it is unnamed AND has no successors. - if err := printOnce(&printed, s, promptSkipped, po.Verbose, dst); err != nil { + if err := printOnce(&printed, s, promptSkipped, printer, dst); err != nil { return nil, err } continue @@ -265,7 +266,8 @@ func doPull(ctx context.Context, src oras.ReadOnlyTarget, dst oras.GraphTarget, } if po.TTY == nil { // none TTY, print status log for downloading - return status.PrintStatus(desc, promptDownloading, po.Verbose) + model.PrintDescriptor(printer, desc, promptDownloading) + return nil } // TTY return nil @@ -278,7 +280,7 @@ func doPull(ctx context.Context, src oras.ReadOnlyTarget, dst oras.GraphTarget, } for _, s := range successors { if _, ok := s.Annotations[ocispec.AnnotationTitle]; ok { - if err := printOnce(&printed, s, promptRestored, po.Verbose, dst); err != nil { + if err := printOnce(&printed, s, promptRestored, printer, dst); err != nil { return err } } @@ -291,7 +293,8 @@ func doPull(ctx context.Context, src oras.ReadOnlyTarget, dst oras.GraphTarget, name = desc.MediaType } printed.Store(generateContentKey(desc), true) - return status.Print(promptDownloaded, status.ShortDigest(desc), name) + printer.Print(promptDownloaded, model.ShortDigest(desc), name) + return nil } // Copy @@ -305,7 +308,7 @@ func generateContentKey(desc ocispec.Descriptor) string { return desc.Digest.String() + desc.Annotations[ocispec.AnnotationTitle] } -func printOnce(printed *sync.Map, s ocispec.Descriptor, msg string, verbose bool, dst any) error { +func printOnce(printed *sync.Map, s ocispec.Descriptor, msg string, printer *status.Printer, dst any) error { if _, loaded := printed.LoadOrStore(generateContentKey(s), true); loaded { return nil } @@ -315,7 +318,8 @@ func printOnce(printed *sync.Map, s ocispec.Descriptor, msg string, verbose bool } // none TTY - return status.PrintStatus(s, msg, verbose) + printer.PrintVerbose(s, msg) + return nil } func getTrackedTarget(gt oras.GraphTarget, tty *os.File, actionPrompt, doneprompt string) (oras.GraphTarget, error) { diff --git a/cmd/oras/root/push.go b/cmd/oras/root/push.go index f41c9f8bc..28392dc2e 100644 --- a/cmd/oras/root/push.go +++ b/cmd/oras/root/push.go @@ -138,7 +138,8 @@ func runPush(cmd *cobra.Command, opts *pushOptions) error { if err != nil { return err } - displayStatus, displayMetadata := display.NewPushHandler(opts.Template, opts.TTY, opts.Verbose) + printer := status.NewPrinter(opts.Verbose) + displayStatus, displayMetadata := display.NewPushHandler(opts.Template, opts.TTY, printer) // prepare pack packOpts := oras.PackManifestOptions{ From 1db763ff56922b840fb70d940dc673517168d9c1 Mon Sep 17 00:00:00 2001 From: Terry Howe Date: Mon, 8 Apr 2024 08:35:49 -0600 Subject: [PATCH 3/3] Latest changes for verbose printing Signed-off-by: Terry Howe --- .../display/metadata/model/descriptor.go | 19 +++++ cmd/oras/internal/display/status/print.go | 71 +++++++------------ .../internal/display/status/track/target.go | 2 +- cmd/oras/root/cp.go | 10 +-- 4 files changed, 47 insertions(+), 55 deletions(-) diff --git a/cmd/oras/internal/display/metadata/model/descriptor.go b/cmd/oras/internal/display/metadata/model/descriptor.go index c4f254054..780a48530 100644 --- a/cmd/oras/internal/display/metadata/model/descriptor.go +++ b/cmd/oras/internal/display/metadata/model/descriptor.go @@ -16,9 +16,12 @@ limitations under the License. package model import ( + "context" "github.com/opencontainers/go-digest" ocispec "github.com/opencontainers/image-spec/specs-go/v1" + "oras.land/oras-go/v2/content" "oras.land/oras/cmd/oras/internal/display/status" + "sync" ) // DigestReference is a reference to an artifact with digest. @@ -94,3 +97,19 @@ func PrintDescriptor(printer *status.Printer, desc ocispec.Descriptor, status st printer.Print(status, ShortDigest(desc), name) return } + +// PrintSuccessorStatus prints transfer status of successors. +func PrintSuccessorStatus(ctx context.Context, desc ocispec.Descriptor, fetcher content.Fetcher, committed *sync.Map, prompt string, printer *status.Printer) error { + successors, err := content.Successors(ctx, fetcher, desc) + if err != nil { + return err + } + for _, s := range successors { + name := s.Annotations[ocispec.AnnotationTitle] + if v, ok := committed.Load(s.Digest.String()); ok && v != name { + // Reprint status for deduplicated content + PrintDescriptor(printer, desc, prompt) + } + } + return nil +} diff --git a/cmd/oras/internal/display/status/print.go b/cmd/oras/internal/display/status/print.go index 9180d7549..8c44bca69 100644 --- a/cmd/oras/internal/display/status/print.go +++ b/cmd/oras/internal/display/status/print.go @@ -19,17 +19,18 @@ import ( "context" "fmt" "io" + "os" "sync" 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" ) type Printer struct { printLock sync.Mutex verbose bool + errors bool } var printer Printer @@ -41,57 +42,31 @@ func NewPrinter(verbose bool) *Printer { } // Print objects to display concurrent-safely. -func (p *Printer) Print(a ...any) error { +func (p *Printer) Print(a ...any) { p.printLock.Lock() defer p.printLock.Unlock() _, err := fmt.Println(a...) - return err -} - -// PrintFunc is the function type returned by StatusPrinter. -type PrintFunc func(ocispec.Descriptor) error - -// Print objects to display concurrent-safely. -func Print(a ...any) error { - return printer.Print(a...) -} - -// StatusPrinter returns a tracking function for transfer status. -func StatusPrinter(status string, verbose bool) PrintFunc { - return func(desc ocispec.Descriptor) error { - return PrintStatus(desc, status, verbose) + if err != nil { + if !p.errors { + p.errors = true + _, _ = fmt.Fprintf(os.Stderr, "Display output error: %w\n", err) + return + } } + return } -// PrintStatus prints transfer status. -func PrintStatus(desc ocispec.Descriptor, status string, verbose bool) error { - name, ok := desc.Annotations[ocispec.AnnotationTitle] - if !ok { - // no status for unnamed content - if !verbose { - return nil - } - name = desc.MediaType +// PrintVerbose display in verbose mode. +func (p *Printer) PrintVerbose(a ...any) { + if p.verbose { + p.Print(a...) } - return Print(status, ShortDigest(desc), name) + return } -// PrintSuccessorStatus prints transfer status of successors. -func PrintSuccessorStatus(ctx context.Context, desc ocispec.Descriptor, fetcher content.Fetcher, committed *sync.Map, print PrintFunc) error { - successors, err := content.Successors(ctx, fetcher, desc) - if err != nil { - return err - } - for _, s := range successors { - name := s.Annotations[ocispec.AnnotationTitle] - if v, ok := committed.Load(s.Digest.String()); ok && v != name { - // Reprint status for deduplicated content - if err := print(s); err != nil { - return err - } - } - } - return nil +// Print objects to display concurrent-safely. +func Print(a ...any) { + printer.Print(a...) } // NewTagStatusPrinter creates a wrapper type for printing tag status. @@ -135,13 +110,14 @@ func (p *tagManifestStatusForRepo) PushReference(ctx context.Context, expected o if p.printHint != nil { p.printHint.Do(func() { ref := p.refPrefix + "@" + expected.Digest.String() - _ = Print("Tagging", ref) + Print("Tagging", ref) }) } if err := p.Repository.PushReference(ctx, expected, content, reference); err != nil { return err } - return Print("Tagged", reference) + Print("Tagged", reference) + return nil } type tagManifestStatusForTarget struct { @@ -155,12 +131,13 @@ func (p *tagManifestStatusForTarget) Tag(ctx context.Context, desc ocispec.Descr if p.printHint != nil { p.printHint.Do(func() { ref := p.refPrefix + "@" + desc.Digest.String() - _ = Print("Tagging", ref) + Print("Tagging", ref) }) } if err := p.Target.Tag(ctx, desc, reference); err != nil { return err } - return Print("Tagged", reference) + Print("Tagged", reference) + return nil } diff --git a/cmd/oras/internal/display/status/track/target.go b/cmd/oras/internal/display/status/track/target.go index 95e087b73..1d39a533e 100644 --- a/cmd/oras/internal/display/status/track/target.go +++ b/cmd/oras/internal/display/status/track/target.go @@ -30,7 +30,7 @@ import ( type GraphTarget interface { oras.GraphTarget io.Closer - Prompt(desc ocispec.Descriptor, prompt string) error + Prompt(desc ocispec.Descriptor) error Inner() oras.GraphTarget } diff --git a/cmd/oras/root/cp.go b/cmd/oras/root/cp.go index 088ca3c46..40db0a806 100644 --- a/cmd/oras/root/cp.go +++ b/cmd/oras/root/cp.go @@ -19,7 +19,6 @@ import ( "context" "encoding/json" "fmt" - "oras.land/oras/cmd/oras/internal/display/metadata/model" "slices" "strings" "sync" @@ -33,6 +32,7 @@ import ( "oras.land/oras-go/v2/registry/remote" "oras.land/oras-go/v2/registry/remote/auth" "oras.land/oras/cmd/oras/internal/argument" + "oras.land/oras/cmd/oras/internal/display/metadata/model" "oras.land/oras/cmd/oras/internal/display/status" "oras.land/oras/cmd/oras/internal/display/status/track" oerrors "oras.land/oras/cmd/oras/internal/errors" @@ -187,9 +187,7 @@ func doCopy(ctx context.Context, src oras.ReadOnlyGraphTarget, dst oras.GraphTar } extendedCopyOptions.PostCopy = func(ctx context.Context, desc ocispec.Descriptor) error { committed.Store(desc.Digest.String(), desc.Annotations[ocispec.AnnotationTitle]) - if err := status.PrintSuccessorStatus(ctx, desc, dst, committed, status.StatusPrinter(promptSkipped, opts.Verbose)); err != nil { - return err - } + model.PrintSuccessorStatus(ctx, desc, dst, committed, promptSkipped, printer) model.PrintDescriptor(printer, desc, promptCopied) return nil } @@ -212,9 +210,7 @@ func doCopy(ctx context.Context, src oras.ReadOnlyGraphTarget, dst oras.GraphTar } extendedCopyOptions.PostCopy = func(ctx context.Context, desc ocispec.Descriptor) error { committed.Store(desc.Digest.String(), desc.Annotations[ocispec.AnnotationTitle]) - return status.PrintSuccessorStatus(ctx, desc, tracked, committed, func(desc ocispec.Descriptor) error { - return tracked.Prompt(desc, promptSkipped) - }) + return model.PrintSuccessorStatus(ctx, desc, tracked, committed, promptSkipped, nil) } extendedCopyOptions.OnMounted = func(ctx context.Context, desc ocispec.Descriptor) error { committed.Store(desc.Digest.String(), desc.Annotations[ocispec.AnnotationTitle])