diff --git a/cli/command/image/build.go b/cli/command/image/build.go index 93e6f194b8..5d4c2557c7 100644 --- a/cli/command/image/build.go +++ b/cli/command/image/build.go @@ -6,12 +6,9 @@ import ( "bytes" "fmt" "io" - "io/ioutil" "os" - "path/filepath" "regexp" "runtime" - "time" "github.com/docker/cli/cli" "github.com/docker/cli/cli/command" @@ -20,14 +17,11 @@ import ( "github.com/docker/docker/api" "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/container" - "github.com/docker/docker/builder/dockerignore" "github.com/docker/docker/opts" "github.com/docker/docker/pkg/archive" - "github.com/docker/docker/pkg/fileutils" "github.com/docker/docker/pkg/jsonmessage" "github.com/docker/docker/pkg/progress" "github.com/docker/docker/pkg/streamformatter" - "github.com/docker/docker/pkg/stringid" "github.com/docker/docker/pkg/urlutil" runconfigopts "github.com/docker/docker/runconfig/opts" units "github.com/docker/go-units" @@ -67,6 +61,18 @@ type buildOptions struct { target string } +// dockerfileFromStdin returns true when the user specified that the Dockerfile +// should be read from stdin instead of a file +func (o buildOptions) dockerfileFromStdin() bool { + return o.dockerfileName == "-" +} + +// contextFromStdin returns true when the user specified that the build context +// should be read from stdin +func (o buildOptions) contextFromStdin() bool { + return o.context == "-" +} + // NewBuildCommand creates a new `docker build` command func NewBuildCommand(dockerCli *command.DockerCli) *cobra.Command { ulimits := make(map[string]*units.Ulimit) @@ -156,6 +162,13 @@ func runBuild(dockerCli *command.DockerCli, options buildOptions) error { buildBuff io.Writer ) + if options.dockerfileFromStdin() { + if options.contextFromStdin() { + return errors.New("invalid argument: can't use stdin for both build context and dockerfile") + } + dockerfileCtx = dockerCli.In() + } + specifiedContext := options.context progBuff = dockerCli.Out() buildBuff = dockerCli.Out() @@ -164,15 +177,8 @@ func runBuild(dockerCli *command.DockerCli, options buildOptions) error { buildBuff = bytes.NewBuffer(nil) } - if options.dockerfileName == "-" { - if specifiedContext == "-" { - return errors.New("invalid argument: can't use stdin for both build context and dockerfile") - } - dockerfileCtx = dockerCli.In() - } - switch { - case specifiedContext == "-": + case options.contextFromStdin(): buildCtx, relDockerfile, err = build.GetContextFromReader(dockerCli.In(), options.dockerfileName) case isLocalDir(specifiedContext): contextDir, relDockerfile, err = build.GetContextFromLocalDir(specifiedContext, options.dockerfileName) @@ -197,44 +203,22 @@ func runBuild(dockerCli *command.DockerCli, options buildOptions) error { } if buildCtx == nil { + excludes, err := build.ReadDockerignore(contextDir) + if err != nil { + return err + } + + if err := build.ValidateContextDirectory(contextDir, excludes); err != nil { + return errors.Errorf("error checking context: '%s'.", err) + } + // And canonicalize dockerfile name to a platform-independent one relDockerfile, err = archive.CanonicalTarNameForPath(relDockerfile) if err != nil { return errors.Errorf("cannot canonicalize dockerfile path %s: %v", relDockerfile, err) } - f, err := os.Open(filepath.Join(contextDir, ".dockerignore")) - if err != nil && !os.IsNotExist(err) { - return err - } - defer f.Close() - - var excludes []string - if err == nil { - excludes, err = dockerignore.ReadAll(f) - if err != nil { - return err - } - } - - if err := build.ValidateContextDirectory(contextDir, excludes); err != nil { - return errors.Errorf("Error checking context: '%s'.", err) - } - - // If .dockerignore mentions .dockerignore or the Dockerfile then make - // sure we send both files over to the daemon because Dockerfile is, - // obviously, needed no matter what, and .dockerignore is needed to know - // if either one needs to be removed. The daemon will remove them - // if necessary, after it parses the Dockerfile. Ignore errors here, as - // they will have been caught by validateContextDirectory above. - // Excludes are used instead of includes to maintain the order of files - // in the archive. - if keep, _ := fileutils.Matches(".dockerignore", excludes); keep { - excludes = append(excludes, "!.dockerignore") - } - if keep, _ := fileutils.Matches(relDockerfile, excludes); keep && dockerfileCtx == nil { - excludes = append(excludes, "!"+relDockerfile) - } + excludes = build.TrimBuildFilesFromExcludes(excludes, relDockerfile, options.dockerfileFromStdin()) compression := archive.Uncompressed if options.compress { @@ -251,7 +235,7 @@ func runBuild(dockerCli *command.DockerCli, options buildOptions) error { // replace Dockerfile if added dynamically if dockerfileCtx != nil { - buildCtx, relDockerfile, err = addDockerfileToBuildContext(dockerfileCtx, buildCtx) + buildCtx, relDockerfile, err = build.AddDockerfileToBuildContext(dockerfileCtx, buildCtx) if err != nil { return err } @@ -361,50 +345,6 @@ func runBuild(dockerCli *command.DockerCli, options buildOptions) error { return nil } -func addDockerfileToBuildContext(dockerfileCtx io.ReadCloser, buildCtx io.ReadCloser) (io.ReadCloser, string, error) { - file, err := ioutil.ReadAll(dockerfileCtx) - dockerfileCtx.Close() - if err != nil { - return nil, "", err - } - now := time.Now() - hdrTmpl := &tar.Header{ - Mode: 0600, - Uid: 0, - Gid: 0, - ModTime: now, - Typeflag: tar.TypeReg, - AccessTime: now, - ChangeTime: now, - } - randomName := ".dockerfile." + stringid.GenerateRandomID()[:20] - - buildCtx = archive.ReplaceFileTarWrapper(buildCtx, map[string]archive.TarModifierFunc{ - // Add the dockerfile with a random filename - randomName: func(_ string, h *tar.Header, content io.Reader) (*tar.Header, []byte, error) { - return hdrTmpl, file, nil - }, - // Update .dockerignore to include the random filename - ".dockerignore": func(_ string, h *tar.Header, content io.Reader) (*tar.Header, []byte, error) { - if h == nil { - h = hdrTmpl - } - - b := &bytes.Buffer{} - if content != nil { - if _, err := b.ReadFrom(content); err != nil { - return nil, nil, err - } - } else { - b.WriteString(".dockerignore") - } - b.WriteString("\n" + randomName + "\n") - return h, b.Bytes(), nil - }, - }) - return buildCtx, randomName, nil -} - func isLocalDir(c string) bool { _, err := os.Stat(c) return err == nil diff --git a/cli/command/image/build/context.go b/cli/command/image/build/context.go index 348c721931..c49b5f7fce 100644 --- a/cli/command/image/build/context.go +++ b/cli/command/image/build/context.go @@ -11,6 +11,8 @@ import ( "runtime" "strings" + "archive/tar" + "bytes" "github.com/docker/docker/pkg/archive" "github.com/docker/docker/pkg/fileutils" "github.com/docker/docker/pkg/gitutils" @@ -18,7 +20,9 @@ import ( "github.com/docker/docker/pkg/ioutils" "github.com/docker/docker/pkg/progress" "github.com/docker/docker/pkg/streamformatter" + "github.com/docker/docker/pkg/stringid" "github.com/pkg/errors" + "time" ) const ( @@ -134,15 +138,21 @@ func GetContextFromReader(r io.ReadCloser, dockerfileName string) (out io.ReadCl // Returns the absolute path to the temporary context directory, the relative // path of the dockerfile in that context directory, and a non-nil error on // success. -func GetContextFromGitURL(gitURL, dockerfileName string) (absContextDir, relDockerfile string, err error) { +func GetContextFromGitURL(gitURL, dockerfileName string) (string, string, error) { if _, err := exec.LookPath("git"); err != nil { - return "", "", errors.Errorf("unable to find 'git': %v", err) + return "", "", errors.Wrapf(err, "unable to find 'git'") } - if absContextDir, err = gitutils.Clone(gitURL); err != nil { - return "", "", errors.Errorf("unable to 'git clone' to temporary context directory: %v", err) + absContextDir, err := gitutils.Clone(gitURL) + if err != nil { + return "", "", errors.Wrapf(err, "unable to 'git clone' to temporary context directory") } - return getDockerfileRelPath(absContextDir, dockerfileName) + absContextDir, err = ResolveAndValidateContextPath(absContextDir) + if err != nil { + return "", "", err + } + relDockerfile, err := getDockerfileRelPath(absContextDir, dockerfileName) + return absContextDir, relDockerfile, err } // GetContextFromURL uses a remote URL as context for a `docker build`. The @@ -166,8 +176,13 @@ func GetContextFromURL(out io.Writer, remoteURL, dockerfileName string) (io.Read // `docker build`. Returns the absolute path to the local context directory, // the relative path of the dockerfile in that context directory, and a non-nil // error on success. -func GetContextFromLocalDir(localDir, dockerfileName string) (absContextDir, relDockerfile string, err error) { - // When using a local context directory, when the Dockerfile is specified +func GetContextFromLocalDir(localDir, dockerfileName string) (string, string, error) { + localDir, err := ResolveAndValidateContextPath(localDir) + if err != nil { + return "", "", err + } + + // When using a local context directory, and the Dockerfile is specified // with the `-f/--file` option then it is considered relative to the // current directory and not the context directory. if dockerfileName != "" && dockerfileName != "-" { @@ -176,15 +191,16 @@ func GetContextFromLocalDir(localDir, dockerfileName string) (absContextDir, rel } } - return getDockerfileRelPath(localDir, dockerfileName) + relDockerfile, err := getDockerfileRelPath(localDir, dockerfileName) + return localDir, relDockerfile, err } -// getDockerfileRelPath uses the given context directory for a `docker build` -// and returns the absolute path to the context directory, the relative path of -// the dockerfile in that context directory, and a non-nil error on success. -func getDockerfileRelPath(givenContextDir, givenDockerfile string) (absContextDir, relDockerfile string, err error) { - if absContextDir, err = filepath.Abs(givenContextDir); err != nil { - return "", "", errors.Errorf("unable to get absolute context directory of given context directory %q: %v", givenContextDir, err) +// ResolveAndValidateContextPath uses the given context directory for a `docker build` +// and returns the absolute path to the context directory. +func ResolveAndValidateContextPath(givenContextDir string) (string, error) { + absContextDir, err := filepath.Abs(givenContextDir) + if err != nil { + return "", errors.Errorf("unable to get absolute context directory of given context directory %q: %v", givenContextDir, err) } // The context dir might be a symbolic link, so follow it to the actual @@ -197,17 +213,28 @@ func getDockerfileRelPath(givenContextDir, givenDockerfile string) (absContextDi if !isUNC(absContextDir) { absContextDir, err = filepath.EvalSymlinks(absContextDir) if err != nil { - return "", "", errors.Errorf("unable to evaluate symlinks in context path: %v", err) + return "", errors.Errorf("unable to evaluate symlinks in context path: %v", err) } } stat, err := os.Lstat(absContextDir) if err != nil { - return "", "", errors.Errorf("unable to stat context directory %q: %v", absContextDir, err) + return "", errors.Errorf("unable to stat context directory %q: %v", absContextDir, err) } if !stat.IsDir() { - return "", "", errors.Errorf("context must be a directory: %s", absContextDir) + return "", errors.Errorf("context must be a directory: %s", absContextDir) + } + return absContextDir, err +} + +// getDockerfileRelPath returns the dockerfile path relative to the context +// directory +func getDockerfileRelPath(absContextDir, givenDockerfile string) (string, error) { + var err error + + if givenDockerfile == "-" { + return givenDockerfile, nil } absDockerfile := givenDockerfile @@ -224,8 +251,6 @@ func getDockerfileRelPath(givenContextDir, givenDockerfile string) (absContextDi absDockerfile = altPath } } - } else if absDockerfile == "-" { - absDockerfile = filepath.Join(absContextDir, DefaultDockerfileName) } // If not already an absolute path, the Dockerfile path should be joined to @@ -240,32 +265,31 @@ func getDockerfileRelPath(givenContextDir, givenDockerfile string) (absContextDi // an issue in golang. On Windows, EvalSymLinks does not work on UNC file // paths (those starting with \\). This hack means that when using links // on UNC paths, they will not be followed. - if givenDockerfile != "-" { - if !isUNC(absDockerfile) { - absDockerfile, err = filepath.EvalSymlinks(absDockerfile) - if err != nil { - return "", "", errors.Errorf("unable to evaluate symlinks in Dockerfile path: %v", err) + if !isUNC(absDockerfile) { + absDockerfile, err = filepath.EvalSymlinks(absDockerfile) + if err != nil { + return "", errors.Errorf("unable to evaluate symlinks in Dockerfile path: %v", err) - } - } - - if _, err := os.Lstat(absDockerfile); err != nil { - if os.IsNotExist(err) { - return "", "", errors.Errorf("Cannot locate Dockerfile: %q", absDockerfile) - } - return "", "", errors.Errorf("unable to stat Dockerfile: %v", err) } } - if relDockerfile, err = filepath.Rel(absContextDir, absDockerfile); err != nil { - return "", "", errors.Errorf("unable to get relative Dockerfile path: %v", err) + if _, err := os.Lstat(absDockerfile); err != nil { + if os.IsNotExist(err) { + return "", errors.Errorf("Cannot locate Dockerfile: %q", absDockerfile) + } + return "", errors.Errorf("unable to stat Dockerfile: %v", err) + } + + relDockerfile, err := filepath.Rel(absContextDir, absDockerfile) + if err != nil { + return "", errors.Errorf("unable to get relative Dockerfile path: %v", err) } if strings.HasPrefix(relDockerfile, ".."+string(filepath.Separator)) { - return "", "", errors.Errorf("The Dockerfile (%s) must be within the build context (%s)", givenDockerfile, givenContextDir) + return "", errors.Errorf("the Dockerfile (%s) must be within the build context", givenDockerfile) } - return absContextDir, relDockerfile, nil + return relDockerfile, nil } // isUNC returns true if the path is UNC (one starting \\). It always returns @@ -273,3 +297,49 @@ func getDockerfileRelPath(givenContextDir, givenDockerfile string) (absContextDi func isUNC(path string) bool { return runtime.GOOS == "windows" && strings.HasPrefix(path, `\\`) } + +// AddDockerfileToBuildContext from a ReadCloser, returns a new archive and +// the relative path to the dockerfile in the context. +func AddDockerfileToBuildContext(dockerfileCtx io.ReadCloser, buildCtx io.ReadCloser) (io.ReadCloser, string, error) { + file, err := ioutil.ReadAll(dockerfileCtx) + dockerfileCtx.Close() + if err != nil { + return nil, "", err + } + now := time.Now() + hdrTmpl := &tar.Header{ + Mode: 0600, + Uid: 0, + Gid: 0, + ModTime: now, + Typeflag: tar.TypeReg, + AccessTime: now, + ChangeTime: now, + } + randomName := ".dockerfile." + stringid.GenerateRandomID()[:20] + + buildCtx = archive.ReplaceFileTarWrapper(buildCtx, map[string]archive.TarModifierFunc{ + // Add the dockerfile with a random filename + randomName: func(_ string, h *tar.Header, content io.Reader) (*tar.Header, []byte, error) { + return hdrTmpl, file, nil + }, + // Update .dockerignore to include the random filename + ".dockerignore": func(_ string, h *tar.Header, content io.Reader) (*tar.Header, []byte, error) { + if h == nil { + h = hdrTmpl + } + + b := &bytes.Buffer{} + if content != nil { + if _, err := b.ReadFrom(content); err != nil { + return nil, nil, err + } + } else { + b.WriteString(".dockerignore") + } + b.WriteString("\n" + randomName + "\n") + return h, b.Bytes(), nil + }, + }) + return buildCtx, randomName, nil +} diff --git a/cli/command/image/build/context_test.go b/cli/command/image/build/context_test.go index afa04a4fcd..cda0bfb3f1 100644 --- a/cli/command/image/build/context_test.go +++ b/cli/command/image/build/context_test.go @@ -12,6 +12,9 @@ import ( "testing" "github.com/docker/docker/pkg/archive" + "github.com/docker/docker/pkg/testutil" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) const dockerfileContents = "FROM busybox" @@ -35,29 +38,15 @@ func testValidateContextDirectory(t *testing.T, prepare func(t *testing.T) (stri defer cleanup() err := ValidateContextDirectory(contextDir, excludes) - - if err != nil { - t.Fatalf("Error should be nil, got: %s", err) - } + require.NoError(t, err) } func TestGetContextFromLocalDirNoDockerfile(t *testing.T) { contextDir, cleanup := createTestTempDir(t, "", "builder-context-test") defer cleanup() - absContextDir, relDockerfile, err := GetContextFromLocalDir(contextDir, "") - - if err == nil { - t.Fatalf("Error should not be nil") - } - - if absContextDir != "" { - t.Fatalf("Absolute directory path should be empty, got: %s", absContextDir) - } - - if relDockerfile != "" { - t.Fatalf("Relative path to Dockerfile should be empty, got: %s", relDockerfile) - } + _, _, err := GetContextFromLocalDir(contextDir, "") + testutil.ErrorContains(t, err, "Dockerfile") } func TestGetContextFromLocalDirNotExistingDir(t *testing.T) { @@ -66,19 +55,8 @@ func TestGetContextFromLocalDirNotExistingDir(t *testing.T) { fakePath := filepath.Join(contextDir, "fake") - absContextDir, relDockerfile, err := GetContextFromLocalDir(fakePath, "") - - if err == nil { - t.Fatalf("Error should not be nil") - } - - if absContextDir != "" { - t.Fatalf("Absolute directory path should be empty, got: %s", absContextDir) - } - - if relDockerfile != "" { - t.Fatalf("Relative path to Dockerfile should be empty, got: %s", relDockerfile) - } + _, _, err := GetContextFromLocalDir(fakePath, "") + testutil.ErrorContains(t, err, "fake") } func TestGetContextFromLocalDirNotExistingDockerfile(t *testing.T) { @@ -87,19 +65,8 @@ func TestGetContextFromLocalDirNotExistingDockerfile(t *testing.T) { fakePath := filepath.Join(contextDir, "fake") - absContextDir, relDockerfile, err := GetContextFromLocalDir(contextDir, fakePath) - - if err == nil { - t.Fatalf("Error should not be nil") - } - - if absContextDir != "" { - t.Fatalf("Absolute directory path should be empty, got: %s", absContextDir) - } - - if relDockerfile != "" { - t.Fatalf("Relative path to Dockerfile should be empty, got: %s", relDockerfile) - } + _, _, err := GetContextFromLocalDir(contextDir, fakePath) + testutil.ErrorContains(t, err, "fake") } func TestGetContextFromLocalDirWithNoDirectory(t *testing.T) { @@ -112,18 +79,10 @@ func TestGetContextFromLocalDirWithNoDirectory(t *testing.T) { defer chdirCleanup() absContextDir, relDockerfile, err := GetContextFromLocalDir(contextDir, "") + require.NoError(t, err) - if err != nil { - t.Fatalf("Error when getting context from local dir: %s", err) - } - - if absContextDir != contextDir { - t.Fatalf("Absolute directory path should be equal to %s, got: %s", contextDir, absContextDir) - } - - if relDockerfile != DefaultDockerfileName { - t.Fatalf("Relative path to dockerfile should be equal to %s, got: %s", DefaultDockerfileName, relDockerfile) - } + assert.Equal(t, contextDir, absContextDir) + assert.Equal(t, DefaultDockerfileName, relDockerfile) } func TestGetContextFromLocalDirWithDockerfile(t *testing.T) { @@ -133,18 +92,10 @@ func TestGetContextFromLocalDirWithDockerfile(t *testing.T) { createTestTempFile(t, contextDir, DefaultDockerfileName, dockerfileContents, 0777) absContextDir, relDockerfile, err := GetContextFromLocalDir(contextDir, "") + require.NoError(t, err) - if err != nil { - t.Fatalf("Error when getting context from local dir: %s", err) - } - - if absContextDir != contextDir { - t.Fatalf("Absolute directory path should be equal to %s, got: %s", contextDir, absContextDir) - } - - if relDockerfile != DefaultDockerfileName { - t.Fatalf("Relative path to dockerfile should be equal to %s, got: %s", DefaultDockerfileName, relDockerfile) - } + assert.Equal(t, contextDir, absContextDir) + assert.Equal(t, DefaultDockerfileName, relDockerfile) } func TestGetContextFromLocalDirLocalFile(t *testing.T) { @@ -179,19 +130,10 @@ func TestGetContextFromLocalDirWithCustomDockerfile(t *testing.T) { createTestTempFile(t, contextDir, DefaultDockerfileName, dockerfileContents, 0777) absContextDir, relDockerfile, err := GetContextFromLocalDir(contextDir, DefaultDockerfileName) + require.NoError(t, err) - if err != nil { - t.Fatalf("Error when getting context from local dir: %s", err) - } - - if absContextDir != contextDir { - t.Fatalf("Absolute directory path should be equal to %s, got: %s", contextDir, absContextDir) - } - - if relDockerfile != DefaultDockerfileName { - t.Fatalf("Relative path to dockerfile should be equal to %s, got: %s", DefaultDockerfileName, relDockerfile) - } - + assert.Equal(t, contextDir, absContextDir) + assert.Equal(t, DefaultDockerfileName, relDockerfile) } func TestGetContextFromReaderString(t *testing.T) { @@ -219,9 +161,7 @@ func TestGetContextFromReaderString(t *testing.T) { t.Fatalf("Tar stream too long: %s", err) } - if err = tarArchive.Close(); err != nil { - t.Fatalf("Error when closing tar stream: %s", err) - } + require.NoError(t, tarArchive.Close()) if dockerfileContents != contents { t.Fatalf("Uncompressed tar archive does not equal: %s, got: %s", dockerfileContents, contents) @@ -239,24 +179,15 @@ func TestGetContextFromReaderTar(t *testing.T) { createTestTempFile(t, contextDir, DefaultDockerfileName, dockerfileContents, 0777) tarStream, err := archive.Tar(contextDir, archive.Uncompressed) - - if err != nil { - t.Fatalf("Error when creating tar: %s", err) - } + require.NoError(t, err) tarArchive, relDockerfile, err := GetContextFromReader(tarStream, DefaultDockerfileName) - - if err != nil { - t.Fatalf("Error when executing GetContextFromReader: %s", err) - } + require.NoError(t, err) tarReader := tar.NewReader(tarArchive) header, err := tarReader.Next() - - if err != nil { - t.Fatalf("Error when reading tar archive: %s", err) - } + require.NoError(t, err) if header.Name != DefaultDockerfileName { t.Fatalf("Dockerfile name should be: %s, got: %s", DefaultDockerfileName, header.Name) @@ -272,9 +203,7 @@ func TestGetContextFromReaderTar(t *testing.T) { t.Fatalf("Tar stream too long: %s", err) } - if err = tarArchive.Close(); err != nil { - t.Fatalf("Error when closing tar stream: %s", err) - } + require.NoError(t, tarArchive.Close()) if dockerfileContents != contents { t.Fatalf("Uncompressed tar archive does not equal: %s, got: %s", dockerfileContents, contents) @@ -314,33 +243,8 @@ func TestValidateContextDirectoryWithOneFileExcludes(t *testing.T) { // When an error occurs, it terminates the test. func createTestTempDir(t *testing.T, dir, prefix string) (string, func()) { path, err := ioutil.TempDir(dir, prefix) - - if err != nil { - t.Fatalf("Error when creating directory %s with prefix %s: %s", dir, prefix, err) - } - - return path, func() { - err = os.RemoveAll(path) - - if err != nil { - t.Fatalf("Error when removing directory %s: %s", path, err) - } - } -} - -// createTestTempSubdir creates a temporary directory for testing. -// It returns the created path but doesn't provide a cleanup function, -// so createTestTempSubdir should be used only for creating temporary subdirectories -// whose parent directories are properly cleaned up. -// When an error occurs, it terminates the test. -func createTestTempSubdir(t *testing.T, dir, prefix string) string { - path, err := ioutil.TempDir(dir, prefix) - - if err != nil { - t.Fatalf("Error when creating directory %s with prefix %s: %s", dir, prefix, err) - } - - return path + require.NoError(t, err) + return path, func() { require.NoError(t, os.RemoveAll(path)) } } // createTestTempFile creates a temporary file within dir with specific contents and permissions. @@ -348,11 +252,7 @@ func createTestTempSubdir(t *testing.T, dir, prefix string) string { func createTestTempFile(t *testing.T, dir, filename, contents string, perm os.FileMode) string { filePath := filepath.Join(dir, filename) err := ioutil.WriteFile(filePath, []byte(contents), perm) - - if err != nil { - t.Fatalf("Error when creating %s file: %s", filename, err) - } - + require.NoError(t, err) return filePath } @@ -362,22 +262,7 @@ func createTestTempFile(t *testing.T, dir, filename, contents string, perm os.Fi // When an error occurs, it terminates the test. func chdir(t *testing.T, dir string) func() { workingDirectory, err := os.Getwd() - - if err != nil { - t.Fatalf("Error when retrieving working directory: %s", err) - } - - err = os.Chdir(dir) - - if err != nil { - t.Fatalf("Error when changing directory to %s: %s", dir, err) - } - - return func() { - err = os.Chdir(workingDirectory) - - if err != nil { - t.Fatalf("Error when changing back to working directory (%s): %s", workingDirectory, err) - } - } + require.NoError(t, err) + require.NoError(t, os.Chdir(dir)) + return func() { require.NoError(t, os.Chdir(workingDirectory)) } } diff --git a/cli/command/image/build/dockerignore.go b/cli/command/image/build/dockerignore.go new file mode 100644 index 0000000000..497c3f24f3 --- /dev/null +++ b/cli/command/image/build/dockerignore.go @@ -0,0 +1,39 @@ +package build + +import ( + "os" + "path/filepath" + + "github.com/docker/docker/builder/dockerignore" + "github.com/docker/docker/pkg/fileutils" +) + +// ReadDockerignore reads the .dockerignore file in the context directory and +// returns the list of paths to exclude +func ReadDockerignore(contextDir string) ([]string, error) { + var excludes []string + + f, err := os.Open(filepath.Join(contextDir, ".dockerignore")) + switch { + case os.IsNotExist(err): + return excludes, nil + case err != nil: + return nil, err + } + defer f.Close() + + return dockerignore.ReadAll(f) +} + +// TrimBuildFilesFromExcludes removes the named Dockerfile and .dockerignore from +// the list of excluded files. The daemon will remove them from the final context +// but they must be in available in the context when passed to the API. +func TrimBuildFilesFromExcludes(excludes []string, dockerfile string, dockerfileFromStdin bool) []string { + if keep, _ := fileutils.Matches(".dockerignore", excludes); keep { + excludes = append(excludes, "!.dockerignore") + } + if keep, _ := fileutils.Matches(dockerfile, excludes); keep && !dockerfileFromStdin { + excludes = append(excludes, "!"+dockerfile) + } + return excludes +}