Skip to content

Commit

Permalink
build: support more variations on context and dockerfile + iidfile
Browse files Browse the repository at this point in the history
Signed-off-by: Tibor Vass <[email protected]>
  • Loading branch information
Tibor Vass committed Apr 17, 2019
1 parent 037af0e commit dc07613
Show file tree
Hide file tree
Showing 13 changed files with 894 additions and 42 deletions.
156 changes: 137 additions & 19 deletions build/build.go
Original file line number Diff line number Diff line change
@@ -1,29 +1,41 @@
package build

import (
"bufio"
"context"
"io"
"io/ioutil"
"os"
"path/filepath"
"strconv"
"strings"
"sync"

"github.com/containerd/containerd/platforms"
"github.com/docker/distribution/reference"
"github.com/docker/docker/pkg/urlutil"
"github.com/moby/buildkit/client"
"github.com/moby/buildkit/session"
"github.com/moby/buildkit/session/upload/uploadprovider"
specs "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors"
"github.com/tonistiigi/buildx/driver"
"github.com/tonistiigi/buildx/util/progress"
"golang.org/x/sync/errgroup"
)

var (
errStdinConflict = errors.New("invalid argument: can't use stdin for both build context and dockerfile")
errDockerfileConflict = errors.New("ambiguous Dockerfile source: both stdin and flag correspond to Dockerfiles")
)

