github.com/webwurst/docker@v1.7.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  	"runtime"
    16  	"sort"
    17  	"strings"
    18  
    19  	"github.com/Sirupsen/logrus"
    20  	"github.com/docker/docker/nat"
    21  	flag "github.com/docker/docker/pkg/mflag"
    22  	"github.com/docker/docker/runconfig"
    23  )
    24  
    25  const (
    26  	// NoBaseImageSpecifier is the symbol used by the FROM
    27  	// command to specify that no base image is to be used.
    28  	NoBaseImageSpecifier string = "scratch"
    29  )
    30  
    31  // dispatch with no layer / parsing. This is effectively not a command.
    32  func nullDispatch(b *Builder, args []string, attributes map[string]bool, original string) error {
    33  	return nil
    34  }
    35  
    36  // ENV foo bar
    37  //
    38  // Sets the environment variable foo to bar, also makes interpolation
    39  // in the dockerfile available from the next statement on via ${foo}.
    40  //
    41  func env(b *Builder, args []string, attributes map[string]bool, original string) error {
    42  	if runtime.GOOS == "windows" {
    43  		return fmt.Errorf("ENV is not supported on Windows.")
    44  	}
    45  	if len(args) == 0 {
    46  		return fmt.Errorf("ENV requires at least one argument")
    47  	}
    48  
    49  	if len(args)%2 != 0 {
    50  		// should never get here, but just in case
    51  		return fmt.Errorf("Bad input to ENV, too many args")
    52  	}
    53  
    54  	if err := b.BuilderFlags.Parse(); err != nil {
    55  		return err
    56  	}
    57  
    58  	// TODO/FIXME/NOT USED
    59  	// Just here to show how to use the builder flags stuff within the
    60  	// context of a builder command. Will remove once we actually add
    61  	// a builder command to something!
    62  	/*
    63  		flBool1 := b.BuilderFlags.AddBool("bool1", false)
    64  		flStr1 := b.BuilderFlags.AddString("str1", "HI")
    65  
    66  		if err := b.BuilderFlags.Parse(); err != nil {
    67  			return err
    68  		}
    69  
    70  		fmt.Printf("Bool1:%v\n", flBool1)
    71  		fmt.Printf("Str1:%v\n", flStr1)
    72  	*/
    73  
    74  	commitStr := "ENV"
    75  
    76  	for j := 0; j < len(args); j++ {
    77  		// name  ==> args[j]
    78  		// value ==> args[j+1]
    79  		newVar := args[j] + "=" + args[j+1] + ""
    80  		commitStr += " " + newVar
    81  
    82  		gotOne := false
    83  		for i, envVar := range b.Config.Env {
    84  			envParts := strings.SplitN(envVar, "=", 2)
    85  			if envParts[0] == args[j] {
    86  				b.Config.Env[i] = newVar
    87  				gotOne = true
    88  				break
    89  			}
    90  		}
    91  		if !gotOne {
    92  			b.Config.Env = append(b.Config.Env, newVar)
    93  		}
    94  		j++
    95  	}
    96  
    97  	return b.commit("", b.Config.Cmd, commitStr)
    98  }
    99  
   100  // MAINTAINER some text <maybe@an.email.address>
   101  //
   102  // Sets the maintainer metadata.
   103  func maintainer(b *Builder, args []string, attributes map[string]bool, original string) error {
   104  	if len(args) != 1 {
   105  		return fmt.Errorf("MAINTAINER requires exactly one argument")
   106  	}
   107  
   108  	if err := b.BuilderFlags.Parse(); err != nil {
   109  		return err
   110  	}
   111  
   112  	b.maintainer = args[0]
   113  	return b.commit("", b.Config.Cmd, fmt.Sprintf("MAINTAINER %s", b.maintainer))
   114  }
   115  
   116  // LABEL some json data describing the image
   117  //
   118  // Sets the Label variable foo to bar,
   119  //
   120  func label(b *Builder, args []string, attributes map[string]bool, original string) error {
   121  	if len(args) == 0 {
   122  		return fmt.Errorf("LABEL requires at least one argument")
   123  	}
   124  	if len(args)%2 != 0 {
   125  		// should never get here, but just in case
   126  		return fmt.Errorf("Bad input to LABEL, too many args")
   127  	}
   128  
   129  	if err := b.BuilderFlags.Parse(); err != nil {
   130  		return err
   131  	}
   132  
   133  	commitStr := "LABEL"
   134  
   135  	if b.Config.Labels == nil {
   136  		b.Config.Labels = map[string]string{}
   137  	}
   138  
   139  	for j := 0; j < len(args); j++ {
   140  		// name  ==> args[j]
   141  		// value ==> args[j+1]
   142  		newVar := args[j] + "=" + args[j+1] + ""
   143  		commitStr += " " + newVar
   144  
   145  		b.Config.Labels[args[j]] = args[j+1]
   146  		j++
   147  	}
   148  	return b.commit("", b.Config.Cmd, commitStr)
   149  }
   150  
   151  // ADD foo /path
   152  //
   153  // Add the file 'foo' to '/path'. Tarball and Remote URL (git, http) handling
   154  // exist here. If you do not wish to have this automatic handling, use COPY.
   155  //
   156  func add(b *Builder, args []string, attributes map[string]bool, original string) error {
   157  	if len(args) < 2 {
   158  		return fmt.Errorf("ADD requires at least two arguments")
   159  	}
   160  
   161  	if err := b.BuilderFlags.Parse(); err != nil {
   162  		return err
   163  	}
   164  
   165  	return b.runContextCommand(args, true, true, "ADD")
   166  }
   167  
   168  // COPY foo /path
   169  //
   170  // Same as 'ADD' but without the tar and remote url handling.
   171  //
   172  func dispatchCopy(b *Builder, args []string, attributes map[string]bool, original string) error {
   173  	if len(args) < 2 {
   174  		return fmt.Errorf("COPY requires at least two arguments")
   175  	}
   176  
   177  	if err := b.BuilderFlags.Parse(); err != nil {
   178  		return err
   179  	}
   180  
   181  	return b.runContextCommand(args, false, false, "COPY")
   182  }
   183  
   184  // FROM imagename
   185  //
   186  // This sets the image the dockerfile will build on top of.
   187  //
   188  func from(b *Builder, args []string, attributes map[string]bool, original string) error {
   189  	if len(args) != 1 {
   190  		return fmt.Errorf("FROM requires one argument")
   191  	}
   192  
   193  	if err := b.BuilderFlags.Parse(); err != nil {
   194  		return err
   195  	}
   196  
   197  	name := args[0]
   198  
   199  	if name == NoBaseImageSpecifier {
   200  		b.image = ""
   201  		b.noBaseImage = true
   202  		return nil
   203  	}
   204  
   205  	image, err := b.Daemon.Repositories().LookupImage(name)
   206  	if b.Pull {
   207  		image, err = b.pullImage(name)
   208  		if err != nil {
   209  			return err
   210  		}
   211  	}
   212  	if err != nil {
   213  		if b.Daemon.Graph().IsNotExist(err, name) {
   214  			image, err = b.pullImage(name)
   215  		}
   216  
   217  		// note that the top level err will still be !nil here if IsNotExist is
   218  		// not the error. This approach just simplifies the logic a bit.
   219  		if err != nil {
   220  			return err
   221  		}
   222  	}
   223  
   224  	return b.processImageFrom(image)
   225  }
   226  
   227  // ONBUILD RUN echo yo
   228  //
   229  // ONBUILD triggers run when the image is used in a FROM statement.
   230  //
   231  // ONBUILD handling has a lot of special-case functionality, the heading in
   232  // evaluator.go and comments around dispatch() in the same file explain the
   233  // special cases. search for 'OnBuild' in internals.go for additional special
   234  // cases.
   235  //
   236  func onbuild(b *Builder, args []string, attributes map[string]bool, original string) error {
   237  	if len(args) == 0 {
   238  		return fmt.Errorf("ONBUILD requires at least one argument")
   239  	}
   240  
   241  	if err := b.BuilderFlags.Parse(); err != nil {
   242  		return err
   243  	}
   244  
   245  	triggerInstruction := strings.ToUpper(strings.TrimSpace(args[0]))
   246  	switch triggerInstruction {
   247  	case "ONBUILD":
   248  		return fmt.Errorf("Chaining ONBUILD via `ONBUILD ONBUILD` isn't allowed")
   249  	case "MAINTAINER", "FROM":
   250  		return fmt.Errorf("%s isn't allowed as an ONBUILD trigger", triggerInstruction)
   251  	}
   252  
   253  	original = regexp.MustCompile(`(?i)^\s*ONBUILD\s*`).ReplaceAllString(original, "")
   254  
   255  	b.Config.OnBuild = append(b.Config.OnBuild, original)
   256  	return b.commit("", b.Config.Cmd, fmt.Sprintf("ONBUILD %s", original))
   257  }
   258  
   259  // WORKDIR /tmp
   260  //
   261  // Set the working directory for future RUN/CMD/etc statements.
   262  //
   263  func workdir(b *Builder, args []string, attributes map[string]bool, original string) error {
   264  	if len(args) != 1 {
   265  		return fmt.Errorf("WORKDIR requires exactly one argument")
   266  	}
   267  
   268  	if err := b.BuilderFlags.Parse(); err != nil {
   269  		return err
   270  	}
   271  
   272  	workdir := args[0]
   273  
   274  	if !filepath.IsAbs(workdir) {
   275  		workdir = filepath.Join("/", b.Config.WorkingDir, workdir)
   276  	}
   277  
   278  	b.Config.WorkingDir = workdir
   279  
   280  	return b.commit("", b.Config.Cmd, fmt.Sprintf("WORKDIR %v", workdir))
   281  }
   282  
   283  // RUN some command yo
   284  //
   285  // run a command and commit the image. Args are automatically prepended with
   286  // 'sh -c' under linux or 'cmd /S /C' under Windows, in the event there is
   287  // only one argument. The difference in processing:
   288  //
   289  // RUN echo hi          # sh -c echo hi       (Linux)
   290  // RUN echo hi          # cmd /S /C echo hi   (Windows)
   291  // RUN [ "echo", "hi" ] # echo hi
   292  //
   293  func run(b *Builder, args []string, attributes map[string]bool, original string) error {
   294  	if b.image == "" && !b.noBaseImage {
   295  		return fmt.Errorf("Please provide a source image with `from` prior to run")
   296  	}
   297  
   298  	if err := b.BuilderFlags.Parse(); err != nil {
   299  		return err
   300  	}
   301  
   302  	args = handleJsonArgs(args, attributes)
   303  
   304  	if !attributes["json"] {
   305  		if runtime.GOOS != "windows" {
   306  			args = append([]string{"/bin/sh", "-c"}, args...)
   307  		} else {
   308  			args = append([]string{"cmd", "/S /C"}, args...)
   309  		}
   310  	}
   311  
   312  	runCmd := flag.NewFlagSet("run", flag.ContinueOnError)
   313  	runCmd.SetOutput(ioutil.Discard)
   314  	runCmd.Usage = nil
   315  
   316  	config, _, _, err := runconfig.Parse(runCmd, append([]string{b.image}, args...))
   317  	if err != nil {
   318  		return err
   319  	}
   320  
   321  	cmd := b.Config.Cmd
   322  	// set Cmd manually, this is special case only for Dockerfiles
   323  	b.Config.Cmd = config.Cmd
   324  	runconfig.Merge(b.Config, config)
   325  
   326  	defer func(cmd *runconfig.Command) { b.Config.Cmd = cmd }(cmd)
   327  
   328  	logrus.Debugf("[BUILDER] Command to be executed: %v", b.Config.Cmd)
   329  
   330  	hit, err := b.probeCache()
   331  	if err != nil {
   332  		return err
   333  	}
   334  	if hit {
   335  		return nil
   336  	}
   337  
   338  	c, err := b.create()
   339  	if err != nil {
   340  		return err
   341  	}
   342  
   343  	// Ensure that we keep the container mounted until the commit
   344  	// to avoid unmounting and then mounting directly again
   345  	c.Mount()
   346  	defer c.Unmount()
   347  
   348  	err = b.run(c)
   349  	if err != nil {
   350  		return err
   351  	}
   352  	if err := b.commit(c.ID, cmd, "run"); err != nil {
   353  		return err
   354  	}
   355  
   356  	return nil
   357  }
   358  
   359  // CMD foo
   360  //
   361  // Set the default command to run in the container (which may be empty).
   362  // Argument handling is the same as RUN.
   363  //
   364  func cmd(b *Builder, args []string, attributes map[string]bool, original string) error {
   365  	if err := b.BuilderFlags.Parse(); err != nil {
   366  		return err
   367  	}
   368  
   369  	cmdSlice := handleJsonArgs(args, attributes)
   370  
   371  	if !attributes["json"] {
   372  		if runtime.GOOS != "windows" {
   373  			cmdSlice = append([]string{"/bin/sh", "-c"}, cmdSlice...)
   374  		} else {
   375  			cmdSlice = append([]string{"cmd", "/S /C"}, cmdSlice...)
   376  		}
   377  	}
   378  
   379  	b.Config.Cmd = runconfig.NewCommand(cmdSlice...)
   380  
   381  	if err := b.commit("", b.Config.Cmd, fmt.Sprintf("CMD %q", cmdSlice)); err != nil {
   382  		return err
   383  	}
   384  
   385  	if len(args) != 0 {
   386  		b.cmdSet = true
   387  	}
   388  
   389  	return nil
   390  }
   391  
   392  // ENTRYPOINT /usr/sbin/nginx
   393  //
   394  // Set the entrypoint (which defaults to sh -c on linux, or cmd /S /C on Windows) to
   395  // /usr/sbin/nginx. Will accept the CMD as the arguments to /usr/sbin/nginx.
   396  //
   397  // Handles command processing similar to CMD and RUN, only b.Config.Entrypoint
   398  // is initialized at NewBuilder time instead of through argument parsing.
   399  //
   400  func entrypoint(b *Builder, args []string, attributes map[string]bool, original string) error {
   401  	if err := b.BuilderFlags.Parse(); err != nil {
   402  		return err
   403  	}
   404  
   405  	parsed := handleJsonArgs(args, attributes)
   406  
   407  	switch {
   408  	case attributes["json"]:
   409  		// ENTRYPOINT ["echo", "hi"]
   410  		b.Config.Entrypoint = runconfig.NewEntrypoint(parsed...)
   411  	case len(parsed) == 0:
   412  		// ENTRYPOINT []
   413  		b.Config.Entrypoint = nil
   414  	default:
   415  		// ENTRYPOINT echo hi
   416  		if runtime.GOOS != "windows" {
   417  			b.Config.Entrypoint = runconfig.NewEntrypoint("/bin/sh", "-c", parsed[0])
   418  		} else {
   419  			b.Config.Entrypoint = runconfig.NewEntrypoint("cmd", "/S /C", parsed[0])
   420  		}
   421  	}
   422  
   423  	// when setting the entrypoint if a CMD was not explicitly set then
   424  	// set the command to nil
   425  	if !b.cmdSet {
   426  		b.Config.Cmd = nil
   427  	}
   428  
   429  	if err := b.commit("", b.Config.Cmd, fmt.Sprintf("ENTRYPOINT %q", b.Config.Entrypoint)); err != nil {
   430  		return err
   431  	}
   432  
   433  	return nil
   434  }
   435  
   436  // EXPOSE 6667/tcp 7000/tcp
   437  //
   438  // Expose ports for links and port mappings. This all ends up in
   439  // b.Config.ExposedPorts for runconfig.
   440  //
   441  func expose(b *Builder, args []string, attributes map[string]bool, original string) error {
   442  	portsTab := args
   443  
   444  	if len(args) == 0 {
   445  		return fmt.Errorf("EXPOSE requires at least one argument")
   446  	}
   447  
   448  	if err := b.BuilderFlags.Parse(); err != nil {
   449  		return err
   450  	}
   451  
   452  	if b.Config.ExposedPorts == nil {
   453  		b.Config.ExposedPorts = make(nat.PortSet)
   454  	}
   455  
   456  	ports, bindingMap, err := nat.ParsePortSpecs(append(portsTab, b.Config.PortSpecs...))
   457  	if err != nil {
   458  		return err
   459  	}
   460  
   461  	for _, bindings := range bindingMap {
   462  		if bindings[0].HostIp != "" || bindings[0].HostPort != "" {
   463  			fmt.Fprintf(b.ErrStream, " ---> Using Dockerfile's EXPOSE instruction"+
   464  				"      to map host ports to container ports (ip:hostPort:containerPort) is deprecated.\n"+
   465  				"      Please use -p to publish the ports.\n")
   466  		}
   467  	}
   468  
   469  	// instead of using ports directly, we build a list of ports and sort it so
   470  	// the order is consistent. This prevents cache burst where map ordering
   471  	// changes between builds
   472  	portList := make([]string, len(ports))
   473  	var i int
   474  	for port := range ports {
   475  		if _, exists := b.Config.ExposedPorts[port]; !exists {
   476  			b.Config.ExposedPorts[port] = struct{}{}
   477  		}
   478  		portList[i] = string(port)
   479  		i++
   480  	}
   481  	sort.Strings(portList)
   482  	b.Config.PortSpecs = nil
   483  	return b.commit("", b.Config.Cmd, fmt.Sprintf("EXPOSE %s", strings.Join(portList, " ")))
   484  }
   485  
   486  // USER foo
   487  //
   488  // Set the user to 'foo' for future commands and when running the
   489  // ENTRYPOINT/CMD at container run time.
   490  //
   491  func user(b *Builder, args []string, attributes map[string]bool, original string) error {
   492  	if runtime.GOOS == "windows" {
   493  		return fmt.Errorf("USER is not supported on Windows.")
   494  	}
   495  
   496  	if len(args) != 1 {
   497  		return fmt.Errorf("USER requires exactly one argument")
   498  	}
   499  
   500  	if err := b.BuilderFlags.Parse(); err != nil {
   501  		return err
   502  	}
   503  
   504  	b.Config.User = args[0]
   505  	return b.commit("", b.Config.Cmd, fmt.Sprintf("USER %v", args))
   506  }
   507  
   508  // VOLUME /foo
   509  //
   510  // Expose the volume /foo for use. Will also accept the JSON array form.
   511  //
   512  func volume(b *Builder, args []string, attributes map[string]bool, original string) error {
   513  	if runtime.GOOS == "windows" {
   514  		return fmt.Errorf("VOLUME is not supported on Windows.")
   515  	}
   516  	if len(args) == 0 {
   517  		return fmt.Errorf("VOLUME requires at least one argument")
   518  	}
   519  
   520  	if err := b.BuilderFlags.Parse(); err != nil {
   521  		return err
   522  	}
   523  
   524  	if b.Config.Volumes == nil {
   525  		b.Config.Volumes = map[string]struct{}{}
   526  	}
   527  	for _, v := range args {
   528  		v = strings.TrimSpace(v)
   529  		if v == "" {
   530  			return fmt.Errorf("Volume specified can not be an empty string")
   531  		}
   532  		b.Config.Volumes[v] = struct{}{}
   533  	}
   534  	if err := b.commit("", b.Config.Cmd, fmt.Sprintf("VOLUME %v", args)); err != nil {
   535  		return err
   536  	}
   537  	return nil
   538  }