Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
26 changes: 16 additions & 10 deletions cli/command/container/cp.go
Original file line number Diff line number Diff line change
Expand Up @@ -335,23 +335,27 @@ func copyFromContainer(ctx context.Context, dockerCLI command.Cli, copyConfig cp
}

restore, done := copyProgress(ctx, dockerCLI.Err(), copyFromContainerHeader, &copiedSize)
res := archive.CopyTo(preArchive, srcInfo, dstPath)
err = archive.CopyTo(preArchive, srcInfo, dstPath)
cancel()
<-done
restore()
reportedSize := copiedSize
if !cpRes.Stat.Mode.IsDir() {
reportedSize = cpRes.Stat.Size
if err == nil {
reportedSize := copiedSize
if !cpRes.Stat.Mode.IsDir() {
reportedSize = cpRes.Stat.Size
}
_, _ = fmt.Fprint(dockerCLI.Err(), copySummary(reportedSize, copiedSize, dstPath))
}
_, _ = fmt.Fprint(dockerCLI.Err(), copySummary(reportedSize, copiedSize, dstPath))

return res
return err
}

// In order to get the copy behavior right, we need to know information
// about both the source and destination. The API is a simple tar
// archive/extract API but we can use the stat info header about the
// destination to be more informed about exactly what the destination is.
//
//nolint:gocyclo
func copyToContainer(ctx context.Context, dockerCLI command.Cli, copyConfig cpConfig) error {
srcPath := copyConfig.sourcePath
dstPath := copyConfig.destPath
Expand Down Expand Up @@ -474,11 +478,13 @@ func copyToContainer(ctx context.Context, dockerCLI command.Cli, copyConfig cpCo
cancel()
<-done
restore()
reportedSize := copiedSize
if sizeErr == nil {
reportedSize = contentSize
if err == nil {
reportedSize := copiedSize
if sizeErr == nil {
reportedSize = contentSize
}
_, _ = fmt.Fprint(dockerCLI.Err(), copySummary(reportedSize, copiedSize, copyConfig.container+":"+dstInfo.Path))
}
_, _ = fmt.Fprint(dockerCLI.Err(), copySummary(reportedSize, copiedSize, copyConfig.container+":"+dstInfo.Path))

return err
}
Expand Down
62 changes: 62 additions & 0 deletions cli/command/container/cp_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package container

import (
"context"
"errors"
"io"
"os"
"runtime"
Expand Down Expand Up @@ -446,3 +447,64 @@ func TestCopyToContainerStdinReportsTransferredSize(t *testing.T) {
// "(transferred ...)" should not appear.
assert.Check(t, !strings.Contains(errOut, "(transferred"))
}

func TestCopyFromContainerDoesNotReportSuccessOnFailure(t *testing.T) {
// Copying a directory onto an existing regular file fails while
// extracting ("cannot copy directory"). The failure must not be
// preceded by a "Successfully copied" message.
srcDir := fs.NewDir(t, "cp-test-fromdir-fail",
fs.WithFile("file1", "hello"))
destFile := fs.NewFile(t, "cp-test-fromdir-fail-dest", fs.WithContent("original\n"))

fakeCli := test.NewFakeCli(&fakeClient{
containerCopyFromFunc: func(ctr, srcPath string) (client.CopyFromContainerResult, error) {
readCloser, err := archive.Tar(srcDir.Path(), compression.None)
return client.CopyFromContainerResult{
Content: readCloser,
Stat: container.PathStat{
Name: "mydir",
Mode: os.ModeDir | 0o755,
},
}, err
},
})
err := runCopy(context.TODO(), fakeCli, copyOptions{
source: "container:/mydir",
destination: destFile.Path(),
})
assert.ErrorContains(t, err, "cannot copy directory")
assert.Check(t, !strings.Contains(fakeCli.ErrBuffer().String(), "Successfully copied"),
"unexpected success message in output: %q", fakeCli.ErrBuffer().String())

content, err := os.ReadFile(destFile.Path())
assert.NilError(t, err)
assert.Check(t, is.Equal("original\n", string(content)))
}

func TestCopyToContainerDoesNotReportSuccessOnFailure(t *testing.T) {
// A failed upload must not be followed by a "Successfully copied"
// message.
srcFile := fs.NewFile(t, "cp-test-to-fail", fs.WithContent("hello"))

fakeCli := test.NewFakeCli(&fakeClient{
containerStatPathFunc: func(containerID, path string) (client.ContainerStatPathResult, error) {
return client.ContainerStatPathResult{
Stat: container.PathStat{
Name: "tmp",
Mode: os.ModeDir | 0o755,
},
}, nil
},
containerCopyToFunc: func(containerID string, options client.CopyToContainerOptions) (client.CopyToContainerResult, error) {
_, _ = io.Copy(io.Discard, options.Content)
return client.CopyToContainerResult{}, errors.New("copy to container failed")
},
})
err := runCopy(context.TODO(), fakeCli, copyOptions{
source: srcFile.Path(),
destination: "container:/tmp",
})
assert.ErrorContains(t, err, "copy to container failed")
assert.Check(t, !strings.Contains(fakeCli.ErrBuffer().String(), "Successfully copied"),
"unexpected success message in output: %q", fakeCli.ErrBuffer().String())
}
Loading