github.com/ld86/docker@v1.7.1-rc3/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  	"strings"
    28  
    29  	"github.com/Sirupsen/logrus"
    30  	"github.com/docker/docker/api"
    31  	"github.com/docker/docker/builder/command"
    32  	"github.com/docker/docker/builder/parser"
    33  	"github.com/docker/docker/cliconfig"
    34  	"github.com/docker/docker/daemon"
    35  	"github.com/docker/docker/pkg/fileutils"
    36  	"github.com/docker/docker/pkg/streamformatter"
    37  	"github.com/docker/docker/pkg/stringid"
    38  	"github.com/docker/docker/pkg/symlink"
    39  	"github.com/docker/docker/pkg/tarsum"
    40  	"github.com/docker/docker/runconfig"
    41  	"github.com/docker/docker/utils"
    42  )
    43  
    44  // Environment variable interpolation will happen on these statements only.
    45  var replaceEnvAllowed = map[string]struct{}{
    46  	command.Env:     {},
    47  	command.Label:   {},
    48  	command.Add:     {},
    49  	command.Copy:    {},
    50  	command.Workdir: {},
    51  	command.Expose:  {},
    52  	command.Volume:  {},
    53  	command.User:    {},
    54  }
    55  
    56  var evaluateTable map[string]func(*Builder, []string, map[string]bool, string) error
    57  
    58  func init() {
    59  	evaluateTable = map[string]func(*Builder, []string, map[string]bool, string) error{
    60  		command.Env:        env,
    61  		command.Label:      label,
    62  		command.Maintainer: maintainer,
    63  		command.Add:        add,
    64  		command.Copy:       dispatchCopy, // copy() is a go builtin
    65  		command.From:       from,
    66  		command.Onbuild:    onbuild,
    67  		command.Workdir:    workdir,
    68  		command.Run:        run,
    69  		command.Cmd:        cmd,
    70  		command.Entrypoint: entrypoint,
    71  		command.Expose:     expose,
    72  		command.Volume:     volume,
    73  		command.User:       user,
    74  	}
    75  }
    76  
    77  // internal struct, used to maintain configuration of the Dockerfile's
    78  // processing as it evaluates the parsing result.
    79  type Builder struct {
    80  	Daemon *daemon.Daemon
    81  
    82  	// effectively stdio for the run. Because it is not stdio, I said
    83  	// "Effectively". Do not use stdio anywhere in this package for any reason.
    84  	OutStream io.Writer
    85  	ErrStream io.Writer
    86  
    87  	Verbose      bool
    88  	UtilizeCache bool
    89  	cacheBusted  bool
    90  
    91  	// controls how images and containers are handled between steps.
    92  	Remove      bool
    93  	ForceRemove bool
    94  	Pull        bool
    95  
    96  	// set this to true if we want the builder to not commit between steps.
    97  	// This is useful when we only want to use the evaluator table to generate
    98  	// the final configs of the Dockerfile but dont want the layers
    99  	disableCommit bool
   100  
   101  	// Registry server auth configs used to pull images when handling `FROM`.
   102  	AuthConfigs map[string]cliconfig.AuthConfig
   103  
   104  	// Deprecated, original writer used for ImagePull. To be removed.
   105  	OutOld          io.Writer
   106  	StreamFormatter *streamformatter.StreamFormatter
   107  
   108  	Config *runconfig.Config // runconfig for cmd, run, entrypoint etc.
   109  
   110  	// both of these are controlled by the Remove and ForceRemove options in BuildOpts
   111  	TmpContainers map[string]struct{} // a map of containers used for removes
   112  
   113  	dockerfileName string        // name of Dockerfile
   114  	dockerfile     *parser.Node  // the syntax tree of the dockerfile
   115  	image          string        // image name for commit processing
   116  	maintainer     string        // maintainer name. could probably be removed.
   117  	cmdSet         bool          // indicates is CMD was set in current Dockerfile
   118  	BuilderFlags   *BuilderFlags // current cmd's BuilderFlags - temporary
   119  	context        tarsum.TarSum // the context is a tarball that is uploaded by the client
   120  	contextPath    string        // the path of the temporary directory the local context is unpacked to (server side)
   121  	noBaseImage    bool          // indicates that this build does not start from any base image, but is being built from an empty file system.
   122  
   123  	// Set resource restrictions for build containers
   124  	cpuSetCpus   string
   125  	cpuSetMems   string
   126  	cpuShares    int64
   127  	cpuPeriod    int64
   128  	cpuQuota     int64
   129  	cgroupParent string
   130  	memory       int64
   131  	memorySwap   int64
   132  
   133  	cancelled <-chan struct{} // When closed, job was cancelled.
   134  }
   135  
   136  // Run the builder with the context. This is the lynchpin of this package. This
   137  // will (barring errors):
   138  //
   139  // * call readContext() which will set up the temporary directory and unpack
   140  //   the context into it.
   141  // * read the dockerfile
   142  // * parse the dockerfile
   143  // * walk the parse tree and execute it by dispatching to handlers. If Remove
   144  //   or ForceRemove is set, additional cleanup around containers happens after
   145  //   processing.
   146  // * Print a happy message and return the image ID.
   147  //
   148  func (b *Builder) Run(context io.Reader) (string, error) {
   149  	if err := b.readContext(context); err != nil {
   150  		return "", err
   151  	}
   152  
   153  	defer func() {
   154  		if err := os.RemoveAll(b.contextPath); err != nil {
   155  			logrus.Debugf("[BUILDER] failed to remove temporary context: %s", err)
   156  		}
   157  	}()
   158  
   159  	if err := b.readDockerfile(); err != nil {
   160  		return "", err
   161  	}
   162  
   163  	// some initializations that would not have been supplied by the caller.
   164  	b.Config = &runconfig.Config{}
   165  
   166  	b.TmpContainers = map[string]struct{}{}
   167  
   168  	for i, n := range b.dockerfile.Children {
   169  		select {
   170  		case <-b.cancelled:
   171  			logrus.Debug("Builder: build cancelled!")
   172  			fmt.Fprintf(b.OutStream, "Build cancelled")
   173  			return "", fmt.Errorf("Build cancelled")
   174  		default:
   175  			// Not cancelled yet, keep going...
   176  		}
   177  		if err := b.dispatch(i, n); err != nil {
   178  			if b.ForceRemove {
   179  				b.clearTmp()
   180  			}
   181  			return "", err
   182  		}
   183  		fmt.Fprintf(b.OutStream, " ---> %s\n", stringid.TruncateID(b.image))
   184  		if b.Remove {
   185  			b.clearTmp()
   186  		}
   187  	}
   188  
   189  	if b.image == "" {
   190  		return "", fmt.Errorf("No image was generated. Is your Dockerfile empty?")
   191  	}
   192  
   193  	fmt.Fprintf(b.OutStream, "Successfully built %s\n", stringid.TruncateID(b.image))
   194  	return b.image, nil
   195  }
   196  
   197  // Reads a Dockerfile from the current context. It assumes that the
   198  // 'filename' is a relative path from the root of the context
   199  func (b *Builder) readDockerfile() error {
   200  	// If no -f was specified then look for 'Dockerfile'. If we can't find
   201  	// that then look for 'dockerfile'.  If neither are found then default
   202  	// back to 'Dockerfile' and use that in the error message.
   203  	if b.dockerfileName == "" {
   204  		b.dockerfileName = api.DefaultDockerfileName
   205  		tmpFN := filepath.Join(b.contextPath, api.DefaultDockerfileName)
   206  		if _, err := os.Lstat(tmpFN); err != nil {
   207  			tmpFN = filepath.Join(b.contextPath, strings.ToLower(api.DefaultDockerfileName))
   208  			if _, err := os.Lstat(tmpFN); err == nil {
   209  				b.dockerfileName = strings.ToLower(api.DefaultDockerfileName)
   210  			}
   211  		}
   212  	}
   213  
   214  	origFile := b.dockerfileName
   215  
   216  	filename, err := symlink.FollowSymlinkInScope(filepath.Join(b.contextPath, origFile), b.contextPath)
   217  	if err != nil {
   218  		return fmt.Errorf("The Dockerfile (%s) must be within the build context", origFile)
   219  	}
   220  
   221  	fi, err := os.Lstat(filename)
   222  	if os.IsNotExist(err) {
   223  		return fmt.Errorf("Cannot locate specified Dockerfile: %s", origFile)
   224  	}
   225  	if fi.Size() == 0 {
   226  		return fmt.Errorf("The Dockerfile (%s) cannot be empty", origFile)
   227  	}
   228  
   229  	f, err := os.Open(filename)
   230  	if err != nil {
   231  		return err
   232  	}
   233  
   234  	b.dockerfile, err = parser.Parse(f)
   235  	f.Close()
   236  
   237  	if err != nil {
   238  		return err
   239  	}
   240  
   241  	// After the Dockerfile has been parsed, we need to check the .dockerignore
   242  	// file for either "Dockerfile" or ".dockerignore", and if either are
   243  	// present then erase them from the build context. These files should never
   244  	// have been sent from the client but we did send them to make sure that
   245  	// we had the Dockerfile to actually parse, and then we also need the
   246  	// .dockerignore file to know whether either file should be removed.
   247  	// Note that this assumes the Dockerfile has been read into memory and
   248  	// is now safe to be removed.
   249  
   250  	excludes, _ := utils.ReadDockerIgnore(filepath.Join(b.contextPath, ".dockerignore"))
   251  	if rm, _ := fileutils.Matches(".dockerignore", excludes); rm == true {
   252  		os.Remove(filepath.Join(b.contextPath, ".dockerignore"))
   253  		b.context.(tarsum.BuilderContext).Remove(".dockerignore")
   254  	}
   255  	if rm, _ := fileutils.Matches(b.dockerfileName, excludes); rm == true {
   256  		os.Remove(filepath.Join(b.contextPath, b.dockerfileName))
   257  		b.context.(tarsum.BuilderContext).Remove(b.dockerfileName)
   258  	}
   259  
   260  	return nil
   261  }
   262  
   263  // This method is the entrypoint to all statement handling routines.
   264  //
   265  // Almost all nodes will have this structure:
   266  // Child[Node, Node, Node] where Child is from parser.Node.Children and each
   267  // node comes from parser.Node.Next. This forms a "line" with a statement and
   268  // arguments and we process them in this normalized form by hitting
   269  // evaluateTable with the leaf nodes of the command and the Builder object.
   270  //
   271  // ONBUILD is a special case; in this case the parser will emit:
   272  // Child[Node, Child[Node, Node...]] where the first node is the literal
   273  // "onbuild" and the child entrypoint is the command of the ONBUILD statmeent,
   274  // such as `RUN` in ONBUILD RUN foo. There is special case logic in here to
   275  // deal with that, at least until it becomes more of a general concern with new
   276  // features.
   277  func (b *Builder) dispatch(stepN int, ast *parser.Node) error {
   278  	cmd := ast.Value
   279  	attrs := ast.Attributes
   280  	original := ast.Original
   281  	flags := ast.Flags
   282  	strs := []string{}
   283  	msg := fmt.Sprintf("Step %d : %s", stepN, strings.ToUpper(cmd))
   284  
   285  	if len(ast.Flags) > 0 {
   286  		msg += " " + strings.Join(ast.Flags, " ")
   287  	}
   288  
   289  	if cmd == "onbuild" {
   290  		if ast.Next == nil {
   291  			return fmt.Errorf("ONBUILD requires at least one argument")
   292  		}
   293  		ast = ast.Next.Children[0]
   294  		strs = append(strs, ast.Value)
   295  		msg += " " + ast.Value
   296  
   297  		if len(ast.Flags) > 0 {
   298  			msg += " " + strings.Join(ast.Flags, " ")
   299  		}
   300  
   301  	}
   302  
   303  	// count the number of nodes that we are going to traverse first
   304  	// so we can pre-create the argument and message array. This speeds up the
   305  	// allocation of those list a lot when they have a lot of arguments
   306  	cursor := ast
   307  	var n int
   308  	for cursor.Next != nil {
   309  		cursor = cursor.Next
   310  		n++
   311  	}
   312  	l := len(strs)
   313  	strList := make([]string, n+l)
   314  	copy(strList, strs)
   315  	msgList := make([]string, n)
   316  
   317  	var i int
   318  	for ast.Next != nil {
   319  		ast = ast.Next
   320  		var str string
   321  		str = ast.Value
   322  		if _, ok := replaceEnvAllowed[cmd]; ok {
   323  			var err error
   324  			str, err = ProcessWord(ast.Value, b.Config.Env)
   325  			if err != nil {
   326  				return err
   327  			}
   328  		}
   329  		strList[i+l] = str
   330  		msgList[i] = ast.Value
   331  		i++
   332  	}
   333  
   334  	msg += " " + strings.Join(msgList, " ")
   335  	fmt.Fprintln(b.OutStream, msg)
   336  
   337  	// XXX yes, we skip any cmds that are not valid; the parser should have
   338  	// picked these out already.
   339  	if f, ok := evaluateTable[cmd]; ok {
   340  		b.BuilderFlags = NewBuilderFlags()
   341  		b.BuilderFlags.Args = flags
   342  		return f(b, strList, attrs, original)
   343  	}
   344  
   345  	return fmt.Errorf("Unknown instruction: %s", strings.ToUpper(cmd))
   346  }