github.com/raychaser/docker@v1.5.0/builder/dispatchers.go (about)

     1  package builder
     2  
     3  // This file contains the dispatchers for each command. Note that
     4  // `nullDispatch` is not actually a command, but support for commands we parse
     5  // but do nothing with.
     6  //
     7  // See evaluator.go for a higher level discussion of the whole evaluator
     8  // package.
     9  
    10  import (
    11  	"fmt"
    12  	"io/ioutil"
    13  	"path/filepath"
    14  	"regexp"
    15  	"sort"
    16  	"strings"
    17  
    18  	log "github.com/Sirupsen/logrus"
    19  	"github.com/docker/docker/nat"
    20  	flag "github.com/docker/docker/pkg/mflag"
    21  	"github.com/docker/docker/runconfig"
    22  )
    23  
    24  const (
    25  	// NoBaseImageSpecifier is the symbol used by the FROM
    26  	// command to specify that no base image is to be used.
    27  	NoBaseImageSpecifier string = "scratch"
    28  )
    29  
    30  // dispatch with no layer / parsing. This is effectively not a command.
    31  func nullDispatch(b *Builder, args []string, attributes map[string]bool, original string) error {
    32  	return nil
    33  }
    34  
    35  // ENV foo bar
    36  //
    37  // Sets the environment variable foo to bar, also makes interpolation
    38  // in the dockerfile available from the next statement on via ${foo}.
    39  //
    40  func env(b *Builder, args []string, attributes map[string]bool, original string) error {
    41  	if len(args) == 0 {
    42  		return fmt.Errorf("ENV is missing arguments")
    43  	}
    44  
    45  	if len(args)%2 != 0 {
    46  		// should never get here, but just in case
    47  		return fmt.Errorf("Bad input to ENV, too many args")
    48  	}
    49  
    50  	commitStr := "ENV"
    51  
    52  	for j := 0; j < len(args); j++ {
    53  		// name  ==> args[j]
    54  		// value ==> args[j+1]
    55  		newVar := args[j] + "=" + args[j+1] + ""
    56  		commitStr += " " + newVar
    57  
    58  		gotOne := false
    59  		for i, envVar := range b.Config.Env {
    60  			envParts := strings.SplitN(envVar, "=", 2)
    61  			if envParts[0] == args[j] {
    62  				b.Config.Env[i] = newVar
    63  				gotOne = true
    64  				break
    65  			}
    66  		}
    67  		if !gotOne {
    68  			b.Config.Env = append(b.Config.Env, newVar)
    69  		}
    70  		j++
    71  	}
    72  
    73  	return b.commit("", b.Config.Cmd, commitStr)
    74  }
    75  
    76  // MAINTAINER some text <maybe@an.email.address>
    77  //
    78  // Sets the maintainer metadata.
    79  func maintainer(b *Builder, args []string, attributes map[string]bool, original string) error {
    80  	if len(args) != 1 {
    81  		return fmt.Errorf("MAINTAINER requires only one argument")
    82  	}
    83  
    84  	b.maintainer = args[0]
    85  	return b.commit("", b.Config.Cmd, fmt.Sprintf("MAINTAINER %s", b.maintainer))
    86  }
    87  
    88  // ADD foo /path
    89  //
    90  // Add the file 'foo' to '/path'. Tarball and Remote URL (git, http) handling
    91  // exist here. If you do not wish to have this automatic handling, use COPY.
    92  //
    93  func add(b *Builder, args []string, attributes map[string]bool, original string) error {
    94  	if len(args) < 2 {
    95  		return fmt.Errorf("ADD requires at least two arguments")
    96  	}
    97  
    98  	return b.runContextCommand(args, true, true, "ADD")
    99  }
   100  
   101  // COPY foo /path
   102  //
   103  // Same as 'ADD' but without the tar and remote url handling.
   104  //
   105  func dispatchCopy(b *Builder, args []string, attributes map[string]bool, original string) error {
   106  	if len(args) < 2 {
   107  		return fmt.Errorf("COPY requires at least two arguments")
   108  	}
   109  
   110  	return b.runContextCommand(args, false, false, "COPY")
   111  }
   112  
   113  // FROM imagename
   114  //
   115  // This sets the image the dockerfile will build on top of.
   116  //
   117  func from(b *Builder, args []string, attributes map[string]bool, original string) error {
   118  	if len(args) != 1 {
   119  		return fmt.Errorf("FROM requires one argument")
   120  	}
   121  
   122  	name := args[0]
   123  
   124  	if name == NoBaseImageSpecifier {
   125  		b.image = ""
   126  		b.noBaseImage = true
   127  		return nil
   128  	}
   129  
   130  	image, err := b.Daemon.Repositories().LookupImage(name)
   131  	if b.Pull {
   132  		image, err = b.pullImage(name)
   133  		if err != nil {
   134  			return err
   135  		}
   136  	}
   137  	if err != nil {
   138  		if b.Daemon.Graph().IsNotExist(err) {
   139  			image, err = b.pullImage(name)
   140  		}
   141  
   142  		// note that the top level err will still be !nil here if IsNotExist is
   143  		// not the error. This approach just simplifies hte logic a bit.
   144  		if err != nil {
   145  			return err
   146  		}
   147  	}
   148  
   149  	return b.processImageFrom(image)
   150  }
   151  
   152  // ONBUILD RUN echo yo
   153  //
   154  // ONBUILD triggers run when the image is used in a FROM statement.
   155  //
   156  // ONBUILD handling has a lot of special-case functionality, the heading in
   157  // evaluator.go and comments around dispatch() in the same file explain the
   158  // special cases. search for 'OnBuild' in internals.go for additional special
   159  // cases.
   160  //
   161  func onbuild(b *Builder, args []string, attributes map[string]bool, original string) error {
   162  	triggerInstruction := strings.ToUpper(strings.TrimSpace(args[0]))
   163  	switch triggerInstruction {
   164  	case "ONBUILD":
   165  		return fmt.Errorf("Chaining ONBUILD via `ONBUILD ONBUILD` isn't allowed")
   166  	case "MAINTAINER", "FROM":
   167  		return fmt.Errorf("%s isn't allowed as an ONBUILD trigger", triggerInstruction)
   168  	}
   169  
   170  	original = regexp.MustCompile(`(?i)^\s*ONBUILD\s*`).ReplaceAllString(original, "")
   171  
   172  	b.Config.OnBuild = append(b.Config.OnBuild, original)
   173  	return b.commit("", b.Config.Cmd, fmt.Sprintf("ONBUILD %s", original))
   174  }
   175  
   176  // WORKDIR /tmp
   177  //
   178  // Set the working directory for future RUN/CMD/etc statements.
   179  //
   180  func workdir(b *Builder, args []string, attributes map[string]bool, original string) error {
   181  	if len(args) != 1 {
   182  		return fmt.Errorf("WORKDIR requires exactly one argument")
   183  	}
   184  
   185  	workdir := args[0]
   186  
   187  	if !filepath.IsAbs(workdir) {
   188  		workdir = filepath.Join("/", b.Config.WorkingDir, workdir)
   189  	}
   190  
   191  	b.Config.WorkingDir = workdir
   192  
   193  	return b.commit("", b.Config.Cmd, fmt.Sprintf("WORKDIR %v", workdir))
   194  }
   195  
   196  // RUN some command yo
   197  //
   198  // run a command and commit the image. Args are automatically prepended with
   199  // 'sh -c' in the event there is only one argument. The difference in
   200  // processing:
   201  //
   202  // RUN echo hi          # sh -c echo hi
   203  // RUN [ "echo", "hi" ] # echo hi
   204  //
   205  func run(b *Builder, args []string, attributes map[string]bool, original string) error {
   206  	if b.image == "" && !b.noBaseImage {
   207  		return fmt.Errorf("Please provide a source image with `from` prior to run")
   208  	}
   209  
   210  	args = handleJsonArgs(args, attributes)
   211  
   212  	if len(args) == 1 {
   213  		args = append([]string{"/bin/sh", "-c"}, args[0])
   214  	}
   215  
   216  	runCmd := flag.NewFlagSet("run", flag.ContinueOnError)
   217  	runCmd.SetOutput(ioutil.Discard)
   218  	runCmd.Usage = nil
   219  
   220  	config, _, _, err := runconfig.Parse(runCmd, append([]string{b.image}, args...))
   221  	if err != nil {
   222  		return err
   223  	}
   224  
   225  	cmd := b.Config.Cmd
   226  	// set Cmd manually, this is special case only for Dockerfiles
   227  	b.Config.Cmd = config.Cmd
   228  	runconfig.Merge(b.Config, config)
   229  
   230  	defer func(cmd []string) { b.Config.Cmd = cmd }(cmd)
   231  
   232  	log.Debugf("[BUILDER] Command to be executed: %v", b.Config.Cmd)
   233  
   234  	hit, err := b.probeCache()
   235  	if err != nil {
   236  		return err
   237  	}
   238  	if hit {
   239  		return nil
   240  	}
   241  
   242  	c, err := b.create()
   243  	if err != nil {
   244  		return err
   245  	}
   246  
   247  	// Ensure that we keep the container mounted until the commit
   248  	// to avoid unmounting and then mounting directly again
   249  	c.Mount()
   250  	defer c.Unmount()
   251  
   252  	err = b.run(c)
   253  	if err != nil {
   254  		return err
   255  	}
   256  	if err := b.commit(c.ID, cmd, "run"); err != nil {
   257  		return err
   258  	}
   259  
   260  	return nil
   261  }
   262  
   263  // CMD foo
   264  //
   265  // Set the default command to run in the container (which may be empty).
   266  // Argument handling is the same as RUN.
   267  //
   268  func cmd(b *Builder, args []string, attributes map[string]bool, original string) error {
   269  	b.Config.Cmd = handleJsonArgs(args, attributes)
   270  
   271  	if !attributes["json"] {
   272  		b.Config.Cmd = append([]string{"/bin/sh", "-c"}, b.Config.Cmd...)
   273  	}
   274  
   275  	if err := b.commit("", b.Config.Cmd, fmt.Sprintf("CMD %v", b.Config.Cmd)); err != nil {
   276  		return err
   277  	}
   278  
   279  	if len(args) != 0 {
   280  		b.cmdSet = true
   281  	}
   282  
   283  	return nil
   284  }
   285  
   286  // ENTRYPOINT /usr/sbin/nginx
   287  //
   288  // Set the entrypoint (which defaults to sh -c) to /usr/sbin/nginx. Will
   289  // accept the CMD as the arguments to /usr/sbin/nginx.
   290  //
   291  // Handles command processing similar to CMD and RUN, only b.Config.Entrypoint
   292  // is initialized at NewBuilder time instead of through argument parsing.
   293  //
   294  func entrypoint(b *Builder, args []string, attributes map[string]bool, original string) error {
   295  	parsed := handleJsonArgs(args, attributes)
   296  
   297  	switch {
   298  	case attributes["json"]:
   299  		// ENTRYPOINT ["echo", "hi"]
   300  		b.Config.Entrypoint = parsed
   301  	case len(parsed) == 0:
   302  		// ENTRYPOINT []
   303  		b.Config.Entrypoint = nil
   304  	default:
   305  		// ENTRYPOINT echo hi
   306  		b.Config.Entrypoint = []string{"/bin/sh", "-c", parsed[0]}
   307  	}
   308  
   309  	// when setting the entrypoint if a CMD was not explicitly set then
   310  	// set the command to nil
   311  	if !b.cmdSet {
   312  		b.Config.Cmd = nil
   313  	}
   314  
   315  	if err := b.commit("", b.Config.Cmd, fmt.Sprintf("ENTRYPOINT %v", b.Config.Entrypoint)); err != nil {
   316  		return err
   317  	}
   318  
   319  	return nil
   320  }
   321  
   322  // EXPOSE 6667/tcp 7000/tcp
   323  //
   324  // Expose ports for links and port mappings. This all ends up in
   325  // b.Config.ExposedPorts for runconfig.
   326  //
   327  func expose(b *Builder, args []string, attributes map[string]bool, original string) error {
   328  	portsTab := args
   329  
   330  	if b.Config.ExposedPorts == nil {
   331  		b.Config.ExposedPorts = make(nat.PortSet)
   332  	}
   333  
   334  	ports, _, err := nat.ParsePortSpecs(append(portsTab, b.Config.PortSpecs...))
   335  	if err != nil {
   336  		return err
   337  	}
   338  
   339  	// instead of using ports directly, we build a list of ports and sort it so
   340  	// the order is consistent. This prevents cache burst where map ordering
   341  	// changes between builds
   342  	portList := make([]string, len(ports))
   343  	var i int
   344  	for port := range ports {
   345  		if _, exists := b.Config.ExposedPorts[port]; !exists {
   346  			b.Config.ExposedPorts[port] = struct{}{}
   347  		}
   348  		portList[i] = string(port)
   349  		i++
   350  	}
   351  	sort.Strings(portList)
   352  	b.Config.PortSpecs = nil
   353  	return b.commit("", b.Config.Cmd, fmt.Sprintf("EXPOSE %s", strings.Join(portList, " ")))
   354  }
   355  
   356  // USER foo
   357  //
   358  // Set the user to 'foo' for future commands and when running the
   359  // ENTRYPOINT/CMD at container run time.
   360  //
   361  func user(b *Builder, args []string, attributes map[string]bool, original string) error {
   362  	if len(args) != 1 {
   363  		return fmt.Errorf("USER requires exactly one argument")
   364  	}
   365  
   366  	b.Config.User = args[0]
   367  	return b.commit("", b.Config.Cmd, fmt.Sprintf("USER %v", args))
   368  }
   369  
   370  // VOLUME /foo
   371  //
   372  // Expose the volume /foo for use. Will also accept the JSON array form.
   373  //
   374  func volume(b *Builder, args []string, attributes map[string]bool, original string) error {
   375  	if len(args) == 0 {
   376  		return fmt.Errorf("Volume cannot be empty")
   377  	}
   378  
   379  	if b.Config.Volumes == nil {
   380  		b.Config.Volumes = map[string]struct{}{}
   381  	}
   382  	for _, v := range args {
   383  		b.Config.Volumes[v] = struct{}{}
   384  	}
   385  	if err := b.commit("", b.Config.Cmd, fmt.Sprintf("VOLUME %v", args)); err != nil {
   386  		return err
   387  	}
   388  	return nil
   389  }
   390  
   391  // INSERT is no longer accepted, but we still parse it.
   392  func insert(b *Builder, args []string, attributes map[string]bool, original string) error {
   393  	return fmt.Errorf("INSERT has been deprecated. Please use ADD instead")
   394  }