build: Add support for using context from stdin with buildkit

Signed-off-by: Tibor Vass <tibor@docker.com>
This commit is contained in:
Tibor Vass 2018-05-19 02:56:22 +00:00
parent 8cf213bd0c
commit e0b3921a03
2 changed files with 92 additions and 46 deletions

View File

@ -81,59 +81,81 @@ func ValidateContextDirectory(srcPath string, excludes []string) error {
})
}
// GetContextFromReader will read the contents of the given reader as either a
// Dockerfile or tar archive. Returns a tar archive used as a context and a
// path to the Dockerfile inside the tar.
func GetContextFromReader(r io.ReadCloser, dockerfileName string) (out io.ReadCloser, relDockerfile string, err error) {
buf := bufio.NewReader(r)
// DetectArchiveReader detects whether the input stream is an archive or a
// Dockerfile and returns a buffered version of input, safe to consume in lieu
// of input. If an archive is detected, isArchive is set to true, and to false
// otherwise, in which case it is safe to assume input represents the contents
// of a Dockerfile.
func DetectArchiveReader(input io.ReadCloser) (rc io.ReadCloser, isArchive bool, err error) {
buf := bufio.NewReader(input)
magic, err := buf.Peek(archiveHeaderSize)
if err != nil && err != io.EOF {
return nil, "", errors.Errorf("failed to peek context header from STDIN: %v", err)
return nil, false, errors.Errorf("failed to peek context header from STDIN: %v", err)
}
if IsArchive(magic) {
return ioutils.NewReadCloserWrapper(buf, func() error { return r.Close() }), dockerfileName, nil
return ioutils.NewReadCloserWrapper(buf, func() error { return input.Close() }), IsArchive(magic), nil
}
// WriteTempDockerfile writes a Dockerfile stream to a temporary file with a
// name specified by DefaultDockerfileName and returns the path to the
// temporary directory containing the Dockerfile.
func WriteTempDockerfile(rc io.ReadCloser) (string, error) {
dockerfileDir, err := ioutil.TempDir("", "docker-build-tempdockerfile-")
if err != nil {
return "", errors.Errorf("unable to create temporary context directory: %v", err)
}
f, err := os.Create(filepath.Join(dockerfileDir, DefaultDockerfileName))
if err != nil {
return "", err
}
_, err = io.Copy(f, rc)
if err != nil {
f.Close()
return "", err
}
if err := f.Close(); err != nil {
return "", err
}
return dockerfileDir, rc.Close()
}
// GetContextFromReader will read the contents of the given reader as either a
// Dockerfile or tar archive. Returns a tar archive used as a context and a
// path to the Dockerfile inside the tar.
func GetContextFromReader(rc io.ReadCloser, dockerfileName string) (out io.ReadCloser, relDockerfile string, err error) {
rc, isArchive, err := DetectArchiveReader(rc)
if err != nil {
return nil, "", err
}
if isArchive {
return rc, dockerfileName, nil
}
// Input should be read as a Dockerfile.
if dockerfileName == "-" {
return nil, "", errors.New("build context is not an archive")
}
// Input should be read as a Dockerfile.
tmpDir, err := ioutil.TempDir("", "docker-build-context-")
if err != nil {
return nil, "", errors.Errorf("unable to create temporary context directory: %v", err)
}
f, err := os.Create(filepath.Join(tmpDir, DefaultDockerfileName))
dockerfileDir, err := WriteTempDockerfile(rc)
if err != nil {
return nil, "", err
}
_, err = io.Copy(f, buf)
if err != nil {
f.Close()
return nil, "", err
}
if err := f.Close(); err != nil {
return nil, "", err
}
if err := r.Close(); err != nil {
return nil, "", err
}
tar, err := archive.Tar(tmpDir, archive.Uncompressed)
tar, err := archive.Tar(dockerfileDir, archive.Uncompressed)
if err != nil {
return nil, "", err
}
return ioutils.NewReadCloserWrapper(tar, func() error {
err := tar.Close()
os.RemoveAll(tmpDir)
os.RemoveAll(dockerfileDir)
return err
}), DefaultDockerfileName, nil
}
// IsArchive checks for the magic bytes of a tar or any supported compression

View File

@ -4,12 +4,14 @@ import (
"context"
"encoding/json"
"io"
"io/ioutil"
"os"
"path/filepath"
"github.com/containerd/console"
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/image/build"
"github.com/docker/cli/opts"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
@ -46,9 +48,45 @@ func runBuildBuildKit(dockerCli command.Cli, options buildOptions) error {
var body io.Reader
switch {
case options.contextFromStdin():
body = os.Stdin
remote = uploadRequestRemote
rc, isArchive, err := build.DetectArchiveReader(os.Stdin)
if err != nil {
return err
}
if isArchive {
body = rc
remote = uploadRequestRemote
} else {
dockerfileDir, err := build.WriteTempDockerfile(rc)
if err != nil {
return err
}
defer os.RemoveAll(dockerfileDir)
emptyDir, _ := ioutil.TempDir("", "stupid-empty-dir")
defer os.RemoveAll(emptyDir)
s.Allow(filesync.NewFSSyncProvider([]filesync.SyncedDir{
{
Name: "context",
Dir: emptyDir,
},
{
Name: "dockerfile",
Dir: string(dockerfileDir),
},
}))
remote = clientSessionRemote
}
case isLocalDir(options.context):
s.Allow(filesync.NewFSSyncProvider([]filesync.SyncedDir{
{
Name: "context",
Dir: options.context,
Map: resetUIDAndGID,
},
{
Name: "dockerfile",
Dir: filepath.Dir(options.dockerfileName),
},
}))
remote = clientSessionRemote
case urlutil.IsGitURL(options.context):
remote = options.context
@ -65,20 +103,6 @@ func runBuildBuildKit(dockerCli command.Cli, options buildOptions) error {
// statusContext = opentracing.ContextWithSpan(statusContext, span)
// }
if remote == clientSessionRemote {
s.Allow(filesync.NewFSSyncProvider([]filesync.SyncedDir{
{
Name: "context",
Dir: options.context,
Map: resetUIDAndGID,
},
{
Name: "dockerfile",
Dir: filepath.Dir(options.dockerfileName),
},
}))
}
s.Allow(authprovider.NewDockerAuthProvider())
eg, ctx := errgroup.WithContext(ctx)