github.com/raychaser/docker@v1.5.0/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/builder/parser"
    32  	"github.com/docker/docker/daemon"
    33  	"github.com/docker/docker/engine"
    34  	"github.com/docker/docker/pkg/fileutils"
    35  	"github.com/docker/docker/pkg/symlink"
    36  	"github.com/docker/docker/pkg/tarsum"
    37  	"github.com/docker/docker/registry"
    38  	"github.com/docker/docker/runconfig"
    39  	"github.com/docker/docker/utils"
    40  )
    41  
    42  var (
    43  	ErrDockerfileEmpty = errors.New("Dockerfile cannot be empty")
    44  )
    45  
    46  // Environment variable interpolation will happen on these statements only.
    47  var replaceEnvAllowed = map[string]struct{}{
    48  	"env":     {},
    49  	"add":     {},
    50  	"copy":    {},
    51  	"workdir": {},
    52  	"expose":  {},
    53  	"volume":  {},
    54  	"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  		"env":        env,
    62  		"maintainer": maintainer,
    63  		"add":        add,
    64  		"copy":       dispatchCopy, // copy() is a go builtin
    65  		"from":       from,
    66  		"onbuild":    onbuild,
    67  		"workdir":    workdir,
    68  		"run":        run,
    69  		"cmd":        cmd,
    70  		"entrypoint": entrypoint,
    71  		"expose":     expose,
    72  		"volume":     volume,
    73  		"user":       user,
    74  		"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  
    92  	// controls how images and containers are handled between steps.
    93  	Remove      bool
    94  	ForceRemove bool
    95  	Pull        bool
    96  
    97  	AuthConfig     *registry.AuthConfig
    98  	AuthConfigFile *registry.ConfigFile
    99  
   100  	// Deprecated, original writer used for ImagePull. To be removed.
   101  	OutOld          io.Writer
   102  	StreamFormatter *utils.StreamFormatter
   103  
   104  	Config *runconfig.Config // runconfig for cmd, run, entrypoint etc.
   105  
   106  	// both of these are controlled by the Remove and ForceRemove options in BuildOpts
   107  	TmpContainers map[string]struct{} // a map of containers used for removes
   108  
   109  	dockerfileName string        // name of Dockerfile
   110  	dockerfile     *parser.Node  // the syntax tree of the dockerfile
   111  	image          string        // image name for commit processing
   112  	maintainer     string        // maintainer name. could probably be removed.
   113  	cmdSet         bool          // indicates is CMD was set in current Dockerfile
   114  	context        tarsum.TarSum // the context is a tarball that is uploaded by the client
   115  	contextPath    string        // the path of the temporary directory the local context is unpacked to (server side)
   116  	noBaseImage    bool          // indicates that this build does not start from any base image, but is being built from an empty file system.
   117  }
   118  
   119  // Run the builder with the context. This is the lynchpin of this package. This
   120  // will (barring errors):
   121  //
   122  // * call readContext() which will set up the temporary directory and unpack
   123  //   the context into it.
   124  // * read the dockerfile
   125  // * parse the dockerfile
   126  // * walk the parse tree and execute it by dispatching to handlers. If Remove
   127  //   or ForceRemove is set, additional cleanup around containers happens after
   128  //   processing.
   129  // * Print a happy message and return the image ID.
   130  //
   131  func (b *Builder) Run(context io.Reader) (string, error) {
   132  	if err := b.readContext(context); err != nil {
   133  		return "", err
   134  	}
   135  
   136  	defer func() {
   137  		if err := os.RemoveAll(b.contextPath); err != nil {
   138  			log.Debugf("[BUILDER] failed to remove temporary context: %s", err)
   139  		}
   140  	}()
   141  
   142  	if err := b.readDockerfile(b.dockerfileName); err != nil {
   143  		return "", err
   144  	}
   145  
   146  	// some initializations that would not have been supplied by the caller.
   147  	b.Config = &runconfig.Config{}
   148  	b.TmpContainers = map[string]struct{}{}
   149  
   150  	for i, n := range b.dockerfile.Children {
   151  		if err := b.dispatch(i, n); err != nil {
   152  			if b.ForceRemove {
   153  				b.clearTmp()
   154  			}
   155  			return "", err
   156  		}
   157  		fmt.Fprintf(b.OutStream, " ---> %s\n", utils.TruncateID(b.image))
   158  		if b.Remove {
   159  			b.clearTmp()
   160  		}
   161  	}
   162  
   163  	if b.image == "" {
   164  		return "", fmt.Errorf("No image was generated. Is your Dockerfile empty?\n")
   165  	}
   166  
   167  	fmt.Fprintf(b.OutStream, "Successfully built %s\n", utils.TruncateID(b.image))
   168  	return b.image, nil
   169  }
   170  
   171  // Reads a Dockerfile from the current context. It assumes that the
   172  // 'filename' is a relative path from the root of the context
   173  func (b *Builder) readDockerfile(origFile string) error {
   174  	filename, err := symlink.FollowSymlinkInScope(filepath.Join(b.contextPath, origFile), b.contextPath)
   175  	if err != nil {
   176  		return fmt.Errorf("The Dockerfile (%s) must be within the build context", origFile)
   177  	}
   178  
   179  	fi, err := os.Lstat(filename)
   180  	if os.IsNotExist(err) {
   181  		return fmt.Errorf("Cannot locate specified Dockerfile: %s", origFile)
   182  	}
   183  	if fi.Size() == 0 {
   184  		return ErrDockerfileEmpty
   185  	}
   186  
   187  	f, err := os.Open(filename)
   188  	if err != nil {
   189  		return err
   190  	}
   191  
   192  	b.dockerfile, err = parser.Parse(f)
   193  	f.Close()
   194  
   195  	if err != nil {
   196  		return err
   197  	}
   198  
   199  	// After the Dockerfile has been parsed, we need to check the .dockerignore
   200  	// file for either "Dockerfile" or ".dockerignore", and if either are
   201  	// present then erase them from the build context. These files should never
   202  	// have been sent from the client but we did send them to make sure that
   203  	// we had the Dockerfile to actually parse, and then we also need the
   204  	// .dockerignore file to know whether either file should be removed.
   205  	// Note that this assumes the Dockerfile has been read into memory and
   206  	// is now safe to be removed.
   207  
   208  	excludes, _ := utils.ReadDockerIgnore(filepath.Join(b.contextPath, ".dockerignore"))
   209  	if rm, _ := fileutils.Matches(".dockerignore", excludes); rm == true {
   210  		os.Remove(filepath.Join(b.contextPath, ".dockerignore"))
   211  		b.context.(tarsum.BuilderContext).Remove(".dockerignore")
   212  	}
   213  	if rm, _ := fileutils.Matches(b.dockerfileName, excludes); rm == true {
   214  		os.Remove(filepath.Join(b.contextPath, b.dockerfileName))
   215  		b.context.(tarsum.BuilderContext).Remove(b.dockerfileName)
   216  	}
   217  
   218  	return nil
   219  }
   220  
   221  // This method is the entrypoint to all statement handling routines.
   222  //
   223  // Almost all nodes will have this structure:
   224  // Child[Node, Node, Node] where Child is from parser.Node.Children and each
   225  // node comes from parser.Node.Next. This forms a "line" with a statement and
   226  // arguments and we process them in this normalized form by hitting
   227  // evaluateTable with the leaf nodes of the command and the Builder object.
   228  //
   229  // ONBUILD is a special case; in this case the parser will emit:
   230  // Child[Node, Child[Node, Node...]] where the first node is the literal
   231  // "onbuild" and the child entrypoint is the command of the ONBUILD statmeent,
   232  // such as `RUN` in ONBUILD RUN foo. There is special case logic in here to
   233  // deal with that, at least until it becomes more of a general concern with new
   234  // features.
   235  func (b *Builder) dispatch(stepN int, ast *parser.Node) error {
   236  	cmd := ast.Value
   237  	attrs := ast.Attributes
   238  	original := ast.Original
   239  	strs := []string{}
   240  	msg := fmt.Sprintf("Step %d : %s", stepN, strings.ToUpper(cmd))
   241  
   242  	if cmd == "onbuild" {
   243  		ast = ast.Next.Children[0]
   244  		strs = append(strs, ast.Value)
   245  		msg += " " + ast.Value
   246  	}
   247  
   248  	// count the number of nodes that we are going to traverse first
   249  	// so we can pre-create the argument and message array. This speeds up the
   250  	// allocation of those list a lot when they have a lot of arguments
   251  	cursor := ast
   252  	var n int
   253  	for cursor.Next != nil {
   254  		cursor = cursor.Next
   255  		n++
   256  	}
   257  	l := len(strs)
   258  	strList := make([]string, n+l)
   259  	copy(strList, strs)
   260  	msgList := make([]string, n)
   261  
   262  	var i int
   263  	for ast.Next != nil {
   264  		ast = ast.Next
   265  		var str string
   266  		str = ast.Value
   267  		if _, ok := replaceEnvAllowed[cmd]; ok {
   268  			str = b.replaceEnv(ast.Value)
   269  		}
   270  		strList[i+l] = str
   271  		msgList[i] = ast.Value
   272  		i++
   273  	}
   274  
   275  	msg += " " + strings.Join(msgList, " ")
   276  	fmt.Fprintln(b.OutStream, msg)
   277  
   278  	// XXX yes, we skip any cmds that are not valid; the parser should have
   279  	// picked these out already.
   280  	if f, ok := evaluateTable[cmd]; ok {
   281  		return f(b, strList, attrs, original)
   282  	}
   283  
   284  	fmt.Fprintf(b.ErrStream, "# Skipping unknown instruction %s\n", strings.ToUpper(cmd))
   285  
   286  	return nil
   287  }