github.com/vieux/docker@v0.6.3-0.20161004191708-e097c2a938c7/builder/dockerfile/builder.go (about)

     1  package dockerfile
     2  
     3  import (
     4  	"bytes"
     5  	"errors"
     6  	"fmt"
     7  	"io"
     8  	"io/ioutil"
     9  	"os"
    10  	"strings"
    11  
    12  	"github.com/Sirupsen/logrus"
    13  	"github.com/docker/docker/api/types"
    14  	"github.com/docker/docker/api/types/backend"
    15  	"github.com/docker/docker/api/types/container"
    16  	"github.com/docker/docker/builder"
    17  	"github.com/docker/docker/builder/dockerfile/parser"
    18  	"github.com/docker/docker/image"
    19  	"github.com/docker/docker/pkg/stringid"
    20  	"github.com/docker/docker/reference"
    21  	"golang.org/x/net/context"
    22  )
    23  
    24  var validCommitCommands = map[string]bool{
    25  	"cmd":         true,
    26  	"entrypoint":  true,
    27  	"healthcheck": true,
    28  	"env":         true,
    29  	"expose":      true,
    30  	"label":       true,
    31  	"onbuild":     true,
    32  	"user":        true,
    33  	"volume":      true,
    34  	"workdir":     true,
    35  }
    36  
    37  // BuiltinAllowedBuildArgs is list of built-in allowed build args
    38  var BuiltinAllowedBuildArgs = map[string]bool{
    39  	"HTTP_PROXY":  true,
    40  	"http_proxy":  true,
    41  	"HTTPS_PROXY": true,
    42  	"https_proxy": true,
    43  	"FTP_PROXY":   true,
    44  	"ftp_proxy":   true,
    45  	"NO_PROXY":    true,
    46  	"no_proxy":    true,
    47  }
    48  
    49  // Builder is a Dockerfile builder
    50  // It implements the builder.Backend interface.
    51  type Builder struct {
    52  	options *types.ImageBuildOptions
    53  
    54  	Stdout io.Writer
    55  	Stderr io.Writer
    56  	Output io.Writer
    57  
    58  	docker    builder.Backend
    59  	context   builder.Context
    60  	clientCtx context.Context
    61  	cancel    context.CancelFunc
    62  
    63  	dockerfile       *parser.Node
    64  	runConfig        *container.Config // runconfig for cmd, run, entrypoint etc.
    65  	flags            *BFlags
    66  	tmpContainers    map[string]struct{}
    67  	image            string // imageID
    68  	noBaseImage      bool
    69  	maintainer       string
    70  	cmdSet           bool
    71  	disableCommit    bool
    72  	cacheBusted      bool
    73  	allowedBuildArgs map[string]bool // list of build-time args that are allowed for expansion/substitution and passing to commands in 'run'.
    74  	directive        parser.Directive
    75  
    76  	// TODO: remove once docker.Commit can receive a tag
    77  	id string
    78  
    79  	imageCache builder.ImageCache
    80  }
    81  
    82  // BuildManager implements builder.Backend and is shared across all Builder objects.
    83  type BuildManager struct {
    84  	backend builder.Backend
    85  }
    86  
    87  // NewBuildManager creates a BuildManager.
    88  func NewBuildManager(b builder.Backend) (bm *BuildManager) {
    89  	return &BuildManager{backend: b}
    90  }
    91  
    92  // BuildFromContext builds a new image from a given context.
    93  func (bm *BuildManager) BuildFromContext(ctx context.Context, src io.ReadCloser, remote string, buildOptions *types.ImageBuildOptions, pg backend.ProgressWriter) (string, error) {
    94  	buildContext, dockerfileName, err := builder.DetectContextFromRemoteURL(src, remote, pg.ProgressReaderFunc)
    95  	if err != nil {
    96  		return "", err
    97  	}
    98  	defer func() {
    99  		if err := buildContext.Close(); err != nil {
   100  			logrus.Debugf("[BUILDER] failed to remove temporary context: %v", err)
   101  		}
   102  	}()
   103  	if len(dockerfileName) > 0 {
   104  		buildOptions.Dockerfile = dockerfileName
   105  	}
   106  	b, err := NewBuilder(ctx, buildOptions, bm.backend, builder.DockerIgnoreContext{ModifiableContext: buildContext}, nil)
   107  	if err != nil {
   108  		return "", err
   109  	}
   110  	return b.build(pg.StdoutFormatter, pg.StderrFormatter, pg.Output)
   111  }
   112  
   113  // NewBuilder creates a new Dockerfile builder from an optional dockerfile and a Config.
   114  // If dockerfile is nil, the Dockerfile specified by Config.DockerfileName,
   115  // will be read from the Context passed to Build().
   116  func NewBuilder(clientCtx context.Context, config *types.ImageBuildOptions, backend builder.Backend, buildContext builder.Context, dockerfile io.ReadCloser) (b *Builder, err error) {
   117  	if config == nil {
   118  		config = new(types.ImageBuildOptions)
   119  	}
   120  	if config.BuildArgs == nil {
   121  		config.BuildArgs = make(map[string]string)
   122  	}
   123  	ctx, cancel := context.WithCancel(clientCtx)
   124  	b = &Builder{
   125  		clientCtx:        ctx,
   126  		cancel:           cancel,
   127  		options:          config,
   128  		Stdout:           os.Stdout,
   129  		Stderr:           os.Stderr,
   130  		docker:           backend,
   131  		context:          buildContext,
   132  		runConfig:        new(container.Config),
   133  		tmpContainers:    map[string]struct{}{},
   134  		id:               stringid.GenerateNonCryptoID(),
   135  		allowedBuildArgs: make(map[string]bool),
   136  		directive: parser.Directive{
   137  			EscapeSeen:           false,
   138  			LookingForDirectives: true,
   139  		},
   140  	}
   141  	if icb, ok := backend.(builder.ImageCacheBuilder); ok {
   142  		b.imageCache = icb.MakeImageCache(config.CacheFrom)
   143  	}
   144  
   145  	parser.SetEscapeToken(parser.DefaultEscapeToken, &b.directive) // Assume the default token for escape
   146  
   147  	if dockerfile != nil {
   148  		b.dockerfile, err = parser.Parse(dockerfile, &b.directive)
   149  		if err != nil {
   150  			return nil, err
   151  		}
   152  	}
   153  
   154  	return b, nil
   155  }
   156  
   157  // sanitizeRepoAndTags parses the raw "t" parameter received from the client
   158  // to a slice of repoAndTag.
   159  // It also validates each repoName and tag.
   160  func sanitizeRepoAndTags(names []string) ([]reference.Named, error) {
   161  	var (
   162  		repoAndTags []reference.Named
   163  		// This map is used for deduplicating the "-t" parameter.
   164  		uniqNames = make(map[string]struct{})
   165  	)
   166  	for _, repo := range names {
   167  		if repo == "" {
   168  			continue
   169  		}
   170  
   171  		ref, err := reference.ParseNamed(repo)
   172  		if err != nil {
   173  			return nil, err
   174  		}
   175  
   176  		ref = reference.WithDefaultTag(ref)
   177  
   178  		if _, isCanonical := ref.(reference.Canonical); isCanonical {
   179  			return nil, errors.New("build tag cannot contain a digest")
   180  		}
   181  
   182  		if _, isTagged := ref.(reference.NamedTagged); !isTagged {
   183  			ref, err = reference.WithTag(ref, reference.DefaultTag)
   184  			if err != nil {
   185  				return nil, err
   186  			}
   187  		}
   188  
   189  		nameWithTag := ref.String()
   190  
   191  		if _, exists := uniqNames[nameWithTag]; !exists {
   192  			uniqNames[nameWithTag] = struct{}{}
   193  			repoAndTags = append(repoAndTags, ref)
   194  		}
   195  	}
   196  	return repoAndTags, nil
   197  }
   198  
   199  // build runs the Dockerfile builder from a context and a docker object that allows to make calls
   200  // to Docker.
   201  //
   202  // This will (barring errors):
   203  //
   204  // * read the dockerfile from context
   205  // * parse the dockerfile if not already parsed
   206  // * walk the AST and execute it by dispatching to handlers. If Remove
   207  //   or ForceRemove is set, additional cleanup around containers happens after
   208  //   processing.
   209  // * Tag image, if applicable.
   210  // * Print a happy message and return the image ID.
   211  //
   212  func (b *Builder) build(stdout io.Writer, stderr io.Writer, out io.Writer) (string, error) {
   213  	b.Stdout = stdout
   214  	b.Stderr = stderr
   215  	b.Output = out
   216  
   217  	// If Dockerfile was not parsed yet, extract it from the Context
   218  	if b.dockerfile == nil {
   219  		if err := b.readDockerfile(); err != nil {
   220  			return "", err
   221  		}
   222  	}
   223  
   224  	repoAndTags, err := sanitizeRepoAndTags(b.options.Tags)
   225  	if err != nil {
   226  		return "", err
   227  	}
   228  
   229  	if len(b.options.Labels) > 0 {
   230  		line := "LABEL "
   231  		for k, v := range b.options.Labels {
   232  			line += fmt.Sprintf("%q=%q ", k, v)
   233  		}
   234  		_, node, err := parser.ParseLine(line, &b.directive)
   235  		if err != nil {
   236  			return "", err
   237  		}
   238  		b.dockerfile.Children = append(b.dockerfile.Children, node)
   239  	}
   240  
   241  	var shortImgID string
   242  	total := len(b.dockerfile.Children)
   243  	for _, n := range b.dockerfile.Children {
   244  		if err := b.checkDispatch(n, false); err != nil {
   245  			return "", err
   246  		}
   247  	}
   248  
   249  	for i, n := range b.dockerfile.Children {
   250  		select {
   251  		case <-b.clientCtx.Done():
   252  			logrus.Debug("Builder: build cancelled!")
   253  			fmt.Fprintf(b.Stdout, "Build cancelled")
   254  			return "", fmt.Errorf("Build cancelled")
   255  		default:
   256  			// Not cancelled yet, keep going...
   257  		}
   258  
   259  		if err := b.dispatch(i, total, n); err != nil {
   260  			if b.options.ForceRemove {
   261  				b.clearTmp()
   262  			}
   263  			return "", err
   264  		}
   265  
   266  		shortImgID = stringid.TruncateID(b.image)
   267  		fmt.Fprintf(b.Stdout, " ---> %s\n", shortImgID)
   268  		if b.options.Remove {
   269  			b.clearTmp()
   270  		}
   271  	}
   272  
   273  	// check if there are any leftover build-args that were passed but not
   274  	// consumed during build. Return an error, if there are any.
   275  	leftoverArgs := []string{}
   276  	for arg := range b.options.BuildArgs {
   277  		if !b.isBuildArgAllowed(arg) {
   278  			leftoverArgs = append(leftoverArgs, arg)
   279  		}
   280  	}
   281  	if len(leftoverArgs) > 0 {
   282  		return "", fmt.Errorf("One or more build-args %v were not consumed, failing build.", leftoverArgs)
   283  	}
   284  
   285  	if b.image == "" {
   286  		return "", fmt.Errorf("No image was generated. Is your Dockerfile empty?")
   287  	}
   288  
   289  	imageID := image.ID(b.image)
   290  	for _, rt := range repoAndTags {
   291  		if err := b.docker.TagImageWithReference(imageID, rt); err != nil {
   292  			return "", err
   293  		}
   294  	}
   295  
   296  	fmt.Fprintf(b.Stdout, "Successfully built %s\n", shortImgID)
   297  	return b.image, nil
   298  }
   299  
   300  // Cancel cancels an ongoing Dockerfile build.
   301  func (b *Builder) Cancel() {
   302  	b.cancel()
   303  }
   304  
   305  // BuildFromConfig builds directly from `changes`, treating it as if it were the contents of a Dockerfile
   306  // It will:
   307  // - Call parse.Parse() to get an AST root for the concatenated Dockerfile entries.
   308  // - Do build by calling builder.dispatch() to call all entries' handling routines
   309  //
   310  // BuildFromConfig is used by the /commit endpoint, with the changes
   311  // coming from the query parameter of the same name.
   312  //
   313  // TODO: Remove?
   314  func BuildFromConfig(config *container.Config, changes []string) (*container.Config, error) {
   315  	b, err := NewBuilder(context.Background(), nil, nil, nil, nil)
   316  	if err != nil {
   317  		return nil, err
   318  	}
   319  
   320  	ast, err := parser.Parse(bytes.NewBufferString(strings.Join(changes, "\n")), &b.directive)
   321  	if err != nil {
   322  		return nil, err
   323  	}
   324  
   325  	// ensure that the commands are valid
   326  	for _, n := range ast.Children {
   327  		if !validCommitCommands[n.Value] {
   328  			return nil, fmt.Errorf("%s is not a valid change command", n.Value)
   329  		}
   330  	}
   331  
   332  	b.runConfig = config
   333  	b.Stdout = ioutil.Discard
   334  	b.Stderr = ioutil.Discard
   335  	b.disableCommit = true
   336  
   337  	total := len(ast.Children)
   338  	for _, n := range ast.Children {
   339  		if err := b.checkDispatch(n, false); err != nil {
   340  			return nil, err
   341  		}
   342  	}
   343  
   344  	for i, n := range ast.Children {
   345  		if err := b.dispatch(i, total, n); err != nil {
   346  			return nil, err
   347  		}
   348  	}
   349  
   350  	return b.runConfig, nil
   351  }