build: accept -f - to read Dockerfile from stdin

Heavily based on implementation by David Sheets

Signed-off-by: David Sheets <sheets@alum.mit.edu>
Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
This commit is contained in:
David Sheets 2017-02-21 12:07:45 -08:00 committed by Daniel Nephin
parent 738ac9e797
commit 924af54d98
2 changed files with 80 additions and 17 deletions

View File

@ -6,10 +6,12 @@ import (
"bytes" "bytes"
"fmt" "fmt"
"io" "io"
"io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
"regexp" "regexp"
"runtime" "runtime"
"time"
"github.com/docker/distribution/reference" "github.com/docker/distribution/reference"
"github.com/docker/docker/api" "github.com/docker/docker/api"
@ -25,6 +27,7 @@ import (
"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"
@ -141,6 +144,7 @@ func (out *lastProgressOutput) WriteProgress(prog progress.Progress) error {
func runBuild(dockerCli *command.DockerCli, options buildOptions) error { func runBuild(dockerCli *command.DockerCli, options buildOptions) error {
var ( var (
buildCtx io.ReadCloser buildCtx io.ReadCloser
dockerfileCtx io.ReadCloser
err error err error
contextDir string contextDir string
tempDir string tempDir string
@ -157,6 +161,13 @@ 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 specifiedContext == "-":
buildCtx, relDockerfile, err = build.GetContextFromReader(dockerCli.In(), options.dockerfileName) buildCtx, relDockerfile, err = build.GetContextFromReader(dockerCli.In(), options.dockerfileName)
@ -214,11 +225,11 @@ func runBuild(dockerCli *command.DockerCli, options buildOptions) error {
// removed. The daemon will remove them for us, if needed, after it // removed. The daemon will remove them for us, if needed, after it
// parses the Dockerfile. Ignore errors here, as they will have been // parses the Dockerfile. Ignore errors here, as they will have been
// caught by validateContextDirectory above. // caught by validateContextDirectory above.
var includes = []string{"."} if keep, _ := fileutils.Matches(".dockerignore", excludes); keep {
keepThem1, _ := fileutils.Matches(".dockerignore", excludes) excludes = append(excludes, "!.dockerignore")
keepThem2, _ := fileutils.Matches(relDockerfile, excludes) }
if keepThem1 || keepThem2 { if keep, _ := fileutils.Matches(relDockerfile, excludes); keep && dockerfileCtx == nil {
includes = append(includes, ".dockerignore", relDockerfile) excludes = append(excludes, "!"+relDockerfile)
} }
compression := archive.Uncompressed compression := archive.Uncompressed
@ -228,13 +239,56 @@ func runBuild(dockerCli *command.DockerCli, options buildOptions) error {
buildCtx, err = archive.TarWithOptions(contextDir, &archive.TarOptions{ buildCtx, err = archive.TarWithOptions(contextDir, &archive.TarOptions{
Compression: compression, Compression: compression,
ExcludePatterns: excludes, ExcludePatterns: excludes,
IncludeFiles: includes,
}) })
if err != nil { if err != nil {
return err return err
} }
} }
// replace Dockerfile if added dynamically
if dockerfileCtx != nil {
file, err := ioutil.ReadAll(dockerfileCtx)
dockerfileCtx.Close()
if err != nil {
return 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{
randomName: func(_ string, h *tar.Header, content io.Reader) (*tar.Header, []byte, error) {
return hdrTmpl, file, nil
},
".dockerignore": func(_ string, h *tar.Header, content io.Reader) (*tar.Header, []byte, error) {
if h == nil {
h = hdrTmpl
}
extraIgnore := randomName + "\n"
b := &bytes.Buffer{}
if content != nil {
_, err := b.ReadFrom(content)
if err != nil {
return nil, nil, err
}
} else {
extraIgnore += ".dockerignore\n"
}
b.Write([]byte("\n" + extraIgnore))
return h, b.Bytes(), nil
},
})
relDockerfile = randomName
}
ctx := context.Background() ctx := context.Background()
var resolvedTags []*resolvedTag var resolvedTags []*resolvedTag

View File

@ -89,6 +89,10 @@ func GetContextFromReader(r io.ReadCloser, dockerfileName string) (out io.ReadCl
return ioutils.NewReadCloserWrapper(buf, func() error { return r.Close() }), dockerfileName, nil return ioutils.NewReadCloserWrapper(buf, func() error { return r.Close() }), dockerfileName, nil
} }
if dockerfileName == "-" {
return nil, "", errors.New("build context is not an archive")
}
// Input should be read as a Dockerfile. // Input should be read as a Dockerfile.
tmpDir, err := ioutil.TempDir("", "docker-build-context-") tmpDir, err := ioutil.TempDir("", "docker-build-context-")
if err != nil { if err != nil {
@ -166,7 +170,7 @@ func GetContextFromLocalDir(localDir, dockerfileName string) (absContextDir, rel
// When using a local context directory, when the Dockerfile is specified // When using a local context directory, when 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 != "" { if dockerfileName != "" && dockerfileName != "-" {
if dockerfileName, err = filepath.Abs(dockerfileName); err != nil { if dockerfileName, err = filepath.Abs(dockerfileName); err != nil {
return "", "", errors.Errorf("unable to get absolute path to Dockerfile: %v", err) return "", "", errors.Errorf("unable to get absolute path to Dockerfile: %v", err)
} }
@ -220,6 +224,8 @@ 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
@ -234,18 +240,21 @@ 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 !isUNC(absDockerfile) { if givenDockerfile != "-" {
absDockerfile, err = filepath.EvalSymlinks(absDockerfile) if !isUNC(absDockerfile) {
if err != nil { absDockerfile, err = filepath.EvalSymlinks(absDockerfile)
return "", "", errors.Errorf("unable to evaluate symlinks in Dockerfile path: %v", err) 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)
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)
} }
return "", "", errors.Errorf("unable to stat Dockerfile: %v", err)
} }
if relDockerfile, err = filepath.Rel(absContextDir, absDockerfile); err != nil { if relDockerfile, err = filepath.Rel(absContextDir, absDockerfile); err != nil {