api/client/build: allow tar as context for docker build -

Docker-DCO-1.1-Signed-off-by: Johan Euphrosine <proppy@google.com> (github: proppy)
This commit is contained in:
Johan Euphrosine 2014-05-09 15:26:41 -07:00
parent 3f600c831b
commit edcb41451a
7 changed files with 96 additions and 13 deletions

View File

@ -36,6 +36,10 @@ import (
"github.com/dotcloud/docker/utils/filters"
)
const (
tarHeaderSize = 512
)
func (cli *DockerCli) CmdHelp(args ...string) error {
if len(args) > 0 {
method, exists := cli.getMethod(args[0])
@ -113,13 +117,22 @@ func (cli *DockerCli) CmdBuild(args ...string) error {
_, err = exec.LookPath("git")
hasGit := err == nil
if cmd.Arg(0) == "-" {
// As a special case, 'docker build -' will build from an empty context with the
// contents of stdin as a Dockerfile
dockerfile, err := ioutil.ReadAll(cli.in)
if err != nil {
return err
// As a special case, 'docker build -' will build from either an empty context with the
// contents of stdin as a Dockerfile, or a tar-ed context from stdin.
buf := bufio.NewReader(cli.in)
magic, err := buf.Peek(tarHeaderSize)
if err != nil && err != io.EOF {
return fmt.Errorf("failed to peek context header from stdin: %v", err)
}
if !archive.IsArchive(magic) {
dockerfile, err := ioutil.ReadAll(buf)
if err != nil {
return fmt.Errorf("failed to read Dockerfile from stdin: %v", err)
}
context, err = archive.Generate("Dockerfile", string(dockerfile))
} else {
context = ioutil.NopCloser(buf)
}
context, err = archive.Generate("Dockerfile", string(dockerfile))
} else if utils.IsURL(cmd.Arg(0)) && (!utils.IsGIT(cmd.Arg(0)) || !hasGit) {
isRemote = true
} else {

View File

@ -43,6 +43,16 @@ const (
Xz
)
func IsArchive(header []byte) bool {
compression := DetectCompression(header)
if compression != Uncompressed {
return true
}
r := tar.NewReader(bytes.NewBuffer(header))
_, err := r.Next()
return err == nil
}
func DetectCompression(source []byte) Compression {
for compression, m := range map[Compression][]byte{
Bzip2: {0x42, 0x5A, 0x68},

View File

@ -238,10 +238,15 @@ All new files and directories are created with a uid and gid of 0.
In the case where `<src>` is a remote file URL, the destination will have permissions 600.
> **Note**:
> If you build using STDIN (`docker build - < somefile`), there is no
> build context, so the Dockerfile can only contain a URL based ADD
> statement.
> If you build by passing a Dockerfile through STDIN (`docker build - < somefile`),
> there is no build context, so the Dockerfile can only contain a URL
> based ADD statement.
> You can also pass a compressed archive through STDIN:
> (`docker build - < archive.tar.gz`), the `Dockerfile` at the root of
> the archive and the rest of the archive will get used at the context
> of the build.
>
> **Note**:
> If your URL files are protected using authentication, you will need to
> use `RUN wget` , `RUN curl`
@ -361,7 +366,7 @@ execute in `/bin/sh -c`:
FROM ubuntu
ENTRYPOINT wc -l -
For example, that Dockerfile's image will *always* take stdin as input
For example, that Dockerfile's image will *always* take STDIN as input
("-") and print the number of lines ("-l"). If you wanted to make this
optional but default, you could use a CMD:

View File

@ -274,12 +274,17 @@ and the tag will be `2.0`
$ sudo docker build - < Dockerfile
This will read a Dockerfile from *stdin* without
This will read a Dockerfile from STDIN without
context. Due to the lack of a context, no contents of any local
directory will be sent to the `docker` daemon. Since
there is no context, a Dockerfile `ADD`
only works if it refers to a remote URL.
$ sudo docker build - < context.tar.gz
This will build an image for a compressed context read from STDIN.
Supported formats are: bzip2, gzip and xz.
$ sudo docker build github.com/creack/docker-firefox
This will clone the GitHub repository and use the cloned repository as
@ -531,7 +536,7 @@ URLs must start with `http` and point to a single
file archive (.tar, .tar.gz, .tgz, .bzip, .tar.xz, or .txz) containing a
root filesystem. If you would like to import from a local directory or
archive, you can use the `-` parameter to take the
data from *stdin*.
data from STDIN.
### Examples
@ -543,7 +548,7 @@ This will create a new untagged image.
**Import from a local file:**
Import to docker via pipe and *stdin*.
Import to docker via pipe and STDIN.
$ cat exampleimage.tgz | sudo docker import - exampleimagelocal:new

View File

@ -0,0 +1,3 @@
FROM busybox
ADD foo /foo
CMD ["cat", "/foo"]

View File

@ -0,0 +1 @@
foo

View File

@ -9,6 +9,8 @@ import (
"strings"
"testing"
"time"
"github.com/dotcloud/docker/archive"
)
func TestBuildCacheADD(t *testing.T) {
@ -1130,6 +1132,50 @@ func TestBuildADDLocalAndRemoteFilesWithCache(t *testing.T) {
logDone("build - add local and remote file with cache")
}
func testContextTar(t *testing.T, compression archive.Compression) {
contextDirectory := filepath.Join(workingDirectory, "build_tests", "TestContextTar")
context, err := archive.Tar(contextDirectory, compression)
if err != nil {
t.Fatalf("failed to build context tar: %v", err)
}
buildCmd := exec.Command(dockerBinary, "build", "-t", "contexttar", "-")
buildCmd.Stdin = context
out, exitCode, err := runCommandWithOutput(buildCmd)
if err != nil || exitCode != 0 {
t.Fatalf("build failed to complete: %v %v", out, err)
}
deleteImages("contexttar")
logDone(fmt.Sprintf("build - build an image with a context tar, compression: %v", compression))
}
func TestContextTarGzip(t *testing.T) {
testContextTar(t, archive.Gzip)
}
func TestContextTarNoCompression(t *testing.T) {
testContextTar(t, archive.Uncompressed)
}
func TestNoContext(t *testing.T) {
buildCmd := exec.Command(dockerBinary, "build", "-t", "nocontext", "-")
buildCmd.Stdin = strings.NewReader("FROM busybox\nCMD echo ok\n")
out, exitCode, err := runCommandWithOutput(buildCmd)
if err != nil || exitCode != 0 {
t.Fatalf("build failed to complete: %v %v", out, err)
}
out, exitCode, err = cmd(t, "run", "nocontext")
if out != "ok\n" {
t.Fatalf("run produced invalid output: %q, expected %q", out, "ok")
}
deleteImages("nocontext")
logDone("build - build an image with no context")
}
// TODO: TestCaching
func TestBuildADDLocalAndRemoteFilesWithoutCache(t *testing.T) {
name := "testbuildaddlocalandremotefilewithoutcache"