Skip to content

Commit 0c950fe

Browse files
committed
Compress after rewriting the archive.
Write a test showing compress failure. Signed-off-by: Daniel Nephin <[email protected]>
1 parent 868cb74 commit 0c950fe

5 files changed

Lines changed: 122 additions & 14 deletions

File tree

cli/command/image/build.go

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -75,16 +75,20 @@ func (o buildOptions) contextFromStdin() bool {
7575
return o.context == "-"
7676
}
7777

78-
// NewBuildCommand creates a new `docker build` command
79-
func NewBuildCommand(dockerCli command.Cli) *cobra.Command {
78+
func newBuildOptions() buildOptions {
8079
ulimits := make(map[string]*units.Ulimit)
81-
options := buildOptions{
80+
return buildOptions{
8281
tags: opts.NewListOpts(validateTag),
8382
buildArgs: opts.NewListOpts(opts.ValidateEnv),
8483
ulimits: opts.NewUlimitOpt(&ulimits),
8584
labels: opts.NewListOpts(opts.ValidateEnv),
8685
extraHosts: opts.NewListOpts(opts.ValidateExtraHost),
8786
}
87+
}
88+
89+
// NewBuildCommand creates a new `docker build` command
90+
func NewBuildCommand(dockerCli command.Cli) *cobra.Command {
91+
options := newBuildOptions()
8892

8993
cmd := &cobra.Command{
9094
Use: "build [OPTIONS] PATH | URL | -",
@@ -228,13 +232,7 @@ func runBuild(dockerCli command.Cli, options buildOptions) error {
228232
}
229233

230234
excludes = build.TrimBuildFilesFromExcludes(excludes, relDockerfile, options.dockerfileFromStdin())
231-
232-
compression := archive.Uncompressed
233-
if options.compress {
234-
compression = archive.Gzip
235-
}
236235
buildCtx, err = archive.TarWithOptions(contextDir, &archive.TarOptions{
237-
Compression: compression,
238236
ExcludePatterns: excludes,
239237
})
240238
if err != nil {
@@ -262,12 +260,18 @@ func runBuild(dockerCli command.Cli, options buildOptions) error {
262260
buildCtx = replaceDockerfileTarWrapper(ctx, buildCtx, relDockerfile, translator, &resolvedTags)
263261
}
264262

263+
if options.compress {
264+
buildCtx, err = build.Compress(buildCtx)
265+
if err != nil {
266+
return err
267+
}
268+
}
269+
265270
// Setup an upload progress bar
266271
progressOutput := streamformatter.NewProgressOutput(progBuff)
267272
if !dockerCli.Out().IsTerminal() {
268273
progressOutput = &lastProgressOutput{output: progressOutput}
269274
}
270-
271275
var body io.Reader = progress.NewProgressReader(buildCtx, progressOutput, 0, "", "Sending build context to Docker daemon")
272276

273277
configFile := dockerCli.ConfigFile()

cli/command/image/build/context.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import (
1919
"github.com/docker/docker/pkg/archive"
2020
"github.com/docker/docker/pkg/fileutils"
2121
"github.com/docker/docker/pkg/ioutils"
22+
"github.com/docker/docker/pkg/pools"
2223
"github.com/docker/docker/pkg/progress"
2324
"github.com/docker/docker/pkg/streamformatter"
2425
"github.com/docker/docker/pkg/stringid"
@@ -375,3 +376,27 @@ func AddDockerfileToBuildContext(dockerfileCtx io.ReadCloser, buildCtx io.ReadCl
375376
})
376377
return buildCtx, randomName, nil
377378
}
379+
380+
// Compress the build context for sending to the API
381+
func Compress(buildCtx io.ReadCloser) (io.ReadCloser, error) {
382+
pipeReader, pipeWriter := io.Pipe()
383+
384+
go func() {
385+
compressWriter, err := archive.CompressStream(pipeWriter, archive.Gzip)
386+
if err != nil {
387+
pipeWriter.CloseWithError(err)
388+
}
389+
defer buildCtx.Close()
390+
391+
if _, err := pools.Copy(compressWriter, buildCtx); err != nil {
392+
pipeWriter.CloseWithError(
393+
errors.Wrap(err, "failed to compress context"))
394+
compressWriter.Close()
395+
return
396+
}
397+
compressWriter.Close()
398+
pipeWriter.Close()
399+
}()
400+
401+
return pipeReader, nil
402+
}

cli/command/image/build_test.go

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
package image
2+
3+
import (
4+
"bytes"
5+
"io"
6+
"io/ioutil"
7+
"os"
8+
"path/filepath"
9+
"sort"
10+
"testing"
11+
12+
"github.com/docker/cli/cli/command"
13+
"github.com/docker/cli/cli/internal/test"
14+
"github.com/docker/docker/api/types"
15+
"github.com/docker/docker/pkg/archive"
16+
"github.com/stretchr/testify/assert"
17+
"github.com/stretchr/testify/require"
18+
"golang.org/x/net/context"
19+
)
20+
21+
func TestRunBuildDockerfileFromStdinWithCompress(t *testing.T) {
22+
dest, err := ioutil.TempDir("", "test-build-compress-dest")
23+
require.NoError(t, err)
24+
defer os.RemoveAll(dest)
25+
26+
var dockerfileName string
27+
fakeImageBuild := func(_ context.Context, context io.Reader, options types.ImageBuildOptions) (types.ImageBuildResponse, error) {
28+
buffer := new(bytes.Buffer)
29+
tee := io.TeeReader(context, buffer)
30+
31+
assert.NoError(t, archive.Untar(tee, dest, nil))
32+
dockerfileName = options.Dockerfile
33+
34+
header := buffer.Bytes()[:10]
35+
assert.Equal(t, archive.Gzip, archive.DetectCompression(header))
36+
37+
body := new(bytes.Buffer)
38+
return types.ImageBuildResponse{Body: ioutil.NopCloser(body)}, nil
39+
}
40+
41+
cli := test.NewFakeCli(&fakeClient{imageBuildFunc: fakeImageBuild}, ioutil.Discard)
42+
dockerfile := bytes.NewBufferString(`
43+
FROM alpine:3.6
44+
COPY foo /
45+
`)
46+
cli.SetIn(command.NewInStream(ioutil.NopCloser(dockerfile)))
47+
48+
dir, err := ioutil.TempDir("", "test-build-compress")
49+
require.NoError(t, err)
50+
defer os.RemoveAll(dir)
51+
52+
ioutil.WriteFile(filepath.Join(dir, "foo"), []byte("some content"), 0644)
53+
54+
options := newBuildOptions()
55+
options.compress = true
56+
options.dockerfileName = "-"
57+
options.context = dir
58+
59+
err = runBuild(cli, options)
60+
require.NoError(t, err)
61+
62+
files, err := ioutil.ReadDir(dest)
63+
require.NoError(t, err)
64+
actual := []string{}
65+
for _, fileInfo := range files {
66+
actual = append(actual, fileInfo.Name())
67+
}
68+
sort.Strings(actual)
69+
assert.Equal(t, []string{dockerfileName, ".dockerignore", "foo"}, actual)
70+
}

cli/command/image/client_test.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ type fakeClient struct {
2727
imageInspectFunc func(image string) (types.ImageInspect, []byte, error)
2828
imageImportFunc func(source types.ImageImportSource, ref string, options types.ImageImportOptions) (io.ReadCloser, error)
2929
imageHistoryFunc func(image string) ([]image.HistoryResponseItem, error)
30+
imageBuildFunc func(context.Context, io.Reader, types.ImageBuildOptions) (types.ImageBuildResponse, error)
3031
}
3132

3233
func (cli *fakeClient) ImageTag(_ context.Context, image, ref string) error {
@@ -114,3 +115,10 @@ func (cli *fakeClient) ImageHistory(_ context.Context, img string) ([]image.Hist
114115
}
115116
return []image.HistoryResponseItem{{ID: img, Created: time.Now().Unix()}}, nil
116117
}
118+
119+
func (cli *fakeClient) ImageBuild(ctx context.Context, context io.Reader, options types.ImageBuildOptions) (types.ImageBuildResponse, error) {
120+
if cli.imageBuildFunc != nil {
121+
return cli.imageBuildFunc(ctx, context, options)
122+
}
123+
return types.ImageBuildResponse{Body: ioutil.NopCloser(strings.NewReader(""))}, nil
124+
}

cli/internal/test/cli.go

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,11 @@ type FakeCli struct {
2525
// NewFakeCli returns a Cli backed by the fakeCli
2626
func NewFakeCli(client client.APIClient, out io.Writer) *FakeCli {
2727
return &FakeCli{
28-
client: client,
29-
out: command.NewOutStream(out),
30-
err: ioutil.Discard,
31-
in: command.NewInStream(ioutil.NopCloser(strings.NewReader(""))),
28+
client: client,
29+
out: command.NewOutStream(out),
30+
err: ioutil.Discard,
31+
in: command.NewInStream(ioutil.NopCloser(strings.NewReader(""))),
32+
configfile: configfile.NewConfigFile("configfile"),
3233
}
3334
}
3435

0 commit comments

Comments
 (0)