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