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 // DetectArchiveReader detects whether the input stream is an archive or a
// Dockerfile or tar archive. Returns a tar archive used as a context and a // Dockerfile and returns a buffered version of input, safe to consume in lieu
// path to the Dockerfile inside the tar. // of input. If an archive is detected, isArchive is set to true, and to false
func GetContextFromReader(r io.ReadCloser, dockerfileName string) (out io.ReadCloser, relDockerfile string, err error) { // otherwise, in which case it is safe to assume input represents the contents
buf := bufio.NewReader(r) // of a Dockerfile.
func DetectArchiveReader(input io.ReadCloser) (rc io.ReadCloser, isArchive bool, err error) {
buf := bufio.NewReader(input)
magic, err := buf.Peek(archiveHeaderSize) magic, err := buf.Peek(archiveHeaderSize)
if err != nil && err != io.EOF { 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 input.Close() }), IsArchive(magic), nil
return ioutils.NewReadCloserWrapper(buf, func() error { return r.Close() }), dockerfileName, 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 == "-" { if dockerfileName == "-" {
return nil, "", errors.New("build context is not an archive") return nil, "", errors.New("build context is not an archive")
} }
// Input should be read as a Dockerfile. dockerfileDir, err := WriteTempDockerfile(rc)
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))
if err != nil { if err != nil {
return nil, "", err return nil, "", err
} }
_, err = io.Copy(f, buf)
if err != nil {
f.Close()
return nil, "", err
}
if err := f.Close(); err != nil { tar, err := archive.Tar(dockerfileDir, archive.Uncompressed)
return nil, "", err
}
if err := r.Close(); err != nil {
return nil, "", err
}
tar, err := archive.Tar(tmpDir, archive.Uncompressed)
if err != nil { if err != nil {
return nil, "", err return nil, "", err
} }
return ioutils.NewReadCloserWrapper(tar, func() error { return ioutils.NewReadCloserWrapper(tar, func() error {
err := tar.Close() err := tar.Close()
os.RemoveAll(tmpDir) os.RemoveAll(dockerfileDir)
return err return err
}), DefaultDockerfileName, nil }), DefaultDockerfileName, nil
} }
// IsArchive checks for the magic bytes of a tar or any supported compression // IsArchive checks for the magic bytes of a tar or any supported compression

View File

@ -4,12 +4,14 @@ import (
"context" "context"
"encoding/json" "encoding/json"
"io" "io"
"io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
"github.com/containerd/console" "github.com/containerd/console"
"github.com/docker/cli/cli" "github.com/docker/cli/cli"
"github.com/docker/cli/cli/command" "github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/image/build"
"github.com/docker/cli/opts" "github.com/docker/cli/opts"
"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"
@ -46,9 +48,45 @@ func runBuildBuildKit(dockerCli command.Cli, options buildOptions) error {
var body io.Reader var body io.Reader
switch { switch {
case options.contextFromStdin(): case options.contextFromStdin():
body = os.Stdin rc, isArchive, err := build.DetectArchiveReader(os.Stdin)
remote = uploadRequestRemote 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): 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 remote = clientSessionRemote
case urlutil.IsGitURL(options.context): case urlutil.IsGitURL(options.context):
remote = options.context remote = options.context
@ -65,20 +103,6 @@ func runBuildBuildKit(dockerCli command.Cli, options buildOptions) error {
// statusContext = opentracing.ContextWithSpan(statusContext, span) // 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()) s.Allow(authprovider.NewDockerAuthProvider())
eg, ctx := errgroup.WithContext(ctx) eg, ctx := errgroup.WithContext(ctx)