github.com/sijibomii/docker@v0.0.0-20231230191044-5cf6ca554647/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  	"fmt"
    12  	"os"
    13  	"path/filepath"
    14  	"regexp"
    15  	"runtime"
    16  	"sort"
    17  	"strings"
    18  
    19  	"github.com/Sirupsen/logrus"
    20  	"github.com/docker/docker/api"
    21  	"github.com/docker/docker/builder"
    22  	"github.com/docker/docker/pkg/signal"
    23  	"github.com/docker/docker/pkg/system"
    24  	runconfigopts "github.com/docker/docker/runconfig/opts"
    25  	"github.com/docker/engine-api/types/container"
    26  	"github.com/docker/engine-api/types/strslice"
    27  	"github.com/docker/go-connections/nat"
    28  )
    29  
    30  // ENV foo bar
    31  //
    32  // Sets the environment variable foo to bar, also makes interpolation
    33  // in the dockerfile available from the next statement on via ${foo}.
    34  //
    35  func env(b *Builder, args []string, attributes map[string]bool, original string) error {
    36  	if len(args) == 0 {
    37  		return errAtLeastOneArgument("ENV")
    38  	}
    39  
    40  	if len(args)%2 != 0 {
    41  		// should never get here, but just in case
    42  		return errTooManyArguments("ENV")
    43  	}
    44  
    45  	if err := b.flags.Parse(); err != nil {
    46  		return err
    47  	}
    48  
    49  	// TODO/FIXME/NOT USED
    50  	// Just here to show how to use the builder flags stuff within the
    51  	// context of a builder command. Will remove once we actually add
    52  	// a builder command to something!
    53  	/*
    54  		flBool1 := b.flags.AddBool("bool1", false)
    55  		flStr1 := b.flags.AddString("str1", "HI")
    56  
    57  		if err := b.flags.Parse(); err != nil {
    58  			return err
    59  		}
    60  
    61  		fmt.Printf("Bool1:%v\n", flBool1)
    62  		fmt.Printf("Str1:%v\n", flStr1)
    63  	*/
    64  
    65  	commitStr := "ENV"
    66  
    67  	for j := 0; j < len(args); j++ {
    68  		// name  ==> args[j]
    69  		// value ==> args[j+1]
    70  		newVar := args[j] + "=" + args[j+1] + ""
    71  		commitStr += " " + newVar
    72  
    73  		gotOne := false
    74  		for i, envVar := range b.runConfig.Env {
    75  			envParts := strings.SplitN(envVar, "=", 2)
    76  			if envParts[0] == args[j] {
    77  				b.runConfig.Env[i] = newVar
    78  				gotOne = true
    79  				break
    80  			}
    81  		}
    82  		if !gotOne {
    83  			b.runConfig.Env = append(b.runConfig.Env, newVar)
    84  		}
    85  		j++
    86  	}
    87  
    88  	return b.commit("", b.runConfig.Cmd, commitStr)
    89  }
    90  
    91  // MAINTAINER some text <maybe@an.email.address>
    92  //
    93  // Sets the maintainer metadata.
    94  func maintainer(b *Builder, args []string, attributes map[string]bool, original string) error {
    95  	if len(args) != 1 {
    96  		return errExactlyOneArgument("MAINTAINER")
    97  	}
    98  
    99  	if err := b.flags.Parse(); err != nil {
   100  		return err
   101  	}
   102  
   103  	b.maintainer = args[0]
   104  	return b.commit("", b.runConfig.Cmd, fmt.Sprintf("MAINTAINER %s", b.maintainer))
   105  }
   106  
   107  // LABEL some json data describing the image
   108  //
   109  // Sets the Label variable foo to bar,
   110  //
   111  func label(b *Builder, args []string, attributes map[string]bool, original string) error {
   112  	if len(args) == 0 {
   113  		return errAtLeastOneArgument("LABEL")
   114  	}
   115  	if len(args)%2 != 0 {
   116  		// should never get here, but just in case
   117  		return errTooManyArguments("LABEL")
   118  	}
   119  
   120  	if err := b.flags.Parse(); err != nil {
   121  		return err
   122  	}
   123  
   124  	commitStr := "LABEL"
   125  
   126  	if b.runConfig.Labels == nil {
   127  		b.runConfig.Labels = map[string]string{}
   128  	}
   129  
   130  	for j := 0; j < len(args); j++ {
   131  		// name  ==> args[j]
   132  		// value ==> args[j+1]
   133  		newVar := args[j] + "=" + args[j+1] + ""
   134  		commitStr += " " + newVar
   135  
   136  		b.runConfig.Labels[args[j]] = args[j+1]
   137  		j++
   138  	}
   139  	return b.commit("", b.runConfig.Cmd, commitStr)
   140  }
   141  
   142  // ADD foo /path
   143  //
   144  // Add the file 'foo' to '/path'. Tarball and Remote URL (git, http) handling
   145  // exist here. If you do not wish to have this automatic handling, use COPY.
   146  //
   147  func add(b *Builder, args []string, attributes map[string]bool, original string) error {
   148  	if len(args) < 2 {
   149  		return errAtLeastOneArgument("ADD")
   150  	}
   151  
   152  	if err := b.flags.Parse(); err != nil {
   153  		return err
   154  	}
   155  
   156  	return b.runContextCommand(args, true, true, "ADD")
   157  }
   158  
   159  // COPY foo /path
   160  //
   161  // Same as 'ADD' but without the tar and remote url handling.
   162  //
   163  func dispatchCopy(b *Builder, args []string, attributes map[string]bool, original string) error {
   164  	if len(args) < 2 {
   165  		return errAtLeastOneArgument("COPY")
   166  	}
   167  
   168  	if err := b.flags.Parse(); err != nil {
   169  		return err
   170  	}
   171  
   172  	return b.runContextCommand(args, false, false, "COPY")
   173  }
   174  
   175  // FROM imagename
   176  //
   177  // This sets the image the dockerfile will build on top of.
   178  //
   179  func from(b *Builder, args []string, attributes map[string]bool, original string) error {
   180  	if len(args) != 1 {
   181  		return errExactlyOneArgument("FROM")
   182  	}
   183  
   184  	if err := b.flags.Parse(); err != nil {
   185  		return err
   186  	}
   187  
   188  	name := args[0]
   189  
   190  	var (
   191  		image builder.Image
   192  		err   error
   193  	)
   194  
   195  	// Windows cannot support a container with no base image.
   196  	if name == api.NoBaseImageSpecifier {
   197  		if runtime.GOOS == "windows" {
   198  			return fmt.Errorf("Windows does not support FROM scratch")
   199  		}
   200  		b.image = ""
   201  		b.noBaseImage = true
   202  	} else {
   203  		// TODO: don't use `name`, instead resolve it to a digest
   204  		if !b.options.PullParent {
   205  			image, err = b.docker.GetImageOnBuild(name)
   206  			// TODO: shouldn't we error out if error is different from "not found" ?
   207  		}
   208  		if image == nil {
   209  			image, err = b.docker.PullOnBuild(b.clientCtx, name, b.options.AuthConfigs, b.Output)
   210  			if err != nil {
   211  				return err
   212  			}
   213  		}
   214  	}
   215  
   216  	return b.processImageFrom(image)
   217  }
   218  
   219  // ONBUILD RUN echo yo
   220  //
   221  // ONBUILD triggers run when the image is used in a FROM statement.
   222  //
   223  // ONBUILD handling has a lot of special-case functionality, the heading in
   224  // evaluator.go and comments around dispatch() in the same file explain the
   225  // special cases. search for 'OnBuild' in internals.go for additional special
   226  // cases.
   227  //
   228  func onbuild(b *Builder, args []string, attributes map[string]bool, original string) error {
   229  	if len(args) == 0 {
   230  		return errAtLeastOneArgument("ONBUILD")
   231  	}
   232  
   233  	if err := b.flags.Parse(); err != nil {
   234  		return err
   235  	}
   236  
   237  	triggerInstruction := strings.ToUpper(strings.TrimSpace(args[0]))
   238  	switch triggerInstruction {
   239  	case "ONBUILD":
   240  		return fmt.Errorf("Chaining ONBUILD via `ONBUILD ONBUILD` isn't allowed")
   241  	case "MAINTAINER", "FROM":
   242  		return fmt.Errorf("%s isn't allowed as an ONBUILD trigger", triggerInstruction)
   243  	}
   244  
   245  	original = regexp.MustCompile(`(?i)^\s*ONBUILD\s*`).ReplaceAllString(original, "")
   246  
   247  	b.runConfig.OnBuild = append(b.runConfig.OnBuild, original)
   248  	return b.commit("", b.runConfig.Cmd, fmt.Sprintf("ONBUILD %s", original))
   249  }
   250  
   251  // WORKDIR /tmp
   252  //
   253  // Set the working directory for future RUN/CMD/etc statements.
   254  //
   255  func workdir(b *Builder, args []string, attributes map[string]bool, original string) error {
   256  	if len(args) != 1 {
   257  		return errExactlyOneArgument("WORKDIR")
   258  	}
   259  
   260  	if err := b.flags.Parse(); err != nil {
   261  		return err
   262  	}
   263  
   264  	// This is from the Dockerfile and will not necessarily be in platform
   265  	// specific semantics, hence ensure it is converted.
   266  	workdir := filepath.FromSlash(args[0])
   267  
   268  	if !system.IsAbs(workdir) {
   269  		current := filepath.FromSlash(b.runConfig.WorkingDir)
   270  		workdir = filepath.Join(string(os.PathSeparator), current, workdir)
   271  	}
   272  
   273  	b.runConfig.WorkingDir = workdir
   274  
   275  	return b.commit("", b.runConfig.Cmd, fmt.Sprintf("WORKDIR %v", workdir))
   276  }
   277  
   278  // RUN some command yo
   279  //
   280  // run a command and commit the image. Args are automatically prepended with
   281  // 'sh -c' under linux or 'cmd /S /C' under Windows, in the event there is
   282  // only one argument. The difference in processing:
   283  //
   284  // RUN echo hi          # sh -c echo hi       (Linux)
   285  // RUN echo hi          # cmd /S /C echo hi   (Windows)
   286  // RUN [ "echo", "hi" ] # echo hi
   287  //
   288  func run(b *Builder, args []string, attributes map[string]bool, original string) error {
   289  	if b.image == "" && !b.noBaseImage {
   290  		return fmt.Errorf("Please provide a source image with `from` prior to run")
   291  	}
   292  
   293  	if err := b.flags.Parse(); err != nil {
   294  		return err
   295  	}
   296  
   297  	args = handleJSONArgs(args, attributes)
   298  
   299  	if !attributes["json"] {
   300  		if runtime.GOOS != "windows" {
   301  			args = append([]string{"/bin/sh", "-c"}, args...)
   302  		} else {
   303  			args = append([]string{"cmd", "/S", "/C"}, args...)
   304  		}
   305  	}
   306  
   307  	config := &container.Config{
   308  		Cmd:   strslice.StrSlice(args),
   309  		Image: b.image,
   310  	}
   311  
   312  	// stash the cmd
   313  	cmd := b.runConfig.Cmd
   314  	if len(b.runConfig.Entrypoint) == 0 && len(b.runConfig.Cmd) == 0 {
   315  		b.runConfig.Cmd = config.Cmd
   316  	}
   317  
   318  	// stash the config environment
   319  	env := b.runConfig.Env
   320  
   321  	defer func(cmd strslice.StrSlice) { b.runConfig.Cmd = cmd }(cmd)
   322  	defer func(env []string) { b.runConfig.Env = env }(env)
   323  
   324  	// derive the net build-time environment for this run. We let config
   325  	// environment override the build time environment.
   326  	// This means that we take the b.buildArgs list of env vars and remove
   327  	// any of those variables that are defined as part of the container. In other
   328  	// words, anything in b.Config.Env. What's left is the list of build-time env
   329  	// vars that we need to add to each RUN command - note the list could be empty.
   330  	//
   331  	// We don't persist the build time environment with container's config
   332  	// environment, but just sort and prepend it to the command string at time
   333  	// of commit.
   334  	// This helps with tracing back the image's actual environment at the time
   335  	// of RUN, without leaking it to the final image. It also aids cache
   336  	// lookup for same image built with same build time environment.
   337  	cmdBuildEnv := []string{}
   338  	configEnv := runconfigopts.ConvertKVStringsToMap(b.runConfig.Env)
   339  	for key, val := range b.options.BuildArgs {
   340  		if !b.isBuildArgAllowed(key) {
   341  			// skip build-args that are not in allowed list, meaning they have
   342  			// not been defined by an "ARG" Dockerfile command yet.
   343  			// This is an error condition but only if there is no "ARG" in the entire
   344  			// Dockerfile, so we'll generate any necessary errors after we parsed
   345  			// the entire file (see 'leftoverArgs' processing in evaluator.go )
   346  			continue
   347  		}
   348  		if _, ok := configEnv[key]; !ok {
   349  			cmdBuildEnv = append(cmdBuildEnv, fmt.Sprintf("%s=%s", key, val))
   350  		}
   351  	}
   352  
   353  	// derive the command to use for probeCache() and to commit in this container.
   354  	// Note that we only do this if there are any build-time env vars.  Also, we
   355  	// use the special argument "|#" at the start of the args array. This will
   356  	// avoid conflicts with any RUN command since commands can not
   357  	// start with | (vertical bar). The "#" (number of build envs) is there to
   358  	// help ensure proper cache matches. We don't want a RUN command
   359  	// that starts with "foo=abc" to be considered part of a build-time env var.
   360  	saveCmd := config.Cmd
   361  	if len(cmdBuildEnv) > 0 {
   362  		sort.Strings(cmdBuildEnv)
   363  		tmpEnv := append([]string{fmt.Sprintf("|%d", len(cmdBuildEnv))}, cmdBuildEnv...)
   364  		saveCmd = strslice.StrSlice(append(tmpEnv, saveCmd...))
   365  	}
   366  
   367  	b.runConfig.Cmd = saveCmd
   368  	hit, err := b.probeCache()
   369  	if err != nil {
   370  		return err
   371  	}
   372  	if hit {
   373  		return nil
   374  	}
   375  
   376  	// set Cmd manually, this is special case only for Dockerfiles
   377  	b.runConfig.Cmd = config.Cmd
   378  	// set build-time environment for 'run'.
   379  	b.runConfig.Env = append(b.runConfig.Env, cmdBuildEnv...)
   380  	// set config as already being escaped, this prevents double escaping on windows
   381  	b.runConfig.ArgsEscaped = true
   382  
   383  	logrus.Debugf("[BUILDER] Command to be executed: %v", b.runConfig.Cmd)
   384  
   385  	cID, err := b.create()
   386  	if err != nil {
   387  		return err
   388  	}
   389  
   390  	if err := b.run(cID); err != nil {
   391  		return err
   392  	}
   393  
   394  	// revert to original config environment and set the command string to
   395  	// have the build-time env vars in it (if any) so that future cache look-ups
   396  	// properly match it.
   397  	b.runConfig.Env = env
   398  	b.runConfig.Cmd = saveCmd
   399  	return b.commit(cID, cmd, "run")
   400  }
   401  
   402  // CMD foo
   403  //
   404  // Set the default command to run in the container (which may be empty).
   405  // Argument handling is the same as RUN.
   406  //
   407  func cmd(b *Builder, args []string, attributes map[string]bool, original string) error {
   408  	if err := b.flags.Parse(); err != nil {
   409  		return err
   410  	}
   411  
   412  	cmdSlice := handleJSONArgs(args, attributes)
   413  
   414  	if !attributes["json"] {
   415  		if runtime.GOOS != "windows" {
   416  			cmdSlice = append([]string{"/bin/sh", "-c"}, cmdSlice...)
   417  		} else {
   418  			cmdSlice = append([]string{"cmd", "/S", "/C"}, cmdSlice...)
   419  		}
   420  	}
   421  
   422  	b.runConfig.Cmd = strslice.StrSlice(cmdSlice)
   423  
   424  	if err := b.commit("", b.runConfig.Cmd, fmt.Sprintf("CMD %q", cmdSlice)); err != nil {
   425  		return err
   426  	}
   427  
   428  	if len(args) != 0 {
   429  		b.cmdSet = true
   430  	}
   431  
   432  	return nil
   433  }
   434  
   435  // ENTRYPOINT /usr/sbin/nginx
   436  //
   437  // Set the entrypoint (which defaults to sh -c on linux, or cmd /S /C on Windows) to
   438  // /usr/sbin/nginx. Will accept the CMD as the arguments to /usr/sbin/nginx.
   439  //
   440  // Handles command processing similar to CMD and RUN, only b.runConfig.Entrypoint
   441  // is initialized at NewBuilder time instead of through argument parsing.
   442  //
   443  func entrypoint(b *Builder, args []string, attributes map[string]bool, original string) error {
   444  	if err := b.flags.Parse(); err != nil {
   445  		return err
   446  	}
   447  
   448  	parsed := handleJSONArgs(args, attributes)
   449  
   450  	switch {
   451  	case attributes["json"]:
   452  		// ENTRYPOINT ["echo", "hi"]
   453  		b.runConfig.Entrypoint = strslice.StrSlice(parsed)
   454  	case len(parsed) == 0:
   455  		// ENTRYPOINT []
   456  		b.runConfig.Entrypoint = nil
   457  	default:
   458  		// ENTRYPOINT echo hi
   459  		if runtime.GOOS != "windows" {
   460  			b.runConfig.Entrypoint = strslice.StrSlice{"/bin/sh", "-c", parsed[0]}
   461  		} else {
   462  			b.runConfig.Entrypoint = strslice.StrSlice{"cmd", "/S", "/C", parsed[0]}
   463  		}
   464  	}
   465  
   466  	// when setting the entrypoint if a CMD was not explicitly set then
   467  	// set the command to nil
   468  	if !b.cmdSet {
   469  		b.runConfig.Cmd = nil
   470  	}
   471  
   472  	if err := b.commit("", b.runConfig.Cmd, fmt.Sprintf("ENTRYPOINT %q", b.runConfig.Entrypoint)); err != nil {
   473  		return err
   474  	}
   475  
   476  	return nil
   477  }
   478  
   479  // EXPOSE 6667/tcp 7000/tcp
   480  //
   481  // Expose ports for links and port mappings. This all ends up in
   482  // b.runConfig.ExposedPorts for runconfig.
   483  //
   484  func expose(b *Builder, args []string, attributes map[string]bool, original string) error {
   485  	portsTab := args
   486  
   487  	if len(args) == 0 {
   488  		return errAtLeastOneArgument("EXPOSE")
   489  	}
   490  
   491  	if err := b.flags.Parse(); err != nil {
   492  		return err
   493  	}
   494  
   495  	if b.runConfig.ExposedPorts == nil {
   496  		b.runConfig.ExposedPorts = make(nat.PortSet)
   497  	}
   498  
   499  	ports, _, err := nat.ParsePortSpecs(portsTab)
   500  	if err != nil {
   501  		return err
   502  	}
   503  
   504  	// instead of using ports directly, we build a list of ports and sort it so
   505  	// the order is consistent. This prevents cache burst where map ordering
   506  	// changes between builds
   507  	portList := make([]string, len(ports))
   508  	var i int
   509  	for port := range ports {
   510  		if _, exists := b.runConfig.ExposedPorts[port]; !exists {
   511  			b.runConfig.ExposedPorts[port] = struct{}{}
   512  		}
   513  		portList[i] = string(port)
   514  		i++
   515  	}
   516  	sort.Strings(portList)
   517  	return b.commit("", b.runConfig.Cmd, fmt.Sprintf("EXPOSE %s", strings.Join(portList, " ")))
   518  }
   519  
   520  // USER foo
   521  //
   522  // Set the user to 'foo' for future commands and when running the
   523  // ENTRYPOINT/CMD at container run time.
   524  //
   525  func user(b *Builder, args []string, attributes map[string]bool, original string) error {
   526  	if len(args) != 1 {
   527  		return errExactlyOneArgument("USER")
   528  	}
   529  
   530  	if err := b.flags.Parse(); err != nil {
   531  		return err
   532  	}
   533  
   534  	b.runConfig.User = args[0]
   535  	return b.commit("", b.runConfig.Cmd, fmt.Sprintf("USER %v", args))
   536  }
   537  
   538  // VOLUME /foo
   539  //
   540  // Expose the volume /foo for use. Will also accept the JSON array form.
   541  //
   542  func volume(b *Builder, args []string, attributes map[string]bool, original string) error {
   543  	if len(args) == 0 {
   544  		return errAtLeastOneArgument("VOLUME")
   545  	}
   546  
   547  	if err := b.flags.Parse(); err != nil {
   548  		return err
   549  	}
   550  
   551  	if b.runConfig.Volumes == nil {
   552  		b.runConfig.Volumes = map[string]struct{}{}
   553  	}
   554  	for _, v := range args {
   555  		v = strings.TrimSpace(v)
   556  		if v == "" {
   557  			return fmt.Errorf("Volume specified can not be an empty string")
   558  		}
   559  		b.runConfig.Volumes[v] = struct{}{}
   560  	}
   561  	if err := b.commit("", b.runConfig.Cmd, fmt.Sprintf("VOLUME %v", args)); err != nil {
   562  		return err
   563  	}
   564  	return nil
   565  }
   566  
   567  // STOPSIGNAL signal
   568  //
   569  // Set the signal that will be used to kill the container.
   570  func stopSignal(b *Builder, args []string, attributes map[string]bool, original string) error {
   571  	if len(args) != 1 {
   572  		return fmt.Errorf("STOPSIGNAL requires exactly one argument")
   573  	}
   574  
   575  	sig := args[0]
   576  	_, err := signal.ParseSignal(sig)
   577  	if err != nil {
   578  		return err
   579  	}
   580  
   581  	b.runConfig.StopSignal = sig
   582  	return b.commit("", b.runConfig.Cmd, fmt.Sprintf("STOPSIGNAL %v", args))
   583  }
   584  
   585  // ARG name[=value]
   586  //
   587  // Adds the variable foo to the trusted list of variables that can be passed
   588  // to builder using the --build-arg flag for expansion/subsitution or passing to 'run'.
   589  // Dockerfile author may optionally set a default value of this variable.
   590  func arg(b *Builder, args []string, attributes map[string]bool, original string) error {
   591  	if len(args) != 1 {
   592  		return fmt.Errorf("ARG requires exactly one argument definition")
   593  	}
   594  
   595  	var (
   596  		name       string
   597  		value      string
   598  		hasDefault bool
   599  	)
   600  
   601  	arg := args[0]
   602  	// 'arg' can just be a name or name-value pair. Note that this is different
   603  	// from 'env' that handles the split of name and value at the parser level.
   604  	// The reason for doing it differently for 'arg' is that we support just
   605  	// defining an arg and not assign it a value (while 'env' always expects a
   606  	// name-value pair). If possible, it will be good to harmonize the two.
   607  	if strings.Contains(arg, "=") {
   608  		parts := strings.SplitN(arg, "=", 2)
   609  		name = parts[0]
   610  		value = parts[1]
   611  		hasDefault = true
   612  	} else {
   613  		name = arg
   614  		hasDefault = false
   615  	}
   616  	// add the arg to allowed list of build-time args from this step on.
   617  	b.allowedBuildArgs[name] = true
   618  
   619  	// If there is a default value associated with this arg then add it to the
   620  	// b.buildArgs if one is not already passed to the builder. The args passed
   621  	// to builder override the default value of 'arg'.
   622  	if _, ok := b.options.BuildArgs[name]; !ok && hasDefault {
   623  		b.options.BuildArgs[name] = value
   624  	}
   625  
   626  	return b.commit("", b.runConfig.Cmd, fmt.Sprintf("ARG %s", arg))
   627  }
   628  
   629  func errAtLeastOneArgument(command string) error {
   630  	return fmt.Errorf("%s requires at least one argument", command)
   631  }
   632  
   633  func errExactlyOneArgument(command string) error {
   634  	return fmt.Errorf("%s requires exactly one argument", command)
   635  }
   636  
   637  func errTooManyArguments(command string) error {
   638  	return fmt.Errorf("Bad input to %s, too many arguments", command)
   639  }