github.com/mheon/docker@v0.11.2-0.20150922122814-44f47903a831/builder/evaluator.go (about)

     1  // Package builder is the evaluation step in the Dockerfile parse/evaluate pipeline.
     2  //
     3  // It incorporates a dispatch table based on the parser.Node values (see the
     4  // parser package for more information) that are yielded from the parser itself.
     5  // Calling NewBuilder with the BuildOpts struct can be used to customize the
     6  // experience for execution purposes only. Parsing is controlled in the parser
     7  // package, and this division of resposibility should be respected.
     8  //
     9  // Please see the jump table targets for the actual invocations, most of which
    10  // will call out to the functions in internals.go to deal with their tasks.
    11  //
    12  // ONBUILD is a special case, which is covered in the onbuild() func in
    13  // dispatchers.go.
    14  //
    15  // The evaluator uses the concept of "steps", which are usually each processable
    16  // line in the Dockerfile. Each step is numbered and certain actions are taken
    17  // before and after each step, such as creating an image ID and removing temporary
    18  // containers and images. Note that ONBUILD creates a kinda-sorta "sub run" which
    19  // includes its own set of steps (usually only one of them).
    20  package builder
    21  
    22  import (
    23  	"fmt"
    24  	"io"
    25  	"os"
    26  	"path/filepath"
    27  	"runtime"
    28  	"strings"
    29  
    30  	"github.com/Sirupsen/logrus"
    31  	"github.com/docker/docker/api"
    32  	"github.com/docker/docker/builder/command"
    33  	"github.com/docker/docker/builder/parser"
    34  	"github.com/docker/docker/cliconfig"
    35  	"github.com/docker/docker/daemon"
    36  	"github.com/docker/docker/pkg/fileutils"
    37  	"github.com/docker/docker/pkg/streamformatter"
    38  	"github.com/docker/docker/pkg/stringid"
    39  	"github.com/docker/docker/pkg/symlink"
    40  	"github.com/docker/docker/pkg/tarsum"
    41  	"github.com/docker/docker/pkg/ulimit"
    42  	"github.com/docker/docker/runconfig"
    43  	"github.com/docker/docker/utils"
    44  )
    45  
    46  // Environment variable interpolation will happen on these statements only.
    47  var replaceEnvAllowed = map[string]struct{}{
    48  	command.Env:        {},
    49  	command.Label:      {},
    50  	command.Add:        {},
    51  	command.Copy:       {},
    52  	command.Workdir:    {},
    53  	command.Expose:     {},
    54  	command.Volume:     {},
    55  	command.User:       {},
    56  	command.StopSignal: {},
    57  	command.Arg:        {},
    58  }
    59  
    60  var evaluateTable map[string]func(*builder, []string, map[string]bool, string) error
    61  
    62  func init() {
    63  	evaluateTable = map[string]func(*builder, []string, map[string]bool, string) error{
    64  		command.Env:        env,
    65  		command.Label:      label,
    66  		command.Maintainer: maintainer,
    67  		command.Add:        add,
    68  		command.Copy:       dispatchCopy, // copy() is a go builtin
    69  		command.From:       from,
    70  		command.Onbuild:    onbuild,
    71  		command.Workdir:    workdir,
    72  		command.Run:        run,
    73  		command.Cmd:        cmd,
    74  		command.Entrypoint: entrypoint,
    75  		command.Expose:     expose,
    76  		command.Volume:     volume,
    77  		command.User:       user,
    78  		command.StopSignal: stopSignal,
    79  		command.Arg:        arg,
    80  	}
    81  }
    82  
    83  // builder is an internal struct, used to maintain configuration of the Dockerfile's
    84  // processing as it evaluates the parsing result.
    85  type builder struct {
    86  	Daemon *daemon.Daemon
    87  
    88  	// effectively stdio for the run. Because it is not stdio, I said
    89  	// "Effectively". Do not use stdio anywhere in this package for any reason.
    90  	OutStream io.Writer
    91  	ErrStream io.Writer
    92  
    93  	Verbose      bool
    94  	UtilizeCache bool
    95  	cacheBusted  bool
    96  
    97  	// controls how images and containers are handled between steps.
    98  	Remove      bool
    99  	ForceRemove bool
   100  	Pull        bool
   101  
   102  	// set this to true if we want the builder to not commit between steps.
   103  	// This is useful when we only want to use the evaluator table to generate
   104  	// the final configs of the Dockerfile but dont want the layers
   105  	disableCommit bool
   106  
   107  	// Registry server auth configs used to pull images when handling `FROM`.
   108  	AuthConfigs map[string]cliconfig.AuthConfig
   109  
   110  	// Deprecated, original writer used for ImagePull. To be removed.
   111  	OutOld          io.Writer
   112  	StreamFormatter *streamformatter.StreamFormatter
   113  
   114  	Config *runconfig.Config // runconfig for cmd, run, entrypoint etc.
   115  
   116  	buildArgs        map[string]string // build-time args received in build context for expansion/substitution and commands in 'run'.
   117  	allowedBuildArgs map[string]bool   // list of build-time args that are allowed for expansion/substitution and passing to commands in 'run'.
   118  
   119  	// both of these are controlled by the Remove and ForceRemove options in BuildOpts
   120  	TmpContainers map[string]struct{} // a map of containers used for removes
   121  
   122  	dockerfileName string        // name of Dockerfile
   123  	dockerfile     *parser.Node  // the syntax tree of the dockerfile
   124  	image          string        // image name for commit processing
   125  	maintainer     string        // maintainer name. could probably be removed.
   126  	cmdSet         bool          // indicates is CMD was set in current Dockerfile
   127  	BuilderFlags   *BFlags       // current cmd's BuilderFlags - temporary
   128  	context        tarsum.TarSum // the context is a tarball that is uploaded by the client
   129  	contextPath    string        // the path of the temporary directory the local context is unpacked to (server side)
   130  	noBaseImage    bool          // indicates that this build does not start from any base image, but is being built from an empty file system.
   131  
   132  	// Set resource restrictions for build containers
   133  	cpuSetCpus   string
   134  	cpuSetMems   string
   135  	cpuShares    int64
   136  	cpuPeriod    int64
   137  	cpuQuota     int64
   138  	cgroupParent string
   139  	memory       int64
   140  	memorySwap   int64
   141  	ulimits      []*ulimit.Ulimit
   142  
   143  	cancelled <-chan struct{} // When closed, job was cancelled.
   144  
   145  	activeImages []string
   146  	id           string // Used to hold reference images
   147  }
   148  
   149  // Run the builder with the context. This is the lynchpin of this package. This
   150  // will (barring errors):
   151  //
   152  // * call readContext() which will set up the temporary directory and unpack
   153  //   the context into it.
   154  // * read the dockerfile
   155  // * parse the dockerfile
   156  // * walk the parse tree and execute it by dispatching to handlers. If Remove
   157  //   or ForceRemove is set, additional cleanup around containers happens after
   158  //   processing.
   159  // * Print a happy message and return the image ID.
   160  //
   161  func (b *builder) Run(context io.Reader) (string, error) {
   162  	if err := b.readContext(context); err != nil {
   163  		return "", err
   164  	}
   165  
   166  	defer func() {
   167  		if err := os.RemoveAll(b.contextPath); err != nil {
   168  			logrus.Debugf("[BUILDER] failed to remove temporary context: %s", err)
   169  		}
   170  	}()
   171  
   172  	if err := b.readDockerfile(); err != nil {
   173  		return "", err
   174  	}
   175  
   176  	// some initializations that would not have been supplied by the caller.
   177  	b.Config = &runconfig.Config{}
   178  
   179  	b.TmpContainers = map[string]struct{}{}
   180  
   181  	for i, n := range b.dockerfile.Children {
   182  		select {
   183  		case <-b.cancelled:
   184  			logrus.Debug("Builder: build cancelled!")
   185  			fmt.Fprintf(b.OutStream, "Build cancelled")
   186  			return "", fmt.Errorf("Build cancelled")
   187  		default:
   188  			// Not cancelled yet, keep going...
   189  		}
   190  		if err := b.dispatch(i, n); err != nil {
   191  			if b.ForceRemove {
   192  				b.clearTmp()
   193  			}
   194  			return "", err
   195  		}
   196  		fmt.Fprintf(b.OutStream, " ---> %s\n", stringid.TruncateID(b.image))
   197  		if b.Remove {
   198  			b.clearTmp()
   199  		}
   200  	}
   201  
   202  	// check if there are any leftover build-args that were passed but not
   203  	// consumed during build. Return an error, if there are any.
   204  	leftoverArgs := []string{}
   205  	for arg := range b.buildArgs {
   206  		if !b.isBuildArgAllowed(arg) {
   207  			leftoverArgs = append(leftoverArgs, arg)
   208  		}
   209  	}
   210  	if len(leftoverArgs) > 0 {
   211  		return "", fmt.Errorf("One or more build-args %v were not consumed, failing build.", leftoverArgs)
   212  	}
   213  
   214  	if b.image == "" {
   215  		return "", fmt.Errorf("No image was generated. Is your Dockerfile empty?")
   216  	}
   217  
   218  	fmt.Fprintf(b.OutStream, "Successfully built %s\n", stringid.TruncateID(b.image))
   219  	return b.image, nil
   220  }
   221  
   222  // Reads a Dockerfile from the current context. It assumes that the
   223  // 'filename' is a relative path from the root of the context
   224  func (b *builder) readDockerfile() error {
   225  	// If no -f was specified then look for 'Dockerfile'. If we can't find
   226  	// that then look for 'dockerfile'.  If neither are found then default
   227  	// back to 'Dockerfile' and use that in the error message.
   228  	if b.dockerfileName == "" {
   229  		b.dockerfileName = api.DefaultDockerfileName
   230  		tmpFN := filepath.Join(b.contextPath, api.DefaultDockerfileName)
   231  		if _, err := os.Lstat(tmpFN); err != nil {
   232  			tmpFN = filepath.Join(b.contextPath, strings.ToLower(api.DefaultDockerfileName))
   233  			if _, err := os.Lstat(tmpFN); err == nil {
   234  				b.dockerfileName = strings.ToLower(api.DefaultDockerfileName)
   235  			}
   236  		}
   237  	}
   238  
   239  	origFile := b.dockerfileName
   240  
   241  	filename, err := symlink.FollowSymlinkInScope(filepath.Join(b.contextPath, origFile), b.contextPath)
   242  	if err != nil {
   243  		return fmt.Errorf("The Dockerfile (%s) must be within the build context", origFile)
   244  	}
   245  
   246  	fi, err := os.Lstat(filename)
   247  	if os.IsNotExist(err) {
   248  		return fmt.Errorf("Cannot locate specified Dockerfile: %s", origFile)
   249  	}
   250  	if fi.Size() == 0 {
   251  		return fmt.Errorf("The Dockerfile (%s) cannot be empty", origFile)
   252  	}
   253  
   254  	f, err := os.Open(filename)
   255  	if err != nil {
   256  		return err
   257  	}
   258  
   259  	b.dockerfile, err = parser.Parse(f)
   260  	f.Close()
   261  
   262  	if err != nil {
   263  		return err
   264  	}
   265  
   266  	// After the Dockerfile has been parsed, we need to check the .dockerignore
   267  	// file for either "Dockerfile" or ".dockerignore", and if either are
   268  	// present then erase them from the build context. These files should never
   269  	// have been sent from the client but we did send them to make sure that
   270  	// we had the Dockerfile to actually parse, and then we also need the
   271  	// .dockerignore file to know whether either file should be removed.
   272  	// Note that this assumes the Dockerfile has been read into memory and
   273  	// is now safe to be removed.
   274  
   275  	excludes, _ := utils.ReadDockerIgnore(filepath.Join(b.contextPath, ".dockerignore"))
   276  	if rm, _ := fileutils.Matches(".dockerignore", excludes); rm == true {
   277  		os.Remove(filepath.Join(b.contextPath, ".dockerignore"))
   278  		b.context.(tarsum.BuilderContext).Remove(".dockerignore")
   279  	}
   280  	if rm, _ := fileutils.Matches(b.dockerfileName, excludes); rm == true {
   281  		os.Remove(filepath.Join(b.contextPath, b.dockerfileName))
   282  		b.context.(tarsum.BuilderContext).Remove(b.dockerfileName)
   283  	}
   284  
   285  	return nil
   286  }
   287  
   288  // determine if build arg is part of built-in args or user
   289  // defined args in Dockerfile at any point in time.
   290  func (b *builder) isBuildArgAllowed(arg string) bool {
   291  	if _, ok := BuiltinAllowedBuildArgs[arg]; ok {
   292  		return true
   293  	}
   294  	if _, ok := b.allowedBuildArgs[arg]; ok {
   295  		return true
   296  	}
   297  	return false
   298  }
   299  
   300  // This method is the entrypoint to all statement handling routines.
   301  //
   302  // Almost all nodes will have this structure:
   303  // Child[Node, Node, Node] where Child is from parser.Node.Children and each
   304  // node comes from parser.Node.Next. This forms a "line" with a statement and
   305  // arguments and we process them in this normalized form by hitting
   306  // evaluateTable with the leaf nodes of the command and the Builder object.
   307  //
   308  // ONBUILD is a special case; in this case the parser will emit:
   309  // Child[Node, Child[Node, Node...]] where the first node is the literal
   310  // "onbuild" and the child entrypoint is the command of the ONBUILD statmeent,
   311  // such as `RUN` in ONBUILD RUN foo. There is special case logic in here to
   312  // deal with that, at least until it becomes more of a general concern with new
   313  // features.
   314  func (b *builder) dispatch(stepN int, ast *parser.Node) error {
   315  	cmd := ast.Value
   316  
   317  	// To ensure the user is give a decent error message if the platform
   318  	// on which the daemon is running does not support a builder command.
   319  	if err := platformSupports(strings.ToLower(cmd)); err != nil {
   320  		return err
   321  	}
   322  
   323  	attrs := ast.Attributes
   324  	original := ast.Original
   325  	flags := ast.Flags
   326  	strs := []string{}
   327  	msg := fmt.Sprintf("Step %d : %s", stepN+1, strings.ToUpper(cmd))
   328  
   329  	if len(ast.Flags) > 0 {
   330  		msg += " " + strings.Join(ast.Flags, " ")
   331  	}
   332  
   333  	if cmd == "onbuild" {
   334  		if ast.Next == nil {
   335  			return fmt.Errorf("ONBUILD requires at least one argument")
   336  		}
   337  		ast = ast.Next.Children[0]
   338  		strs = append(strs, ast.Value)
   339  		msg += " " + ast.Value
   340  
   341  		if len(ast.Flags) > 0 {
   342  			msg += " " + strings.Join(ast.Flags, " ")
   343  		}
   344  
   345  	}
   346  
   347  	// count the number of nodes that we are going to traverse first
   348  	// so we can pre-create the argument and message array. This speeds up the
   349  	// allocation of those list a lot when they have a lot of arguments
   350  	cursor := ast
   351  	var n int
   352  	for cursor.Next != nil {
   353  		cursor = cursor.Next
   354  		n++
   355  	}
   356  	l := len(strs)
   357  	strList := make([]string, n+l)
   358  	copy(strList, strs)
   359  	msgList := make([]string, n)
   360  
   361  	var i int
   362  	// Append the build-time args to config-environment.
   363  	// This allows builder config to override the variables, making the behavior similar to
   364  	// a shell script i.e. `ENV foo bar` overrides value of `foo` passed in build
   365  	// context. But `ENV foo $foo` will use the value from build context if one
   366  	// isn't already been defined by a previous ENV primitive.
   367  	// Note, we get this behavior because we know that ProcessWord() will
   368  	// stop on the first occurrence of a variable name and not notice
   369  	// a subsequent one. So, putting the buildArgs list after the Config.Env
   370  	// list, in 'envs', is safe.
   371  	envs := b.Config.Env
   372  	for key, val := range b.buildArgs {
   373  		if !b.isBuildArgAllowed(key) {
   374  			// skip build-args that are not in allowed list, meaning they have
   375  			// not been defined by an "ARG" Dockerfile command yet.
   376  			// This is an error condition but only if there is no "ARG" in the entire
   377  			// Dockerfile, so we'll generate any necessary errors after we parsed
   378  			// the entire file (see 'leftoverArgs' processing in evaluator.go )
   379  			continue
   380  		}
   381  		envs = append(envs, fmt.Sprintf("%s=%s", key, val))
   382  	}
   383  	for ast.Next != nil {
   384  		ast = ast.Next
   385  		var str string
   386  		str = ast.Value
   387  		if _, ok := replaceEnvAllowed[cmd]; ok {
   388  			var err error
   389  			str, err = ProcessWord(ast.Value, envs)
   390  			if err != nil {
   391  				return err
   392  			}
   393  		}
   394  		strList[i+l] = str
   395  		msgList[i] = ast.Value
   396  		i++
   397  	}
   398  
   399  	msg += " " + strings.Join(msgList, " ")
   400  	fmt.Fprintln(b.OutStream, msg)
   401  
   402  	// XXX yes, we skip any cmds that are not valid; the parser should have
   403  	// picked these out already.
   404  	if f, ok := evaluateTable[cmd]; ok {
   405  		b.BuilderFlags = NewBFlags()
   406  		b.BuilderFlags.Args = flags
   407  		return f(b, strList, attrs, original)
   408  	}
   409  
   410  	return fmt.Errorf("Unknown instruction: %s", strings.ToUpper(cmd))
   411  }
   412  
   413  // platformSupports is a short-term function to give users a quality error
   414  // message if a Dockerfile uses a command not supported on the platform.
   415  func platformSupports(command string) error {
   416  	if runtime.GOOS != "windows" {
   417  		return nil
   418  	}
   419  	switch command {
   420  	case "expose", "volume", "user", "stopsignal", "arg":
   421  		return fmt.Errorf("The daemon on this platform does not support the command '%s'", command)
   422  	}
   423  	return nil
   424  }