Merge pull request #2708 from maximillianfx/2564-add-spinner-loading

Add spinner loading to docker cp command
This commit is contained in:
Silvin Lubecki 2021-04-28 18:25:08 +02:00 committed by GitHub
commit 87add19e2f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 84 additions and 4 deletions

View File

@ -2,6 +2,7 @@ package container
import ( import (
"context" "context"
"fmt"
"io" "io"
"os" "os"
"path/filepath" "path/filepath"
@ -12,6 +13,8 @@ import (
"github.com/docker/docker/api/types" "github.com/docker/docker/api/types"
"github.com/docker/docker/pkg/archive" "github.com/docker/docker/pkg/archive"
"github.com/docker/docker/pkg/system" "github.com/docker/docker/pkg/system"
units "github.com/docker/go-units"
"github.com/morikuni/aec"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
@ -21,6 +24,7 @@ type copyOptions struct {
destination string destination string
followLink bool followLink bool
copyUIDGID bool copyUIDGID bool
quiet bool
} }
type copyDirection int type copyDirection int
@ -34,11 +38,38 @@ const (
type cpConfig struct { type cpConfig struct {
followLink bool followLink bool
copyUIDGID bool copyUIDGID bool
quiet bool
sourcePath string sourcePath string
destPath string destPath string
container string container string
} }
// copyProgressPrinter wraps io.ReadCloser to print progress information when
// copying files to/from a container.
type copyProgressPrinter struct {
io.ReadCloser
toContainer bool
total *float64
writer io.Writer
}
func (pt *copyProgressPrinter) Read(p []byte) (int, error) {
n, err := pt.ReadCloser.Read(p)
*pt.total += float64(n)
if err == nil {
fmt.Fprint(pt.writer, aec.Restore)
fmt.Fprint(pt.writer, aec.EraseLine(aec.EraseModes.All))
if pt.toContainer {
fmt.Fprintln(pt.writer, "Copying to container - "+units.HumanSize(*pt.total))
} else {
fmt.Fprintln(pt.writer, "Copying from container - "+units.HumanSize(*pt.total))
}
}
return n, err
}
// NewCopyCommand creates a new `docker cp` command // NewCopyCommand creates a new `docker cp` command
func NewCopyCommand(dockerCli command.Cli) *cobra.Command { func NewCopyCommand(dockerCli command.Cli) *cobra.Command {
var opts copyOptions var opts copyOptions
@ -64,6 +95,10 @@ func NewCopyCommand(dockerCli command.Cli) *cobra.Command {
} }
opts.source = args[0] opts.source = args[0]
opts.destination = args[1] opts.destination = args[1]
if !cmd.Flag("quiet").Changed {
// User did not specify "quiet" flag; suppress output if no terminal is attached
opts.quiet = !dockerCli.Out().IsTerminal()
}
return runCopy(dockerCli, opts) return runCopy(dockerCli, opts)
}, },
} }
@ -71,6 +106,7 @@ func NewCopyCommand(dockerCli command.Cli) *cobra.Command {
flags := cmd.Flags() flags := cmd.Flags()
flags.BoolVarP(&opts.followLink, "follow-link", "L", false, "Always follow symbol link in SRC_PATH") flags.BoolVarP(&opts.followLink, "follow-link", "L", false, "Always follow symbol link in SRC_PATH")
flags.BoolVarP(&opts.copyUIDGID, "archive", "a", false, "Archive mode (copy all uid/gid information)") flags.BoolVarP(&opts.copyUIDGID, "archive", "a", false, "Archive mode (copy all uid/gid information)")
flags.BoolVarP(&opts.quiet, "quiet", "q", false, "Suppress progress output during copy. Progress output is automatically suppressed if no terminal is attached")
return cmd return cmd
} }
@ -81,6 +117,7 @@ func runCopy(dockerCli command.Cli, opts copyOptions) error {
copyConfig := cpConfig{ copyConfig := cpConfig{
followLink: opts.followLink, followLink: opts.followLink,
copyUIDGID: opts.copyUIDGID, copyUIDGID: opts.copyUIDGID,
quiet: opts.quiet,
sourcePath: srcPath, sourcePath: srcPath,
destPath: destPath, destPath: destPath,
} }
@ -171,12 +208,34 @@ func copyFromContainer(ctx context.Context, dockerCli command.Cli, copyConfig cp
RebaseName: rebaseName, RebaseName: rebaseName,
} }
var copiedSize float64
if !copyConfig.quiet {
content = &copyProgressPrinter{
ReadCloser: content,
toContainer: false,
writer: dockerCli.Err(),
total: &copiedSize,
}
}
preArchive := content preArchive := content
if len(srcInfo.RebaseName) != 0 { if len(srcInfo.RebaseName) != 0 {
_, srcBase := archive.SplitPathDirEntry(srcInfo.Path) _, srcBase := archive.SplitPathDirEntry(srcInfo.Path)
preArchive = archive.RebaseArchiveEntries(content, srcBase, srcInfo.RebaseName) preArchive = archive.RebaseArchiveEntries(content, srcBase, srcInfo.RebaseName)
} }
return archive.CopyTo(preArchive, srcInfo, dstPath)
if copyConfig.quiet {
return archive.CopyTo(preArchive, srcInfo, dstPath)
}
fmt.Fprint(dockerCli.Err(), aec.Save)
fmt.Fprintln(dockerCli.Err(), "Preparing to copy...")
res := archive.CopyTo(preArchive, srcInfo, dstPath)
fmt.Fprint(dockerCli.Err(), aec.Restore)
fmt.Fprint(dockerCli.Err(), aec.EraseLine(aec.EraseModes.All))
fmt.Fprintln(dockerCli.Err(), "Successfully copied", units.HumanSize(copiedSize), "to", dstPath)
return res
} }
// In order to get the copy behavior right, we need to know information // In order to get the copy behavior right, we need to know information
@ -229,8 +288,9 @@ func copyToContainer(ctx context.Context, dockerCli command.Cli, copyConfig cpCo
} }
var ( var (
content io.Reader content io.ReadCloser
resolvedDstPath string resolvedDstPath string
copiedSize float64
) )
if srcPath == "-" { if srcPath == "-" {
@ -272,13 +332,33 @@ func copyToContainer(ctx context.Context, dockerCli command.Cli, copyConfig cpCo
resolvedDstPath = dstDir resolvedDstPath = dstDir
content = preparedArchive content = preparedArchive
if !copyConfig.quiet {
content = &copyProgressPrinter{
ReadCloser: content,
toContainer: true,
writer: dockerCli.Err(),
total: &copiedSize,
}
}
} }
options := types.CopyToContainerOptions{ options := types.CopyToContainerOptions{
AllowOverwriteDirWithFile: false, AllowOverwriteDirWithFile: false,
CopyUIDGID: copyConfig.copyUIDGID, CopyUIDGID: copyConfig.copyUIDGID,
} }
return client.CopyToContainer(ctx, copyConfig.container, resolvedDstPath, content, options)
if copyConfig.quiet {
return client.CopyToContainer(ctx, copyConfig.container, resolvedDstPath, content, options)
}
fmt.Fprint(dockerCli.Err(), aec.Save)
fmt.Fprintln(dockerCli.Err(), "Preparing to copy...")
res := client.CopyToContainer(ctx, copyConfig.container, resolvedDstPath, content, options)
fmt.Fprint(dockerCli.Err(), aec.Restore)
fmt.Fprint(dockerCli.Err(), aec.EraseLine(aec.EraseModes.All))
fmt.Fprintln(dockerCli.Err(), "Successfully copied", units.HumanSize(copiedSize), "to", copyConfig.container+":"+dstInfo.Path)
return res
} }
// We use `:` as a delimiter between CONTAINER and PATH, but `:` could also be // We use `:` as a delimiter between CONTAINER and PATH, but `:` could also be

View File

@ -77,7 +77,7 @@ func TestRunCopyFromContainerToFilesystem(t *testing.T) {
return readCloser, types.ContainerPathStat{}, err return readCloser, types.ContainerPathStat{}, err
}, },
} }
options := copyOptions{source: "container:/path", destination: destDir.Path()} options := copyOptions{source: "container:/path", destination: destDir.Path(), quiet: true}
cli := test.NewFakeCli(fakeClient) cli := test.NewFakeCli(fakeClient)
err := runCopy(cli, options) err := runCopy(cli, options)
assert.NilError(t, err) assert.NilError(t, err)