From d327a75dc5ac912fac875c89183fbba55d58dbff Mon Sep 17 00:00:00 2001 From: Shiwei Zhang Date: Mon, 30 Dec 2024 15:20:46 +0800 Subject: [PATCH] feat!: migrate to the new progress package Signed-off-by: Shiwei Zhang --- .../display/status/progress/manager.go | 10 +-- .../display/status/progress/messenger.go | 10 +-- .../internal/display/status/track/reader.go | 28 +++++---- .../internal/display/status/track/target.go | 43 ++++++++----- cmd/oras/internal/display/status/tty.go | 58 +++++++++-------- cmd/oras/root/blob/fetch.go | 9 ++- cmd/oras/root/blob/push.go | 11 ++-- cmd/oras/root/cp.go | 45 -------------- internal/experimental/track/interface.go | 62 ------------------- internal/experimental/track/reader.go | 56 ----------------- 10 files changed, 98 insertions(+), 234 deletions(-) delete mode 100644 internal/experimental/track/interface.go delete mode 100644 internal/experimental/track/reader.go diff --git a/cmd/oras/internal/display/status/progress/manager.go b/cmd/oras/internal/display/status/progress/manager.go index db5981f03..19a627b4a 100644 --- a/cmd/oras/internal/display/status/progress/manager.go +++ b/cmd/oras/internal/display/status/progress/manager.go @@ -23,7 +23,7 @@ import ( ocispec "github.com/opencontainers/image-spec/specs-go/v1" "oras.land/oras/cmd/oras/internal/display/status/console" - "oras.land/oras/internal/experimental/track" + "oras.land/oras/internal/progress" ) const ( @@ -42,11 +42,11 @@ type manager struct { updating sync.WaitGroup renderDone chan struct{} renderClosed chan struct{} - prompt map[track.State]string + prompt map[progress.State]string } // NewManager initialized a new progress manager. -func NewManager(tty *os.File, prompt map[track.State]string) (track.Manager, error) { +func NewManager(tty *os.File, prompt map[progress.State]string) (progress.Manager, error) { c, err := console.NewConsole(tty) if err != nil { return nil, err @@ -99,7 +99,7 @@ func (m *manager) render() { } // Track appends a new status with 2-line space for rendering. -func (m *manager) Track(desc ocispec.Descriptor) (track.Tracker, error) { +func (m *manager) Track(desc ocispec.Descriptor) (progress.Tracker, error) { if m.closed() { return nil, errManagerStopped } @@ -114,7 +114,7 @@ func (m *manager) Track(desc ocispec.Descriptor) (track.Tracker, error) { return m.statusChan(s, desc), nil } -func (m *manager) statusChan(s *status, desc ocispec.Descriptor) track.Tracker { +func (m *manager) statusChan(s *status, desc ocispec.Descriptor) progress.Tracker { ch := make(chan *status, BufferSize) m.updating.Add(1) go func() { diff --git a/cmd/oras/internal/display/status/progress/messenger.go b/cmd/oras/internal/display/status/progress/messenger.go index 37da92314..8d1e201ec 100644 --- a/cmd/oras/internal/display/status/progress/messenger.go +++ b/cmd/oras/internal/display/status/progress/messenger.go @@ -20,7 +20,7 @@ import ( ocispec "github.com/opencontainers/image-spec/specs-go/v1" "oras.land/oras/cmd/oras/internal/display/status/progress/humanize" - "oras.land/oras/internal/experimental/track" + "oras.land/oras/internal/progress" ) // Messenger is progress message channel. @@ -28,11 +28,11 @@ type Messenger struct { ch chan *status closed bool desc ocispec.Descriptor - prompt map[track.State]string + prompt map[progress.State]string } -func (m *Messenger) Update(status track.Status) error { - if status.State == track.StateInitialized { +func (m *Messenger) Update(status progress.Status) error { + if status.State == progress.StateInitialized { m.start() } m.send(m.prompt[status.State], status.Offset) @@ -40,7 +40,7 @@ func (m *Messenger) Update(status track.Status) error { } func (m *Messenger) Fail(err error) error { - return err + return nil } func (m *Messenger) Close() error { diff --git a/cmd/oras/internal/display/status/track/reader.go b/cmd/oras/internal/display/status/track/reader.go index 0a1e046d9..1f2070a00 100644 --- a/cmd/oras/internal/display/status/track/reader.go +++ b/cmd/oras/internal/display/status/track/reader.go @@ -20,45 +20,47 @@ import ( "os" ocispec "github.com/opencontainers/image-spec/specs-go/v1" - "oras.land/oras/cmd/oras/internal/display/status/progress" - "oras.land/oras/internal/experimental/track" + sprogress "oras.land/oras/cmd/oras/internal/display/status/progress" + "oras.land/oras/internal/progress" ) type reader struct { - *track.ReadTracker + io.Reader + progress.Tracker - manager track.Manager + manager progress.Manager } // NewReader returns a new reader with tracked progress. func NewReader(r io.Reader, descriptor ocispec.Descriptor, actionPrompt string, donePrompt string, tty *os.File) (*reader, error) { - prompt := map[track.State]string{ - track.StateInitialized: actionPrompt, - track.StateTransmitting: actionPrompt, - track.StateTransmitted: donePrompt, + prompt := map[progress.State]string{ + progress.StateInitialized: actionPrompt, + progress.StateTransmitting: actionPrompt, + progress.StateTransmitted: donePrompt, } - manager, err := progress.NewManager(tty, prompt) + manager, err := sprogress.NewManager(tty, prompt) if err != nil { return nil, err } return managedReader(r, descriptor, manager) } -func managedReader(r io.Reader, descriptor ocispec.Descriptor, manager track.Manager) (*reader, error) { +func managedReader(r io.Reader, descriptor ocispec.Descriptor, manager progress.Manager) (*reader, error) { tracker, err := manager.Track(descriptor) if err != nil { return nil, err } return &reader{ - ReadTracker: track.NewReadTracker(tracker, r), - manager: manager, + Reader: progress.TrackReader(tracker, r), + Tracker: tracker, + manager: manager, }, nil } // StopManager stops the messenger channel and related manager. func (r *reader) StopManager() { - r.Close() + _ = r.Tracker.Close() _ = r.manager.Close() } diff --git a/cmd/oras/internal/display/status/track/target.go b/cmd/oras/internal/display/status/track/target.go index 7c5421506..50dd3747d 100644 --- a/cmd/oras/internal/display/status/track/target.go +++ b/cmd/oras/internal/display/status/track/target.go @@ -25,20 +25,20 @@ import ( "oras.land/oras-go/v2" "oras.land/oras-go/v2/errdef" "oras.land/oras-go/v2/registry" - "oras.land/oras/cmd/oras/internal/display/status/progress" - "oras.land/oras/internal/experimental/track" + sprogress "oras.land/oras/cmd/oras/internal/display/status/progress" + "oras.land/oras/internal/progress" ) // GraphTarget is a tracked oras.GraphTarget. type GraphTarget interface { oras.GraphTarget io.Closer - Report(desc ocispec.Descriptor, state track.State) error + Report(desc ocispec.Descriptor, state progress.State) error } type graphTarget struct { oras.GraphTarget - manager track.Manager + manager progress.Manager } type referenceGraphTarget struct { @@ -46,8 +46,8 @@ type referenceGraphTarget struct { } // NewTarget creates a new tracked Target. -func NewTarget(t oras.GraphTarget, prompt map[track.State]string, tty *os.File) (GraphTarget, error) { - manager, err := progress.NewManager(tty, prompt) +func NewTarget(t oras.GraphTarget, prompt map[progress.State]string, tty *os.File) (GraphTarget, error) { + manager, err := sprogress.NewManager(tty, prompt) if err != nil { return nil, err } @@ -78,16 +78,19 @@ func (t *graphTarget) Push(ctx context.Context, expected ocispec.Descriptor, con return err } defer r.Close() - r.Start() + if err := progress.Start(r); err != nil { + return err + } if err := t.GraphTarget.Push(ctx, expected, r); err != nil { if errors.Is(err, errdef.ErrAlreadyExists) { // allowed error types in oras-go oci and memory store - r.Done() + if err := progress.Done(r); err != nil { + return err + } } return err } - r.Done() - return nil + return progress.Done(r) } // PushReference pushes the content to the base oras.GraphTarget with tracking. @@ -97,13 +100,14 @@ func (rgt *referenceGraphTarget) PushReference(ctx context.Context, expected oci return err } defer r.Close() - r.Start() + if err := progress.Start(r); err != nil { + return err + } err = rgt.GraphTarget.(registry.ReferencePusher).PushReference(ctx, expected, r, reference) if err != nil { return err } - r.Done() - return nil + return progress.Done(r) } // Close closes the tracking manager. @@ -112,9 +116,16 @@ func (t *graphTarget) Close() error { } // Report prompts the user with the provided state and descriptor. -func (t *graphTarget) Report(desc ocispec.Descriptor, state track.State) error { - return track.Record(t.manager, desc, track.Status{ +func (t *graphTarget) Report(desc ocispec.Descriptor, state progress.State) error { + tracker, err := t.manager.Track(desc) + if err != nil { + return err + } + if err = tracker.Update(progress.Status{ State: state, Offset: desc.Size, - }) + }); err != nil { + return err + } + return tracker.Close() } diff --git a/cmd/oras/internal/display/status/tty.go b/cmd/oras/internal/display/status/tty.go index 304a4faae..adf1b8f39 100644 --- a/cmd/oras/internal/display/status/tty.go +++ b/cmd/oras/internal/display/status/tty.go @@ -25,14 +25,14 @@ import ( ocispec "github.com/opencontainers/image-spec/specs-go/v1" "oras.land/oras-go/v2" "oras.land/oras-go/v2/content" - strack "oras.land/oras/cmd/oras/internal/display/status/track" - "oras.land/oras/internal/experimental/track" + "oras.land/oras/cmd/oras/internal/display/status/track" + "oras.land/oras/internal/progress" ) // TTYPushHandler handles TTY status output for push command. type TTYPushHandler struct { tty *os.File - tracked strack.GraphTarget + tracked track.GraphTarget committed *sync.Map fetcher content.Fetcher } @@ -58,13 +58,13 @@ func (ph *TTYPushHandler) OnEmptyArtifact() error { // TrackTarget returns a tracked target. func (ph *TTYPushHandler) TrackTarget(gt oras.GraphTarget) (oras.GraphTarget, StopTrackTargetFunc, error) { - prompt := map[track.State]string{ - track.StateInitialized: PushPromptUploading, - track.StateTransmitting: PushPromptUploading, - track.StateTransmitted: PushPromptUploaded, - track.StateExists: PushPromptExists, + prompt := map[progress.State]string{ + progress.StateInitialized: PushPromptUploading, + progress.StateTransmitting: PushPromptUploading, + progress.StateTransmitted: PushPromptUploaded, + progress.StateExists: PushPromptExists, } - tracked, err := strack.NewTarget(gt, prompt, ph.tty) + tracked, err := track.NewTarget(gt, prompt, ph.tty) if err != nil { return nil, nil, err } @@ -75,7 +75,7 @@ func (ph *TTYPushHandler) TrackTarget(gt oras.GraphTarget) (oras.GraphTarget, St // OnCopySkipped is called when an object already exists. func (ph *TTYPushHandler) OnCopySkipped(_ context.Context, desc ocispec.Descriptor) error { ph.committed.Store(desc.Digest.String(), desc.Annotations[ocispec.AnnotationTitle]) - return ph.tracked.Report(desc, track.StateExists) + return ph.tracked.Report(desc, progress.StateExists) } // PreCopy implements PreCopy of CopyHandler. @@ -91,7 +91,7 @@ func (ph *TTYPushHandler) PostCopy(ctx context.Context, desc ocispec.Descriptor) return err } for _, successor := range successors { - if err = ph.tracked.Report(successor, track.StateSkipped); err != nil { + if err = ph.tracked.Report(successor, progress.StateSkipped); err != nil { return err } } @@ -106,7 +106,7 @@ func NewTTYAttachHandler(tty *os.File, fetcher content.Fetcher) AttachHandler { // TTYPullHandler handles TTY status output for pull events. type TTYPullHandler struct { tty *os.File - tracked strack.GraphTarget + tracked track.GraphTarget } // NewTTYPullHandler returns a new handler for Pull status events. @@ -133,24 +133,24 @@ func (ph *TTYPullHandler) OnNodeProcessing(_ ocispec.Descriptor) error { // OnNodeRestored implements PullHandler. func (ph *TTYPullHandler) OnNodeRestored(desc ocispec.Descriptor) error { - return ph.tracked.Report(desc, track.StateMounted) + return ph.tracked.Report(desc, progress.StateMounted) } // OnNodeSkipped implements PullHandler. func (ph *TTYPullHandler) OnNodeSkipped(desc ocispec.Descriptor) error { - return ph.tracked.Report(desc, track.StateSkipped) + return ph.tracked.Report(desc, progress.StateSkipped) } // TrackTarget returns a tracked target. func (ph *TTYPullHandler) TrackTarget(gt oras.GraphTarget) (oras.GraphTarget, StopTrackTargetFunc, error) { - prompt := map[track.State]string{ - track.StateInitialized: PullPromptDownloading, - track.StateTransmitting: PullPromptDownloading, - track.StateTransmitted: PullPromptPulled, - track.StateSkipped: PullPromptSkipped, - track.StateMounted: PullPromptRestored, + prompt := map[progress.State]string{ + progress.StateInitialized: PullPromptDownloading, + progress.StateTransmitting: PullPromptDownloading, + progress.StateTransmitted: PullPromptPulled, + progress.StateSkipped: PullPromptSkipped, + progress.StateMounted: PullPromptRestored, } - tracked, err := strack.NewTarget(gt, prompt, ph.tty) + tracked, err := track.NewTarget(gt, prompt, ph.tty) if err != nil { return nil, nil, err } @@ -174,8 +174,16 @@ func NewTTYCopyHandler(tty *os.File) CopyHandler { // StartTracking returns a tracked target from a graph target. func (ch *TTYCopyHandler) StartTracking(gt oras.GraphTarget) (oras.GraphTarget, error) { + prompt := map[progress.State]string{ + progress.StateInitialized: copyPromptCopying, + progress.StateTransmitting: copyPromptCopying, + progress.StateTransmitted: copyPromptCopied, + progress.StateExists: copyPromptExists, + progress.StateSkipped: copyPromptSkipped, + progress.StateMounted: copyPromptMounted, + } var err error - ch.tracked, err = track.NewTarget(gt, copyPromptCopying, copyPromptCopied, ch.tty) + ch.tracked, err = track.NewTarget(gt, prompt, ch.tty) if err != nil { return nil, err } @@ -190,7 +198,7 @@ func (ch *TTYCopyHandler) StopTracking() error { // OnCopySkipped is called when an object already exists. func (ch *TTYCopyHandler) OnCopySkipped(_ context.Context, desc ocispec.Descriptor) error { ch.committed.Store(desc.Digest.String(), desc.Annotations[ocispec.AnnotationTitle]) - return ch.tracked.Prompt(desc, copyPromptExists) + return ch.tracked.Report(desc, progress.StateExists) } // PreCopy implements PreCopy of CopyHandler. @@ -206,7 +214,7 @@ func (ch *TTYCopyHandler) PostCopy(ctx context.Context, desc ocispec.Descriptor) return err } for _, successor := range successors { - if err = ch.tracked.Prompt(successor, copyPromptSkipped); err != nil { + if err = ch.tracked.Report(successor, progress.StateSkipped); err != nil { return err } } @@ -216,5 +224,5 @@ func (ch *TTYCopyHandler) PostCopy(ctx context.Context, desc ocispec.Descriptor) // OnMounted implements OnMounted of CopyHandler. func (ch *TTYCopyHandler) OnMounted(_ context.Context, desc ocispec.Descriptor) error { ch.committed.Store(desc.Digest.String(), desc.Annotations[ocispec.AnnotationTitle]) - return ch.tracked.Prompt(desc, copyPromptMounted) + return ch.tracked.Report(desc, progress.StateMounted) } diff --git a/cmd/oras/root/blob/fetch.go b/cmd/oras/root/blob/fetch.go index 44694c428..a41af6ecc 100644 --- a/cmd/oras/root/blob/fetch.go +++ b/cmd/oras/root/blob/fetch.go @@ -31,6 +31,7 @@ import ( "oras.land/oras/cmd/oras/internal/display/status/track" oerrors "oras.land/oras/cmd/oras/internal/errors" "oras.land/oras/cmd/oras/internal/option" + "oras.land/oras/internal/progress" ) type fetchBlobOptions struct { @@ -176,11 +177,15 @@ func (opts *fetchBlobOptions) doFetch(ctx context.Context, src oras.ReadOnlyTarg return ocispec.Descriptor{}, err } defer trackedReader.StopManager() - trackedReader.Start() + if err := progress.Start(trackedReader); err != nil { + return ocispec.Descriptor{}, err + } if _, err = io.Copy(writer, trackedReader); err != nil { return ocispec.Descriptor{}, err } - trackedReader.Done() + if err := progress.Done(trackedReader); err != nil { + return ocispec.Descriptor{}, err + } } if err := vr.Verify(); err != nil { return ocispec.Descriptor{}, err diff --git a/cmd/oras/root/blob/push.go b/cmd/oras/root/blob/push.go index 883de2fe5..b5bb4fac6 100644 --- a/cmd/oras/root/blob/push.go +++ b/cmd/oras/root/blob/push.go @@ -31,6 +31,7 @@ import ( "oras.land/oras/cmd/oras/internal/option" "oras.land/oras/cmd/oras/internal/output" "oras.land/oras/internal/file" + "oras.land/oras/internal/progress" ) type pushBlobOptions struct { @@ -164,11 +165,11 @@ func (opts *pushBlobOptions) doPush(ctx context.Context, printer *output.Printer return err } defer trackedReader.StopManager() - trackedReader.Start() - r = trackedReader - if err := t.Push(ctx, desc, r); err != nil { + if err := progress.Start(trackedReader); err != nil { return err } - trackedReader.Done() - return nil + if err := t.Push(ctx, desc, trackedReader); err != nil { + return err + } + return progress.Done(trackedReader) } diff --git a/cmd/oras/root/cp.go b/cmd/oras/root/cp.go index 2a5cc5cd4..c6693edcb 100644 --- a/cmd/oras/root/cp.go +++ b/cmd/oras/root/cp.go @@ -34,11 +34,9 @@ import ( "oras.land/oras/cmd/oras/internal/command" "oras.land/oras/cmd/oras/internal/display" "oras.land/oras/cmd/oras/internal/display/status" - strack "oras.land/oras/cmd/oras/internal/display/status/track" oerrors "oras.land/oras/cmd/oras/internal/errors" "oras.land/oras/cmd/oras/internal/option" "oras.land/oras/internal/docker" - "oras.land/oras/internal/experimental/track" "oras.land/oras/internal/graph" "oras.land/oras/internal/listener" "oras.land/oras/internal/registryutil" @@ -177,49 +175,6 @@ func doCopy(ctx context.Context, copyHandler status.CopyHandler, src oras.ReadOn dst, err = copyHandler.StartTracking(dst) if err != nil { return desc, err - if opts.TTY == nil { - // no TTY output - extendedCopyOptions.OnCopySkipped = copyHandler.OnCopySkipped - extendedCopyOptions.PreCopy = copyHandler.PreCopy - extendedCopyOptions.PostCopy = copyHandler.PostCopy - extendedCopyOptions.OnMounted = copyHandler.OnMounted - } else { - // TTY output - prompt := map[track.State]string{ - track.StateInitialized: promptCopying, - track.StateTransmitting: promptCopying, - track.StateTransmitted: promptCopied, - track.StateExists: promptExists, - track.StateSkipped: promptSkipped, - track.StateMounted: promptMounted, - } - tracked, err := strack.NewTarget(dst, prompt, opts.TTY) - if err != nil { - return ocispec.Descriptor{}, err - } - defer tracked.Close() - dst = tracked - extendedCopyOptions.OnCopySkipped = func(ctx context.Context, desc ocispec.Descriptor) error { - committed.Store(desc.Digest.String(), desc.Annotations[ocispec.AnnotationTitle]) - return tracked.Report(desc, track.StateExists) - } - extendedCopyOptions.PostCopy = func(ctx context.Context, desc ocispec.Descriptor) error { - committed.Store(desc.Digest.String(), desc.Annotations[ocispec.AnnotationTitle]) - successors, err := graph.FilteredSuccessors(ctx, desc, tracked, status.DeduplicatedFilter(committed)) - if err != nil { - return err - } - for _, successor := range successors { - if err = tracked.Report(successor, track.StateSkipped); err != nil { - return err - } - } - return nil - } - extendedCopyOptions.OnMounted = func(ctx context.Context, desc ocispec.Descriptor) error { - committed.Store(desc.Digest.String(), desc.Annotations[ocispec.AnnotationTitle]) - return tracked.Report(desc, track.StateMounted) - } } defer func() { stopErr := copyHandler.StopTracking() diff --git a/internal/experimental/track/interface.go b/internal/experimental/track/interface.go deleted file mode 100644 index 972d8d519..000000000 --- a/internal/experimental/track/interface.go +++ /dev/null @@ -1,62 +0,0 @@ -package track - -import ( - "io" - - ocispec "github.com/opencontainers/image-spec/specs-go/v1" -) - -// State represents the state of a descriptor. -type State int - -const ( - StateUnknown State = iota - StateInitialized - StateTransmitting - StateTransmitted - StateExists - StateSkipped - StateMounted -) - -// Status represents the status of a descriptor. -type Status struct { - // State represents the state of the descriptor. - State State - - // Offset represents the current offset of the descriptor. - // Offset is discarded if set to a negative value. - Offset int64 -} - -// Tracker updates the status of a descriptor. -type Tracker interface { - io.Closer - - // Update updates the status of the descriptor. - Update(status Status) error - - // Fail marks the descriptor as failed. - Fail(err error) error -} - -// Manager tracks the progress of multiple descriptors. -type Manager interface { - io.Closer - - // Track starts tracking the progress of a descriptor. - Track(desc ocispec.Descriptor) (Tracker, error) -} - -// Record adds the progress of a descriptor as a single entry. -func Record(m Manager, desc ocispec.Descriptor, status Status) error { - tracker, err := m.Track(desc) - if err != nil { - return err - } - err = tracker.Update(status) - if err != nil { - return err - } - return tracker.Close() -} diff --git a/internal/experimental/track/reader.go b/internal/experimental/track/reader.go deleted file mode 100644 index d53da0374..000000000 --- a/internal/experimental/track/reader.go +++ /dev/null @@ -1,56 +0,0 @@ -package track - -import "io" - -// ReadTracker tracks the transmission based on the read operation. -type ReadTracker struct { - base io.Reader - tracker Tracker - offset int64 -} - -// NewReadTracker attaches a tracker to a reader. -func NewReadTracker(track Tracker, r io.Reader) *ReadTracker { - return &ReadTracker{ - base: r, - tracker: track, - } -} - -// Read reads from the base reader and updates the status. -func (rt *ReadTracker) Read(p []byte) (n int, err error) { - n, err = rt.base.Read(p) - rt.offset += int64(n) - _ = rt.tracker.Update(Status{ - State: StateTransmitting, - Offset: rt.offset, - }) - if err != nil && err != io.EOF { - _ = rt.tracker.Fail(err) - } - return n, err -} - -// Close closes the tracker. -func (rt *ReadTracker) Close() error { - return rt.tracker.Close() -} - -// Start starts tracking the transmission. -func (rt *ReadTracker) Start() error { - return rt.tracker.Update(Status{ - State: StateInitialized, - Offset: -1, - }) -} - -// Done marks the transmission as complete. -// Done should be called after the transmission is complete. -// Note: Reading all content from the reader does not imply the transmission is -// complete. -func (rt *ReadTracker) Done() error { - return rt.tracker.Update(Status{ - State: StateTransmitted, - Offset: -1, - }) -}