2015-09-06 13:26:40 -04:00
|
|
|
package dockerfile
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"fmt"
|
|
|
|
"io"
|
|
|
|
"io/ioutil"
|
|
|
|
"strings"
|
|
|
|
|
|
|
|
"github.com/Sirupsen/logrus"
|
2016-09-06 11:18:12 -07:00
|
|
|
"github.com/docker/docker/api/types"
|
2016-04-13 10:21:00 -07:00
|
|
|
"github.com/docker/docker/api/types/backend"
|
2016-09-06 11:18:12 -07:00
|
|
|
"github.com/docker/docker/api/types/container"
|
2015-09-06 13:26:40 -04:00
|
|
|
"github.com/docker/docker/builder"
|
2017-04-10 15:27:42 -07:00
|
|
|
"github.com/docker/docker/builder/dockerfile/command"
|
2015-09-06 13:26:40 -04:00
|
|
|
"github.com/docker/docker/builder/dockerfile/parser"
|
2017-03-20 15:22:29 -07:00
|
|
|
"github.com/docker/docker/builder/remotecontext"
|
2015-09-06 13:26:40 -04:00
|
|
|
"github.com/docker/docker/pkg/stringid"
|
2017-04-04 12:28:59 -04:00
|
|
|
"github.com/pkg/errors"
|
2016-03-18 14:42:40 -07:00
|
|
|
"golang.org/x/net/context"
|
2017-04-13 14:37:32 -04:00
|
|
|
"golang.org/x/sync/syncmap"
|
2015-09-06 13:26:40 -04:00
|
|
|
)
|
|
|
|
|
|
|
|
var validCommitCommands = map[string]bool{
|
2016-04-18 10:48:13 +01:00
|
|
|
"cmd": true,
|
|
|
|
"entrypoint": true,
|
|
|
|
"healthcheck": true,
|
|
|
|
"env": true,
|
|
|
|
"expose": true,
|
|
|
|
"label": true,
|
|
|
|
"onbuild": true,
|
|
|
|
"user": true,
|
|
|
|
"volume": true,
|
|
|
|
"workdir": true,
|
2015-09-06 13:26:40 -04:00
|
|
|
}
|
|
|
|
|
2016-12-19 15:18:06 -05:00
|
|
|
var defaultLogConfig = container.LogConfig{Type: "none"}
|
|
|
|
|
2017-04-13 14:37:32 -04:00
|
|
|
// BuildManager is shared across all Builder objects
|
|
|
|
type BuildManager struct {
|
|
|
|
backend builder.Backend
|
|
|
|
pathCache pathCache // TODO: make this persistent
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewBuildManager creates a BuildManager
|
|
|
|
func NewBuildManager(b builder.Backend) *BuildManager {
|
|
|
|
return &BuildManager{backend: b, pathCache: &syncmap.Map{}}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Build starts a new build from a BuildConfig
|
|
|
|
func (bm *BuildManager) Build(ctx context.Context, config backend.BuildConfig) (*builder.Result, error) {
|
|
|
|
if config.Options.Dockerfile == "" {
|
|
|
|
config.Options.Dockerfile = builder.DefaultDockerfileName
|
|
|
|
}
|
|
|
|
|
|
|
|
source, dockerfile, err := remotecontext.Detect(config)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if source != nil {
|
|
|
|
defer func() {
|
|
|
|
if err := source.Close(); err != nil {
|
|
|
|
logrus.Debugf("[BUILDER] failed to remove temporary context: %v", err)
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
}
|
|
|
|
|
|
|
|
builderOptions := builderOptions{
|
|
|
|
Options: config.Options,
|
|
|
|
ProgressWriter: config.ProgressWriter,
|
|
|
|
Backend: bm.backend,
|
|
|
|
PathCache: bm.pathCache,
|
|
|
|
}
|
|
|
|
return newBuilder(ctx, builderOptions).build(source, dockerfile)
|
|
|
|
}
|
|
|
|
|
|
|
|
// builderOptions are the dependencies required by the builder
|
|
|
|
type builderOptions struct {
|
|
|
|
Options *types.ImageBuildOptions
|
|
|
|
Backend builder.Backend
|
|
|
|
ProgressWriter backend.ProgressWriter
|
|
|
|
PathCache pathCache
|
|
|
|
}
|
|
|
|
|
2015-09-06 13:26:40 -04:00
|
|
|
// Builder is a Dockerfile builder
|
2015-12-17 16:17:50 -08:00
|
|
|
// It implements the builder.Backend interface.
|
2015-09-06 13:26:40 -04:00
|
|
|
type Builder struct {
|
2015-12-29 12:49:17 -08:00
|
|
|
options *types.ImageBuildOptions
|
2015-09-06 13:26:40 -04:00
|
|
|
|
|
|
|
Stdout io.Writer
|
|
|
|
Stderr io.Writer
|
2016-01-20 15:32:02 -08:00
|
|
|
Output io.Writer
|
2015-09-06 13:26:40 -04:00
|
|
|
|
2016-03-18 14:42:40 -07:00
|
|
|
docker builder.Backend
|
2017-03-20 15:22:29 -07:00
|
|
|
source builder.Source
|
2016-03-18 14:42:40 -07:00
|
|
|
clientCtx context.Context
|
2015-09-06 13:26:40 -04:00
|
|
|
|
2017-04-06 17:38:02 -04:00
|
|
|
runConfig *container.Config // runconfig for cmd, run, entrypoint etc.
|
|
|
|
tmpContainers map[string]struct{}
|
|
|
|
imageContexts *imageContexts // helper for storing contexts from builds
|
|
|
|
disableCommit bool
|
|
|
|
cacheBusted bool
|
|
|
|
buildArgs *buildArgs
|
2017-04-11 14:34:05 -04:00
|
|
|
imageCache builder.ImageCache
|
|
|
|
|
|
|
|
// TODO: these move to DispatchState
|
|
|
|
maintainer string
|
|
|
|
cmdSet bool
|
|
|
|
noBaseImage bool // A flag to track the use of `scratch` as the base image
|
|
|
|
image string // imageID
|
|
|
|
from builder.Image
|
2016-01-20 15:32:02 -08:00
|
|
|
}
|
|
|
|
|
2017-04-13 14:37:32 -04:00
|
|
|
// newBuilder creates a new Dockerfile builder from an optional dockerfile and a Options.
|
|
|
|
func newBuilder(clientCtx context.Context, options builderOptions) *Builder {
|
|
|
|
config := options.Options
|
2015-09-06 13:26:40 -04:00
|
|
|
if config == nil {
|
2015-12-29 12:49:17 -08:00
|
|
|
config = new(types.ImageBuildOptions)
|
2015-09-06 13:26:40 -04:00
|
|
|
}
|
2017-04-13 14:37:32 -04:00
|
|
|
b := &Builder{
|
2017-04-11 14:06:51 -04:00
|
|
|
clientCtx: clientCtx,
|
2017-04-06 17:38:02 -04:00
|
|
|
options: config,
|
2017-04-13 14:37:32 -04:00
|
|
|
Stdout: options.ProgressWriter.StdoutFormatter,
|
|
|
|
Stderr: options.ProgressWriter.StderrFormatter,
|
|
|
|
Output: options.ProgressWriter.Output,
|
|
|
|
docker: options.Backend,
|
2017-04-06 17:38:02 -04:00
|
|
|
runConfig: new(container.Config),
|
|
|
|
tmpContainers: map[string]struct{}{},
|
|
|
|
buildArgs: newBuildArgs(config.BuildArgs),
|
2015-09-06 13:26:40 -04:00
|
|
|
}
|
2017-04-13 14:37:32 -04:00
|
|
|
b.imageContexts = &imageContexts{b: b, cache: options.PathCache}
|
|
|
|
return b
|
2015-09-06 13:26:40 -04:00
|
|
|
}
|
|
|
|
|
2017-03-16 16:17:49 -07:00
|
|
|
func (b *Builder) resetImageCache() {
|
|
|
|
if icb, ok := b.docker.(builder.ImageCacheBuilder); ok {
|
|
|
|
b.imageCache = icb.MakeImageCache(b.options.CacheFrom)
|
|
|
|
}
|
|
|
|
b.noBaseImage = false
|
|
|
|
b.cacheBusted = false
|
|
|
|
}
|
|
|
|
|
2017-04-13 14:37:32 -04:00
|
|
|
// Build runs the Dockerfile builder by parsing the Dockerfile and executing
|
|
|
|
// the instructions from the file.
|
|
|
|
func (b *Builder) build(source builder.Source, dockerfile *parser.Result) (*builder.Result, error) {
|
2017-03-15 15:28:06 -07:00
|
|
|
defer b.imageContexts.unmount()
|
|
|
|
|
2017-04-13 14:37:32 -04:00
|
|
|
// TODO: Remove source field from Builder
|
|
|
|
b.source = source
|
2016-01-20 15:32:02 -08:00
|
|
|
|
2017-04-12 13:47:19 -04:00
|
|
|
addNodesForLabelOption(dockerfile.AST, b.options.Labels)
|
2016-04-23 12:59:17 -07:00
|
|
|
|
2017-04-12 13:47:19 -04:00
|
|
|
if err := checkDispatchDockerfile(dockerfile.AST); err != nil {
|
2017-04-13 14:37:32 -04:00
|
|
|
return nil, err
|
2017-04-04 16:34:19 -04:00
|
|
|
}
|
|
|
|
|
2017-04-11 18:17:02 -04:00
|
|
|
imageID, err := b.dispatchDockerfileWithCancellation(dockerfile)
|
2017-04-04 16:34:19 -04:00
|
|
|
if err != nil {
|
2017-04-13 14:37:32 -04:00
|
|
|
return nil, err
|
2017-04-04 16:34:19 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
b.warnOnUnusedBuildArgs()
|
|
|
|
|
2017-04-11 18:17:02 -04:00
|
|
|
if imageID == "" {
|
2017-04-13 14:37:32 -04:00
|
|
|
return nil, errors.New("No image was generated. Is your Dockerfile empty?")
|
2017-04-04 16:34:19 -04:00
|
|
|
}
|
2017-04-13 14:37:32 -04:00
|
|
|
return &builder.Result{ImageID: imageID, FromImage: b.from}, nil
|
2017-04-04 16:34:19 -04:00
|
|
|
}
|
|
|
|
|
2017-04-12 13:47:19 -04:00
|
|
|
func (b *Builder) dispatchDockerfileWithCancellation(dockerfile *parser.Result) (string, error) {
|
2017-04-26 18:24:41 -04:00
|
|
|
shlex := NewShellLex(dockerfile.EscapeToken)
|
2017-04-12 13:47:19 -04:00
|
|
|
|
|
|
|
total := len(dockerfile.AST.Children)
|
2017-04-11 18:17:02 -04:00
|
|
|
var imageID string
|
2017-04-12 13:47:19 -04:00
|
|
|
for i, n := range dockerfile.AST.Children {
|
2015-09-06 13:26:40 -04:00
|
|
|
select {
|
2016-03-22 10:49:03 -07:00
|
|
|
case <-b.clientCtx.Done():
|
2015-09-06 13:26:40 -04:00
|
|
|
logrus.Debug("Builder: build cancelled!")
|
2016-12-25 14:37:31 +08:00
|
|
|
fmt.Fprint(b.Stdout, "Build cancelled")
|
|
|
|
return "", errors.New("Build cancelled")
|
2015-09-06 13:26:40 -04:00
|
|
|
default:
|
|
|
|
// Not cancelled yet, keep going...
|
|
|
|
}
|
2016-09-12 21:06:04 -07:00
|
|
|
|
2017-04-10 15:27:42 -07:00
|
|
|
if command.From == n.Value && b.imageContexts.isCurrentTarget(b.options.Target) {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
2017-04-26 18:24:41 -04:00
|
|
|
if err := b.dispatch(i, total, n, shlex); err != nil {
|
2015-12-29 12:49:17 -08:00
|
|
|
if b.options.ForceRemove {
|
2015-09-06 13:26:40 -04:00
|
|
|
b.clearTmp()
|
|
|
|
}
|
|
|
|
return "", err
|
|
|
|
}
|
2016-03-30 17:26:02 -04:00
|
|
|
|
2017-04-11 18:17:02 -04:00
|
|
|
// TODO: get this from dispatch
|
|
|
|
imageID = b.image
|
|
|
|
|
|
|
|
fmt.Fprintf(b.Stdout, " ---> %s\n", stringid.TruncateID(imageID))
|
2015-12-29 12:49:17 -08:00
|
|
|
if b.options.Remove {
|
2015-09-06 13:26:40 -04:00
|
|
|
b.clearTmp()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-04-10 15:27:42 -07:00
|
|
|
if b.options.Target != "" && !b.imageContexts.isCurrentTarget(b.options.Target) {
|
2017-04-04 12:28:59 -04:00
|
|
|
return "", errors.Errorf("failed to reach build target %s in Dockerfile", b.options.Target)
|
2017-04-10 15:27:42 -07:00
|
|
|
}
|
|
|
|
|
2017-04-11 18:17:02 -04:00
|
|
|
return imageID, nil
|
2017-04-04 16:34:19 -04:00
|
|
|
}
|
2015-09-06 13:26:40 -04:00
|
|
|
|
2017-04-04 14:44:40 -04:00
|
|
|
func addNodesForLabelOption(dockerfile *parser.Node, labels map[string]string) {
|
|
|
|
if len(labels) == 0 {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
node := parser.NodeFromLabels(labels)
|
|
|
|
dockerfile.Children = append(dockerfile.Children, node)
|
|
|
|
}
|
|
|
|
|
2017-03-02 14:30:35 -05:00
|
|
|
// check if there are any leftover build-args that were passed but not
|
|
|
|
// consumed during build. Print a warning, if there are any.
|
|
|
|
func (b *Builder) warnOnUnusedBuildArgs() {
|
2017-04-06 17:38:02 -04:00
|
|
|
leftoverArgs := b.buildArgs.UnreferencedOptionArgs()
|
2017-03-02 14:30:35 -05:00
|
|
|
if len(leftoverArgs) > 0 {
|
|
|
|
fmt.Fprintf(b.Stderr, "[Warning] One or more build-args %v were not consumed\n", leftoverArgs)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-02-24 17:19:45 -05:00
|
|
|
// hasFromImage returns true if the builder has processed a `FROM <image>` line
|
2017-04-11 14:34:05 -04:00
|
|
|
// TODO: move to DispatchState
|
2017-02-24 17:19:45 -05:00
|
|
|
func (b *Builder) hasFromImage() bool {
|
|
|
|
return b.image != "" || b.noBaseImage
|
|
|
|
}
|
|
|
|
|
2016-01-29 14:28:30 -05:00
|
|
|
// BuildFromConfig builds directly from `changes`, treating it as if it were the contents of a Dockerfile
|
|
|
|
// It will:
|
2016-01-05 14:33:20 +01:00
|
|
|
// - Call parse.Parse() to get an AST root for the concatenated Dockerfile entries.
|
|
|
|
// - Do build by calling builder.dispatch() to call all entries' handling routines
|
|
|
|
//
|
|
|
|
// BuildFromConfig is used by the /commit endpoint, with the changes
|
|
|
|
// coming from the query parameter of the same name.
|
|
|
|
//
|
|
|
|
// TODO: Remove?
|
2015-12-18 13:36:17 -05:00
|
|
|
func BuildFromConfig(config *container.Config, changes []string) (*container.Config, error) {
|
2017-04-13 14:37:32 -04:00
|
|
|
b := newBuilder(context.Background(), builderOptions{})
|
2016-06-27 13:20:47 -07:00
|
|
|
|
2017-04-12 13:47:19 -04:00
|
|
|
result, err := parser.Parse(bytes.NewBufferString(strings.Join(changes, "\n")))
|
2015-09-06 13:26:40 -04:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// ensure that the commands are valid
|
2017-04-12 13:47:19 -04:00
|
|
|
for _, n := range result.AST.Children {
|
2015-09-06 13:26:40 -04:00
|
|
|
if !validCommitCommands[n.Value] {
|
|
|
|
return nil, fmt.Errorf("%s is not a valid change command", n.Value)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
b.runConfig = config
|
|
|
|
b.Stdout = ioutil.Discard
|
|
|
|
b.Stderr = ioutil.Discard
|
|
|
|
b.disableCommit = true
|
|
|
|
|
2017-04-12 13:47:19 -04:00
|
|
|
if err := checkDispatchDockerfile(result.AST); err != nil {
|
2017-04-04 16:34:19 -04:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2017-04-12 13:47:19 -04:00
|
|
|
if err := dispatchFromDockerfile(b, result); err != nil {
|
2017-04-04 16:34:19 -04:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return b.runConfig, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func checkDispatchDockerfile(dockerfile *parser.Node) error {
|
|
|
|
for _, n := range dockerfile.Children {
|
|
|
|
if err := checkDispatch(n); err != nil {
|
|
|
|
return errors.Wrapf(err, "Dockerfile parse error line %d", n.StartLine)
|
2016-09-12 21:06:04 -07:00
|
|
|
}
|
|
|
|
}
|
2017-04-04 16:34:19 -04:00
|
|
|
return nil
|
|
|
|
}
|
2016-09-12 21:06:04 -07:00
|
|
|
|
2017-04-12 13:47:19 -04:00
|
|
|
func dispatchFromDockerfile(b *Builder, result *parser.Result) error {
|
2017-04-26 18:24:41 -04:00
|
|
|
shlex := NewShellLex(result.EscapeToken)
|
2017-04-12 13:47:19 -04:00
|
|
|
ast := result.AST
|
2017-04-04 16:34:19 -04:00
|
|
|
total := len(ast.Children)
|
2017-04-12 13:47:19 -04:00
|
|
|
|
2015-09-06 13:26:40 -04:00
|
|
|
for i, n := range ast.Children {
|
2017-04-26 18:24:41 -04:00
|
|
|
if err := b.dispatch(i, total, n, shlex); err != nil {
|
2017-04-04 16:34:19 -04:00
|
|
|
return err
|
2015-09-06 13:26:40 -04:00
|
|
|
}
|
|
|
|
}
|
2017-04-04 16:34:19 -04:00
|
|
|
return nil
|
2015-09-06 13:26:40 -04:00
|
|
|
}
|