type Options struct {
Inputs Inputs
Tags []string
Labels map[string]string
BuildArgs map[string]string
Pull bool
Inputs Inputs
Tags []string
Labels map[string]string
BuildArgs map[string]string
Pull bool
ImageIDFile string

NoCache bool
Target string
Expand Down Expand Up @@ -96,9 +108,20 @@ func Build(ctx context.Context, drivers []DriverInfo, opt map[string]Options, pw
for k, opt := range opt {
pw := mw.WithPrefix(k, withPrefix)

if opt.ImageIDFile != "" {
if len(opt.Platforms) != 0 {
return nil, errors.Errorf("image ID file cannot be specified when building for multiple platforms")
}
// Avoid leaving a stale file if we eventually fail
if err := os.Remove(opt.ImageIDFile); err != nil && !os.IsNotExist(err) {
return nil, errors.Wrap(err, "removing image ID file")
}
}

so := client.SolveOpt{
Frontend: "dockerfile.v0",
FrontendAttrs: map[string]string{},
LocalDirs: map[string]string{},
}

switch len(opt.Exports) {
Expand All @@ -115,10 +138,18 @@ func Build(ctx context.Context, drivers []DriverInfo, opt map[string]Options, pw
}

if len(opt.Tags) > 0 {
tags := make([]string, len(opt.Tags))
for i, tag := range opt.Tags {
ref, err := reference.Parse(tag)
if err != nil {
return nil, errors.Wrapf(err, "invalid tag %q", tag)
}
tags[i] = ref.String()
}
for i, e := range opt.Exports {
switch e.Type {
case "image", "oci", "docker":
opt.Exports[i].Attrs["name"] = strings.Join(opt.Tags, ",")
opt.Exports[i].Attrs["name"] = strings.Join(tags, ",")
}
}
} else {
Expand All @@ -132,6 +163,9 @@ func Build(ctx context.Context, drivers []DriverInfo, opt map[string]Options, pw
}

for i, e := range opt.Exports {
if (e.Type == "local" || e.Type == "tar") && opt.ImageIDFile != "" {
return nil, errors.Errorf("local and tar exporters are incompatible with image ID file")
}
if e.Type == "oci" && !d.Features()[driver.OCIExporter] {
return nil, notSupported(d, driver.OCIExporter)
}
Expand Down Expand Up @@ -160,9 +194,11 @@ func Build(ctx context.Context, drivers []DriverInfo, opt map[string]Options, pw
so.Exports = opt.Exports
so.Session = opt.Session

if err := LoadInputs(opt.Inputs, &so); err != nil {
release, err := LoadInputs(opt.Inputs, &so)
if err != nil {
return nil, err
}
defer release()

if opt.Pull {
so.FrontendAttrs["image-resolve-mode"] = "pull"
Expand Down Expand Up @@ -208,6 +244,9 @@ func Build(ctx context.Context, drivers []DriverInfo, opt map[string]Options, pw
mu.Lock()
resp[k] = rr
mu.Unlock()
if opt.ImageIDFile != "" {
return ioutil.WriteFile(opt.ImageIDFile, []byte(rr.ExporterResponse["containerimage.digest"]), 0644)
}
return nil
})
}
Expand All @@ -219,30 +258,109 @@ func Build(ctx context.Context, drivers []DriverInfo, opt map[string]Options, pw
return resp, nil
}

func LoadInputs(inp Inputs, target *client.SolveOpt) error {
func createTempDockerfile(r io.Reader) (string, error) {
dir, err := ioutil.TempDir("", "dockerfile")
if err != nil {
return "", err
}
f, err := os.Create(filepath.Join(dir, "Dockerfile"))
if err != nil {
return "", err
}
defer f.Close()
if _, err := io.Copy(f, r); err != nil {
return "", err
}
return dir, err
}

func LoadInputs(inp Inputs, target *client.SolveOpt) (func(), error) {
if inp.ContextPath == "" {
return errors.New("please specify build context (e.g. \".\" for the current directory)")
return nil, errors.New("please specify build context (e.g. \".\" for the current directory)")
}

// TODO: handle stdin, symlinks, remote contexts, check files exist

if inp.DockerfilePath == "" {
inp.DockerfilePath = filepath.Join(inp.ContextPath, "Dockerfile")
var (
err error
dockerfileReader io.Reader
dockerfileDir string
dockerfileName = inp.DockerfilePath
toRemove []string
)

switch {
case inp.ContextPath == "-":
if inp.DockerfilePath == "-" {
return nil, errStdinConflict
}

buf := bufio.NewReader(os.Stdin)
magic, err := buf.Peek(archiveHeaderSize * 2)
if err != nil && err != io.EOF {
return nil, errors.Wrap(err, "failed to peek context header from STDIN")
}

if isArchive(magic) {
// stdin is context
up := uploadprovider.New()
target.FrontendAttrs["context"] = up.Add(buf)
target.Session = append(target.Session, up)
} else {
if inp.DockerfilePath != "" {
return nil, errDockerfileConflict
}
// stdin is dockerfile
dockerfileReader = buf
inp.ContextPath, _ = ioutil.TempDir("", "empty-dir")
toRemove = append(toRemove, inp.ContextPath)
target.LocalDirs["context"] = inp.ContextPath
}

case isLocalDir(inp.ContextPath):
target.LocalDirs["context"] = inp.ContextPath
switch inp.DockerfilePath {
case "-":
dockerfileReader = os.Stdin
case "":
dockerfileDir = inp.ContextPath
default:
dockerfileDir = filepath.Dir(inp.DockerfilePath)
dockerfileName = filepath.Base(inp.DockerfilePath)
}

case urlutil.IsGitURL(inp.ContextPath), urlutil.IsURL(inp.ContextPath):
if inp.DockerfilePath == "-" {
return nil, errors.Errorf("Dockerfile from stdin is not supported with remote contexts")
}
target.FrontendAttrs["context"] = inp.ContextPath
default:
return nil, errors.Errorf("unable to prepare context: path %q not found", inp.ContextPath)
}

if target.LocalDirs == nil {
target.LocalDirs = map[string]string{}
if dockerfileReader != nil {
dockerfileDir, err = createTempDockerfile(dockerfileReader)
if err != nil {
return nil, err
}
toRemove = append(toRemove, dockerfileDir)
}

target.LocalDirs["context"] = inp.ContextPath
target.LocalDirs["dockerfile"] = filepath.Dir(inp.DockerfilePath)
if dockerfileName == "" {
dockerfileName = "Dockerfile"
}
target.FrontendAttrs["filename"] = dockerfileName

if target.FrontendAttrs == nil {
target.FrontendAttrs = map[string]string{}
if dockerfileDir != "" {
target.LocalDirs["dockerfile"] = dockerfileDir
}

target.FrontendAttrs["filename"] = filepath.Base(inp.DockerfilePath)
return nil
release := func() {
for _, dir := range toRemove {
os.RemoveAll(dir)
}
}
return release, nil
}

func notSupported(d driver.Driver, f driver.Feature) error {
Expand Down
2 changes: 1 addition & 1 deletion build/output.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ func ParseOutputs(inp []string) ([]client.ExportEntry, error) {
if len(parts) != 2 {
return nil, errors.Errorf("invalid value %s", field)
}
key := strings.ToLower(parts[0])
key := strings.TrimSpace(strings.ToLower(parts[0]))
value := parts[1]
switch key {
case "type":
Expand Down
34 changes: 34 additions & 0 deletions build/utils.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package build

import (
"archive/tar"
"bytes"
"os"
)

// archiveHeaderSize is the number of bytes in an archive header
const archiveHeaderSize = 512

func isLocalDir(c string) bool {
st, err := os.Stat(c)
return err == nil && st.IsDir()
}

func isArchive(header []byte) bool {
for _, m := range [][]byte{
{0x42, 0x5A, 0x68}, // bzip2
{0x1F, 0x8B, 0x08}, // gzip
{0xFD, 0x37, 0x7A, 0x58, 0x5A, 0x00}, // xz
} {
if len(header) < len(m) {
continue
}
if bytes.Equal(m, header[:len(m)]) {
return true
}
}

r := tar.NewReader(bytes.NewBuffer(header))
_, err := r.Next()
return err == nil
}
31 changes: 14 additions & 17 deletions commands/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,19 +24,19 @@ type buildOptions struct {
labels []string
buildArgs []string

cacheFrom []string
target string
platforms []string
secrets []string
ssh []string
outputs []string
cacheFrom []string
target string
platforms []string
secrets []string
ssh []string
outputs []string
imageIDFile string

// unimplemented
extraHosts []string
squash bool
quiet bool
networkMode string
imageIDFile string

// hidden
// untrusted bool
Expand Down Expand Up @@ -74,9 +74,6 @@ func runBuild(dockerCli command.Cli, in buildOptions) error {
if in.networkMode != "default" {
return errors.Errorf("network currently not implemented")
}
if in.imageIDFile != "" {
return errors.Errorf("iidfile currently not implemented")
}

ctx := appcontext.Context()

Expand All @@ -86,12 +83,13 @@ func runBuild(dockerCli command.Cli, in buildOptions) error {
DockerfilePath: in.dockerfileName,
InStream: os.Stdin,
},
Tags: in.tags,
Labels: listToMap(in.labels),
BuildArgs: listToMap(in.buildArgs),
Pull: in.pull,
NoCache: in.noCache,
Target: in.target,
Tags: in.tags,
Labels: listToMap(in.labels),
BuildArgs: listToMap(in.buildArgs),
Pull: in.pull,
NoCache: in.noCache,
Target: in.target,
ImageIDFile: in.imageIDFile,
}

platforms, err := build.ParsePlatformSpecs(in.platforms)
Expand Down Expand Up @@ -172,7 +170,6 @@ func buildCmd(dockerCli command.Cli) *cobra.Command {
flags.MarkHidden("quiet")
flags.MarkHidden("network")
flags.MarkHidden("add-host")
flags.MarkHidden("iidfile")
flags.MarkHidden("squash")

// hidden flags
Expand Down
3 changes: 2 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ require (
github.com/denisenkom/go-mssqldb v0.0.0-20190315220205-a8ed825ac853 // indirect
github.com/docker/cli v0.0.0-20190321234815-f40f9c240ab0
github.com/docker/compose-on-kubernetes v0.4.19-0.20190128150448-356b2919c496 // indirect
github.com/docker/distribution v2.7.1-0.20190205005809-0d3efadf0154+incompatible
github.com/docker/docker v1.14.0-0.20190410063227-d9d9eccdc862
github.com/docker/docker-credential-helpers v0.6.1 // indirect
github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c // indirect
Expand Down Expand Up @@ -56,7 +57,7 @@ require (
github.com/mattn/go-sqlite3 v1.10.0 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
github.com/miekg/pkcs11 v0.0.0-20190322140431-074fd7a1ed19 // indirect
github.com/moby/buildkit v0.4.1-0.20190410165125-b4a6a0e3a7d1
github.com/moby/buildkit v0.4.1-0.20190410165125-bcd466a748e950507826c30e835b087289aaf384
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.1 // indirect
github.com/opencontainers/go-digest v1.0.0-rc1
Expand Down
7 changes: 5 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ github.com/docker/go-metrics v0.0.0-20170502235133-d466d4f6fd96 h1:HVQ/BC7Ze+bcV
github.com/docker/go-metrics v0.0.0-20170502235133-d466d4f6fd96/go.mod h1:/u0gXw0Gay3ceNrsHubL3BtdOL2fHf93USgMTe0W5dI=
github.com/docker/go-units v0.3.1 h1:QAFdsA6jLCnglbqE6mUsHuPcJlntY94DkxHf4deHKIU=
github.com/docker/go-units v0.3.1/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/docker/libnetwork v0.0.0-20180913200009-36d3bed0e9f4 h1:PmuO8T1R9jxvOI+Y6+8YBF+um2L6xp6/F52oDeYTZkk=
github.com/docker/libnetwork v0.0.0-20180913200009-36d3bed0e9f4/go.mod h1:93m0aTqz6z+g32wla4l4WxTrdtvBRmVzYRkYvasA5Z8=
github.com/docker/libtrust v0.0.0-20150526203908-9cbd2a1374f4 h1:k8TfKGeAcDQFFQOGCQMRN04N4a9YrPlRMMKnzAuvM9Q=
github.com/docker/libtrust v0.0.0-20150526203908-9cbd2a1374f4/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE=
Expand Down Expand Up @@ -187,8 +188,8 @@ github.com/miekg/pkcs11 v0.0.0-20190322140431-074fd7a1ed19/go.mod h1:WCBAbTOdfhH
github.com/mitchellh/hashstructure v0.0.0-20170609045927-2bca23e0e452/go.mod h1:QjSHrPWS+BGUVBYkbTZWEnOh3G1DutKwClXU/ABz6AQ=
github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/moby/buildkit v0.4.1-0.20190410165125-b4a6a0e3a7d1 h1:e8eXboh2H1zYsGDo/nPuZQReAd2eGEtQV1AFX9BncWo=
github.com/moby/buildkit v0.4.1-0.20190410165125-b4a6a0e3a7d1/go.mod h1:ivyIn0/bTW+YAXWeOqMWJ9aHLeac6SKIB4QeGlxzu/Q=
github.com/moby/buildkit v0.4.1-0.20190410165125-bcd466a748e950507826c30e835b087289aaf384 h1:SsOa4L1rQyJlSUm7fBAspkBNHDgmcUU60WOZPGuTTy4=
github.com/moby/buildkit v0.4.1-0.20190410165125-bcd466a748e950507826c30e835b087289aaf384/go.mod h1:ivyIn0/bTW+YAXWeOqMWJ9aHLeac6SKIB4QeGlxzu/Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
Expand Down Expand Up @@ -265,7 +266,9 @@ github.com/uber/jaeger-client-go v0.0.0-20180103221425-e02c85f9069e/go.mod h1:WV
github.com/uber/jaeger-lib v1.2.1/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U=
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
github.com/urfave/cli v0.0.0-20171014202726-7bc6a0acffa5/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
github.com/vishvananda/netlink v1.0.0 h1:bqNY2lgheFIu1meHUFSH3d7vG93AFyqg3oGbJCOJgSM=
github.com/vishvananda/netlink v1.0.0/go.mod h1:+SR5DhBJrl6ZM7CoCKvpw5BKroDKQ+PJqOg65H/2ktk=
github.com/vishvananda/netns v0.0.0-20180720170159-13995c7128cc h1:R83G5ikgLMxrBvLh22JhdfI8K6YXEPHx5P03Uu3DRs4=
github.com/vishvananda/netns v0.0.0-20180720170159-13995c7128cc/go.mod h1:ZjcWmFBXmLKZu9Nxj3WKYEafiSqer2rnvPr0en9UNpI=
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c=
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
Expand Down
Loading

0 comments on commit dc07613

Please sign in to comment.