Quellcode durchsuchen

Merge pull request #6579 from vieux/dockerignore

Rebased Dockerignore
Tibor Vass vor 11 Jahren
Ursprung
Commit
6e3fe93148

+ 2 - 0
.dockerignore

@@ -0,0 +1,2 @@
+bundles
+.gopath

+ 1 - 0
MAINTAINERS

@@ -6,3 +6,4 @@ Michael Crosby <michael@crosbymichael.com> (@crosbymichael)
 AUTHORS: Tianon Gravi <admwiggin@gmail.com> (@tianon)
 Dockerfile: Tianon Gravi <admwiggin@gmail.com> (@tianon)
 Makefile: Tianon Gravi <admwiggin@gmail.com> (@tianon)
+.dockerignore: Tianon Gravi <admwiggin@gmail.com> (@tianon)

+ 20 - 1
api/client/commands.go

@@ -13,6 +13,7 @@ import (
 	"os"
 	"os/exec"
 	"path"
+	"path/filepath"
 	"runtime"
 	"strconv"
 	"strings"
@@ -163,7 +164,25 @@ func (cli *DockerCli) CmdBuild(args ...string) error {
 		if err = utils.ValidateContextDirectory(root); err != nil {
 			return fmt.Errorf("Error checking context is accessible: '%s'. Please check permissions and try again.", err)
 		}
-		context, err = archive.Tar(root, archive.Uncompressed)
+		options := &archive.TarOptions{
+			Compression: archive.Uncompressed,
+		}
+		if ignore, err := ioutil.ReadFile(path.Join(root, ".dockerignore")); err != nil && !os.IsNotExist(err) {
+			return fmt.Errorf("Error reading .dockerignore: '%s'", err)
+		} else if err == nil {
+			for _, pattern := range strings.Split(string(ignore), "\n") {
+				ok, err := filepath.Match(pattern, "Dockerfile")
+				if err != nil {
+					utils.Errorf("Bad .dockerignore pattern: '%s', error: %s", pattern, err)
+					continue
+				}
+				if ok {
+					return fmt.Errorf("Dockerfile was excluded by .dockerignore pattern '%s'", pattern)
+				}
+				options.Excludes = append(options.Excludes, pattern)
+			}
+		}
+		context, err = archive.TarWithOptions(root, options)
 	}
 	var body io.Reader
 	// Setup an upload progress bar

+ 21 - 8
archive/archive.go

@@ -27,6 +27,7 @@ type (
 	Compression   int
 	TarOptions    struct {
 		Includes    []string
+		Excludes    []string
 		Compression Compression
 		NoLchown    bool
 	}
@@ -286,7 +287,7 @@ func createTarFile(path, extractDir string, hdr *tar.Header, reader io.Reader, L
 // Tar creates an archive from the directory at `path`, and returns it as a
 // stream of bytes.
 func Tar(path string, compression Compression) (io.ReadCloser, error) {
-	return TarFilter(path, &TarOptions{Compression: compression})
+	return TarWithOptions(path, &TarOptions{Compression: compression})
 }
 
 func escapeName(name string) string {
@@ -305,12 +306,9 @@ func escapeName(name string) string {
 	return string(escaped)
 }
 
-// TarFilter creates an archive from the directory at `srcPath` with `options`, and returns it as a
-// stream of bytes.
-//
-// Files are included according to `options.Includes`, default to including all files.
-// Stream is compressed according to `options.Compression', default to Uncompressed.
-func TarFilter(srcPath string, options *TarOptions) (io.ReadCloser, error) {
+// TarWithOptions creates an archive from the directory at `path`, only including files whose relative
+// paths are included in `options.Includes` (if non-nil) or not in `options.Excludes`.
+func TarWithOptions(srcPath string, options *TarOptions) (io.ReadCloser, error) {
 	pipeReader, pipeWriter := io.Pipe()
 
 	compressWriter, err := CompressStream(pipeWriter, options.Compression)
@@ -342,6 +340,21 @@ func TarFilter(srcPath string, options *TarOptions) (io.ReadCloser, error) {
 					return nil
 				}
 
+				for _, exclude := range options.Excludes {
+					matched, err := filepath.Match(exclude, relFilePath)
+					if err != nil {
+						utils.Errorf("Error matching: %s (pattern: %s)", relFilePath, exclude)
+						return err
+					}
+					if matched {
+						utils.Debugf("Skipping excluded path: %s", relFilePath)
+						if f.IsDir() {
+							return filepath.SkipDir
+						}
+						return nil
+					}
+				}
+
 				if err := addTarFile(filePath, relFilePath, tw); err != nil {
 					utils.Debugf("Can't add file %s to tar: %s\n", srcPath, err)
 				}
@@ -482,7 +495,7 @@ func Untar(archive io.Reader, dest string, options *TarOptions) error {
 // TarUntar aborts and returns the error.
 func TarUntar(src string, dst string) error {
 	utils.Debugf("TarUntar(%s %s)", src, dst)
-	archive, err := TarFilter(src, &TarOptions{Compression: Uncompressed})
+	archive, err := TarWithOptions(src, &TarOptions{Compression: Uncompressed})
 	if err != nil {
 		return err
 	}

+ 51 - 19
archive/archive_test.go

@@ -63,8 +63,8 @@ func TestCmdStreamGood(t *testing.T) {
 	}
 }
 
-func tarUntar(t *testing.T, origin string, compression Compression) error {
-	archive, err := Tar(origin, compression)
+func tarUntar(t *testing.T, origin string, options *TarOptions) ([]Change, error) {
+	archive, err := TarWithOptions(origin, options)
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -72,37 +72,29 @@ func tarUntar(t *testing.T, origin string, compression Compression) error {
 
 	buf := make([]byte, 10)
 	if _, err := archive.Read(buf); err != nil {
-		return err
+		return nil, err
 	}
 	wrap := io.MultiReader(bytes.NewReader(buf), archive)
 
 	detectedCompression := DetectCompression(buf)
+	compression := options.Compression
 	if detectedCompression.Extension() != compression.Extension() {
-		return fmt.Errorf("Wrong compression detected. Actual compression: %s, found %s", compression.Extension(), detectedCompression.Extension())
+		return nil, fmt.Errorf("Wrong compression detected. Actual compression: %s, found %s", compression.Extension(), detectedCompression.Extension())
 	}
 
 	tmp, err := ioutil.TempDir("", "docker-test-untar")
 	if err != nil {
-		return err
+		return nil, err
 	}
 	defer os.RemoveAll(tmp)
 	if err := Untar(wrap, tmp, nil); err != nil {
-		return err
+		return nil, err
 	}
 	if _, err := os.Stat(tmp); err != nil {
-		return err
+		return nil, err
 	}
 
-	changes, err := ChangesDirs(origin, tmp)
-	if err != nil {
-		return err
-	}
-
-	if len(changes) != 0 {
-		t.Fatalf("Unexpected differences after tarUntar: %v", changes)
-	}
-
-	return nil
+	return ChangesDirs(origin, tmp)
 }
 
 func TestTarUntar(t *testing.T) {
@@ -122,9 +114,49 @@ func TestTarUntar(t *testing.T) {
 		Uncompressed,
 		Gzip,
 	} {
-		if err := tarUntar(t, origin, c); err != nil {
+		changes, err := tarUntar(t, origin, &TarOptions{
+			Compression: c,
+		})
+
+		if err != nil {
 			t.Fatalf("Error tar/untar for compression %s: %s", c.Extension(), err)
 		}
+
+		if len(changes) != 0 {
+			t.Fatalf("Unexpected differences after tarUntar: %v", changes)
+		}
+	}
+}
+
+func TestTarWithOptions(t *testing.T) {
+	origin, err := ioutil.TempDir("", "docker-test-untar-origin")
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer os.RemoveAll(origin)
+	if err := ioutil.WriteFile(path.Join(origin, "1"), []byte("hello world"), 0700); err != nil {
+		t.Fatal(err)
+	}
+	if err := ioutil.WriteFile(path.Join(origin, "2"), []byte("welcome!"), 0700); err != nil {
+		t.Fatal(err)
+	}
+
+	cases := []struct {
+		opts       *TarOptions
+		numChanges int
+	}{
+		{&TarOptions{Includes: []string{"1"}}, 1},
+		{&TarOptions{Excludes: []string{"2"}}, 1},
+	}
+	for _, testCase := range cases {
+		changes, err := tarUntar(t, origin, testCase.opts)
+		if err != nil {
+			t.Fatalf("Error tar/untar when testing inclusion/exclusion: %s", err)
+		}
+		if len(changes) != testCase.numChanges {
+			t.Errorf("Expected %d changes, got %d for %+v:",
+				testCase.numChanges, len(changes), testCase.opts)
+		}
 	}
 }
 
@@ -145,7 +177,7 @@ func TestTarUntarFile(t *testing.T) {
 		t.Fatal(err)
 	}
 
-	tar, err := TarFilter(path.Join(origin, "before"), &TarOptions{Compression: Uncompressed, Includes: []string{"file"}})
+	tar, err := TarWithOptions(path.Join(origin, "before"), &TarOptions{Compression: Uncompressed, Includes: []string{"file"}})
 	if err != nil {
 		t.Fatal(err)
 	}

+ 1 - 1
daemon/container.go

@@ -813,7 +813,7 @@ func (container *Container) Copy(resource string) (io.ReadCloser, error) {
 		basePath = path.Dir(basePath)
 	}
 
-	archive, err := archive.TarFilter(basePath, &archive.TarOptions{
+	archive, err := archive.TarWithOptions(basePath, &archive.TarOptions{
 		Compression: archive.Uncompressed,
 		Includes:    filter,
 	})

+ 1 - 1
daemon/graphdriver/aufs/aufs.go

@@ -295,7 +295,7 @@ func (a *Driver) Put(id string) {
 
 // Returns an archive of the contents for the id
 func (a *Driver) Diff(id string) (archive.Archive, error) {
-	return archive.TarFilter(path.Join(a.rootPath(), "diff", id), &archive.TarOptions{
+	return archive.TarWithOptions(path.Join(a.rootPath(), "diff", id), &archive.TarOptions{
 		Compression: archive.Uncompressed,
 	})
 }

+ 30 - 0
docs/sources/reference/commandline/cli.md

@@ -215,6 +215,12 @@ temporary directory on your local host, and then this is sent to the
 Docker daemon as the context. This way, your local user credentials and
 vpn's etc can be used to access private repositories.
 
+If a file named ``.dockerignore`` exists in the root of ``PATH`` then it is
+interpreted as a newline-separated list of exclusion patterns. Exclusion
+patterns match files or directories relative to ``PATH`` that will be excluded
+from the context. Globbing is done using Go's
+[filepath.Match](http://golang.org/pkg/path/filepath#Match) rules.
+
 See also:
 
 [*Dockerfile Reference*](/reference/builder/#dockerbuilder).
@@ -266,6 +272,30 @@ If you wish to keep the intermediate containers after the build is
 complete, you must use `--rm=false`. This does not
 affect the build cache.
 
+    $ docker build .
+    Uploading context 18.829 MB
+    Uploading context
+    Step 0 : FROM busybox
+     ---> 769b9341d937
+    Step 1 : CMD echo Hello World
+     ---> Using cache
+     ---> 99cc1ad10469
+    Successfully built 99cc1ad10469
+    $ echo ".git" > .dockerignore
+    $ docker build .
+    Uploading context  6.76 MB
+    Uploading context
+    Step 0 : FROM busybox
+     ---> 769b9341d937
+    Step 1 : CMD echo Hello World
+     ---> Using cache
+     ---> 99cc1ad10469
+    Successfully built 99cc1ad10469
+
+This example shows the use of the ``.dockerignore`` file to exclude the ``.git``
+directory the context. Its effect can be seen in the changed size of the
+uploaded context.
+
     $ sudo docker build -t vieux/apache:2.0 .
 
 This will build like the previous example, but it will then tag the

+ 50 - 0
integration-cli/docker_cli_build_test.go

@@ -1514,3 +1514,53 @@ docker.com>"
 
 	logDone("build - validate escaping whitespace")
 }
+
+func TestDockerignore(t *testing.T) {
+	name := "testbuilddockerignore"
+	defer deleteImages(name)
+	dockerfile := `
+        FROM busybox
+        ADD . /bla
+		RUN [[ -f /bla/src/x.go ]]
+		RUN [[ -f /bla/Makefile ]]
+		RUN [[ ! -e /bla/src/_vendor ]]
+		RUN [[ ! -e /bla/.gitignore ]]
+		RUN [[ ! -e /bla/README.md ]]
+		RUN [[ ! -e /bla/.git ]]`
+	ctx, err := fakeContext(dockerfile, map[string]string{
+		"Makefile":         "all:",
+		".git/HEAD":        "ref: foo",
+		"src/x.go":         "package main",
+		"src/_vendor/v.go": "package main",
+		".gitignore":       "",
+		"README.md":        "readme",
+		".dockerignore":    ".git\npkg\n.gitignore\nsrc/_vendor\n*.md",
+	})
+	defer ctx.Close()
+	if err != nil {
+		t.Fatal(err)
+	}
+	if _, err := buildImageFromContext(name, ctx, true); err != nil {
+		t.Fatal(err)
+	}
+	logDone("build - test .dockerignore")
+}
+
+func TestDockerignoringDockerfile(t *testing.T) {
+	name := "testbuilddockerignoredockerfile"
+	defer deleteImages(name)
+	dockerfile := `
+        FROM scratch`
+	ctx, err := fakeContext(dockerfile, map[string]string{
+		"Dockerfile":    "FROM scratch",
+		".dockerignore": "Dockerfile\n",
+	})
+	defer ctx.Close()
+	if err != nil {
+		t.Fatal(err)
+	}
+	if _, err = buildImageFromContext(name, ctx, true); err == nil {
+		t.Fatalf("Didn't get expected error from ignoring Dockerfile")
+	}
+	logDone("build - test .dockerignore of Dockerfile")
+}