github.com/eljojo/docker@v1.6.0-rc4/builder/evaluator.go (about)

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