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