Pārlūkot izejas kodu

Merge pull request #5715 from proppy/context-tar

allow docker build - to accept a context tar from stdin
Victor Vieux 11 gadi atpakaļ
vecāks
revīzija
2d195fb6f8

+ 19 - 6
api/client/commands.go

@@ -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 {

+ 10 - 0
archive/archive.go

@@ -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},

+ 10 - 5
docs/sources/reference/builder.md

@@ -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:
 

+ 8 - 3
docs/sources/reference/commandline/cli.md

@@ -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
 

+ 3 - 0
integration-cli/build_tests/TestContextTar/Dockerfile

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

+ 1 - 0
integration-cli/build_tests/TestContextTar/foo

@@ -0,0 +1 @@
+foo

+ 46 - 0
integration-cli/docker_cli_build_test.go

@@ -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"