Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support progress output for attach, push and copy commands #1138

Merged
merged 127 commits into from
Oct 23, 2023
Merged
Show file tree
Hide file tree
Changes from 119 commits
Commits
Show all changes
127 commits
Select commit Hold shift + click to select a range
b088f2b
feat: support progressed output in blob push and blob get
qweeah Sep 12, 2023
e773471
add error when --debug is used with terminal output
qweeah Sep 19, 2023
6044594
bug fix
qweeah Sep 19, 2023
b5d89dd
Merge branch 'main' into progress-bar-reader
qweeah Sep 20, 2023
6324b93
add test for console
qweeah Sep 20, 2023
f1077bd
fix lint & pass tty to manager
qweeah Sep 20, 2023
4c362f8
Merge branch 'main' into progress-bar-reader
qweeah Sep 20, 2023
941188f
rename flag to disable tty
qweeah Sep 20, 2023
c72ebe5
add test for blob push
qweeah Sep 21, 2023
5fc75a8
fix render racing
qweeah Sep 21, 2023
a2d3f8c
fix race
qweeah Sep 21, 2023
4a065fe
add test for `blob fetch`
qweeah Sep 21, 2023
00ca2fc
resolve comment
qweeah Sep 22, 2023
c85e809
resolve comment
qweeah Sep 22, 2023
997a63e
resolve comment
qweeah Sep 22, 2023
144e907
fix lint
qweeah Sep 22, 2023
1b1f23c
refactor spinner mark
qweeah Sep 22, 2023
008a61d
refactor: improve manager and status
qweeah Sep 25, 2023
95ad473
add error for manager functions
qweeah Sep 25, 2023
ed84a69
remove unnecessary lock
qweeah Sep 25, 2023
47ef494
update reader naming
qweeah Sep 25, 2023
7e249ed
remove reader.once
qweeah Sep 25, 2023
ca19602
Merge remote-tracking branch 'origin_src/main' into progress-bar-reader
qweeah Sep 25, 2023
a6476d4
ignore returned error of manager
qweeah Sep 25, 2023
8c7458b
code clean
qweeah Sep 25, 2023
1584fd2
resolve comment
qweeah Sep 28, 2023
8fef80b
Merge remote-tracking branch 'origin_src/main' into progress-bar-reader
qweeah Sep 28, 2023
b44e2e8
code clean
qweeah Sep 28, 2023
ea6476f
code clean
qweeah Sep 28, 2023
b06383c
feat: support progress bar for `oras cp`
qweeah Sep 28, 2023
720c6e6
Merge branch 'progress-bar-reader' into progress-bar-cp
qweeah Sep 28, 2023
ebcc114
Merge remote-tracking branch 'origin_src/main' into progress-bar-reader
qweeah Oct 7, 2023
ad0608d
add test for spinner
qweeah Oct 7, 2023
1aab8bd
add cross-package coverage generation
qweeah Oct 7, 2023
78b0d42
Refactor tests
qweeah Oct 7, 2023
2b13d6a
cover common tests
qweeah Oct 7, 2023
6bf4f3d
add status coverage
qweeah Oct 7, 2023
a7f3f96
code clean
qweeah Oct 7, 2023
cfc7a9b
Merge branch 'progress-bar-reader' into progress-bar-cp
qweeah Oct 7, 2023
5183dc9
add doc for stdout
qweeah Oct 7, 2023
1f45188
add status tests
qweeah Oct 7, 2023
7133edd
resolve comment
qweeah Oct 7, 2023
3ad8eaa
add doc for stdout
qweeah Oct 7, 2023
243dc37
add status tests
qweeah Oct 7, 2023
5f5a1d9
resolve comment
qweeah Oct 7, 2023
7554534
NIT
qweeah Oct 7, 2023
76de650
align timing and percentage
qweeah Oct 7, 2023
51ab043
Merge branch 'progress-bar-reader' into progress-bar-cp
qweeah Oct 7, 2023
486a8ed
fix unit test
qweeah Oct 7, 2023
5b24511
Merge branch 'progress-bar-reader' into progress-bar-cp
qweeah Oct 7, 2023
39b0247
Merge remote-tracking branch 'origin_src/main' into progress-bar-reader
qweeah Oct 7, 2023
4fe990e
increase coverage
qweeah Oct 7, 2023
3a7ed21
fix: won't show complete when aborted
qweeah Oct 8, 2023
e916c17
Merge branch 'progress-bar-reader' into progress-bar-cp
qweeah Oct 8, 2023
455ca08
fix: won't show complete when aborted
qweeah Oct 8, 2023
ff2c230
code clean
qweeah Oct 8, 2023
9426fac
code clean
qweeah Oct 8, 2023
50c4181
code clean
qweeah Oct 8, 2023
aa5b847
code clean
qweeah Oct 8, 2023
f49bdcb
align units of processed data to total size
qweeah Oct 8, 2023
1b774e0
Merge branch 'progress-bar-reader' into progress-bar-cp
qweeah Oct 8, 2023
8bc5c37
add instant download speed
qweeah Oct 8, 2023
5ed9251
Merge branch 'progress-bar-reader' into progress-bar-cp
qweeah Oct 8, 2023
e1486cc
remove unnecessary file
qweeah Oct 8, 2023
3193b48
code clean
qweeah Oct 8, 2023
a46c218
code clean
qweeah Oct 8, 2023
151ad0a
lint
qweeah Oct 8, 2023
7327022
code clean
qweeah Oct 9, 2023
1abda95
change base for sizing
qweeah Oct 9, 2023
636f424
fix speed display
qweeah Oct 9, 2023
2ea395d
add test coverage
qweeah Oct 9, 2023
d7276cb
Merge branch 'progress-bar-reader' into progress-bar-cp
qweeah Oct 9, 2023
feb73b4
add test for copying
qweeah Oct 9, 2023
4f7ac01
bug fix
qweeah Oct 9, 2023
25c155a
Merge branch 'progress-bar-reader' into progress-bar-cp
qweeah Oct 9, 2023
cfdfe46
code clean
qweeah Oct 9, 2023
8c71709
feat: support progress output in attach and push
qweeah Oct 9, 2023
b9c5bec
fix nil pointer
qweeah Oct 9, 2023
c47f967
reset render interval to 200ms
qweeah Oct 9, 2023
2489a61
turn humanize into package
qweeah Oct 10, 2023
9e7daec
bug fix
qweeah Oct 10, 2023
c4564fe
rename pts device
qweeah Oct 10, 2023
167adb1
Merge remote-tracking branch 'origin_src/main' into progress-bar-reader
qweeah Oct 10, 2023
6c324d1
reduce buffer size
qweeah Oct 10, 2023
d4298c1
fix typo
qweeah Oct 10, 2023
67574dd
change switch to if-else
qweeah Oct 10, 2023
25d8935
bug fix
qweeah Oct 10, 2023
c76c464
change switch to if-else
qweeah Oct 10, 2023
9403cca
revert rendering
qweeah Oct 10, 2023
8a1f088
Merge branch 'progress-bar-reader' into progress-bar-cp
qweeah Oct 10, 2023
0741396
reduce buffer size to 1
qweeah Oct 10, 2023
2439121
Merge branch 'progress-bar-reader' into progress-bar-cp
qweeah Oct 11, 2023
85d8514
Merge branch 'progress-bar-cp' into progress-bar-push-attach
qweeah Oct 11, 2023
8d6c017
fix: enable pipe when output to stdout
qweeah Oct 11, 2023
c5ab1ee
fix: enable pipe when output to stdout
qweeah Oct 11, 2023
360a5ea
Merge branch 'progress-bar-reader' into progress-bar-cp
qweeah Oct 11, 2023
6611186
fix display bug
qweeah Oct 11, 2023
c4fae1f
Merge branch 'progress-bar-reader' into progress-bar-cp
qweeah Oct 11, 2023
e858d1a
revert change
qweeah Oct 11, 2023
8692963
Merge branch 'progress-bar-reader' into progress-bar-cp
qweeah Oct 11, 2023
49818cf
Merge branch 'progress-bar-cp' into progress-bar-push-attach
qweeah Oct 11, 2023
e4d4439
fix bug
qweeah Oct 11, 2023
6f2de09
Merge branch 'progress-bar-reader' into progress-bar-cp
qweeah Oct 11, 2023
b01433d
Merge branch 'progress-bar-cp' into progress-bar-push-attach
qweeah Oct 11, 2023
7c7510f
add skipped status
qweeah Oct 13, 2023
e26cf77
Merge remote-tracking branch 'origin_src/main' into progress-bar-push…
qweeah Oct 13, 2023
eba5761
fix merging
qweeah Oct 13, 2023
6a97757
fix pushing
qweeah Oct 13, 2023
1a4c490
fix cp
qweeah Oct 13, 2023
a4b0379
Merge branch 'main' into progress-bar-push-attach
qweeah Oct 16, 2023
781874f
resolve comment
qweeah Oct 17, 2023
0dd031a
change target to graph target
qweeah Oct 17, 2023
d2c8f16
resolve comment
qweeah Oct 17, 2023
0de9997
remove printStatus output target.Prompt
qweeah Oct 17, 2023
e0f810a
fix attach
qweeah Oct 17, 2023
060940b
refactor target
qweeah Oct 17, 2023
692fd31
resolve comment
qweeah Oct 17, 2023
8a52cc9
doc clean
qweeah Oct 17, 2023
1ccb8d1
Merge remote-tracking branch 'origin_src/main' into progress-bar-push…
qweeah Oct 18, 2023
826872f
resolve comment
qweeah Oct 19, 2023
26cb0b7
resolve comment for push
qweeah Oct 19, 2023
d648b94
resolve comment
qweeah Oct 19, 2023
f6ab85d
remove unnecessary code
qweeah Oct 19, 2023
4841c1c
resolve comments
qweeah Oct 23, 2023
f30a5d5
remove unused code
qweeah Oct 23, 2023
0406738
merge declaration
qweeah Oct 23, 2023
4e8d585
Merge remote-tracking branch 'origin_src/main' into progress-bar-push…
qweeah Oct 23, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions cmd/oras/internal/display/print.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ import (

var printLock sync.Mutex

type printFunc func(ocispec.Descriptor) error
qweeah marked this conversation as resolved.
Show resolved Hide resolved

// Print objects to display concurrent-safely
func Print(a ...any) error {
printLock.Lock()
Expand All @@ -38,8 +40,8 @@ func Print(a ...any) error {
}

// StatusPrinter returns a tracking function for transfer status.
func StatusPrinter(status string, verbose bool) func(context.Context, ocispec.Descriptor) error {
return func(ctx context.Context, desc ocispec.Descriptor) error {
func StatusPrinter(status string, verbose bool) printFunc {
return func(desc ocispec.Descriptor) error {
return PrintStatus(desc, status, verbose)
}
}
Expand All @@ -58,7 +60,7 @@ func PrintStatus(desc ocispec.Descriptor, status string, verbose bool) error {
}

// PrintSuccessorStatus prints transfer status of successors.
func PrintSuccessorStatus(ctx context.Context, desc ocispec.Descriptor, status string, fetcher content.Fetcher, committed *sync.Map, verbose bool) error {
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
Expand All @@ -67,7 +69,7 @@ func PrintSuccessorStatus(ctx context.Context, desc ocispec.Descriptor, status s
name := s.Annotations[ocispec.AnnotationTitle]
if v, ok := committed.Load(s.Digest.String()); ok && v != name {
// Reprint status for deduplicated content
if err := PrintStatus(s, status, verbose); err != nil {
if err := print(s); err != nil {
return err
}
}
Expand Down
119 changes: 119 additions & 0 deletions cmd/oras/internal/display/track/target.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
/*
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 track

import (
"context"
"errors"
"io"
"os"

ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"oras.land/oras-go/v2"
"oras.land/oras-go/v2/registry"
"oras.land/oras/cmd/oras/internal/display/progress"
)

// GraphTarget is a tracked oras.GraphTarget.
type GraphTarget interface {
oras.GraphTarget
io.Closer
Prompt(desc ocispec.Descriptor, prompt string, verbose bool) error
}

type graphTarget struct {
oras.GraphTarget
manager progress.Manager
actionPrompt string
donePrompt string
}

type referenceGraphTarget struct {
*graphTarget
}

// NewTarget creates a new tracked Target.
func NewTarget(t oras.GraphTarget, actionPrompt, donePrompt string, tty *os.File) (GraphTarget, error) {
manager, err := progress.NewManager(tty)
if err != nil {
return nil, err
}
gt := &graphTarget{
GraphTarget: t,
manager: manager,
actionPrompt: actionPrompt,
donePrompt: donePrompt,
}

if _, ok := t.(registry.ReferencePusher); ok {
return &referenceGraphTarget{
graphTarget: gt,
}, nil
}
return gt, nil
}

// Push pushes the content to the base oras.GraphTarget with tracking.
func (t *graphTarget) Push(ctx context.Context, expected ocispec.Descriptor, content io.Reader) error {
r, err := managedReader(content, expected, t.manager, t.actionPrompt, t.donePrompt)
if err != nil {
return err
}
defer r.Close()
r.Start()
if err := t.GraphTarget.Push(ctx, expected, r); err != nil {
return err
}
r.Done()
return nil
}

// PushReference pushes the content to the base oras.GraphTarget with tracking.
func (rgt *referenceGraphTarget) PushReference(ctx context.Context, expected ocispec.Descriptor, content io.Reader, reference string) error {
r, err := managedReader(content, expected, rgt.manager, rgt.actionPrompt, rgt.donePrompt)
if err != nil {
return err
}
defer r.Close()
r.Start()
err = rgt.GraphTarget.(registry.ReferencePusher).PushReference(ctx, expected, r, reference)
if err != nil {
return err
}
r.Done()
return nil
}

// Close closes the tracking manager.
func (t *graphTarget) Close() error {
return t.manager.Close()
}

// Prompt prompts the user with the provided prompt and descriptor.
func (t *graphTarget) Prompt(desc ocispec.Descriptor, prompt string, verbose bool) error {
if t == nil {
// this should not happen
return errors.New("cannot output progress with nil tracked target")
}
qweeah marked this conversation as resolved.
Show resolved Hide resolved
status, err := t.manager.Add()
if err != nil {
return err
}
defer close(status)
status <- progress.NewStatus(prompt, desc, desc.Size)
status <- progress.EndTiming()
return nil
}
17 changes: 12 additions & 5 deletions cmd/oras/root/attach.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import (
"oras.land/oras-go/v2/content"
"oras.land/oras-go/v2/content/file"
"oras.land/oras-go/v2/registry/remote/auth"
"oras.land/oras/cmd/oras/internal/display/track"
"oras.land/oras/cmd/oras/internal/option"
"oras.land/oras/internal/graph"
"oras.land/oras/internal/registryutil"
Expand Down Expand Up @@ -131,6 +132,15 @@ func runAttach(ctx context.Context, opts attachOptions) error {
}

// prepare push
var tracked track.GraphTarget
dst, tracked, err = getTrackedTarget(dst, opts.TTY)
if err != nil {
return err
}
graphCopyOptions := oras.DefaultCopyGraphOptions
graphCopyOptions.Concurrency = opts.concurrency
updateDisplayOption(&graphCopyOptions, store, opts.Verbose, tracked)

packOpts := oras.PackManifestOptions{
Subject: &subject,
ManifestAnnotations: annotations[option.AnnotationManifest],
Expand All @@ -140,9 +150,6 @@ func runAttach(ctx context.Context, opts attachOptions) error {
return oras.PackManifest(ctx, store, oras.PackManifestVersion1_1_RC4, opts.artifactType, packOpts)
}

graphCopyOptions := oras.DefaultCopyGraphOptions
graphCopyOptions.Concurrency = opts.concurrency
updateDisplayOption(&graphCopyOptions, store, opts.Verbose)
copy := func(root ocispec.Descriptor) error {
graphCopyOptions.FindSuccessors = func(ctx context.Context, fetcher content.Fetcher, node ocispec.Descriptor) ([]ocispec.Descriptor, error) {
if content.Equal(node, root) {
Expand All @@ -161,11 +168,11 @@ func runAttach(ctx context.Context, opts attachOptions) error {
return oras.CopyGraph(ctx, store, dst, root, graphCopyOptions)
}

root, err := pushArtifact(dst, pack, copy)
// Attach
root, err := doPush(dst, pack, copy)
if err != nil {
return err
}

digest := subject.Digest.String()
if !strings.HasSuffix(opts.RawReference, digest) {
opts.RawReference = fmt.Sprintf("%s@%s", opts.Path, subject.Digest)
Expand Down
92 changes: 63 additions & 29 deletions cmd/oras/root/cp.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import (
"oras.land/oras-go/v2/content"
"oras.land/oras-go/v2/registry/remote/auth"
"oras.land/oras/cmd/oras/internal/display"
"oras.land/oras/cmd/oras/internal/display/track"
"oras.land/oras/cmd/oras/internal/option"
"oras.land/oras/internal/docker"
"oras.land/oras/internal/graph"
Expand Down Expand Up @@ -120,24 +121,77 @@ func runCopy(ctx context.Context, opts copyOptions) error {
}
ctx = registryutil.WithScopeHint(ctx, dst, auth.ActionPull, auth.ActionPush)

desc, err := doCopy(ctx, src, dst, opts)
if err != nil {
return err
}

if from, err := digest.Parse(opts.From.Reference); err == nil && from != desc.Digest {
// correct source digest
opts.From.RawReference = fmt.Sprintf("%s@%s", opts.From.Path, desc.Digest.String())
}
fmt.Println("Copied", opts.From.AnnotatedReference(), "=>", opts.To.AnnotatedReference())

if len(opts.extraRefs) != 0 {
tagNOpts := oras.DefaultTagNOptions
tagNOpts.Concurrency = opts.concurrency
if _, err = oras.TagN(ctx, display.NewTagStatusPrinter(dst), opts.To.Reference, opts.extraRefs, tagNOpts); err != nil {
return err
}
}

fmt.Println("Digest:", desc.Digest)

return nil
}

func doCopy(ctx context.Context, src oras.ReadOnlyGraphTarget, dst oras.GraphTarget, opts copyOptions) (ocispec.Descriptor, error) {
var tracked track.GraphTarget
var err error
// Prepare copy options
committed := &sync.Map{}
extendedCopyOptions := oras.DefaultExtendedCopyOptions
extendedCopyOptions.Concurrency = opts.concurrency
extendedCopyOptions.FindPredecessors = func(ctx context.Context, src content.ReadOnlyGraphStorage, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) {
return graph.Referrers(ctx, src, desc, "")
}
extendedCopyOptions.PreCopy = display.StatusPrinter("Copying", opts.Verbose)
successorPrinter := display.StatusPrinter("Skipped", opts.Verbose)
extendedCopyOptions.PostCopy = func(ctx context.Context, desc ocispec.Descriptor) error {
committed.Store(desc.Digest.String(), desc.Annotations[ocispec.AnnotationTitle])
if err := display.PrintSuccessorStatus(ctx, desc, "Skipped", dst, committed, opts.Verbose); err != nil {
return err
}
return display.PrintStatus(desc, "Copied ", opts.Verbose)
return display.PrintSuccessorStatus(ctx, desc, dst, committed, func(d ocispec.Descriptor) error {
return successorPrinter(d)
})
}
qweeah marked this conversation as resolved.
Show resolved Hide resolved
extendedCopyOptions.OnCopySkipped = func(ctx context.Context, desc ocispec.Descriptor) error {
committed.Store(desc.Digest.String(), desc.Annotations[ocispec.AnnotationTitle])
return display.PrintStatus(desc, "Exists ", opts.Verbose)
if tracked == nil {
return display.PrintStatus(desc, "Skipped", opts.Verbose)
qweeah marked this conversation as resolved.
Show resolved Hide resolved
}
return tracked.Prompt(desc, "Exists ", opts.Verbose)
}
qweeah marked this conversation as resolved.
Show resolved Hide resolved
if opts.TTY == nil {
// none TTY output
extendedCopyOptions.PreCopy = func(ctx context.Context, desc ocispec.Descriptor) error {
return display.PrintStatus(desc, "Copying", opts.Verbose)
}
postCopy := extendedCopyOptions.PostCopy
extendedCopyOptions.PostCopy = func(ctx context.Context, desc ocispec.Descriptor) error {
if err := postCopy(ctx, desc); err != nil {
return err
}
return display.PrintStatus(desc, "Copied ", opts.Verbose)
}
} else {
// TTY output
successorPrinter = func(desc ocispec.Descriptor) error {
return tracked.Prompt(desc, "Skipped", opts.Verbose)
}
tracked, err = track.NewTarget(dst, "Copying ", "Copied ", opts.TTY)
if err != nil {
return ocispec.Descriptor{}, err
}
defer tracked.Close()
dst = tracked
}

var desc ocispec.Descriptor
Expand All @@ -146,14 +200,14 @@ func runCopy(ctx context.Context, opts copyOptions) error {
if opts.recursive {
desc, err = oras.Resolve(ctx, src, opts.From.Reference, rOpts)
if err != nil {
return fmt.Errorf("failed to resolve %s: %w", opts.From.Reference, err)
return ocispec.Descriptor{}, fmt.Errorf("failed to resolve %s: %w", opts.From.Reference, err)
}
err = recursiveCopy(ctx, src, dst, opts.To.Reference, desc, extendedCopyOptions)
} else {
if opts.To.Reference == "" {
desc, err = oras.Resolve(ctx, src, opts.From.Reference, rOpts)
if err != nil {
return fmt.Errorf("failed to resolve %s: %w", opts.From.Reference, err)
return ocispec.Descriptor{}, fmt.Errorf("failed to resolve %s: %w", opts.From.Reference, err)
}
err = oras.CopyGraph(ctx, src, dst, desc, extendedCopyOptions.CopyGraphOptions)
} else {
Expand All @@ -166,27 +220,7 @@ func runCopy(ctx context.Context, opts copyOptions) error {
desc, err = oras.Copy(ctx, src, opts.From.Reference, dst, opts.To.Reference, copyOptions)
}
}
if err != nil {
return err
}

if from, err := digest.Parse(opts.From.Reference); err == nil && from != desc.Digest {
// correct source digest
opts.From.RawReference = fmt.Sprintf("%s@%s", opts.From.Path, desc.Digest.String())
}
fmt.Println("Copied", opts.From.AnnotatedReference(), "=>", opts.To.AnnotatedReference())

if len(opts.extraRefs) != 0 {
tagNOpts := oras.DefaultTagNOptions
tagNOpts.Concurrency = opts.concurrency
if _, err = oras.TagN(ctx, display.NewTagStatusPrinter(dst), opts.To.Reference, opts.extraRefs, tagNOpts); err != nil {
return err
}
}

fmt.Println("Digest:", desc.Digest)

return nil
return desc, err
}

// recursiveCopy copies an artifact and its referrers from one target to another.
Expand Down
Loading
Loading