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

Build stdin with compress #233

Merged
merged 1 commit into from
Jun 30, 2017
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
23 changes: 14 additions & 9 deletions cli/command/image/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,16 +77,20 @@ func (o buildOptions) contextFromStdin() bool {
return o.context == "-"
}

// NewBuildCommand creates a new `docker build` command
func NewBuildCommand(dockerCli command.Cli) *cobra.Command {
func newBuildOptions() buildOptions {
ulimits := make(map[string]*units.Ulimit)
options := buildOptions{
return buildOptions{
tags: opts.NewListOpts(validateTag),
buildArgs: opts.NewListOpts(opts.ValidateEnv),
ulimits: opts.NewUlimitOpt(&ulimits),
labels: opts.NewListOpts(opts.ValidateEnv),
extraHosts: opts.NewListOpts(opts.ValidateExtraHost),
}
}

// NewBuildCommand creates a new `docker build` command
func NewBuildCommand(dockerCli command.Cli) *cobra.Command {
options := newBuildOptions()

cmd := &cobra.Command{
Use: "build [OPTIONS] PATH | URL | -",
Expand Down Expand Up @@ -237,13 +241,7 @@ func runBuild(dockerCli command.Cli, options buildOptions) error {
}

excludes = build.TrimBuildFilesFromExcludes(excludes, relDockerfile, options.dockerfileFromStdin())

compression := archive.Uncompressed
if options.compress {
compression = archive.Gzip
}
buildCtx, err = archive.TarWithOptions(contextDir, &archive.TarOptions{
Compression: compression,
ExcludePatterns: excludes,
})
if err != nil {
Expand Down Expand Up @@ -292,6 +290,13 @@ func runBuild(dockerCli command.Cli, options buildOptions) error {
}
}

if options.compress {
buildCtx, err = build.Compress(buildCtx)
if err != nil {
return err
}
}

// Setup an upload progress bar
progressOutput := streamformatter.NewProgressOutput(progBuff)
if !dockerCli.Out().IsTerminal() {
Expand Down
25 changes: 25 additions & 0 deletions cli/command/image/build/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
"github.com/docker/docker/pkg/archive"
"github.com/docker/docker/pkg/fileutils"
"github.com/docker/docker/pkg/ioutils"
"github.com/docker/docker/pkg/pools"
"github.com/docker/docker/pkg/progress"
"github.com/docker/docker/pkg/streamformatter"
"github.com/docker/docker/pkg/stringid"
Expand Down Expand Up @@ -375,3 +376,27 @@ func AddDockerfileToBuildContext(dockerfileCtx io.ReadCloser, buildCtx io.ReadCl
})
return buildCtx, randomName, nil
}

// Compress the build context for sending to the API
func Compress(buildCtx io.ReadCloser) (io.ReadCloser, error) {
pipeReader, pipeWriter := io.Pipe()

go func() {
compressWriter, err := archive.CompressStream(pipeWriter, archive.Gzip)
if err != nil {
pipeWriter.CloseWithError(err)
}
defer buildCtx.Close()

if _, err := pools.Copy(compressWriter, buildCtx); err != nil {
pipeWriter.CloseWithError(
errors.Wrap(err, "failed to compress context"))
compressWriter.Close()
return
}
compressWriter.Close()
pipeWriter.Close()
}()

return pipeReader, nil
}
70 changes: 70 additions & 0 deletions cli/command/image/build_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package image

import (
"bytes"
"io"
"io/ioutil"
"os"
"path/filepath"
"sort"
"testing"

"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/internal/test"
"github.com/docker/docker/api/types"
"github.com/docker/docker/pkg/archive"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"golang.org/x/net/context"
)

func TestRunBuildDockerfileFromStdinWithCompress(t *testing.T) {
dest, err := ioutil.TempDir("", "test-build-compress-dest")
require.NoError(t, err)
defer os.RemoveAll(dest)

var dockerfileName string
fakeImageBuild := func(_ context.Context, context io.Reader, options types.ImageBuildOptions) (types.ImageBuildResponse, error) {
buffer := new(bytes.Buffer)
tee := io.TeeReader(context, buffer)

assert.NoError(t, archive.Untar(tee, dest, nil))
dockerfileName = options.Dockerfile

header := buffer.Bytes()[:10]
assert.Equal(t, archive.Gzip, archive.DetectCompression(header))

body := new(bytes.Buffer)
return types.ImageBuildResponse{Body: ioutil.NopCloser(body)}, nil
}

cli := test.NewFakeCli(&fakeClient{imageBuildFunc: fakeImageBuild}, ioutil.Discard)
dockerfile := bytes.NewBufferString(`
FROM alpine:3.6
COPY foo /
`)
cli.SetIn(command.NewInStream(ioutil.NopCloser(dockerfile)))

dir, err := ioutil.TempDir("", "test-build-compress")
require.NoError(t, err)
defer os.RemoveAll(dir)

ioutil.WriteFile(filepath.Join(dir, "foo"), []byte("some content"), 0644)

options := newBuildOptions()
options.compress = true
options.dockerfileName = "-"
options.context = dir

err = runBuild(cli, options)
require.NoError(t, err)

files, err := ioutil.ReadDir(dest)
require.NoError(t, err)
actual := []string{}
for _, fileInfo := range files {
actual = append(actual, fileInfo.Name())
}
sort.Strings(actual)
assert.Equal(t, []string{dockerfileName, ".dockerignore", "foo"}, actual)
}
8 changes: 8 additions & 0 deletions cli/command/image/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ type fakeClient struct {
imageInspectFunc func(image string) (types.ImageInspect, []byte, error)
imageImportFunc func(source types.ImageImportSource, ref string, options types.ImageImportOptions) (io.ReadCloser, error)
imageHistoryFunc func(image string) ([]image.HistoryResponseItem, error)
imageBuildFunc func(context.Context, io.Reader, types.ImageBuildOptions) (types.ImageBuildResponse, error)
}

func (cli *fakeClient) ImageTag(_ context.Context, image, ref string) error {
Expand Down Expand Up @@ -114,3 +115,10 @@ func (cli *fakeClient) ImageHistory(_ context.Context, img string) ([]image.Hist
}
return []image.HistoryResponseItem{{ID: img, Created: time.Now().Unix()}}, nil
}

func (cli *fakeClient) ImageBuild(ctx context.Context, context io.Reader, options types.ImageBuildOptions) (types.ImageBuildResponse, error) {
if cli.imageBuildFunc != nil {
return cli.imageBuildFunc(ctx, context, options)
}
return types.ImageBuildResponse{Body: ioutil.NopCloser(strings.NewReader(""))}, nil
}
15 changes: 11 additions & 4 deletions cli/internal/test/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,17 @@ type FakeCli struct {
out *command.OutStream
err io.Writer
in *command.InStream
server command.ServerInfo
}

// NewFakeCli returns a Cli backed by the fakeCli
func NewFakeCli(client client.APIClient, out io.Writer) *FakeCli {
return &FakeCli{
client: client,
out: command.NewOutStream(out),
err: ioutil.Discard,
in: command.NewInStream(ioutil.NopCloser(strings.NewReader(""))),
client: client,
out: command.NewOutStream(out),
err: ioutil.Discard,
in: command.NewInStream(ioutil.NopCloser(strings.NewReader(""))),
configfile: configfile.New("configfile"),
}
}

Expand Down Expand Up @@ -69,3 +71,8 @@ func (c *FakeCli) In() *command.InStream {
func (c *FakeCli) ConfigFile() *configfile.ConfigFile {
return c.configfile
}

// ServerInfo returns API server information for the server used by this client
func (c *FakeCli) ServerInfo() command.ServerInfo {
return c.server
}