github.com/flavio/docker@v0.1.3-0.20170117145210-f63d1a6eec47/builder/dockerfile/dispatchers.go (about)

     1  package dockerfile
     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  	"errors"
    12  	"fmt"
    13  	"regexp"
    14  	"runtime"
    15  	"sort"
    16  	"strconv"
    17  	"strings"
    18  	"time"
    19  
    20  	"github.com/Sirupsen/logrus"
    21  	"github.com/docker/docker/api"
    22  	"github.com/docker/docker/api/types"
    23  	"github.com/docker/docker/api/types/container"
    24  	"github.com/docker/docker/api/types/strslice"
    25  	"github.com/docker/docker/builder"
    26  	"github.com/docker/docker/pkg/signal"
    27  	runconfigopts "github.com/docker/docker/runconfig/opts"
    28  	"github.com/docker/go-connections/nat"
    29  )
    30  
    31  // ENV foo bar
    32  //
    33  // Sets the environment variable foo to bar, also makes interpolation
    34  // in the dockerfile available from the next statement on via ${foo}.
    35  //
    36  func env(b *Builder, args []string, attributes map[string]bool, original string) error {
    37  	if len(args) == 0 {
    38  		return errAtLeastOneArgument("ENV")
    39  	}
    40  
    41  	if len(args)%2 != 0 {
    42  		// should never get here, but just in case
    43  		return errTooManyArguments("ENV")
    44  	}
    45  
    46  	if err := b.flags.Parse(); err != nil {
    47  		return err
    48  	}
    49  
    50  	// TODO/FIXME/NOT USED
    51  	// Just here to show how to use the builder flags stuff within the
    52  	// context of a builder command. Will remove once we actually add
    53  	// a builder command to something!
    54  	/*
    55  		flBool1 := b.flags.AddBool("bool1", false)
    56  		flStr1 := b.flags.AddString("str1", "HI")
    57  
    58  		if err := b.flags.Parse(); err != nil {
    59  			return err
    60  		}
    61  
    62  		fmt.Printf("Bool1:%v\n", flBool1)
    63  		fmt.Printf("Str1:%v\n", flStr1)
    64  	*/
    65  
    66  	commitStr := "ENV"
    67  
    68  	for j := 0; j < len(args); j++ {
    69  		// name  ==> args[j]
    70  		// value ==> args[j+1]
    71  
    72  		if len(args[j]) == 0 {
    73  			return errBlankCommandNames("ENV")
    74  		}
    75  		newVar := args[j] + "=" + args[j+1] + ""
    76  		commitStr += " " + newVar
    77  
    78  		gotOne := false
    79  		for i, envVar := range b.runConfig.Env {
    80  			envParts := strings.SplitN(envVar, "=", 2)
    81  			compareFrom := envParts[0]
    82  			compareTo := args[j]
    83  			if runtime.GOOS == "windows" {
    84  				// Case insensitive environment variables on Windows
    85  				compareFrom = strings.ToUpper(compareFrom)
    86  				compareTo = strings.ToUpper(compareTo)
    87  			}
    88  			if compareFrom == compareTo {
    89  				b.runConfig.Env[i] = newVar
    90  				gotOne = true
    91  				break
    92  			}
    93  		}
    94  		if !gotOne {
    95  			b.runConfig.Env = append(b.runConfig.Env, newVar)
    96  		}
    97  		j++
    98  	}
    99  
   100  	return b.commit("", b.runConfig.Cmd, commitStr)
   101  }
   102  
   103  // MAINTAINER some text <maybe@an.email.address>
   104  //
   105  // Sets the maintainer metadata.
   106  func maintainer(b *Builder, args []string, attributes map[string]bool, original string) error {
   107  	if len(args) != 1 {
   108  		return errExactlyOneArgument("MAINTAINER")
   109  	}
   110  
   111  	if err := b.flags.Parse(); err != nil {
   112  		return err
   113  	}
   114  
   115  	b.maintainer = args[0]
   116  	return b.commit("", b.runConfig.Cmd, fmt.Sprintf("MAINTAINER %s", b.maintainer))
   117  }
   118  
   119  // LABEL some json data describing the image
   120  //
   121  // Sets the Label variable foo to bar,
   122  //
   123  func label(b *Builder, args []string, attributes map[string]bool, original string) error {
   124  	if len(args) == 0 {
   125  		return errAtLeastOneArgument("LABEL")
   126  	}
   127  	if len(args)%2 != 0 {
   128  		// should never get here, but just in case
   129  		return errTooManyArguments("LABEL")
   130  	}
   131  
   132  	if err := b.flags.Parse(); err != nil {
   133  		return err
   134  	}
   135  
   136  	commitStr := "LABEL"
   137  
   138  	if b.runConfig.Labels == nil {
   139  		b.runConfig.Labels = map[string]string{}
   140  	}
   141  
   142  	for j := 0; j < len(args); j++ {
   143  		// name  ==> args[j]
   144  		// value ==> args[j+1]
   145  
   146  		if len(args[j]) == 0 {
   147  			return errBlankCommandNames("LABEL")
   148  		}
   149  
   150  		newVar := args[j] + "=" + args[j+1] + ""
   151  		commitStr += " " + newVar
   152  
   153  		b.runConfig.Labels[args[j]] = args[j+1]
   154  		j++
   155  	}
   156  	return b.commit("", b.runConfig.Cmd, commitStr)
   157  }
   158  
   159  // ADD foo /path
   160  //
   161  // Add the file 'foo' to '/path'. Tarball and Remote URL (git, http) handling
   162  // exist here. If you do not wish to have this automatic handling, use COPY.
   163  //
   164  func add(b *Builder, args []string, attributes map[string]bool, original string) error {
   165  	if len(args) < 2 {
   166  		return errAtLeastTwoArguments("ADD")
   167  	}
   168  
   169  	if err := b.flags.Parse(); err != nil {
   170  		return err
   171  	}
   172  
   173  	return b.runContextCommand(args, true, true, "ADD")
   174  }
   175  
   176  // COPY foo /path
   177  //
   178  // Same as 'ADD' but without the tar and remote url handling.
   179  //
   180  func dispatchCopy(b *Builder, args []string, attributes map[string]bool, original string) error {
   181  	if len(args) < 2 {
   182  		return errAtLeastTwoArguments("COPY")
   183  	}
   184  
   185  	if err := b.flags.Parse(); err != nil {
   186  		return err
   187  	}
   188  
   189  	return b.runContextCommand(args, false, false, "COPY")
   190  }
   191  
   192  // FROM imagename
   193  //
   194  // This sets the image the dockerfile will build on top of.
   195  //
   196  func from(b *Builder, args []string, attributes map[string]bool, original string) error {
   197  	if len(args) != 1 {
   198  		return errExactlyOneArgument("FROM")
   199  	}
   200  
   201  	if err := b.flags.Parse(); err != nil {
   202  		return err
   203  	}
   204  
   205  	name := args[0]
   206  
   207  	var (
   208  		image builder.Image
   209  		err   error
   210  	)
   211  
   212  	// Windows cannot support a container with no base image.
   213  	if name == api.NoBaseImageSpecifier {
   214  		if runtime.GOOS == "windows" {
   215  			return errors.New("Windows does not support FROM scratch")
   216  		}
   217  		b.image = ""
   218  		b.noBaseImage = true
   219  	} else {
   220  		// TODO: don't use `name`, instead resolve it to a digest
   221  		if !b.options.PullParent {
   222  			image, err = b.docker.GetImageOnBuild(name)
   223  			// TODO: shouldn't we error out if error is different from "not found" ?
   224  		}
   225  		if image == nil {
   226  			image, err = b.docker.PullOnBuild(b.clientCtx, name, b.options.AuthConfigs, b.Output)
   227  			if err != nil {
   228  				return err
   229  			}
   230  		}
   231  	}
   232  	b.from = image
   233  
   234  	return b.processImageFrom(image)
   235  }
   236  
   237  // ONBUILD RUN echo yo
   238  //
   239  // ONBUILD triggers run when the image is used in a FROM statement.
   240  //
   241  // ONBUILD handling has a lot of special-case functionality, the heading in
   242  // evaluator.go and comments around dispatch() in the same file explain the
   243  // special cases. search for 'OnBuild' in internals.go for additional special
   244  // cases.
   245  //
   246  func onbuild(b *Builder, args []string, attributes map[string]bool, original string) error {
   247  	if len(args) == 0 {
   248  		return errAtLeastOneArgument("ONBUILD")
   249  	}
   250  
   251  	if err := b.flags.Parse(); err != nil {
   252  		return err
   253  	}
   254  
   255  	triggerInstruction := strings.ToUpper(strings.TrimSpace(args[0]))
   256  	switch triggerInstruction {
   257  	case "ONBUILD":
   258  		return errors.New("Chaining ONBUILD via `ONBUILD ONBUILD` isn't allowed")
   259  	case "MAINTAINER", "FROM":
   260  		return fmt.Errorf("%s isn't allowed as an ONBUILD trigger", triggerInstruction)
   261  	}
   262  
   263  	original = regexp.MustCompile(`(?i)^\s*ONBUILD\s*`).ReplaceAllString(original, "")
   264  
   265  	b.runConfig.OnBuild = append(b.runConfig.OnBuild, original)
   266  	return b.commit("", b.runConfig.Cmd, fmt.Sprintf("ONBUILD %s", original))
   267  }
   268  
   269  // WORKDIR /tmp
   270  //
   271  // Set the working directory for future RUN/CMD/etc statements.
   272  //
   273  func workdir(b *Builder, args []string, attributes map[string]bool, original string) error {
   274  	if len(args) != 1 {
   275  		return errExactlyOneArgument("WORKDIR")
   276  	}
   277  
   278  	err := b.flags.Parse()
   279  	if err != nil {
   280  		return err
   281  	}
   282  
   283  	// This is from the Dockerfile and will not necessarily be in platform
   284  	// specific semantics, hence ensure it is converted.
   285  	b.runConfig.WorkingDir, err = normaliseWorkdir(b.runConfig.WorkingDir, args[0])
   286  	if err != nil {
   287  		return err
   288  	}
   289  
   290  	// For performance reasons, we explicitly do a create/mkdir now
   291  	// This avoids having an unnecessary expensive mount/unmount calls
   292  	// (on Windows in particular) during each container create.
   293  	// Prior to 1.13, the mkdir was deferred and not executed at this step.
   294  	if b.disableCommit {
   295  		// Don't call back into the daemon if we're going through docker commit --change "WORKDIR /foo".
   296  		// We've already updated the runConfig and that's enough.
   297  		return nil
   298  	}
   299  	b.runConfig.Image = b.image
   300  
   301  	cmd := b.runConfig.Cmd
   302  	comment := "WORKDIR " + b.runConfig.WorkingDir
   303  	// reset the command for cache detection
   304  	b.runConfig.Cmd = strslice.StrSlice(append(getShell(b.runConfig), "#(nop) "+comment))
   305  	defer func(cmd strslice.StrSlice) { b.runConfig.Cmd = cmd }(cmd)
   306  
   307  	if hit, err := b.probeCache(); err != nil {
   308  		return err
   309  	} else if hit {
   310  		return nil
   311  	}
   312  
   313  	container, err := b.docker.ContainerCreate(types.ContainerCreateConfig{Config: b.runConfig})
   314  	if err != nil {
   315  		return err
   316  	}
   317  	b.tmpContainers[container.ID] = struct{}{}
   318  	if err := b.docker.ContainerCreateWorkdir(container.ID); err != nil {
   319  		return err
   320  	}
   321  
   322  	return b.commit(container.ID, cmd, comment)
   323  }
   324  
   325  // RUN some command yo
   326  //
   327  // run a command and commit the image. Args are automatically prepended with
   328  // the current SHELL which defaults to 'sh -c' under linux or 'cmd /S /C' under
   329  // Windows, in the event there is only one argument The difference in processing:
   330  //
   331  // RUN echo hi          # sh -c echo hi       (Linux)
   332  // RUN echo hi          # cmd /S /C echo hi   (Windows)
   333  // RUN [ "echo", "hi" ] # echo hi
   334  //
   335  func run(b *Builder, args []string, attributes map[string]bool, original string) error {
   336  	if b.image == "" && !b.noBaseImage {
   337  		return errors.New("Please provide a source image with `from` prior to run")
   338  	}
   339  
   340  	if err := b.flags.Parse(); err != nil {
   341  		return err
   342  	}
   343  
   344  	args = handleJSONArgs(args, attributes)
   345  
   346  	if !attributes["json"] {
   347  		args = append(getShell(b.runConfig), args...)
   348  	}
   349  	config := &container.Config{
   350  		Cmd:   strslice.StrSlice(args),
   351  		Image: b.image,
   352  	}
   353  
   354  	// stash the cmd
   355  	cmd := b.runConfig.Cmd
   356  	if len(b.runConfig.Entrypoint) == 0 && len(b.runConfig.Cmd) == 0 {
   357  		b.runConfig.Cmd = config.Cmd
   358  	}
   359  
   360  	// stash the config environment
   361  	env := b.runConfig.Env
   362  
   363  	defer func(cmd strslice.StrSlice) { b.runConfig.Cmd = cmd }(cmd)
   364  	defer func(env []string) { b.runConfig.Env = env }(env)
   365  
   366  	// derive the net build-time environment for this run. We let config
   367  	// environment override the build time environment.
   368  	// This means that we take the b.buildArgs list of env vars and remove
   369  	// any of those variables that are defined as part of the container. In other
   370  	// words, anything in b.Config.Env. What's left is the list of build-time env
   371  	// vars that we need to add to each RUN command - note the list could be empty.
   372  	//
   373  	// We don't persist the build time environment with container's config
   374  	// environment, but just sort and prepend it to the command string at time
   375  	// of commit.
   376  	// This helps with tracing back the image's actual environment at the time
   377  	// of RUN, without leaking it to the final image. It also aids cache
   378  	// lookup for same image built with same build time environment.
   379  	cmdBuildEnv := []string{}
   380  	configEnv := runconfigopts.ConvertKVStringsToMap(b.runConfig.Env)
   381  	for key, val := range b.options.BuildArgs {
   382  		if !b.isBuildArgAllowed(key) {
   383  			// skip build-args that are not in allowed list, meaning they have
   384  			// not been defined by an "ARG" Dockerfile command yet.
   385  			// This is an error condition but only if there is no "ARG" in the entire
   386  			// Dockerfile, so we'll generate any necessary errors after we parsed
   387  			// the entire file (see 'leftoverArgs' processing in evaluator.go )
   388  			continue
   389  		}
   390  		if _, ok := configEnv[key]; !ok && val != nil {
   391  			cmdBuildEnv = append(cmdBuildEnv, fmt.Sprintf("%s=%s", key, *val))
   392  		}
   393  	}
   394  
   395  	// derive the command to use for probeCache() and to commit in this container.
   396  	// Note that we only do this if there are any build-time env vars.  Also, we
   397  	// use the special argument "|#" at the start of the args array. This will
   398  	// avoid conflicts with any RUN command since commands can not
   399  	// start with | (vertical bar). The "#" (number of build envs) is there to
   400  	// help ensure proper cache matches. We don't want a RUN command
   401  	// that starts with "foo=abc" to be considered part of a build-time env var.
   402  	saveCmd := config.Cmd
   403  	if len(cmdBuildEnv) > 0 {
   404  		sort.Strings(cmdBuildEnv)
   405  		tmpEnv := append([]string{fmt.Sprintf("|%d", len(cmdBuildEnv))}, cmdBuildEnv...)
   406  		saveCmd = strslice.StrSlice(append(tmpEnv, saveCmd...))
   407  	}
   408  
   409  	b.runConfig.Cmd = saveCmd
   410  	hit, err := b.probeCache()
   411  	if err != nil {
   412  		return err
   413  	}
   414  	if hit {
   415  		return nil
   416  	}
   417  
   418  	// set Cmd manually, this is special case only for Dockerfiles
   419  	b.runConfig.Cmd = config.Cmd
   420  	// set build-time environment for 'run'.
   421  	b.runConfig.Env = append(b.runConfig.Env, cmdBuildEnv...)
   422  	// set config as already being escaped, this prevents double escaping on windows
   423  	b.runConfig.ArgsEscaped = true
   424  
   425  	logrus.Debugf("[BUILDER] Command to be executed: %v", b.runConfig.Cmd)
   426  
   427  	cID, err := b.create()
   428  	if err != nil {
   429  		return err
   430  	}
   431  
   432  	if err := b.run(cID); err != nil {
   433  		return err
   434  	}
   435  
   436  	// revert to original config environment and set the command string to
   437  	// have the build-time env vars in it (if any) so that future cache look-ups
   438  	// properly match it.
   439  	b.runConfig.Env = env
   440  	b.runConfig.Cmd = saveCmd
   441  	return b.commit(cID, cmd, "run")
   442  }
   443  
   444  // CMD foo
   445  //
   446  // Set the default command to run in the container (which may be empty).
   447  // Argument handling is the same as RUN.
   448  //
   449  func cmd(b *Builder, args []string, attributes map[string]bool, original string) error {
   450  	if err := b.flags.Parse(); err != nil {
   451  		return err
   452  	}
   453  
   454  	cmdSlice := handleJSONArgs(args, attributes)
   455  
   456  	if !attributes["json"] {
   457  		cmdSlice = append(getShell(b.runConfig), cmdSlice...)
   458  	}
   459  
   460  	b.runConfig.Cmd = strslice.StrSlice(cmdSlice)
   461  	// set config as already being escaped, this prevents double escaping on windows
   462  	b.runConfig.ArgsEscaped = true
   463  
   464  	if err := b.commit("", b.runConfig.Cmd, fmt.Sprintf("CMD %q", cmdSlice)); err != nil {
   465  		return err
   466  	}
   467  
   468  	if len(args) != 0 {
   469  		b.cmdSet = true
   470  	}
   471  
   472  	return nil
   473  }
   474  
   475  // parseOptInterval(flag) is the duration of flag.Value, or 0 if
   476  // empty. An error is reported if the value is given and is not positive.
   477  func parseOptInterval(f *Flag) (time.Duration, error) {
   478  	s := f.Value
   479  	if s == "" {
   480  		return 0, nil
   481  	}
   482  	d, err := time.ParseDuration(s)
   483  	if err != nil {
   484  		return 0, err
   485  	}
   486  	if d <= 0 {
   487  		return 0, fmt.Errorf("Interval %#v must be positive", f.name)
   488  	}
   489  	return d, nil
   490  }
   491  
   492  // HEALTHCHECK foo
   493  //
   494  // Set the default healthcheck command to run in the container (which may be empty).
   495  // Argument handling is the same as RUN.
   496  //
   497  func healthcheck(b *Builder, args []string, attributes map[string]bool, original string) error {
   498  	if len(args) == 0 {
   499  		return errAtLeastOneArgument("HEALTHCHECK")
   500  	}
   501  	typ := strings.ToUpper(args[0])
   502  	args = args[1:]
   503  	if typ == "NONE" {
   504  		if len(args) != 0 {
   505  			return errors.New("HEALTHCHECK NONE takes no arguments")
   506  		}
   507  		test := strslice.StrSlice{typ}
   508  		b.runConfig.Healthcheck = &container.HealthConfig{
   509  			Test: test,
   510  		}
   511  	} else {
   512  		if b.runConfig.Healthcheck != nil {
   513  			oldCmd := b.runConfig.Healthcheck.Test
   514  			if len(oldCmd) > 0 && oldCmd[0] != "NONE" {
   515  				fmt.Fprintf(b.Stdout, "Note: overriding previous HEALTHCHECK: %v\n", oldCmd)
   516  			}
   517  		}
   518  
   519  		healthcheck := container.HealthConfig{}
   520  
   521  		flInterval := b.flags.AddString("interval", "")
   522  		flTimeout := b.flags.AddString("timeout", "")
   523  		flRetries := b.flags.AddString("retries", "")
   524  
   525  		if err := b.flags.Parse(); err != nil {
   526  			return err
   527  		}
   528  
   529  		switch typ {
   530  		case "CMD":
   531  			cmdSlice := handleJSONArgs(args, attributes)
   532  			if len(cmdSlice) == 0 {
   533  				return errors.New("Missing command after HEALTHCHECK CMD")
   534  			}
   535  
   536  			if !attributes["json"] {
   537  				typ = "CMD-SHELL"
   538  			}
   539  
   540  			healthcheck.Test = strslice.StrSlice(append([]string{typ}, cmdSlice...))
   541  		default:
   542  			return fmt.Errorf("Unknown type %#v in HEALTHCHECK (try CMD)", typ)
   543  		}
   544  
   545  		interval, err := parseOptInterval(flInterval)
   546  		if err != nil {
   547  			return err
   548  		}
   549  		healthcheck.Interval = interval
   550  
   551  		timeout, err := parseOptInterval(flTimeout)
   552  		if err != nil {
   553  			return err
   554  		}
   555  		healthcheck.Timeout = timeout
   556  
   557  		if flRetries.Value != "" {
   558  			retries, err := strconv.ParseInt(flRetries.Value, 10, 32)
   559  			if err != nil {
   560  				return err
   561  			}
   562  			if retries < 1 {
   563  				return fmt.Errorf("--retries must be at least 1 (not %d)", retries)
   564  			}
   565  			healthcheck.Retries = int(retries)
   566  		} else {
   567  			healthcheck.Retries = 0
   568  		}
   569  
   570  		b.runConfig.Healthcheck = &healthcheck
   571  	}
   572  
   573  	return b.commit("", b.runConfig.Cmd, fmt.Sprintf("HEALTHCHECK %q", b.runConfig.Healthcheck))
   574  }
   575  
   576  // ENTRYPOINT /usr/sbin/nginx
   577  //
   578  // Set the entrypoint to /usr/sbin/nginx. Will accept the CMD as the arguments
   579  // to /usr/sbin/nginx. Uses the default shell if not in JSON format.
   580  //
   581  // Handles command processing similar to CMD and RUN, only b.runConfig.Entrypoint
   582  // is initialized at NewBuilder time instead of through argument parsing.
   583  //
   584  func entrypoint(b *Builder, args []string, attributes map[string]bool, original string) error {
   585  	if err := b.flags.Parse(); err != nil {
   586  		return err
   587  	}
   588  
   589  	parsed := handleJSONArgs(args, attributes)
   590  
   591  	switch {
   592  	case attributes["json"]:
   593  		// ENTRYPOINT ["echo", "hi"]
   594  		b.runConfig.Entrypoint = strslice.StrSlice(parsed)
   595  	case len(parsed) == 0:
   596  		// ENTRYPOINT []
   597  		b.runConfig.Entrypoint = nil
   598  	default:
   599  		// ENTRYPOINT echo hi
   600  		b.runConfig.Entrypoint = strslice.StrSlice(append(getShell(b.runConfig), parsed[0]))
   601  	}
   602  
   603  	// when setting the entrypoint if a CMD was not explicitly set then
   604  	// set the command to nil
   605  	if !b.cmdSet {
   606  		b.runConfig.Cmd = nil
   607  	}
   608  
   609  	if err := b.commit("", b.runConfig.Cmd, fmt.Sprintf("ENTRYPOINT %q", b.runConfig.Entrypoint)); err != nil {
   610  		return err
   611  	}
   612  
   613  	return nil
   614  }
   615  
   616  // EXPOSE 6667/tcp 7000/tcp
   617  //
   618  // Expose ports for links and port mappings. This all ends up in
   619  // b.runConfig.ExposedPorts for runconfig.
   620  //
   621  func expose(b *Builder, args []string, attributes map[string]bool, original string) error {
   622  	portsTab := args
   623  
   624  	if len(args) == 0 {
   625  		return errAtLeastOneArgument("EXPOSE")
   626  	}
   627  
   628  	if err := b.flags.Parse(); err != nil {
   629  		return err
   630  	}
   631  
   632  	if b.runConfig.ExposedPorts == nil {
   633  		b.runConfig.ExposedPorts = make(nat.PortSet)
   634  	}
   635  
   636  	ports, _, err := nat.ParsePortSpecs(portsTab)
   637  	if err != nil {
   638  		return err
   639  	}
   640  
   641  	// instead of using ports directly, we build a list of ports and sort it so
   642  	// the order is consistent. This prevents cache burst where map ordering
   643  	// changes between builds
   644  	portList := make([]string, len(ports))
   645  	var i int
   646  	for port := range ports {
   647  		if _, exists := b.runConfig.ExposedPorts[port]; !exists {
   648  			b.runConfig.ExposedPorts[port] = struct{}{}
   649  		}
   650  		portList[i] = string(port)
   651  		i++
   652  	}
   653  	sort.Strings(portList)
   654  	return b.commit("", b.runConfig.Cmd, fmt.Sprintf("EXPOSE %s", strings.Join(portList, " ")))
   655  }
   656  
   657  // USER foo
   658  //
   659  // Set the user to 'foo' for future commands and when running the
   660  // ENTRYPOINT/CMD at container run time.
   661  //
   662  func user(b *Builder, args []string, attributes map[string]bool, original string) error {
   663  	if len(args) != 1 {
   664  		return errExactlyOneArgument("USER")
   665  	}
   666  
   667  	if err := b.flags.Parse(); err != nil {
   668  		return err
   669  	}
   670  
   671  	b.runConfig.User = args[0]
   672  	return b.commit("", b.runConfig.Cmd, fmt.Sprintf("USER %v", args))
   673  }
   674  
   675  // VOLUME /foo
   676  //
   677  // Expose the volume /foo for use. Will also accept the JSON array form.
   678  //
   679  func volume(b *Builder, args []string, attributes map[string]bool, original string) error {
   680  	if len(args) == 0 {
   681  		return errAtLeastOneArgument("VOLUME")
   682  	}
   683  
   684  	if err := b.flags.Parse(); err != nil {
   685  		return err
   686  	}
   687  
   688  	if b.runConfig.Volumes == nil {
   689  		b.runConfig.Volumes = map[string]struct{}{}
   690  	}
   691  	for _, v := range args {
   692  		v = strings.TrimSpace(v)
   693  		if v == "" {
   694  			return errors.New("VOLUME specified can not be an empty string")
   695  		}
   696  		b.runConfig.Volumes[v] = struct{}{}
   697  	}
   698  	if err := b.commit("", b.runConfig.Cmd, fmt.Sprintf("VOLUME %v", args)); err != nil {
   699  		return err
   700  	}
   701  	return nil
   702  }
   703  
   704  // STOPSIGNAL signal
   705  //
   706  // Set the signal that will be used to kill the container.
   707  func stopSignal(b *Builder, args []string, attributes map[string]bool, original string) error {
   708  	if len(args) != 1 {
   709  		return errExactlyOneArgument("STOPSIGNAL")
   710  	}
   711  
   712  	sig := args[0]
   713  	_, err := signal.ParseSignal(sig)
   714  	if err != nil {
   715  		return err
   716  	}
   717  
   718  	b.runConfig.StopSignal = sig
   719  	return b.commit("", b.runConfig.Cmd, fmt.Sprintf("STOPSIGNAL %v", args))
   720  }
   721  
   722  // ARG name[=value]
   723  //
   724  // Adds the variable foo to the trusted list of variables that can be passed
   725  // to builder using the --build-arg flag for expansion/subsitution or passing to 'run'.
   726  // Dockerfile author may optionally set a default value of this variable.
   727  func arg(b *Builder, args []string, attributes map[string]bool, original string) error {
   728  	if len(args) != 1 {
   729  		return errExactlyOneArgument("ARG")
   730  	}
   731  
   732  	var (
   733  		name       string
   734  		newValue   string
   735  		hasDefault bool
   736  	)
   737  
   738  	arg := args[0]
   739  	// 'arg' can just be a name or name-value pair. Note that this is different
   740  	// from 'env' that handles the split of name and value at the parser level.
   741  	// The reason for doing it differently for 'arg' is that we support just
   742  	// defining an arg and not assign it a value (while 'env' always expects a
   743  	// name-value pair). If possible, it will be good to harmonize the two.
   744  	if strings.Contains(arg, "=") {
   745  		parts := strings.SplitN(arg, "=", 2)
   746  		if len(parts[0]) == 0 {
   747  			return errBlankCommandNames("ARG")
   748  		}
   749  
   750  		name = parts[0]
   751  		newValue = parts[1]
   752  		hasDefault = true
   753  	} else {
   754  		name = arg
   755  		hasDefault = false
   756  	}
   757  	// add the arg to allowed list of build-time args from this step on.
   758  	b.allowedBuildArgs[name] = true
   759  
   760  	// If there is a default value associated with this arg then add it to the
   761  	// b.buildArgs if one is not already passed to the builder. The args passed
   762  	// to builder override the default value of 'arg'. Note that a 'nil' for
   763  	// a value means that the user specified "--build-arg FOO" and "FOO" wasn't
   764  	// defined as an env var - and in that case we DO want to use the default
   765  	// value specified in the ARG cmd.
   766  	if baValue, ok := b.options.BuildArgs[name]; (!ok || baValue == nil) && hasDefault {
   767  		b.options.BuildArgs[name] = &newValue
   768  	}
   769  
   770  	return b.commit("", b.runConfig.Cmd, fmt.Sprintf("ARG %s", arg))
   771  }
   772  
   773  // SHELL powershell -command
   774  //
   775  // Set the non-default shell to use.
   776  func shell(b *Builder, args []string, attributes map[string]bool, original string) error {
   777  	if err := b.flags.Parse(); err != nil {
   778  		return err
   779  	}
   780  	shellSlice := handleJSONArgs(args, attributes)
   781  	switch {
   782  	case len(shellSlice) == 0:
   783  		// SHELL []
   784  		return errAtLeastOneArgument("SHELL")
   785  	case attributes["json"]:
   786  		// SHELL ["powershell", "-command"]
   787  		b.runConfig.Shell = strslice.StrSlice(shellSlice)
   788  	default:
   789  		// SHELL powershell -command - not JSON
   790  		return errNotJSON("SHELL", original)
   791  	}
   792  	return b.commit("", b.runConfig.Cmd, fmt.Sprintf("SHELL %v", shellSlice))
   793  }
   794  
   795  func errAtLeastOneArgument(command string) error {
   796  	return fmt.Errorf("%s requires at least one argument", command)
   797  }
   798  
   799  func errExactlyOneArgument(command string) error {
   800  	return fmt.Errorf("%s requires exactly one argument", command)
   801  }
   802  
   803  func errAtLeastTwoArguments(command string) error {
   804  	return fmt.Errorf("%s requires at least two arguments", command)
   805  }
   806  
   807  func errBlankCommandNames(command string) error {
   808  	return fmt.Errorf("%s names can not be blank", command)
   809  }
   810  
   811  func errTooManyArguments(command string) error {
   812  	return fmt.Errorf("Bad input to %s, too many arguments", command)
   813  }
   814  
   815  // getShell is a helper function which gets the right shell for prefixing the
   816  // shell-form of RUN, ENTRYPOINT and CMD instructions
   817  func getShell(c *container.Config) []string {
   818  	if 0 == len(c.Shell) {
   819  		return defaultShell[:]
   820  	}
   821  	return c.Shell[:]
   822  }