mirror of https://github.com/docker/cli.git
Merge pull request #13 from dnephin/cleanup-post-docker-stin
Small build client cleanup handling dockerfile from stdin
This commit is contained in:
commit
c202b4b987
|
@ -6,12 +6,9 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
|
||||||
"regexp"
|
"regexp"
|
||||||
"runtime"
|
"runtime"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/docker/cli/cli"
|
"github.com/docker/cli/cli"
|
||||||
"github.com/docker/cli/cli/command"
|
"github.com/docker/cli/cli/command"
|
||||||
|
@ -20,14 +17,11 @@ import (
|
||||||
"github.com/docker/docker/api"
|
"github.com/docker/docker/api"
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
"github.com/docker/docker/api/types/container"
|
"github.com/docker/docker/api/types/container"
|
||||||
"github.com/docker/docker/builder/dockerignore"
|
|
||||||
"github.com/docker/docker/opts"
|
"github.com/docker/docker/opts"
|
||||||
"github.com/docker/docker/pkg/archive"
|
"github.com/docker/docker/pkg/archive"
|
||||||
"github.com/docker/docker/pkg/fileutils"
|
|
||||||
"github.com/docker/docker/pkg/jsonmessage"
|
"github.com/docker/docker/pkg/jsonmessage"
|
||||||
"github.com/docker/docker/pkg/progress"
|
"github.com/docker/docker/pkg/progress"
|
||||||
"github.com/docker/docker/pkg/streamformatter"
|
"github.com/docker/docker/pkg/streamformatter"
|
||||||
"github.com/docker/docker/pkg/stringid"
|
|
||||||
"github.com/docker/docker/pkg/urlutil"
|
"github.com/docker/docker/pkg/urlutil"
|
||||||
runconfigopts "github.com/docker/docker/runconfig/opts"
|
runconfigopts "github.com/docker/docker/runconfig/opts"
|
||||||
units "github.com/docker/go-units"
|
units "github.com/docker/go-units"
|
||||||
|
@ -67,6 +61,18 @@ type buildOptions struct {
|
||||||
target string
|
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
|
// NewBuildCommand creates a new `docker build` command
|
||||||
func NewBuildCommand(dockerCli *command.DockerCli) *cobra.Command {
|
func NewBuildCommand(dockerCli *command.DockerCli) *cobra.Command {
|
||||||
ulimits := make(map[string]*units.Ulimit)
|
ulimits := make(map[string]*units.Ulimit)
|
||||||
|
@ -156,6 +162,13 @@ func runBuild(dockerCli *command.DockerCli, options buildOptions) error {
|
||||||
buildBuff io.Writer
|
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
|
specifiedContext := options.context
|
||||||
progBuff = dockerCli.Out()
|
progBuff = dockerCli.Out()
|
||||||
buildBuff = dockerCli.Out()
|
buildBuff = dockerCli.Out()
|
||||||
|
@ -164,15 +177,8 @@ func runBuild(dockerCli *command.DockerCli, options buildOptions) error {
|
||||||
buildBuff = bytes.NewBuffer(nil)
|
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 {
|
switch {
|
||||||
case specifiedContext == "-":
|
case options.contextFromStdin():
|
||||||
buildCtx, relDockerfile, err = build.GetContextFromReader(dockerCli.In(), options.dockerfileName)
|
buildCtx, relDockerfile, err = build.GetContextFromReader(dockerCli.In(), options.dockerfileName)
|
||||||
case isLocalDir(specifiedContext):
|
case isLocalDir(specifiedContext):
|
||||||
contextDir, relDockerfile, err = build.GetContextFromLocalDir(specifiedContext, options.dockerfileName)
|
contextDir, relDockerfile, err = build.GetContextFromLocalDir(specifiedContext, options.dockerfileName)
|
||||||
|
@ -197,44 +203,22 @@ func runBuild(dockerCli *command.DockerCli, options buildOptions) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
if buildCtx == nil {
|
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
|
// And canonicalize dockerfile name to a platform-independent one
|
||||||
relDockerfile, err = archive.CanonicalTarNameForPath(relDockerfile)
|
relDockerfile, err = archive.CanonicalTarNameForPath(relDockerfile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Errorf("cannot canonicalize dockerfile path %s: %v", relDockerfile, err)
|
return errors.Errorf("cannot canonicalize dockerfile path %s: %v", relDockerfile, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
f, err := os.Open(filepath.Join(contextDir, ".dockerignore"))
|
excludes = build.TrimBuildFilesFromExcludes(excludes, relDockerfile, options.dockerfileFromStdin())
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
compression := archive.Uncompressed
|
compression := archive.Uncompressed
|
||||||
if options.compress {
|
if options.compress {
|
||||||
|
@ -251,7 +235,7 @@ func runBuild(dockerCli *command.DockerCli, options buildOptions) error {
|
||||||
|
|
||||||
// replace Dockerfile if added dynamically
|
// replace Dockerfile if added dynamically
|
||||||
if dockerfileCtx != nil {
|
if dockerfileCtx != nil {
|
||||||
buildCtx, relDockerfile, err = addDockerfileToBuildContext(dockerfileCtx, buildCtx)
|
buildCtx, relDockerfile, err = build.AddDockerfileToBuildContext(dockerfileCtx, buildCtx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -361,50 +345,6 @@ func runBuild(dockerCli *command.DockerCli, options buildOptions) error {
|
||||||
return nil
|
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 {
|
func isLocalDir(c string) bool {
|
||||||
_, err := os.Stat(c)
|
_, err := os.Stat(c)
|
||||||
return err == nil
|
return err == nil
|
||||||
|
|
|
@ -11,6 +11,8 @@ import (
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"archive/tar"
|
||||||
|
"bytes"
|
||||||
"github.com/docker/docker/pkg/archive"
|
"github.com/docker/docker/pkg/archive"
|
||||||
"github.com/docker/docker/pkg/fileutils"
|
"github.com/docker/docker/pkg/fileutils"
|
||||||
"github.com/docker/docker/pkg/gitutils"
|
"github.com/docker/docker/pkg/gitutils"
|
||||||
|
@ -18,7 +20,9 @@ import (
|
||||||
"github.com/docker/docker/pkg/ioutils"
|
"github.com/docker/docker/pkg/ioutils"
|
||||||
"github.com/docker/docker/pkg/progress"
|
"github.com/docker/docker/pkg/progress"
|
||||||
"github.com/docker/docker/pkg/streamformatter"
|
"github.com/docker/docker/pkg/streamformatter"
|
||||||
|
"github.com/docker/docker/pkg/stringid"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
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
|
// 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
|
// path of the dockerfile in that context directory, and a non-nil error on
|
||||||
// success.
|
// 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 {
|
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 {
|
absContextDir, err := gitutils.Clone(gitURL)
|
||||||
return "", "", errors.Errorf("unable to 'git clone' to temporary context directory: %v", err)
|
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
|
// 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,
|
// `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
|
// the relative path of the dockerfile in that context directory, and a non-nil
|
||||||
// error on success.
|
// error on success.
|
||||||
func GetContextFromLocalDir(localDir, dockerfileName string) (absContextDir, relDockerfile string, err error) {
|
func GetContextFromLocalDir(localDir, dockerfileName string) (string, string, error) {
|
||||||
// When using a local context directory, when the Dockerfile is specified
|
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
|
// with the `-f/--file` option then it is considered relative to the
|
||||||
// current directory and not the context directory.
|
// current directory and not the context directory.
|
||||||
if dockerfileName != "" && dockerfileName != "-" {
|
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`
|
// ResolveAndValidateContextPath uses the given context directory for a `docker build`
|
||||||
// and returns the absolute path to the context directory, the relative path of
|
// and returns the absolute path to the context directory.
|
||||||
// the dockerfile in that context directory, and a non-nil error on success.
|
func ResolveAndValidateContextPath(givenContextDir string) (string, error) {
|
||||||
func getDockerfileRelPath(givenContextDir, givenDockerfile string) (absContextDir, relDockerfile string, err error) {
|
absContextDir, err := filepath.Abs(givenContextDir)
|
||||||
if absContextDir, err = filepath.Abs(givenContextDir); err != nil {
|
if err != nil {
|
||||||
return "", "", errors.Errorf("unable to get absolute context directory of given context directory %q: %v", givenContextDir, err)
|
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
|
// 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) {
|
if !isUNC(absContextDir) {
|
||||||
absContextDir, err = filepath.EvalSymlinks(absContextDir)
|
absContextDir, err = filepath.EvalSymlinks(absContextDir)
|
||||||
if err != nil {
|
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)
|
stat, err := os.Lstat(absContextDir)
|
||||||
if err != nil {
|
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() {
|
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
|
absDockerfile := givenDockerfile
|
||||||
|
@ -224,8 +251,6 @@ func getDockerfileRelPath(givenContextDir, givenDockerfile string) (absContextDi
|
||||||
absDockerfile = altPath
|
absDockerfile = altPath
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if absDockerfile == "-" {
|
|
||||||
absDockerfile = filepath.Join(absContextDir, DefaultDockerfileName)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// If not already an absolute path, the Dockerfile path should be joined to
|
// 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
|
// an issue in golang. On Windows, EvalSymLinks does not work on UNC file
|
||||||
// paths (those starting with \\). This hack means that when using links
|
// paths (those starting with \\). This hack means that when using links
|
||||||
// on UNC paths, they will not be followed.
|
// on UNC paths, they will not be followed.
|
||||||
if givenDockerfile != "-" {
|
if !isUNC(absDockerfile) {
|
||||||
if !isUNC(absDockerfile) {
|
absDockerfile, err = filepath.EvalSymlinks(absDockerfile)
|
||||||
absDockerfile, err = filepath.EvalSymlinks(absDockerfile)
|
if err != nil {
|
||||||
if err != nil {
|
return "", errors.Errorf("unable to evaluate symlinks in Dockerfile path: %v", err)
|
||||||
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 {
|
if _, err := os.Lstat(absDockerfile); err != nil {
|
||||||
return "", "", errors.Errorf("unable to get relative Dockerfile path: %v", err)
|
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)) {
|
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
|
// 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 {
|
func isUNC(path string) bool {
|
||||||
return runtime.GOOS == "windows" && strings.HasPrefix(path, `\\`)
|
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
|
||||||
|
}
|
||||||
|
|
|
@ -12,6 +12,9 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/docker/docker/pkg/archive"
|
"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"
|
const dockerfileContents = "FROM busybox"
|
||||||
|
@ -35,29 +38,15 @@ func testValidateContextDirectory(t *testing.T, prepare func(t *testing.T) (stri
|
||||||
defer cleanup()
|
defer cleanup()
|
||||||
|
|
||||||
err := ValidateContextDirectory(contextDir, excludes)
|
err := ValidateContextDirectory(contextDir, excludes)
|
||||||
|
require.NoError(t, err)
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Error should be nil, got: %s", err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetContextFromLocalDirNoDockerfile(t *testing.T) {
|
func TestGetContextFromLocalDirNoDockerfile(t *testing.T) {
|
||||||
contextDir, cleanup := createTestTempDir(t, "", "builder-context-test")
|
contextDir, cleanup := createTestTempDir(t, "", "builder-context-test")
|
||||||
defer cleanup()
|
defer cleanup()
|
||||||
|
|
||||||
absContextDir, relDockerfile, err := GetContextFromLocalDir(contextDir, "")
|
_, _, err := GetContextFromLocalDir(contextDir, "")
|
||||||
|
testutil.ErrorContains(t, err, "Dockerfile")
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetContextFromLocalDirNotExistingDir(t *testing.T) {
|
func TestGetContextFromLocalDirNotExistingDir(t *testing.T) {
|
||||||
|
@ -66,19 +55,8 @@ func TestGetContextFromLocalDirNotExistingDir(t *testing.T) {
|
||||||
|
|
||||||
fakePath := filepath.Join(contextDir, "fake")
|
fakePath := filepath.Join(contextDir, "fake")
|
||||||
|
|
||||||
absContextDir, relDockerfile, err := GetContextFromLocalDir(fakePath, "")
|
_, _, err := GetContextFromLocalDir(fakePath, "")
|
||||||
|
testutil.ErrorContains(t, err, "fake")
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetContextFromLocalDirNotExistingDockerfile(t *testing.T) {
|
func TestGetContextFromLocalDirNotExistingDockerfile(t *testing.T) {
|
||||||
|
@ -87,19 +65,8 @@ func TestGetContextFromLocalDirNotExistingDockerfile(t *testing.T) {
|
||||||
|
|
||||||
fakePath := filepath.Join(contextDir, "fake")
|
fakePath := filepath.Join(contextDir, "fake")
|
||||||
|
|
||||||
absContextDir, relDockerfile, err := GetContextFromLocalDir(contextDir, fakePath)
|
_, _, err := GetContextFromLocalDir(contextDir, fakePath)
|
||||||
|
testutil.ErrorContains(t, err, "fake")
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetContextFromLocalDirWithNoDirectory(t *testing.T) {
|
func TestGetContextFromLocalDirWithNoDirectory(t *testing.T) {
|
||||||
|
@ -112,18 +79,10 @@ func TestGetContextFromLocalDirWithNoDirectory(t *testing.T) {
|
||||||
defer chdirCleanup()
|
defer chdirCleanup()
|
||||||
|
|
||||||
absContextDir, relDockerfile, err := GetContextFromLocalDir(contextDir, "")
|
absContextDir, relDockerfile, err := GetContextFromLocalDir(contextDir, "")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
if err != nil {
|
assert.Equal(t, contextDir, absContextDir)
|
||||||
t.Fatalf("Error when getting context from local dir: %s", err)
|
assert.Equal(t, DefaultDockerfileName, relDockerfile)
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetContextFromLocalDirWithDockerfile(t *testing.T) {
|
func TestGetContextFromLocalDirWithDockerfile(t *testing.T) {
|
||||||
|
@ -133,18 +92,10 @@ func TestGetContextFromLocalDirWithDockerfile(t *testing.T) {
|
||||||
createTestTempFile(t, contextDir, DefaultDockerfileName, dockerfileContents, 0777)
|
createTestTempFile(t, contextDir, DefaultDockerfileName, dockerfileContents, 0777)
|
||||||
|
|
||||||
absContextDir, relDockerfile, err := GetContextFromLocalDir(contextDir, "")
|
absContextDir, relDockerfile, err := GetContextFromLocalDir(contextDir, "")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
if err != nil {
|
assert.Equal(t, contextDir, absContextDir)
|
||||||
t.Fatalf("Error when getting context from local dir: %s", err)
|
assert.Equal(t, DefaultDockerfileName, relDockerfile)
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetContextFromLocalDirLocalFile(t *testing.T) {
|
func TestGetContextFromLocalDirLocalFile(t *testing.T) {
|
||||||
|
@ -179,19 +130,10 @@ func TestGetContextFromLocalDirWithCustomDockerfile(t *testing.T) {
|
||||||
createTestTempFile(t, contextDir, DefaultDockerfileName, dockerfileContents, 0777)
|
createTestTempFile(t, contextDir, DefaultDockerfileName, dockerfileContents, 0777)
|
||||||
|
|
||||||
absContextDir, relDockerfile, err := GetContextFromLocalDir(contextDir, DefaultDockerfileName)
|
absContextDir, relDockerfile, err := GetContextFromLocalDir(contextDir, DefaultDockerfileName)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
if err != nil {
|
assert.Equal(t, contextDir, absContextDir)
|
||||||
t.Fatalf("Error when getting context from local dir: %s", err)
|
assert.Equal(t, DefaultDockerfileName, relDockerfile)
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetContextFromReaderString(t *testing.T) {
|
func TestGetContextFromReaderString(t *testing.T) {
|
||||||
|
@ -219,9 +161,7 @@ func TestGetContextFromReaderString(t *testing.T) {
|
||||||
t.Fatalf("Tar stream too long: %s", err)
|
t.Fatalf("Tar stream too long: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = tarArchive.Close(); err != nil {
|
require.NoError(t, tarArchive.Close())
|
||||||
t.Fatalf("Error when closing tar stream: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if dockerfileContents != contents {
|
if dockerfileContents != contents {
|
||||||
t.Fatalf("Uncompressed tar archive does not equal: %s, got: %s", 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)
|
createTestTempFile(t, contextDir, DefaultDockerfileName, dockerfileContents, 0777)
|
||||||
|
|
||||||
tarStream, err := archive.Tar(contextDir, archive.Uncompressed)
|
tarStream, err := archive.Tar(contextDir, archive.Uncompressed)
|
||||||
|
require.NoError(t, err)
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Error when creating tar: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
tarArchive, relDockerfile, err := GetContextFromReader(tarStream, DefaultDockerfileName)
|
tarArchive, relDockerfile, err := GetContextFromReader(tarStream, DefaultDockerfileName)
|
||||||
|
require.NoError(t, err)
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Error when executing GetContextFromReader: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
tarReader := tar.NewReader(tarArchive)
|
tarReader := tar.NewReader(tarArchive)
|
||||||
|
|
||||||
header, err := tarReader.Next()
|
header, err := tarReader.Next()
|
||||||
|
require.NoError(t, err)
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Error when reading tar archive: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if header.Name != DefaultDockerfileName {
|
if header.Name != DefaultDockerfileName {
|
||||||
t.Fatalf("Dockerfile name should be: %s, got: %s", DefaultDockerfileName, header.Name)
|
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)
|
t.Fatalf("Tar stream too long: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = tarArchive.Close(); err != nil {
|
require.NoError(t, tarArchive.Close())
|
||||||
t.Fatalf("Error when closing tar stream: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if dockerfileContents != contents {
|
if dockerfileContents != contents {
|
||||||
t.Fatalf("Uncompressed tar archive does not equal: %s, got: %s", 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.
|
// When an error occurs, it terminates the test.
|
||||||
func createTestTempDir(t *testing.T, dir, prefix string) (string, func()) {
|
func createTestTempDir(t *testing.T, dir, prefix string) (string, func()) {
|
||||||
path, err := ioutil.TempDir(dir, prefix)
|
path, err := ioutil.TempDir(dir, prefix)
|
||||||
|
require.NoError(t, err)
|
||||||
if err != nil {
|
return path, func() { require.NoError(t, os.RemoveAll(path)) }
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// createTestTempFile creates a temporary file within dir with specific contents and permissions.
|
// 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 {
|
func createTestTempFile(t *testing.T, dir, filename, contents string, perm os.FileMode) string {
|
||||||
filePath := filepath.Join(dir, filename)
|
filePath := filepath.Join(dir, filename)
|
||||||
err := ioutil.WriteFile(filePath, []byte(contents), perm)
|
err := ioutil.WriteFile(filePath, []byte(contents), perm)
|
||||||
|
require.NoError(t, err)
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Error when creating %s file: %s", filename, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return filePath
|
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.
|
// When an error occurs, it terminates the test.
|
||||||
func chdir(t *testing.T, dir string) func() {
|
func chdir(t *testing.T, dir string) func() {
|
||||||
workingDirectory, err := os.Getwd()
|
workingDirectory, err := os.Getwd()
|
||||||
|
require.NoError(t, err)
|
||||||
if err != nil {
|
require.NoError(t, os.Chdir(dir))
|
||||||
t.Fatalf("Error when retrieving working directory: %s", err)
|
return func() { require.NoError(t, os.Chdir(workingDirectory)) }
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
Loading…
Reference in New Issue