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