summaryrefslogtreecommitdiff
path: root/vendor/github.com/openshift/imagebuilder/dispatchers.go
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com/openshift/imagebuilder/dispatchers.go')
-rw-r--r--vendor/github.com/openshift/imagebuilder/dispatchers.go570
1 files changed, 570 insertions, 0 deletions
diff --git a/vendor/github.com/openshift/imagebuilder/dispatchers.go b/vendor/github.com/openshift/imagebuilder/dispatchers.go
new file mode 100644
index 000000000..afa04bb89
--- /dev/null
+++ b/vendor/github.com/openshift/imagebuilder/dispatchers.go
@@ -0,0 +1,570 @@
+package imagebuilder
+
+// This file contains the dispatchers for each command. Note that
+// `nullDispatch` is not actually a command, but support for commands we parse
+// but do nothing with.
+//
+// See evaluator.go for a higher level discussion of the whole evaluator
+// package.
+
+import (
+ "flag"
+ "fmt"
+ "os"
+ "path/filepath"
+ "regexp"
+ "runtime"
+ "strconv"
+ "strings"
+
+ docker "github.com/fsouza/go-dockerclient"
+
+ "github.com/openshift/imagebuilder/signal"
+ "github.com/openshift/imagebuilder/strslice"
+)
+
+var (
+ obRgex = regexp.MustCompile(`(?i)^\s*ONBUILD\s*`)
+)
+
+// dispatch with no layer / parsing. This is effectively not a command.
+func nullDispatch(b *Builder, args []string, attributes map[string]bool, flagArgs []string, original string) error {
+ return nil
+}
+
+// ENV foo bar
+//
+// Sets the environment variable foo to bar, also makes interpolation
+// in the dockerfile available from the next statement on via ${foo}.
+//
+func env(b *Builder, args []string, attributes map[string]bool, flagArgs []string, original string) error {
+ if len(args) == 0 {
+ return errAtLeastOneArgument("ENV")
+ }
+
+ if len(args)%2 != 0 {
+ // should never get here, but just in case
+ return errTooManyArguments("ENV")
+ }
+
+ // TODO/FIXME/NOT USED
+ // Just here to show how to use the builder flags stuff within the
+ // context of a builder command. Will remove once we actually add
+ // a builder command to something!
+ /*
+ flBool1 := b.flags.AddBool("bool1", false)
+ flStr1 := b.flags.AddString("str1", "HI")
+
+ if err := b.flags.Parse(); err != nil {
+ return err
+ }
+
+ fmt.Printf("Bool1:%v\n", flBool1)
+ fmt.Printf("Str1:%v\n", flStr1)
+ */
+
+ for j := 0; j < len(args); j++ {
+ // name ==> args[j]
+ // value ==> args[j+1]
+ newVar := args[j] + "=" + args[j+1] + ""
+ gotOne := false
+ for i, envVar := range b.RunConfig.Env {
+ envParts := strings.SplitN(envVar, "=", 2)
+ if envParts[0] == args[j] {
+ b.RunConfig.Env[i] = newVar
+ b.Env = append([]string{newVar}, b.Env...)
+ gotOne = true
+ break
+ }
+ }
+ if !gotOne {
+ b.RunConfig.Env = append(b.RunConfig.Env, newVar)
+ b.Env = append([]string{newVar}, b.Env...)
+ }
+ j++
+ }
+
+ return nil
+}
+
+// MAINTAINER some text <maybe@an.email.address>
+//
+// Sets the maintainer metadata.
+func maintainer(b *Builder, args []string, attributes map[string]bool, flagArgs []string, original string) error {
+ if len(args) != 1 {
+ return errExactlyOneArgument("MAINTAINER")
+ }
+ b.Author = args[0]
+ return nil
+}
+
+// LABEL some json data describing the image
+//
+// Sets the Label variable foo to bar,
+//
+func label(b *Builder, args []string, attributes map[string]bool, flagArgs []string, original string) error {
+ if len(args) == 0 {
+ return errAtLeastOneArgument("LABEL")
+ }
+ if len(args)%2 != 0 {
+ // should never get here, but just in case
+ return errTooManyArguments("LABEL")
+ }
+
+ if b.RunConfig.Labels == nil {
+ b.RunConfig.Labels = map[string]string{}
+ }
+
+ for j := 0; j < len(args); j++ {
+ // name ==> args[j]
+ // value ==> args[j+1]
+ b.RunConfig.Labels[args[j]] = args[j+1]
+ j++
+ }
+ return nil
+}
+
+// ADD foo /path
+//
+// Add the file 'foo' to '/path'. Tarball and Remote URL (git, http) handling
+// exist here. If you do not wish to have this automatic handling, use COPY.
+//
+func add(b *Builder, args []string, attributes map[string]bool, flagArgs []string, original string) error {
+ if len(args) < 2 {
+ return errAtLeastOneArgument("ADD")
+ }
+ last := len(args) - 1
+ dest := makeAbsolute(args[last], b.RunConfig.WorkingDir)
+ b.PendingCopies = append(b.PendingCopies, Copy{Src: args[0:last], Dest: dest, Download: true})
+ return nil
+}
+
+// COPY foo /path
+//
+// Same as 'ADD' but without the tar and remote url handling.
+//
+func dispatchCopy(b *Builder, args []string, attributes map[string]bool, flagArgs []string, original string) error {
+ if len(args) < 2 {
+ return errAtLeastOneArgument("COPY")
+ }
+ last := len(args) - 1
+ dest := makeAbsolute(args[last], b.RunConfig.WorkingDir)
+ var from string
+ if len(flagArgs) > 0 {
+ for _, arg := range flagArgs {
+ switch {
+ case strings.HasPrefix(arg, "--from="):
+ from = strings.TrimPrefix(arg, "--from=")
+ default:
+ return fmt.Errorf("COPY only supports the --from=<image|stage> flag")
+ }
+ }
+ }
+ b.PendingCopies = append(b.PendingCopies, Copy{From: from, Src: args[0:last], Dest: dest, Download: false})
+ return nil
+}
+
+// FROM imagename
+//
+// This sets the image the dockerfile will build on top of.
+//
+func from(b *Builder, args []string, attributes map[string]bool, flagArgs []string, original string) error {
+ switch {
+ case len(args) == 1:
+ case len(args) == 3 && len(args[0]) > 0 && strings.EqualFold(args[1], "as") && len(args[2]) > 0:
+
+ default:
+ return fmt.Errorf("FROM requires either one argument, or three: FROM <source> [as <name>]")
+ }
+
+ name := args[0]
+ // Windows cannot support a container with no base image.
+ if name == NoBaseImageSpecifier {
+ if runtime.GOOS == "windows" {
+ return fmt.Errorf("Windows does not support FROM scratch")
+ }
+ }
+ b.RunConfig.Image = name
+ // TODO: handle onbuild
+ return nil
+}
+
+// ONBUILD RUN echo yo
+//
+// ONBUILD triggers run when the image is used in a FROM statement.
+//
+// ONBUILD handling has a lot of special-case functionality, the heading in
+// evaluator.go and comments around dispatch() in the same file explain the
+// special cases. search for 'OnBuild' in internals.go for additional special
+// cases.
+//
+func onbuild(b *Builder, args []string, attributes map[string]bool, flagArgs []string, original string) error {
+ if len(args) == 0 {
+ return errAtLeastOneArgument("ONBUILD")
+ }
+
+ triggerInstruction := strings.ToUpper(strings.TrimSpace(args[0]))
+ switch triggerInstruction {
+ case "ONBUILD":
+ return fmt.Errorf("Chaining ONBUILD via `ONBUILD ONBUILD` isn't allowed")
+ case "MAINTAINER", "FROM":
+ return fmt.Errorf("%s isn't allowed as an ONBUILD trigger", triggerInstruction)
+ }
+
+ original = obRgex.ReplaceAllString(original, "")
+
+ b.RunConfig.OnBuild = append(b.RunConfig.OnBuild, original)
+ return nil
+}
+
+// WORKDIR /tmp
+//
+// Set the working directory for future RUN/CMD/etc statements.
+//
+func workdir(b *Builder, args []string, attributes map[string]bool, flagArgs []string, original string) error {
+ if len(args) != 1 {
+ return errExactlyOneArgument("WORKDIR")
+ }
+
+ // This is from the Dockerfile and will not necessarily be in platform
+ // specific semantics, hence ensure it is converted.
+ workdir := filepath.FromSlash(args[0])
+
+ if !filepath.IsAbs(workdir) {
+ current := filepath.FromSlash(b.RunConfig.WorkingDir)
+ workdir = filepath.Join(string(os.PathSeparator), current, workdir)
+ }
+
+ b.RunConfig.WorkingDir = workdir
+ return nil
+}
+
+// RUN some command yo
+//
+// run a command and commit the image. Args are automatically prepended with
+// 'sh -c' under linux or 'cmd /S /C' under Windows, in the event there is
+// only one argument. The difference in processing:
+//
+// RUN echo hi # sh -c echo hi (Linux)
+// RUN echo hi # cmd /S /C echo hi (Windows)
+// RUN [ "echo", "hi" ] # echo hi
+//
+func run(b *Builder, args []string, attributes map[string]bool, flagArgs []string, original string) error {
+ if b.RunConfig.Image == "" {
+ return fmt.Errorf("Please provide a source image with `from` prior to run")
+ }
+
+ args = handleJSONArgs(args, attributes)
+
+ run := Run{Args: args}
+
+ if !attributes["json"] {
+ run.Shell = true
+ }
+ b.PendingRuns = append(b.PendingRuns, run)
+ return nil
+}
+
+// CMD foo
+//
+// Set the default command to run in the container (which may be empty).
+// Argument handling is the same as RUN.
+//
+func cmd(b *Builder, args []string, attributes map[string]bool, flagArgs []string, original string) error {
+ cmdSlice := handleJSONArgs(args, attributes)
+
+ if !attributes["json"] {
+ if runtime.GOOS != "windows" {
+ cmdSlice = append([]string{"/bin/sh", "-c"}, cmdSlice...)
+ } else {
+ cmdSlice = append([]string{"cmd", "/S", "/C"}, cmdSlice...)
+ }
+ }
+
+ b.RunConfig.Cmd = strslice.StrSlice(cmdSlice)
+ if len(args) != 0 {
+ b.CmdSet = true
+ }
+ return nil
+}
+
+// ENTRYPOINT /usr/sbin/nginx
+//
+// Set the entrypoint (which defaults to sh -c on linux, or cmd /S /C on Windows) to
+// /usr/sbin/nginx. Will accept the CMD as the arguments to /usr/sbin/nginx.
+//
+// Handles command processing similar to CMD and RUN, only b.RunConfig.Entrypoint
+// is initialized at NewBuilder time instead of through argument parsing.
+//
+func entrypoint(b *Builder, args []string, attributes map[string]bool, flagArgs []string, original string) error {
+ parsed := handleJSONArgs(args, attributes)
+
+ switch {
+ case attributes["json"]:
+ // ENTRYPOINT ["echo", "hi"]
+ b.RunConfig.Entrypoint = strslice.StrSlice(parsed)
+ case len(parsed) == 0:
+ // ENTRYPOINT []
+ b.RunConfig.Entrypoint = nil
+ default:
+ // ENTRYPOINT echo hi
+ if runtime.GOOS != "windows" {
+ b.RunConfig.Entrypoint = strslice.StrSlice{"/bin/sh", "-c", parsed[0]}
+ } else {
+ b.RunConfig.Entrypoint = strslice.StrSlice{"cmd", "/S", "/C", parsed[0]}
+ }
+ }
+
+ // when setting the entrypoint if a CMD was not explicitly set then
+ // set the command to nil
+ if !b.CmdSet {
+ b.RunConfig.Cmd = nil
+ }
+ return nil
+}
+
+// EXPOSE 6667/tcp 7000/tcp
+//
+// Expose ports for links and port mappings. This all ends up in
+// b.RunConfig.ExposedPorts for runconfig.
+//
+func expose(b *Builder, args []string, attributes map[string]bool, flagArgs []string, original string) error {
+ if len(args) == 0 {
+ return errAtLeastOneArgument("EXPOSE")
+ }
+
+ if b.RunConfig.ExposedPorts == nil {
+ b.RunConfig.ExposedPorts = make(map[docker.Port]struct{})
+ }
+
+ existing := map[string]struct{}{}
+ for k := range b.RunConfig.ExposedPorts {
+ existing[k.Port()] = struct{}{}
+ }
+
+ for _, port := range args {
+ dp := docker.Port(port)
+ if _, exists := existing[dp.Port()]; !exists {
+ b.RunConfig.ExposedPorts[docker.Port(fmt.Sprintf("%s/%s", dp.Port(), dp.Proto()))] = struct{}{}
+ }
+ }
+ return nil
+}
+
+// USER foo
+//
+// Set the user to 'foo' for future commands and when running the
+// ENTRYPOINT/CMD at container run time.
+//
+func user(b *Builder, args []string, attributes map[string]bool, flagArgs []string, original string) error {
+ if len(args) != 1 {
+ return errExactlyOneArgument("USER")
+ }
+
+ b.RunConfig.User = args[0]
+ return nil
+}
+
+// VOLUME /foo
+//
+// Expose the volume /foo for use. Will also accept the JSON array form.
+//
+func volume(b *Builder, args []string, attributes map[string]bool, flagArgs []string, original string) error {
+ if len(args) == 0 {
+ return errAtLeastOneArgument("VOLUME")
+ }
+
+ if b.RunConfig.Volumes == nil {
+ b.RunConfig.Volumes = map[string]struct{}{}
+ }
+ for _, v := range args {
+ v = strings.TrimSpace(v)
+ if v == "" {
+ return fmt.Errorf("Volume specified can not be an empty string")
+ }
+ b.RunConfig.Volumes[v] = struct{}{}
+ b.PendingVolumes.Add(v)
+ }
+ return nil
+}
+
+// STOPSIGNAL signal
+//
+// Set the signal that will be used to kill the container.
+func stopSignal(b *Builder, args []string, attributes map[string]bool, flagArgs []string, original string) error {
+ if len(args) != 1 {
+ return errExactlyOneArgument("STOPSIGNAL")
+ }
+
+ sig := args[0]
+ if err := signal.CheckSignal(sig); err != nil {
+ return err
+ }
+
+ b.RunConfig.StopSignal = sig
+ return nil
+}
+
+// HEALTHCHECK foo
+//
+// Set the default healthcheck command to run in the container (which may be empty).
+// Argument handling is the same as RUN.
+//
+func healthcheck(b *Builder, args []string, attributes map[string]bool, flagArgs []string, original string) error {
+ if len(args) == 0 {
+ return errAtLeastOneArgument("HEALTHCHECK")
+ }
+ typ := strings.ToUpper(args[0])
+ args = args[1:]
+ if typ == "NONE" {
+ if len(args) != 0 {
+ return fmt.Errorf("HEALTHCHECK NONE takes no arguments")
+ }
+ test := strslice.StrSlice{typ}
+ b.RunConfig.Healthcheck = &docker.HealthConfig{
+ Test: test,
+ }
+ } else {
+ if b.RunConfig.Healthcheck != nil {
+ oldCmd := b.RunConfig.Healthcheck.Test
+ if len(oldCmd) > 0 && oldCmd[0] != "NONE" {
+ b.Warnings = append(b.Warnings, fmt.Sprintf("Note: overriding previous HEALTHCHECK: %v\n", oldCmd))
+ }
+ }
+
+ healthcheck := docker.HealthConfig{}
+
+ flags := flag.NewFlagSet("", flag.ContinueOnError)
+ flags.String("interval", "", "")
+ flags.String("timeout", "", "")
+ flRetries := flags.String("retries", "", "")
+
+ if err := flags.Parse(flagArgs); err != nil {
+ return err
+ }
+
+ switch typ {
+ case "CMD":
+ cmdSlice := handleJSONArgs(args, attributes)
+ if len(cmdSlice) == 0 {
+ return fmt.Errorf("Missing command after HEALTHCHECK CMD")
+ }
+
+ if !attributes["json"] {
+ typ = "CMD-SHELL"
+ }
+
+ healthcheck.Test = strslice.StrSlice(append([]string{typ}, cmdSlice...))
+ default:
+ return fmt.Errorf("Unknown type %#v in HEALTHCHECK (try CMD)", typ)
+ }
+
+ interval, err := parseOptInterval(flags.Lookup("interval"))
+ if err != nil {
+ return err
+ }
+ healthcheck.Interval = interval
+
+ timeout, err := parseOptInterval(flags.Lookup("timeout"))
+ if err != nil {
+ return err
+ }
+ healthcheck.Timeout = timeout
+
+ if *flRetries != "" {
+ retries, err := strconv.ParseInt(*flRetries, 10, 32)
+ if err != nil {
+ return err
+ }
+ if retries < 1 {
+ return fmt.Errorf("--retries must be at least 1 (not %d)", retries)
+ }
+ healthcheck.Retries = int(retries)
+ } else {
+ healthcheck.Retries = 0
+ }
+ b.RunConfig.Healthcheck = &healthcheck
+ }
+
+ return nil
+}
+
+// ARG name[=value]
+//
+// Adds the variable foo to the trusted list of variables that can be passed
+// to builder using the --build-arg flag for expansion/subsitution or passing to 'run'.
+// Dockerfile author may optionally set a default value of this variable.
+func arg(b *Builder, args []string, attributes map[string]bool, flagArgs []string, original string) error {
+ if len(args) != 1 {
+ return fmt.Errorf("ARG requires exactly one argument definition")
+ }
+
+ var (
+ name string
+ value string
+ hasDefault bool
+ )
+
+ arg := args[0]
+ // 'arg' can just be a name or name-value pair. Note that this is different
+ // from 'env' that handles the split of name and value at the parser level.
+ // The reason for doing it differently for 'arg' is that we support just
+ // defining an arg and not assign it a value (while 'env' always expects a
+ // name-value pair). If possible, it will be good to harmonize the two.
+ if strings.Contains(arg, "=") {
+ parts := strings.SplitN(arg, "=", 2)
+ name = parts[0]
+ value = parts[1]
+ hasDefault = true
+ } else {
+ name = arg
+ hasDefault = false
+ }
+ // add the arg to allowed list of build-time args from this step on.
+ b.AllowedArgs[name] = true
+
+ // If there is a default value associated with this arg then add it to the
+ // b.buildArgs if one is not already passed to the builder. The args passed
+ // to builder override the default value of 'arg'.
+ if _, ok := b.Args[name]; !ok && hasDefault {
+ b.Args[name] = value
+ }
+
+ return nil
+}
+
+// SHELL powershell -command
+//
+// Set the non-default shell to use.
+func shell(b *Builder, args []string, attributes map[string]bool, flagArgs []string, original string) error {
+ shellSlice := handleJSONArgs(args, attributes)
+ switch {
+ case len(shellSlice) == 0:
+ // SHELL []
+ return errAtLeastOneArgument("SHELL")
+ case attributes["json"]:
+ // SHELL ["powershell", "-command"]
+ b.RunConfig.Shell = strslice.StrSlice(shellSlice)
+ // b.RunConfig.Shell = strslice.StrSlice(shellSlice)
+ default:
+ // SHELL powershell -command - not JSON
+ return errNotJSON("SHELL")
+ }
+ return nil
+}
+
+func errAtLeastOneArgument(command string) error {
+ return fmt.Errorf("%s requires at least one argument", command)
+}
+
+func errExactlyOneArgument(command string) error {
+ return fmt.Errorf("%s requires exactly one argument", command)
+}
+
+func errTooManyArguments(command string) error {
+ return fmt.Errorf("Bad input to %s, too many arguments", command)
+}
+
+func errNotJSON(command string) error {
+ return fmt.Errorf("%s requires the arguments to be in JSON form", command)
+}