Bladeren bron

Convert 'docker run' to a cobra command and to use pflags

Move container options into a struct so that tests should pass.
Remove unused FlagSet arg from Parse
Disable interspersed args on docker run

Signed-off-by: Daniel Nephin <dnephin@docker.com>
Daniel Nephin 9 jaren geleden
bovenliggende
commit
a77f2450c7

+ 6 - 6
api/client/attach.go

@@ -66,7 +66,7 @@ func (cli *DockerCli) CmdAttach(args ...string) error {
 	}
 	}
 
 
 	if *proxy && !c.Config.Tty {
 	if *proxy && !c.Config.Tty {
-		sigc := cli.forwardAllSignals(ctx, container)
+		sigc := cli.ForwardAllSignals(ctx, container)
 		defer signal.StopCatch(sigc)
 		defer signal.StopCatch(sigc)
 	}
 	}
 
 
@@ -80,20 +80,20 @@ func (cli *DockerCli) CmdAttach(args ...string) error {
 	defer resp.Close()
 	defer resp.Close()
 
 
 	if c.Config.Tty && cli.isTerminalOut {
 	if c.Config.Tty && cli.isTerminalOut {
-		height, width := cli.getTtySize()
+		height, width := cli.GetTtySize()
 		// To handle the case where a user repeatedly attaches/detaches without resizing their
 		// To handle the case where a user repeatedly attaches/detaches without resizing their
 		// terminal, the only way to get the shell prompt to display for attaches 2+ is to artificially
 		// terminal, the only way to get the shell prompt to display for attaches 2+ is to artificially
 		// resize it, then go back to normal. Without this, every attach after the first will
 		// resize it, then go back to normal. Without this, every attach after the first will
 		// require the user to manually resize or hit enter.
 		// require the user to manually resize or hit enter.
 		cli.resizeTtyTo(ctx, cmd.Arg(0), height+1, width+1, false)
 		cli.resizeTtyTo(ctx, cmd.Arg(0), height+1, width+1, false)
 
 
-		// After the above resizing occurs, the call to monitorTtySize below will handle resetting back
+		// After the above resizing occurs, the call to MonitorTtySize below will handle resetting back
 		// to the actual size.
 		// to the actual size.
-		if err := cli.monitorTtySize(ctx, cmd.Arg(0), false); err != nil {
+		if err := cli.MonitorTtySize(ctx, cmd.Arg(0), false); err != nil {
 			logrus.Debugf("Error monitoring TTY size: %s", err)
 			logrus.Debugf("Error monitoring TTY size: %s", err)
 		}
 		}
 	}
 	}
-	if err := cli.holdHijackedConnection(ctx, c.Config.Tty, in, cli.out, cli.err, resp); err != nil {
+	if err := cli.HoldHijackedConnection(ctx, c.Config.Tty, in, cli.out, cli.err, resp); err != nil {
 		return err
 		return err
 	}
 	}
 
 
@@ -101,7 +101,7 @@ func (cli *DockerCli) CmdAttach(args ...string) error {
 		return errAttach
 		return errAttach
 	}
 	}
 
 
-	_, status, err := cli.getExitCode(ctx, container)
+	_, status, err := cli.GetExitCode(ctx, container)
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}

+ 15 - 0
api/client/cli.go

@@ -75,6 +75,21 @@ func (cli *DockerCli) Err() io.Writer {
 	return cli.err
 	return cli.err
 }
 }
 
 
+// In returns the reader used for stdin
+func (cli *DockerCli) In() io.ReadCloser {
+	return cli.in
+}
+
+// ConfigFile returns the ConfigFile
+func (cli *DockerCli) ConfigFile() *configfile.ConfigFile {
+	return cli.configFile
+}
+
+// IsTerminalOut returns true if the clients stdin is a TTY
+func (cli *DockerCli) IsTerminalOut() bool {
+	return cli.isTerminalOut
+}
+
 // CheckTtyInput checks if we are trying to attach to a container tty
 // CheckTtyInput checks if we are trying to attach to a container tty
 // from a non-tty client input stream, and if so, returns an error.
 // from a non-tty client input stream, and if so, returns an error.
 func (cli *DockerCli) CheckTtyInput(attachStdin, ttyMode bool) error {
 func (cli *DockerCli) CheckTtyInput(attachStdin, ttyMode bool) error {

+ 0 - 1
api/client/commands.go

@@ -38,7 +38,6 @@ func (cli *DockerCli) Command(name string) func(...string) error {
 		"restart":            cli.CmdRestart,
 		"restart":            cli.CmdRestart,
 		"rm":                 cli.CmdRm,
 		"rm":                 cli.CmdRm,
 		"rmi":                cli.CmdRmi,
 		"rmi":                cli.CmdRmi,
-		"run":                cli.CmdRun,
 		"save":               cli.CmdSave,
 		"save":               cli.CmdSave,
 		"start":              cli.CmdStart,
 		"start":              cli.CmdStart,
 		"stats":              cli.CmdStats,
 		"stats":              cli.CmdStats,

+ 119 - 95
api/client/run.go → api/client/container/run.go

@@ -1,4 +1,4 @@
-package client
+package container
 
 
 import (
 import (
 	"fmt"
 	"fmt"
@@ -11,13 +11,16 @@ import (
 	"golang.org/x/net/context"
 	"golang.org/x/net/context"
 
 
 	"github.com/Sirupsen/logrus"
 	"github.com/Sirupsen/logrus"
-	Cli "github.com/docker/docker/cli"
-	"github.com/docker/docker/opts"
+	"github.com/docker/docker/api/client"
+	"github.com/docker/docker/cli"
+	opttypes "github.com/docker/docker/opts"
 	"github.com/docker/docker/pkg/promise"
 	"github.com/docker/docker/pkg/promise"
 	"github.com/docker/docker/pkg/signal"
 	"github.com/docker/docker/pkg/signal"
 	runconfigopts "github.com/docker/docker/runconfig/opts"
 	runconfigopts "github.com/docker/docker/runconfig/opts"
 	"github.com/docker/engine-api/types"
 	"github.com/docker/engine-api/types"
 	"github.com/docker/libnetwork/resolvconf/dns"
 	"github.com/docker/libnetwork/resolvconf/dns"
+	"github.com/spf13/cobra"
+	"github.com/spf13/pflag"
 )
 )
 
 
 const (
 const (
@@ -25,74 +28,74 @@ const (
 	errCmdCouldNotBeInvoked = "could not be invoked"
 	errCmdCouldNotBeInvoked = "could not be invoked"
 )
 )
 
 
-func (cid *cidFile) Close() error {
-	cid.file.Close()
-
-	if !cid.written {
-		if err := os.Remove(cid.path); err != nil {
-			return fmt.Errorf("failed to remove the CID file '%s': %s \n", cid.path, err)
-		}
-	}
-
-	return nil
+type runOptions struct {
+	autoRemove bool
+	detach     bool
+	sigProxy   bool
+	name       string
+	detachKeys string
 }
 }
 
 
-func (cid *cidFile) Write(id string) error {
-	if _, err := cid.file.Write([]byte(id)); err != nil {
-		return fmt.Errorf("Failed to write the container ID to the file: %s", err)
+// NewRunCommand create a new `docker run` command
+func NewRunCommand(dockerCli *client.DockerCli) *cobra.Command {
+	var opts runOptions
+	var copts *runconfigopts.ContainerOptions
+
+	cmd := &cobra.Command{
+		Use:   "run [OPTIONS] IMAGE [COMMAND] [ARG...]",
+		Short: "Run a command in a new container",
+		Args:  cli.RequiresMinArgs(1),
+		RunE: func(cmd *cobra.Command, args []string) error {
+			copts.Image = args[0]
+			if len(args) > 1 {
+				copts.Args = args[1:]
+			}
+			return runRun(dockerCli, cmd.Flags(), &opts, copts)
+		},
 	}
 	}
-	cid.written = true
-	return nil
-}
 
 
-// if container start fails with 'command not found' error, return 127
-// if container start fails with 'command cannot be invoked' error, return 126
-// return 125 for generic docker daemon failures
-func runStartContainerErr(err error) error {
-	trimmedErr := strings.TrimPrefix(err.Error(), "Error response from daemon: ")
-	statusError := Cli.StatusError{StatusCode: 125}
-	if strings.HasPrefix(trimmedErr, "Container command") {
-		if strings.Contains(trimmedErr, errCmdNotFound) {
-			statusError = Cli.StatusError{StatusCode: 127}
-		} else if strings.Contains(trimmedErr, errCmdCouldNotBeInvoked) {
-			statusError = Cli.StatusError{StatusCode: 126}
-		}
-	}
+	flags := cmd.Flags()
+	flags.SetInterspersed(false)
 
 
-	return statusError
+	// These are flags not stored in Config/HostConfig
+	flags.BoolVar(&opts.autoRemove, "rm", false, "Automatically remove the container when it exits")
+	flags.BoolVarP(&opts.detach, "detach", "d", false, "Run container in background and print container ID")
+	flags.BoolVar(&opts.sigProxy, "sig-proxy", true, "Proxy received signals to the process")
+	flags.StringVar(&opts.name, "name", "", "Assign a name to the container")
+	flags.StringVar(&opts.detachKeys, "detach-keys", "", "Override the key sequence for detaching a container")
+
+	// Add an explicit help that doesn't have a `-h` to prevent the conflict
+	// with hostname
+	flags.Bool("help", false, "Print usage")
+
+	client.AddTrustedFlags(flags, true)
+	copts = runconfigopts.AddFlags(flags)
+	return cmd
 }
 }
 
 
-// CmdRun runs a command in a new container.
-//
-// Usage: docker run [OPTIONS] IMAGE [COMMAND] [ARG...]
-func (cli *DockerCli) CmdRun(args ...string) error {
-	cmd := Cli.Subcmd("run", []string{"IMAGE [COMMAND] [ARG...]"}, Cli.DockerCommands["run"].Description, true)
-	addTrustedFlags(cmd, true)
+func runRun(dockerCli *client.DockerCli, flags *pflag.FlagSet, opts *runOptions, copts *runconfigopts.ContainerOptions) error {
+	stdout, stderr, stdin := dockerCli.Out(), dockerCli.Err(), dockerCli.In()
+	client := dockerCli.Client()
+	// TODO: pass this as an argument
+	cmdPath := "run"
 
 
-	// These are flags not stored in Config/HostConfig
 	var (
 	var (
-		flAutoRemove = cmd.Bool([]string{"-rm"}, false, "Automatically remove the container when it exits")
-		flDetach     = cmd.Bool([]string{"d", "-detach"}, false, "Run container in background and print container ID")
-		flSigProxy   = cmd.Bool([]string{"-sig-proxy"}, true, "Proxy received signals to the process")
-		flName       = cmd.String([]string{"-name"}, "", "Assign a name to the container")
-		flDetachKeys = cmd.String([]string{"-detach-keys"}, "", "Override the key sequence for detaching a container")
-		flAttach     *opts.ListOpts
-
+		flAttach                              *opttypes.ListOpts
 		ErrConflictAttachDetach               = fmt.Errorf("Conflicting options: -a and -d")
 		ErrConflictAttachDetach               = fmt.Errorf("Conflicting options: -a and -d")
 		ErrConflictRestartPolicyAndAutoRemove = fmt.Errorf("Conflicting options: --restart and --rm")
 		ErrConflictRestartPolicyAndAutoRemove = fmt.Errorf("Conflicting options: --restart and --rm")
 		ErrConflictDetachAutoRemove           = fmt.Errorf("Conflicting options: --rm and -d")
 		ErrConflictDetachAutoRemove           = fmt.Errorf("Conflicting options: --rm and -d")
 	)
 	)
 
 
-	config, hostConfig, networkingConfig, cmd, err := runconfigopts.Parse(cmd, args)
+	config, hostConfig, networkingConfig, err := runconfigopts.Parse(flags, copts)
 
 
 	// just in case the Parse does not exit
 	// just in case the Parse does not exit
 	if err != nil {
 	if err != nil {
-		cmd.ReportError(err.Error(), true)
+		reportError(stderr, cmdPath, err.Error(), true)
 		os.Exit(125)
 		os.Exit(125)
 	}
 	}
 
 
 	if hostConfig.OomKillDisable != nil && *hostConfig.OomKillDisable && hostConfig.Memory == 0 {
 	if hostConfig.OomKillDisable != nil && *hostConfig.OomKillDisable && hostConfig.Memory == 0 {
-		fmt.Fprintf(cli.err, "WARNING: Disabling the OOM killer on containers without setting a '-m/--memory' limit may be dangerous.\n")
+		fmt.Fprintf(stderr, "WARNING: Disabling the OOM killer on containers without setting a '-m/--memory' limit may be dangerous.\n")
 	}
 	}
 
 
 	if len(hostConfig.DNS) > 0 {
 	if len(hostConfig.DNS) > 0 {
@@ -101,30 +104,26 @@ func (cli *DockerCli) CmdRun(args ...string) error {
 		// set a DNS to a localhost address
 		// set a DNS to a localhost address
 		for _, dnsIP := range hostConfig.DNS {
 		for _, dnsIP := range hostConfig.DNS {
 			if dns.IsLocalhost(dnsIP) {
 			if dns.IsLocalhost(dnsIP) {
-				fmt.Fprintf(cli.err, "WARNING: Localhost DNS setting (--dns=%s) may fail in containers.\n", dnsIP)
+				fmt.Fprintf(stderr, "WARNING: Localhost DNS setting (--dns=%s) may fail in containers.\n", dnsIP)
 				break
 				break
 			}
 			}
 		}
 		}
 	}
 	}
-	if config.Image == "" {
-		cmd.Usage()
-		return nil
-	}
 
 
 	config.ArgsEscaped = false
 	config.ArgsEscaped = false
 
 
-	if !*flDetach {
-		if err := cli.CheckTtyInput(config.AttachStdin, config.Tty); err != nil {
+	if !opts.detach {
+		if err := dockerCli.CheckTtyInput(config.AttachStdin, config.Tty); err != nil {
 			return err
 			return err
 		}
 		}
 	} else {
 	} else {
-		if fl := cmd.Lookup("-attach"); fl != nil {
-			flAttach = fl.Value.(*opts.ListOpts)
+		if fl := flags.Lookup("attach"); fl != nil {
+			flAttach = fl.Value.(*opttypes.ListOpts)
 			if flAttach.Len() != 0 {
 			if flAttach.Len() != 0 {
 				return ErrConflictAttachDetach
 				return ErrConflictAttachDetach
 			}
 			}
 		}
 		}
-		if *flAutoRemove {
+		if opts.autoRemove {
 			return ErrConflictDetachAutoRemove
 			return ErrConflictDetachAutoRemove
 		}
 		}
 
 
@@ -134,28 +133,27 @@ func (cli *DockerCli) CmdRun(args ...string) error {
 		config.StdinOnce = false
 		config.StdinOnce = false
 	}
 	}
 
 
-	// Disable flSigProxy when in TTY mode
-	sigProxy := *flSigProxy
+	// Disable sigProxy when in TTY mode
 	if config.Tty {
 	if config.Tty {
-		sigProxy = false
+		opts.sigProxy = false
 	}
 	}
 
 
 	// Telling the Windows daemon the initial size of the tty during start makes
 	// Telling the Windows daemon the initial size of the tty during start makes
 	// a far better user experience rather than relying on subsequent resizes
 	// a far better user experience rather than relying on subsequent resizes
 	// to cause things to catch up.
 	// to cause things to catch up.
 	if runtime.GOOS == "windows" {
 	if runtime.GOOS == "windows" {
-		hostConfig.ConsoleSize[0], hostConfig.ConsoleSize[1] = cli.getTtySize()
+		hostConfig.ConsoleSize[0], hostConfig.ConsoleSize[1] = dockerCli.GetTtySize()
 	}
 	}
 
 
 	ctx, cancelFun := context.WithCancel(context.Background())
 	ctx, cancelFun := context.WithCancel(context.Background())
 
 
-	createResponse, err := cli.createContainer(ctx, config, hostConfig, networkingConfig, hostConfig.ContainerIDFile, *flName)
+	createResponse, err := dockerCli.CreateContainer(ctx, config, hostConfig, networkingConfig, hostConfig.ContainerIDFile, opts.name)
 	if err != nil {
 	if err != nil {
-		cmd.ReportError(err.Error(), true)
+		reportError(stderr, cmdPath, err.Error(), true)
 		return runStartContainerErr(err)
 		return runStartContainerErr(err)
 	}
 	}
-	if sigProxy {
-		sigc := cli.forwardAllSignals(ctx, createResponse.ID)
+	if opts.sigProxy {
+		sigc := dockerCli.ForwardAllSignals(ctx, createResponse.ID)
 		defer signal.StopCatch(sigc)
 		defer signal.StopCatch(sigc)
 	}
 	}
 	var (
 	var (
@@ -167,34 +165,34 @@ func (cli *DockerCli) CmdRun(args ...string) error {
 		waitDisplayID = make(chan struct{})
 		waitDisplayID = make(chan struct{})
 		go func() {
 		go func() {
 			defer close(waitDisplayID)
 			defer close(waitDisplayID)
-			fmt.Fprintf(cli.out, "%s\n", createResponse.ID)
+			fmt.Fprintf(stdout, "%s\n", createResponse.ID)
 		}()
 		}()
 	}
 	}
-	if *flAutoRemove && (hostConfig.RestartPolicy.IsAlways() || hostConfig.RestartPolicy.IsOnFailure()) {
+	if opts.autoRemove && (hostConfig.RestartPolicy.IsAlways() || hostConfig.RestartPolicy.IsOnFailure()) {
 		return ErrConflictRestartPolicyAndAutoRemove
 		return ErrConflictRestartPolicyAndAutoRemove
 	}
 	}
 	attach := config.AttachStdin || config.AttachStdout || config.AttachStderr
 	attach := config.AttachStdin || config.AttachStdout || config.AttachStderr
 	if attach {
 	if attach {
 		var (
 		var (
-			out, stderr io.Writer
-			in          io.ReadCloser
+			out, cerr io.Writer
+			in        io.ReadCloser
 		)
 		)
 		if config.AttachStdin {
 		if config.AttachStdin {
-			in = cli.in
+			in = stdin
 		}
 		}
 		if config.AttachStdout {
 		if config.AttachStdout {
-			out = cli.out
+			out = stdout
 		}
 		}
 		if config.AttachStderr {
 		if config.AttachStderr {
 			if config.Tty {
 			if config.Tty {
-				stderr = cli.out
+				cerr = stdout
 			} else {
 			} else {
-				stderr = cli.err
+				cerr = stderr
 			}
 			}
 		}
 		}
 
 
-		if *flDetachKeys != "" {
-			cli.configFile.DetachKeys = *flDetachKeys
+		if opts.detachKeys != "" {
+			dockerCli.ConfigFile().DetachKeys = opts.detachKeys
 		}
 		}
 
 
 		options := types.ContainerAttachOptions{
 		options := types.ContainerAttachOptions{
@@ -202,10 +200,10 @@ func (cli *DockerCli) CmdRun(args ...string) error {
 			Stdin:      config.AttachStdin,
 			Stdin:      config.AttachStdin,
 			Stdout:     config.AttachStdout,
 			Stdout:     config.AttachStdout,
 			Stderr:     config.AttachStderr,
 			Stderr:     config.AttachStderr,
-			DetachKeys: cli.configFile.DetachKeys,
+			DetachKeys: dockerCli.ConfigFile().DetachKeys,
 		}
 		}
 
 
-		resp, errAttach := cli.client.ContainerAttach(ctx, createResponse.ID, options)
+		resp, errAttach := client.ContainerAttach(ctx, createResponse.ID, options)
 		if errAttach != nil && errAttach != httputil.ErrPersistEOF {
 		if errAttach != nil && errAttach != httputil.ErrPersistEOF {
 			// ContainerAttach returns an ErrPersistEOF (connection closed)
 			// ContainerAttach returns an ErrPersistEOF (connection closed)
 			// means server met an error and put it in Hijacked connection
 			// means server met an error and put it in Hijacked connection
@@ -215,7 +213,7 @@ func (cli *DockerCli) CmdRun(args ...string) error {
 		defer resp.Close()
 		defer resp.Close()
 
 
 		errCh = promise.Go(func() error {
 		errCh = promise.Go(func() error {
-			errHijack := cli.holdHijackedConnection(ctx, config.Tty, in, out, stderr, resp)
+			errHijack := dockerCli.HoldHijackedConnection(ctx, config.Tty, in, out, cerr, resp)
 			if errHijack == nil {
 			if errHijack == nil {
 				return errAttach
 				return errAttach
 			}
 			}
@@ -223,18 +221,18 @@ func (cli *DockerCli) CmdRun(args ...string) error {
 		})
 		})
 	}
 	}
 
 
-	if *flAutoRemove {
+	if opts.autoRemove {
 		defer func() {
 		defer func() {
 			// Explicitly not sharing the context as it could be "Done" (by calling cancelFun)
 			// Explicitly not sharing the context as it could be "Done" (by calling cancelFun)
 			// and thus the container would not be removed.
 			// and thus the container would not be removed.
-			if err := cli.removeContainer(context.Background(), createResponse.ID, true, false, true); err != nil {
-				fmt.Fprintf(cli.err, "%v\n", err)
+			if err := dockerCli.RemoveContainer(context.Background(), createResponse.ID, true, false, true); err != nil {
+				fmt.Fprintf(stderr, "%v\n", err)
 			}
 			}
 		}()
 		}()
 	}
 	}
 
 
 	//start the container
 	//start the container
-	if err := cli.client.ContainerStart(ctx, createResponse.ID, types.ContainerStartOptions{}); err != nil {
+	if err := client.ContainerStart(ctx, createResponse.ID, types.ContainerStartOptions{}); err != nil {
 		// If we have holdHijackedConnection, we should notify
 		// If we have holdHijackedConnection, we should notify
 		// holdHijackedConnection we are going to exit and wait
 		// holdHijackedConnection we are going to exit and wait
 		// to avoid the terminal are not restored.
 		// to avoid the terminal are not restored.
@@ -243,13 +241,13 @@ func (cli *DockerCli) CmdRun(args ...string) error {
 			<-errCh
 			<-errCh
 		}
 		}
 
 
-		cmd.ReportError(err.Error(), false)
+		reportError(stderr, cmdPath, err.Error(), false)
 		return runStartContainerErr(err)
 		return runStartContainerErr(err)
 	}
 	}
 
 
-	if (config.AttachStdin || config.AttachStdout || config.AttachStderr) && config.Tty && cli.isTerminalOut {
-		if err := cli.monitorTtySize(ctx, createResponse.ID, false); err != nil {
-			fmt.Fprintf(cli.err, "Error monitoring TTY size: %s\n", err)
+	if (config.AttachStdin || config.AttachStdout || config.AttachStderr) && config.Tty && dockerCli.IsTerminalOut() {
+		if err := dockerCli.MonitorTtySize(ctx, createResponse.ID, false); err != nil {
+			fmt.Fprintf(stderr, "Error monitoring TTY size: %s\n", err)
 		}
 		}
 	}
 	}
 
 
@@ -270,32 +268,58 @@ func (cli *DockerCli) CmdRun(args ...string) error {
 	var status int
 	var status int
 
 
 	// Attached mode
 	// Attached mode
-	if *flAutoRemove {
+	if opts.autoRemove {
 		// Autoremove: wait for the container to finish, retrieve
 		// Autoremove: wait for the container to finish, retrieve
 		// the exit code and remove the container
 		// the exit code and remove the container
-		if status, err = cli.client.ContainerWait(ctx, createResponse.ID); err != nil {
+		if status, err = client.ContainerWait(ctx, createResponse.ID); err != nil {
 			return runStartContainerErr(err)
 			return runStartContainerErr(err)
 		}
 		}
-		if _, status, err = cli.getExitCode(ctx, createResponse.ID); err != nil {
+		if _, status, err = dockerCli.GetExitCode(ctx, createResponse.ID); err != nil {
 			return err
 			return err
 		}
 		}
 	} else {
 	} else {
 		// No Autoremove: Simply retrieve the exit code
 		// No Autoremove: Simply retrieve the exit code
 		if !config.Tty {
 		if !config.Tty {
 			// In non-TTY mode, we can't detach, so we must wait for container exit
 			// In non-TTY mode, we can't detach, so we must wait for container exit
-			if status, err = cli.client.ContainerWait(ctx, createResponse.ID); err != nil {
+			if status, err = client.ContainerWait(ctx, createResponse.ID); err != nil {
 				return err
 				return err
 			}
 			}
 		} else {
 		} else {
 			// In TTY mode, there is a race: if the process dies too slowly, the state could
 			// In TTY mode, there is a race: if the process dies too slowly, the state could
 			// be updated after the getExitCode call and result in the wrong exit code being reported
 			// be updated after the getExitCode call and result in the wrong exit code being reported
-			if _, status, err = cli.getExitCode(ctx, createResponse.ID); err != nil {
+			if _, status, err = dockerCli.GetExitCode(ctx, createResponse.ID); err != nil {
 				return err
 				return err
 			}
 			}
 		}
 		}
 	}
 	}
 	if status != 0 {
 	if status != 0 {
-		return Cli.StatusError{StatusCode: status}
+		return cli.StatusError{StatusCode: status}
 	}
 	}
 	return nil
 	return nil
 }
 }
+
+// reportError is a utility method that prints a user-friendly message
+// containing the error that occurred during parsing and a suggestion to get help
+func reportError(stderr io.Writer, name string, str string, withHelp bool) {
+	if withHelp {
+		str += ".\nSee '" + os.Args[0] + " " + name + " --help'"
+	}
+	fmt.Fprintf(stderr, "%s: %s.\n", os.Args[0], str)
+}
+
+// if container start fails with 'command not found' error, return 127
+// if container start fails with 'command cannot be invoked' error, return 126
+// return 125 for generic docker daemon failures
+func runStartContainerErr(err error) error {
+	trimmedErr := strings.TrimPrefix(err.Error(), "Error response from daemon: ")
+	statusError := cli.StatusError{StatusCode: 125}
+	if strings.HasPrefix(trimmedErr, "Container command") {
+		if strings.Contains(trimmedErr, errCmdNotFound) {
+			statusError = cli.StatusError{StatusCode: 127}
+		} else if strings.Contains(trimmedErr, errCmdCouldNotBeInvoked) {
+			statusError = cli.StatusError{StatusCode: 126}
+		}
+	}
+
+	return statusError
+}

+ 45 - 21
api/client/create.go

@@ -12,7 +12,7 @@ import (
 	// FIXME migrate to docker/distribution/reference
 	// FIXME migrate to docker/distribution/reference
 	"github.com/docker/docker/reference"
 	"github.com/docker/docker/reference"
 	"github.com/docker/docker/registry"
 	"github.com/docker/docker/registry"
-	runconfigopts "github.com/docker/docker/runconfig/opts"
+	//runconfigopts "github.com/docker/docker/runconfig/opts"
 	"github.com/docker/engine-api/client"
 	"github.com/docker/engine-api/client"
 	"github.com/docker/engine-api/types"
 	"github.com/docker/engine-api/types"
 	"github.com/docker/engine-api/types/container"
 	"github.com/docker/engine-api/types/container"
@@ -56,6 +56,26 @@ type cidFile struct {
 	written bool
 	written bool
 }
 }
 
 
+func (cid *cidFile) Close() error {
+	cid.file.Close()
+
+	if !cid.written {
+		if err := os.Remove(cid.path); err != nil {
+			return fmt.Errorf("failed to remove the CID file '%s': %s \n", cid.path, err)
+		}
+	}
+
+	return nil
+}
+
+func (cid *cidFile) Write(id string) error {
+	if _, err := cid.file.Write([]byte(id)); err != nil {
+		return fmt.Errorf("Failed to write the container ID to the file: %s", err)
+	}
+	cid.written = true
+	return nil
+}
+
 func newCIDFile(path string) (*cidFile, error) {
 func newCIDFile(path string) (*cidFile, error) {
 	if _, err := os.Stat(path); err == nil {
 	if _, err := os.Stat(path); err == nil {
 		return nil, fmt.Errorf("Container ID file found, make sure the other container isn't running or delete %s", path)
 		return nil, fmt.Errorf("Container ID file found, make sure the other container isn't running or delete %s", path)
@@ -69,7 +89,10 @@ func newCIDFile(path string) (*cidFile, error) {
 	return &cidFile{path: path, file: f}, nil
 	return &cidFile{path: path, file: f}, nil
 }
 }
 
 
-func (cli *DockerCli) createContainer(ctx context.Context, config *container.Config, hostConfig *container.HostConfig, networkingConfig *networktypes.NetworkingConfig, cidfile, name string) (*types.ContainerCreateResponse, error) {
+// CreateContainer creates a container from a config
+// TODO: this can be unexported again once all container commands are under
+// api/client/container
+func (cli *DockerCli) CreateContainer(ctx context.Context, config *container.Config, hostConfig *container.HostConfig, networkingConfig *networktypes.NetworkingConfig, cidfile, name string) (*types.ContainerCreateResponse, error) {
 	var containerIDFile *cidFile
 	var containerIDFile *cidFile
 	if cidfile != "" {
 	if cidfile != "" {
 		var err error
 		var err error
@@ -143,25 +166,26 @@ func (cli *DockerCli) CmdCreate(args ...string) error {
 	cmd := Cli.Subcmd("create", []string{"IMAGE [COMMAND] [ARG...]"}, Cli.DockerCommands["create"].Description, true)
 	cmd := Cli.Subcmd("create", []string{"IMAGE [COMMAND] [ARG...]"}, Cli.DockerCommands["create"].Description, true)
 	addTrustedFlags(cmd, true)
 	addTrustedFlags(cmd, true)
 
 
+	// TODO: tmp disable for PoC, convert to cobra and pflag later
 	// These are flags not stored in Config/HostConfig
 	// These are flags not stored in Config/HostConfig
-	var (
-		flName = cmd.String([]string{"-name"}, "", "Assign a name to the container")
-	)
-
-	config, hostConfig, networkingConfig, cmd, err := runconfigopts.Parse(cmd, args)
-
-	if err != nil {
-		cmd.ReportError(err.Error(), true)
-		os.Exit(1)
-	}
-	if config.Image == "" {
-		cmd.Usage()
-		return nil
-	}
-	response, err := cli.createContainer(context.Background(), config, hostConfig, networkingConfig, hostConfig.ContainerIDFile, *flName)
-	if err != nil {
-		return err
-	}
-	fmt.Fprintf(cli.out, "%s\n", response.ID)
+	//	var (
+	//		flName = cmd.String([]string{"-name"}, "", "Assign a name to the container")
+	//	)
+
+	//	config, hostConfig, networkingConfig, cmd, err := runconfigopts.Parse(cmd, args)
+	//
+	//	if err != nil {
+	//		cmd.ReportError(err.Error(), true)
+	//		os.Exit(1)
+	//	}
+	//	if config.Image == "" {
+	//		cmd.Usage()
+	//		return nil
+	//	}
+	//	response, err := cli.CreateContainer(context.Background(), config, hostConfig, networkingConfig, hostConfig.ContainerIDFile, *flName)
+	//	if err != nil {
+	//		return err
+	//	}
+	//	fmt.Fprintf(cli.out, "%s\n", response.ID)
 	return nil
 	return nil
 }
 }

+ 2 - 2
api/client/exec.go

@@ -93,11 +93,11 @@ func (cli *DockerCli) CmdExec(args ...string) error {
 	}
 	}
 	defer resp.Close()
 	defer resp.Close()
 	errCh = promise.Go(func() error {
 	errCh = promise.Go(func() error {
-		return cli.holdHijackedConnection(ctx, execConfig.Tty, in, out, stderr, resp)
+		return cli.HoldHijackedConnection(ctx, execConfig.Tty, in, out, stderr, resp)
 	})
 	})
 
 
 	if execConfig.Tty && cli.isTerminalIn {
 	if execConfig.Tty && cli.isTerminalIn {
-		if err := cli.monitorTtySize(ctx, execID, true); err != nil {
+		if err := cli.MonitorTtySize(ctx, execID, true); err != nil {
 			fmt.Fprintf(cli.err, "Error monitoring TTY size: %s\n", err)
 			fmt.Fprintf(cli.err, "Error monitoring TTY size: %s\n", err)
 		}
 		}
 	}
 	}

+ 2 - 1
api/client/hijack.go

@@ -11,7 +11,8 @@ import (
 	"github.com/docker/engine-api/types"
 	"github.com/docker/engine-api/types"
 )
 )
 
 
-func (cli *DockerCli) holdHijackedConnection(ctx context.Context, tty bool, inputStream io.ReadCloser, outputStream, errorStream io.Writer, resp types.HijackedResponse) error {
+// HoldHijackedConnection ... TODO docstring
+func (cli *DockerCli) HoldHijackedConnection(ctx context.Context, tty bool, inputStream io.ReadCloser, outputStream, errorStream io.Writer, resp types.HijackedResponse) error {
 	var (
 	var (
 		err         error
 		err         error
 		restoreOnce sync.Once
 		restoreOnce sync.Once

+ 5 - 2
api/client/rm.go

@@ -32,7 +32,7 @@ func (cli *DockerCli) CmdRm(args ...string) error {
 		}
 		}
 		name = strings.Trim(name, "/")
 		name = strings.Trim(name, "/")
 
 
-		if err := cli.removeContainer(ctx, name, *v, *link, *force); err != nil {
+		if err := cli.RemoveContainer(ctx, name, *v, *link, *force); err != nil {
 			errs = append(errs, err.Error())
 			errs = append(errs, err.Error())
 		} else {
 		} else {
 			fmt.Fprintf(cli.out, "%s\n", name)
 			fmt.Fprintf(cli.out, "%s\n", name)
@@ -44,7 +44,10 @@ func (cli *DockerCli) CmdRm(args ...string) error {
 	return nil
 	return nil
 }
 }
 
 
-func (cli *DockerCli) removeContainer(ctx context.Context, container string, removeVolumes, removeLinks, force bool) error {
+// RemoveContainer removes a container
+// TODO: this can be unexported again once all container commands are under
+// api/client/container
+func (cli *DockerCli) RemoveContainer(ctx context.Context, container string, removeVolumes, removeLinks, force bool) error {
 	options := types.ContainerRemoveOptions{
 	options := types.ContainerRemoveOptions{
 		RemoveVolumes: removeVolumes,
 		RemoveVolumes: removeVolumes,
 		RemoveLinks:   removeLinks,
 		RemoveLinks:   removeLinks,

+ 8 - 5
api/client/start.go

@@ -17,7 +17,10 @@ import (
 	"github.com/docker/engine-api/types"
 	"github.com/docker/engine-api/types"
 )
 )
 
 
-func (cli *DockerCli) forwardAllSignals(ctx context.Context, cid string) chan os.Signal {
+// ForwardAllSignals forwards signals to the contianer
+// TODO: this can be unexported again once all container commands are under
+// api/client/container
+func (cli *DockerCli) ForwardAllSignals(ctx context.Context, cid string) chan os.Signal {
 	sigc := make(chan os.Signal, 128)
 	sigc := make(chan os.Signal, 128)
 	signal.CatchAll(sigc)
 	signal.CatchAll(sigc)
 	go func() {
 	go func() {
@@ -74,7 +77,7 @@ func (cli *DockerCli) CmdStart(args ...string) error {
 		}
 		}
 
 
 		if !c.Config.Tty {
 		if !c.Config.Tty {
-			sigc := cli.forwardAllSignals(ctx, container)
+			sigc := cli.ForwardAllSignals(ctx, container)
 			defer signal.StopCatch(sigc)
 			defer signal.StopCatch(sigc)
 		}
 		}
 
 
@@ -105,7 +108,7 @@ func (cli *DockerCli) CmdStart(args ...string) error {
 		}
 		}
 		defer resp.Close()
 		defer resp.Close()
 		cErr := promise.Go(func() error {
 		cErr := promise.Go(func() error {
-			errHijack := cli.holdHijackedConnection(ctx, c.Config.Tty, in, cli.out, cli.err, resp)
+			errHijack := cli.HoldHijackedConnection(ctx, c.Config.Tty, in, cli.out, cli.err, resp)
 			if errHijack == nil {
 			if errHijack == nil {
 				return errAttach
 				return errAttach
 			}
 			}
@@ -121,14 +124,14 @@ func (cli *DockerCli) CmdStart(args ...string) error {
 
 
 		// 4. Wait for attachment to break.
 		// 4. Wait for attachment to break.
 		if c.Config.Tty && cli.isTerminalOut {
 		if c.Config.Tty && cli.isTerminalOut {
-			if err := cli.monitorTtySize(ctx, container, false); err != nil {
+			if err := cli.MonitorTtySize(ctx, container, false); err != nil {
 				fmt.Fprintf(cli.err, "Error monitoring TTY size: %s\n", err)
 				fmt.Fprintf(cli.err, "Error monitoring TTY size: %s\n", err)
 			}
 			}
 		}
 		}
 		if attchErr := <-cErr; attchErr != nil {
 		if attchErr := <-cErr; attchErr != nil {
 			return attchErr
 			return attchErr
 		}
 		}
-		_, status, err := cli.getExitCode(ctx, container)
+		_, status, err := cli.GetExitCode(ctx, container)
 		if err != nil {
 		if err != nil {
 			return err
 			return err
 		}
 		}

+ 15 - 1
api/client/trust.go

@@ -37,6 +37,7 @@ import (
 	"github.com/docker/notary/tuf/data"
 	"github.com/docker/notary/tuf/data"
 	"github.com/docker/notary/tuf/signed"
 	"github.com/docker/notary/tuf/signed"
 	"github.com/docker/notary/tuf/store"
 	"github.com/docker/notary/tuf/store"
+	"github.com/spf13/pflag"
 )
 )
 
 
 var (
 var (
@@ -44,7 +45,20 @@ var (
 	untrusted    bool
 	untrusted    bool
 )
 )
 
 
+// TODO: tmp workaround to get this PoC working, change everything to use
+// exported version
 func addTrustedFlags(fs *flag.FlagSet, verify bool) {
 func addTrustedFlags(fs *flag.FlagSet, verify bool) {
+	trusted, message := setupTrustedFlag(verify)
+	fs.BoolVar(&untrusted, []string{"-disable-content-trust"}, !trusted, message)
+}
+
+// AddTrustedFlags adds the trust flags to a FlagSet
+func AddTrustedFlags(fs *pflag.FlagSet, verify bool) {
+	trusted, message := setupTrustedFlag(verify)
+	fs.BoolVar(&untrusted, "disable-content-trust", !trusted, message)
+}
+
+func setupTrustedFlag(verify bool) (bool, string) {
 	var trusted bool
 	var trusted bool
 	if e := os.Getenv("DOCKER_CONTENT_TRUST"); e != "" {
 	if e := os.Getenv("DOCKER_CONTENT_TRUST"); e != "" {
 		if t, err := strconv.ParseBool(e); t || err != nil {
 		if t, err := strconv.ParseBool(e); t || err != nil {
@@ -56,7 +70,7 @@ func addTrustedFlags(fs *flag.FlagSet, verify bool) {
 	if verify {
 	if verify {
 		message = "Skip image verification"
 		message = "Skip image verification"
 	}
 	}
-	fs.BoolVar(&untrusted, []string{"-disable-content-trust"}, !trusted, message)
+	return trusted, message
 }
 }
 
 
 func isTrusted() bool {
 func isTrusted() bool {

+ 9 - 7
api/client/utils.go

@@ -61,7 +61,7 @@ func (cli *DockerCli) RegistryAuthenticationPrivilegedFunc(index *registrytypes.
 }
 }
 
 
 func (cli *DockerCli) resizeTty(ctx context.Context, id string, isExec bool) {
 func (cli *DockerCli) resizeTty(ctx context.Context, id string, isExec bool) {
-	height, width := cli.getTtySize()
+	height, width := cli.GetTtySize()
 	cli.resizeTtyTo(ctx, id, height, width, isExec)
 	cli.resizeTtyTo(ctx, id, height, width, isExec)
 }
 }
 
 
@@ -87,9 +87,9 @@ func (cli *DockerCli) resizeTtyTo(ctx context.Context, id string, height, width
 	}
 	}
 }
 }
 
 
-// getExitCode perform an inspect on the container. It returns
+// GetExitCode perform an inspect on the container. It returns
 // the running state and the exit code.
 // the running state and the exit code.
-func (cli *DockerCli) getExitCode(ctx context.Context, containerID string) (bool, int, error) {
+func (cli *DockerCli) GetExitCode(ctx context.Context, containerID string) (bool, int, error) {
 	c, err := cli.client.ContainerInspect(ctx, containerID)
 	c, err := cli.client.ContainerInspect(ctx, containerID)
 	if err != nil {
 	if err != nil {
 		// If we can't connect, then the daemon probably died.
 		// If we can't connect, then the daemon probably died.
@@ -117,15 +117,16 @@ func (cli *DockerCli) getExecExitCode(ctx context.Context, execID string) (bool,
 	return resp.Running, resp.ExitCode, nil
 	return resp.Running, resp.ExitCode, nil
 }
 }
 
 
-func (cli *DockerCli) monitorTtySize(ctx context.Context, id string, isExec bool) error {
+// MonitorTtySize updates the container tty size when the terminal tty changes size
+func (cli *DockerCli) MonitorTtySize(ctx context.Context, id string, isExec bool) error {
 	cli.resizeTty(ctx, id, isExec)
 	cli.resizeTty(ctx, id, isExec)
 
 
 	if runtime.GOOS == "windows" {
 	if runtime.GOOS == "windows" {
 		go func() {
 		go func() {
-			prevH, prevW := cli.getTtySize()
+			prevH, prevW := cli.GetTtySize()
 			for {
 			for {
 				time.Sleep(time.Millisecond * 250)
 				time.Sleep(time.Millisecond * 250)
-				h, w := cli.getTtySize()
+				h, w := cli.GetTtySize()
 
 
 				if prevW != w || prevH != h {
 				if prevW != w || prevH != h {
 					cli.resizeTty(ctx, id, isExec)
 					cli.resizeTty(ctx, id, isExec)
@@ -146,7 +147,8 @@ func (cli *DockerCli) monitorTtySize(ctx context.Context, id string, isExec bool
 	return nil
 	return nil
 }
 }
 
 
-func (cli *DockerCli) getTtySize() (int, int) {
+// GetTtySize returns the height and width in characters of the tty
+func (cli *DockerCli) GetTtySize() (int, int) {
 	if !cli.isTerminalOut {
 	if !cli.isTerminalOut {
 		return 0, 0
 		return 0, 0
 	}
 	}

+ 4 - 2
cli/cobraadaptor/adaptor.go

@@ -4,6 +4,7 @@ import (
 	"fmt"
 	"fmt"
 
 
 	"github.com/docker/docker/api/client"
 	"github.com/docker/docker/api/client"
+	"github.com/docker/docker/api/client/container"
 	"github.com/docker/docker/api/client/image"
 	"github.com/docker/docker/api/client/image"
 	"github.com/docker/docker/api/client/volume"
 	"github.com/docker/docker/api/client/volume"
 	"github.com/docker/docker/cli"
 	"github.com/docker/docker/cli"
@@ -34,8 +35,9 @@ func NewCobraAdaptor(clientFlags *cliflags.ClientFlags) CobraAdaptor {
 	rootCmd.SetFlagErrorFunc(flagErrorFunc)
 	rootCmd.SetFlagErrorFunc(flagErrorFunc)
 	rootCmd.SetOutput(stdout)
 	rootCmd.SetOutput(stdout)
 	rootCmd.AddCommand(
 	rootCmd.AddCommand(
-		volume.NewVolumeCommand(dockerCli),
+		container.NewRunCommand(dockerCli),
 		image.NewSearchCommand(dockerCli),
 		image.NewSearchCommand(dockerCli),
+		volume.NewVolumeCommand(dockerCli),
 	)
 	)
 
 
 	rootCmd.PersistentFlags().BoolP("help", "h", false, "Print usage")
 	rootCmd.PersistentFlags().BoolP("help", "h", false, "Print usage")
@@ -52,7 +54,7 @@ func NewCobraAdaptor(clientFlags *cliflags.ClientFlags) CobraAdaptor {
 func (c CobraAdaptor) Usage() []cli.Command {
 func (c CobraAdaptor) Usage() []cli.Command {
 	cmds := []cli.Command{}
 	cmds := []cli.Command{}
 	for _, cmd := range c.rootCmd.Commands() {
 	for _, cmd := range c.rootCmd.Commands() {
-		cmds = append(cmds, cli.Command{Name: cmd.Use, Description: cmd.Short})
+		cmds = append(cmds, cli.Command{Name: cmd.Name(), Description: cmd.Short})
 	}
 	}
 	return cmds
 	return cmds
 }
 }

+ 0 - 1
cli/usage.go

@@ -37,7 +37,6 @@ var DockerCommandUsage = []Command{
 	{"restart", "Restart a container"},
 	{"restart", "Restart a container"},
 	{"rm", "Remove one or more containers"},
 	{"rm", "Remove one or more containers"},
 	{"rmi", "Remove one or more images"},
 	{"rmi", "Remove one or more images"},
-	{"run", "Run a command in a new container"},
 	{"save", "Save one or more images to a tar archive"},
 	{"save", "Save one or more images to a tar archive"},
 	{"start", "Start one or more stopped containers"},
 	{"start", "Start one or more stopped containers"},
 	{"stats", "Display a live stream of container(s) resource usage statistics"},
 	{"stats", "Display a live stream of container(s) resource usage statistics"},

+ 330 - 247
runconfig/opts/parse.go

@@ -8,160 +8,245 @@ import (
 	"path"
 	"path"
 	"strconv"
 	"strconv"
 	"strings"
 	"strings"
+	"time"
 
 
 	"github.com/docker/docker/opts"
 	"github.com/docker/docker/opts"
-	flag "github.com/docker/docker/pkg/mflag"
 	"github.com/docker/docker/pkg/mount"
 	"github.com/docker/docker/pkg/mount"
 	"github.com/docker/docker/pkg/signal"
 	"github.com/docker/docker/pkg/signal"
 	"github.com/docker/engine-api/types/container"
 	"github.com/docker/engine-api/types/container"
 	networktypes "github.com/docker/engine-api/types/network"
 	networktypes "github.com/docker/engine-api/types/network"
 	"github.com/docker/engine-api/types/strslice"
 	"github.com/docker/engine-api/types/strslice"
 	"github.com/docker/go-connections/nat"
 	"github.com/docker/go-connections/nat"
-	"github.com/docker/go-units"
+	units "github.com/docker/go-units"
+	"github.com/spf13/pflag"
 )
 )
 
 
-// Parse parses the specified args for the specified command and generates a Config,
+// ContainerOptions is a data object with all the options for creating a container
+// TODO: remove fl prefix
+type ContainerOptions struct {
+	flAttach            opts.ListOpts
+	flVolumes           opts.ListOpts
+	flTmpfs             opts.ListOpts
+	flBlkioWeightDevice WeightdeviceOpt
+	flDeviceReadBps     ThrottledeviceOpt
+	flDeviceWriteBps    ThrottledeviceOpt
+	flLinks             opts.ListOpts
+	flAliases           opts.ListOpts
+	flDeviceReadIOps    ThrottledeviceOpt
+	flDeviceWriteIOps   ThrottledeviceOpt
+	flEnv               opts.ListOpts
+	flLabels            opts.ListOpts
+	flDevices           opts.ListOpts
+	flUlimits           *UlimitOpt
+	flSysctls           *opts.MapOpts
+	flPublish           opts.ListOpts
+	flExpose            opts.ListOpts
+	flDNS               opts.ListOpts
+	flDNSSearch         opts.ListOpts
+	flDNSOptions        opts.ListOpts
+	flExtraHosts        opts.ListOpts
+	flVolumesFrom       opts.ListOpts
+	flEnvFile           opts.ListOpts
+	flCapAdd            opts.ListOpts
+	flCapDrop           opts.ListOpts
+	flGroupAdd          opts.ListOpts
+	flSecurityOpt       opts.ListOpts
+	flStorageOpt        opts.ListOpts
+	flLabelsFile        opts.ListOpts
+	flLoggingOpts       opts.ListOpts
+	flPrivileged        *bool
+	flPidMode           *string
+	flUTSMode           *string
+	flUsernsMode        *string
+	flPublishAll        *bool
+	flStdin             *bool
+	flTty               *bool
+	flOomKillDisable    *bool
+	flOomScoreAdj       *int
+	flContainerIDFile   *string
+	flEntrypoint        *string
+	flHostname          *string
+	flMemoryString      *string
+	flMemoryReservation *string
+	flMemorySwap        *string
+	flKernelMemory      *string
+	flUser              *string
+	flWorkingDir        *string
+	flCPUShares         *int64
+	flCPUPercent        *int64
+	flCPUPeriod         *int64
+	flCPUQuota          *int64
+	flCpusetCpus        *string
+	flCpusetMems        *string
+	flBlkioWeight       *uint16
+	flIOMaxBandwidth    *string
+	flIOMaxIOps         *uint64
+	flSwappiness        *int64
+	flNetMode           *string
+	flMacAddress        *string
+	flIPv4Address       *string
+	flIPv6Address       *string
+	flIpcMode           *string
+	flPidsLimit         *int64
+	flRestartPolicy     *string
+	flReadonlyRootfs    *bool
+	flLoggingDriver     *string
+	flCgroupParent      *string
+	flVolumeDriver      *string
+	flStopSignal        *string
+	flIsolation         *string
+	flShmSize           *string
+	flNoHealthcheck     *bool
+	flHealthCmd         *string
+	flHealthInterval    *time.Duration
+	flHealthTimeout     *time.Duration
+	flHealthRetries     *int
+
+	Image string
+	Args  []string
+}
+
+// AddFlags adds all command line flags that will be used by Parse to the FlagSet
+func AddFlags(flags *pflag.FlagSet) *ContainerOptions {
+	copts := &ContainerOptions{
+		flAttach:            opts.NewListOpts(ValidateAttach),
+		flVolumes:           opts.NewListOpts(nil),
+		flTmpfs:             opts.NewListOpts(nil),
+		flBlkioWeightDevice: NewWeightdeviceOpt(ValidateWeightDevice),
+		flDeviceReadBps:     NewThrottledeviceOpt(ValidateThrottleBpsDevice),
+		flDeviceWriteBps:    NewThrottledeviceOpt(ValidateThrottleBpsDevice),
+		flLinks:             opts.NewListOpts(ValidateLink),
+		flAliases:           opts.NewListOpts(nil),
+		flDeviceReadIOps:    NewThrottledeviceOpt(ValidateThrottleIOpsDevice),
+		flDeviceWriteIOps:   NewThrottledeviceOpt(ValidateThrottleIOpsDevice),
+		flEnv:               opts.NewListOpts(ValidateEnv),
+		flLabels:            opts.NewListOpts(ValidateEnv),
+		flDevices:           opts.NewListOpts(ValidateDevice),
+
+		flUlimits: NewUlimitOpt(nil),
+		flSysctls: opts.NewMapOpts(nil, opts.ValidateSysctl),
+
+		flPublish:     opts.NewListOpts(nil),
+		flExpose:      opts.NewListOpts(nil),
+		flDNS:         opts.NewListOpts(opts.ValidateIPAddress),
+		flDNSSearch:   opts.NewListOpts(opts.ValidateDNSSearch),
+		flDNSOptions:  opts.NewListOpts(nil),
+		flExtraHosts:  opts.NewListOpts(ValidateExtraHost),
+		flVolumesFrom: opts.NewListOpts(nil),
+		flEnvFile:     opts.NewListOpts(nil),
+		flCapAdd:      opts.NewListOpts(nil),
+		flCapDrop:     opts.NewListOpts(nil),
+		flGroupAdd:    opts.NewListOpts(nil),
+		flSecurityOpt: opts.NewListOpts(nil),
+		flStorageOpt:  opts.NewListOpts(nil),
+		flLabelsFile:  opts.NewListOpts(nil),
+		flLoggingOpts: opts.NewListOpts(nil),
+
+		flPrivileged:        flags.Bool("privileged", false, "Give extended privileges to this container"),
+		flPidMode:           flags.String("pid", "", "PID namespace to use"),
+		flUTSMode:           flags.String("uts", "", "UTS namespace to use"),
+		flUsernsMode:        flags.String("userns", "", "User namespace to use"),
+		flPublishAll:        flags.BoolP("publish-all", "P", false, "Publish all exposed ports to random ports"),
+		flStdin:             flags.BoolP("interactive", "i", false, "Keep STDIN open even if not attached"),
+		flTty:               flags.BoolP("tty", "t", false, "Allocate a pseudo-TTY"),
+		flOomKillDisable:    flags.Bool("oom-kill-disable", false, "Disable OOM Killer"),
+		flOomScoreAdj:       flags.Int("oom-score-adj", 0, "Tune host's OOM preferences (-1000 to 1000)"),
+		flContainerIDFile:   flags.String("cidfile", "", "Write the container ID to the file"),
+		flEntrypoint:        flags.String("entrypoint", "", "Overwrite the default ENTRYPOINT of the image"),
+		flHostname:          flags.StringP("hostname", "h", "", "Container host name"),
+		flMemoryString:      flags.StringP("memory", "m", "", "Memory limit"),
+		flMemoryReservation: flags.String("memory-reservation", "", "Memory soft limit"),
+		flMemorySwap:        flags.String("memory-swap", "", "Swap limit equal to memory plus swap: '-1' to enable unlimited swap"),
+		flKernelMemory:      flags.String("kernel-memory", "", "Kernel memory limit"),
+		flUser:              flags.StringP("user", "u", "", "Username or UID (format: <name|uid>[:<group|gid>])"),
+		flWorkingDir:        flags.StringP("workdir", "w", "", "Working directory inside the container"),
+		flCPUShares:         flags.Int64P("cpu-shares", "c", 0, "CPU shares (relative weight)"),
+		flCPUPercent:        flags.Int64("cpu-percent", 0, "CPU percent (Windows only)"),
+		flCPUPeriod:         flags.Int64("cpu-period", 0, "Limit CPU CFS (Completely Fair Scheduler) period"),
+		flCPUQuota:          flags.Int64("cpu-quota", 0, "Limit CPU CFS (Completely Fair Scheduler) quota"),
+		flCpusetCpus:        flags.String("cpuset-cpus", "", "CPUs in which to allow execution (0-3, 0,1)"),
+		flCpusetMems:        flags.String("cpuset-mems", "", "MEMs in which to allow execution (0-3, 0,1)"),
+		flBlkioWeight:       flags.Uint16("blkio-weight", 0, "Block IO (relative weight), between 10 and 1000"),
+		flIOMaxBandwidth:    flags.String("io-maxbandwidth", "", "Maximum IO bandwidth limit for the system drive (Windows only)"),
+		flIOMaxIOps:         flags.Uint64("io-maxiops", 0, "Maximum IOps limit for the system drive (Windows only)"),
+		flSwappiness:        flags.Int64("memory-swappiness", -1, "Tune container memory swappiness (0 to 100)"),
+		flNetMode:           flags.String("net", "default", "Connect a container to a network"),
+		flMacAddress:        flags.String("mac-address", "", "Container MAC address (e.g. 92:d0:c6:0a:29:33)"),
+		flIPv4Address:       flags.String("ip", "", "Container IPv4 address (e.g. 172.30.100.104)"),
+		flIPv6Address:       flags.String("ip6", "", "Container IPv6 address (e.g. 2001:db8::33)"),
+		flIpcMode:           flags.String("ipc", "", "IPC namespace to use"),
+		flPidsLimit:         flags.Int64("pids-limit", 0, "Tune container pids limit (set -1 for unlimited)"),
+		flRestartPolicy:     flags.String("restart", "no", "Restart policy to apply when a container exits"),
+		flReadonlyRootfs:    flags.Bool("read-only", false, "Mount the container's root filesystem as read only"),
+		flLoggingDriver:     flags.String("log-driver", "", "Logging driver for container"),
+		flCgroupParent:      flags.String("cgroup-parent", "", "Optional parent cgroup for the container"),
+		flVolumeDriver:      flags.String("volume-driver", "", "Optional volume driver for the container"),
+		flStopSignal:        flags.String("stop-signal", signal.DefaultStopSignal, fmt.Sprintf("Signal to stop a container, %v by default", signal.DefaultStopSignal)),
+		flIsolation:         flags.String("isolation", "", "Container isolation technology"),
+		flShmSize:           flags.String("shm-size", "", "Size of /dev/shm, default value is 64MB"),
+		flNoHealthcheck:     cmd.Bool([]string{"-no-healthcheck"}, false, "Disable any container-specified HEALTHCHECK"),
+		flHealthCmd:         cmd.String([]string{"-health-cmd"}, "", "Command to run to check health"),
+		flHealthInterval:    cmd.Duration([]string{"-health-interval"}, 0, "Time between running the check"),
+		flHealthTimeout:     cmd.Duration([]string{"-health-timeout"}, 0, "Maximum time to allow one check to run"),
+		flHealthRetries:     cmd.Int([]string{"-health-retries"}, 0, "Consecutive failures needed to report unhealthy"),
+	}
+
+	flags.VarP(&copts.flAttach, "attach", "a", "Attach to STDIN, STDOUT or STDERR")
+	flags.Var(&copts.flBlkioWeightDevice, "blkio-weight-device", "Block IO weight (relative device weight)")
+	flags.Var(&copts.flDeviceReadBps, "device-read-bps", "Limit read rate (bytes per second) from a device")
+	flags.Var(&copts.flDeviceWriteBps, "device-write-bps", "Limit write rate (bytes per second) to a device")
+	flags.Var(&copts.flDeviceReadIOps, "device-read-iops", "Limit read rate (IO per second) from a device")
+	flags.Var(&copts.flDeviceWriteIOps, "device-write-iops", "Limit write rate (IO per second) to a device")
+	flags.VarP(&copts.flVolumes, "volume", "v", "Bind mount a volume")
+	flags.Var(&copts.flTmpfs, "tmpfs", "Mount a tmpfs directory")
+	flags.Var(&copts.flLinks, "link", "Add link to another container")
+	flags.Var(&copts.flAliases, "net-alias", "Add network-scoped alias for the container")
+	flags.Var(&copts.flDevices, "device", "Add a host device to the container")
+	flags.VarP(&copts.flLabels, "label", "l", "Set meta data on a container")
+	flags.Var(&copts.flLabelsFile, "label-file", "Read in a line delimited file of labels")
+	flags.VarP(&copts.flEnv, "env", "e", "Set environment variables")
+	flags.Var(&copts.flEnvFile, "env-file", "Read in a file of environment variables")
+	flags.VarP(&copts.flPublish, "publish", "p", "Publish a container's port(s) to the host")
+	flags.Var(&copts.flExpose, "expose", "Expose a port or a range of ports")
+	flags.Var(&copts.flDNS, "dns", "Set custom DNS servers")
+	flags.Var(&copts.flDNSSearch, "dns-search", "Set custom DNS search domains")
+	flags.Var(&copts.flDNSOptions, "dns-opt", "Set DNS options")
+	flags.Var(&copts.flExtraHosts, "add-host", "Add a custom host-to-IP mapping (host:ip)")
+	flags.Var(&copts.flVolumesFrom, "volumes-from", "Mount volumes from the specified container(s)")
+	flags.Var(&copts.flCapAdd, "cap-add", "Add Linux capabilities")
+	flags.Var(&copts.flCapDrop, "cap-drop", "Drop Linux capabilities")
+	flags.Var(&copts.flGroupAdd, "group-add", "Add additional groups to join")
+	flags.Var(&copts.flSecurityOpt, "security-opt", "Security Options")
+	flags.Var(&copts.flStorageOpt, "storage-opt", "Set storage driver options per container")
+	flags.Var(copts.flUlimits, "ulimit", "Ulimit options")
+	flags.Var(copts.flSysctls, "sysctl", "Sysctl options")
+	flags.Var(&copts.flLoggingOpts, "log-opt", "Log driver options")
+
+	return copts
+}
+
+// Parse parses the args for the specified command and generates a Config,
 // a HostConfig and returns them with the specified command.
 // a HostConfig and returns them with the specified command.
 // If the specified args are not valid, it will return an error.
 // If the specified args are not valid, it will return an error.
-func Parse(cmd *flag.FlagSet, args []string) (*container.Config, *container.HostConfig, *networktypes.NetworkingConfig, *flag.FlagSet, error) {
-	var (
-		// FIXME: use utils.ListOpts for attach and volumes?
-		flAttach            = opts.NewListOpts(ValidateAttach)
-		flVolumes           = opts.NewListOpts(nil)
-		flTmpfs             = opts.NewListOpts(nil)
-		flBlkioWeightDevice = NewWeightdeviceOpt(ValidateWeightDevice)
-		flDeviceReadBps     = NewThrottledeviceOpt(ValidateThrottleBpsDevice)
-		flDeviceWriteBps    = NewThrottledeviceOpt(ValidateThrottleBpsDevice)
-		flLinks             = opts.NewListOpts(ValidateLink)
-		flAliases           = opts.NewListOpts(nil)
-		flDeviceReadIOps    = NewThrottledeviceOpt(ValidateThrottleIOpsDevice)
-		flDeviceWriteIOps   = NewThrottledeviceOpt(ValidateThrottleIOpsDevice)
-		flEnv               = opts.NewListOpts(ValidateEnv)
-		flLabels            = opts.NewListOpts(ValidateEnv)
-		flDevices           = opts.NewListOpts(ValidateDevice)
-
-		flUlimits = NewUlimitOpt(nil)
-		flSysctls = opts.NewMapOpts(nil, opts.ValidateSysctl)
-
-		flPublish           = opts.NewListOpts(nil)
-		flExpose            = opts.NewListOpts(nil)
-		flDNS               = opts.NewListOpts(opts.ValidateIPAddress)
-		flDNSSearch         = opts.NewListOpts(opts.ValidateDNSSearch)
-		flDNSOptions        = opts.NewListOpts(nil)
-		flExtraHosts        = opts.NewListOpts(ValidateExtraHost)
-		flVolumesFrom       = opts.NewListOpts(nil)
-		flEnvFile           = opts.NewListOpts(nil)
-		flCapAdd            = opts.NewListOpts(nil)
-		flCapDrop           = opts.NewListOpts(nil)
-		flGroupAdd          = opts.NewListOpts(nil)
-		flSecurityOpt       = opts.NewListOpts(nil)
-		flStorageOpt        = opts.NewListOpts(nil)
-		flLabelsFile        = opts.NewListOpts(nil)
-		flLoggingOpts       = opts.NewListOpts(nil)
-		flPrivileged        = cmd.Bool([]string{"-privileged"}, false, "Give extended privileges to this container")
-		flPidMode           = cmd.String([]string{"-pid"}, "", "PID namespace to use")
-		flUTSMode           = cmd.String([]string{"-uts"}, "", "UTS namespace to use")
-		flUsernsMode        = cmd.String([]string{"-userns"}, "", "User namespace to use")
-		flPublishAll        = cmd.Bool([]string{"P", "-publish-all"}, false, "Publish all exposed ports to random ports")
-		flStdin             = cmd.Bool([]string{"i", "-interactive"}, false, "Keep STDIN open even if not attached")
-		flTty               = cmd.Bool([]string{"t", "-tty"}, false, "Allocate a pseudo-TTY")
-		flOomKillDisable    = cmd.Bool([]string{"-oom-kill-disable"}, false, "Disable OOM Killer")
-		flOomScoreAdj       = cmd.Int([]string{"-oom-score-adj"}, 0, "Tune host's OOM preferences (-1000 to 1000)")
-		flContainerIDFile   = cmd.String([]string{"-cidfile"}, "", "Write the container ID to the file")
-		flEntrypoint        = cmd.String([]string{"-entrypoint"}, "", "Overwrite the default ENTRYPOINT of the image")
-		flHostname          = cmd.String([]string{"h", "-hostname"}, "", "Container host name")
-		flMemoryString      = cmd.String([]string{"m", "-memory"}, "", "Memory limit")
-		flMemoryReservation = cmd.String([]string{"-memory-reservation"}, "", "Memory soft limit")
-		flMemorySwap        = cmd.String([]string{"-memory-swap"}, "", "Swap limit equal to memory plus swap: '-1' to enable unlimited swap")
-		flKernelMemory      = cmd.String([]string{"-kernel-memory"}, "", "Kernel memory limit")
-		flUser              = cmd.String([]string{"u", "-user"}, "", "Username or UID (format: <name|uid>[:<group|gid>])")
-		flWorkingDir        = cmd.String([]string{"w", "-workdir"}, "", "Working directory inside the container")
-		flCPUShares         = cmd.Int64([]string{"c", "-cpu-shares"}, 0, "CPU shares (relative weight)")
-		flCPUPercent        = cmd.Int64([]string{"-cpu-percent"}, 0, "CPU percent (Windows only)")
-		flCPUPeriod         = cmd.Int64([]string{"-cpu-period"}, 0, "Limit CPU CFS (Completely Fair Scheduler) period")
-		flCPUQuota          = cmd.Int64([]string{"-cpu-quota"}, 0, "Limit CPU CFS (Completely Fair Scheduler) quota")
-		flCpusetCpus        = cmd.String([]string{"-cpuset-cpus"}, "", "CPUs in which to allow execution (0-3, 0,1)")
-		flCpusetMems        = cmd.String([]string{"-cpuset-mems"}, "", "MEMs in which to allow execution (0-3, 0,1)")
-		flBlkioWeight       = cmd.Uint16([]string{"-blkio-weight"}, 0, "Block IO (relative weight), between 10 and 1000")
-		flIOMaxBandwidth    = cmd.String([]string{"-io-maxbandwidth"}, "", "Maximum IO bandwidth limit for the system drive (Windows only)")
-		flIOMaxIOps         = cmd.Uint64([]string{"-io-maxiops"}, 0, "Maximum IOps limit for the system drive (Windows only)")
-		flSwappiness        = cmd.Int64([]string{"-memory-swappiness"}, -1, "Tune container memory swappiness (0 to 100)")
-		flNetMode           = cmd.String([]string{"-net"}, "default", "Connect a container to a network")
-		flMacAddress        = cmd.String([]string{"-mac-address"}, "", "Container MAC address (e.g. 92:d0:c6:0a:29:33)")
-		flIPv4Address       = cmd.String([]string{"-ip"}, "", "Container IPv4 address (e.g. 172.30.100.104)")
-		flIPv6Address       = cmd.String([]string{"-ip6"}, "", "Container IPv6 address (e.g. 2001:db8::33)")
-		flIpcMode           = cmd.String([]string{"-ipc"}, "", "IPC namespace to use")
-		flPidsLimit         = cmd.Int64([]string{"-pids-limit"}, 0, "Tune container pids limit (set -1 for unlimited)")
-		flRestartPolicy     = cmd.String([]string{"-restart"}, "no", "Restart policy to apply when a container exits")
-		flReadonlyRootfs    = cmd.Bool([]string{"-read-only"}, false, "Mount the container's root filesystem as read only")
-		flLoggingDriver     = cmd.String([]string{"-log-driver"}, "", "Logging driver for container")
-		flCgroupParent      = cmd.String([]string{"-cgroup-parent"}, "", "Optional parent cgroup for the container")
-		flVolumeDriver      = cmd.String([]string{"-volume-driver"}, "", "Optional volume driver for the container")
-		flStopSignal        = cmd.String([]string{"-stop-signal"}, signal.DefaultStopSignal, fmt.Sprintf("Signal to stop a container, %v by default", signal.DefaultStopSignal))
-		flIsolation         = cmd.String([]string{"-isolation"}, "", "Container isolation technology")
-		flShmSize           = cmd.String([]string{"-shm-size"}, "", "Size of /dev/shm, default value is 64MB")
-		// Healthcheck
-		flNoHealthcheck  = cmd.Bool([]string{"-no-healthcheck"}, false, "Disable any container-specified HEALTHCHECK")
-		flHealthCmd      = cmd.String([]string{"-health-cmd"}, "", "Command to run to check health")
-		flHealthInterval = cmd.Duration([]string{"-health-interval"}, 0, "Time between running the check")
-		flHealthTimeout  = cmd.Duration([]string{"-health-timeout"}, 0, "Maximum time to allow one check to run")
-		flHealthRetries  = cmd.Int([]string{"-health-retries"}, 0, "Consecutive failures needed to report unhealthy")
-	)
-
-	cmd.Var(&flAttach, []string{"a", "-attach"}, "Attach to STDIN, STDOUT or STDERR")
-	cmd.Var(&flBlkioWeightDevice, []string{"-blkio-weight-device"}, "Block IO weight (relative device weight)")
-	cmd.Var(&flDeviceReadBps, []string{"-device-read-bps"}, "Limit read rate (bytes per second) from a device")
-	cmd.Var(&flDeviceWriteBps, []string{"-device-write-bps"}, "Limit write rate (bytes per second) to a device")
-	cmd.Var(&flDeviceReadIOps, []string{"-device-read-iops"}, "Limit read rate (IO per second) from a device")
-	cmd.Var(&flDeviceWriteIOps, []string{"-device-write-iops"}, "Limit write rate (IO per second) to a device")
-	cmd.Var(&flVolumes, []string{"v", "-volume"}, "Bind mount a volume")
-	cmd.Var(&flTmpfs, []string{"-tmpfs"}, "Mount a tmpfs directory")
-	cmd.Var(&flLinks, []string{"-link"}, "Add link to another container")
-	cmd.Var(&flAliases, []string{"-net-alias"}, "Add network-scoped alias for the container")
-	cmd.Var(&flDevices, []string{"-device"}, "Add a host device to the container")
-	cmd.Var(&flLabels, []string{"l", "-label"}, "Set meta data on a container")
-	cmd.Var(&flLabelsFile, []string{"-label-file"}, "Read in a line delimited file of labels")
-	cmd.Var(&flEnv, []string{"e", "-env"}, "Set environment variables")
-	cmd.Var(&flEnvFile, []string{"-env-file"}, "Read in a file of environment variables")
-	cmd.Var(&flPublish, []string{"p", "-publish"}, "Publish a container's port(s) to the host")
-	cmd.Var(&flExpose, []string{"-expose"}, "Expose a port or a range of ports")
-	cmd.Var(&flDNS, []string{"-dns"}, "Set custom DNS servers")
-	cmd.Var(&flDNSSearch, []string{"-dns-search"}, "Set custom DNS search domains")
-	cmd.Var(&flDNSOptions, []string{"-dns-opt"}, "Set DNS options")
-	cmd.Var(&flExtraHosts, []string{"-add-host"}, "Add a custom host-to-IP mapping (host:ip)")
-	cmd.Var(&flVolumesFrom, []string{"-volumes-from"}, "Mount volumes from the specified container(s)")
-	cmd.Var(&flCapAdd, []string{"-cap-add"}, "Add Linux capabilities")
-	cmd.Var(&flCapDrop, []string{"-cap-drop"}, "Drop Linux capabilities")
-	cmd.Var(&flGroupAdd, []string{"-group-add"}, "Add additional groups to join")
-	cmd.Var(&flSecurityOpt, []string{"-security-opt"}, "Security Options")
-	cmd.Var(&flStorageOpt, []string{"-storage-opt"}, "Set storage driver options per container")
-	cmd.Var(flUlimits, []string{"-ulimit"}, "Ulimit options")
-	cmd.Var(flSysctls, []string{"-sysctl"}, "Sysctl options")
-	cmd.Var(&flLoggingOpts, []string{"-log-opt"}, "Log driver options")
-
-	cmd.Require(flag.Min, 1)
-
-	if err := cmd.ParseFlags(args, true); err != nil {
-		return nil, nil, nil, cmd, err
-	}
+func Parse(flags *pflag.FlagSet, copts *ContainerOptions) (*container.Config, *container.HostConfig, *networktypes.NetworkingConfig, error) {
 
 
 	var (
 	var (
-		attachStdin  = flAttach.Get("stdin")
-		attachStdout = flAttach.Get("stdout")
-		attachStderr = flAttach.Get("stderr")
+		attachStdin  = copts.flAttach.Get("stdin")
+		attachStdout = copts.flAttach.Get("stdout")
+		attachStderr = copts.flAttach.Get("stderr")
 	)
 	)
 
 
 	// Validate the input mac address
 	// Validate the input mac address
-	if *flMacAddress != "" {
-		if _, err := ValidateMACAddress(*flMacAddress); err != nil {
-			return nil, nil, nil, cmd, fmt.Errorf("%s is not a valid mac address", *flMacAddress)
+	if *copts.flMacAddress != "" {
+		if _, err := ValidateMACAddress(*copts.flMacAddress); err != nil {
+			return nil, nil, nil, fmt.Errorf("%s is not a valid mac address", *copts.flMacAddress)
 		}
 		}
 	}
 	}
-	if *flStdin {
+	if *copts.flStdin {
 		attachStdin = true
 		attachStdin = true
 	}
 	}
 	// If -a is not set, attach to stdout and stderr
 	// If -a is not set, attach to stdout and stderr
-	if flAttach.Len() == 0 {
+	if copts.flAttach.Len() == 0 {
 		attachStdout = true
 		attachStdout = true
 		attachStderr = true
 		attachStderr = true
 	}
 	}
@@ -169,83 +254,83 @@ func Parse(cmd *flag.FlagSet, args []string) (*container.Config, *container.Host
 	var err error
 	var err error
 
 
 	var flMemory int64
 	var flMemory int64
-	if *flMemoryString != "" {
-		flMemory, err = units.RAMInBytes(*flMemoryString)
+	if *copts.flMemoryString != "" {
+		flMemory, err = units.RAMInBytes(*copts.flMemoryString)
 		if err != nil {
 		if err != nil {
-			return nil, nil, nil, cmd, err
+			return nil, nil, nil, err
 		}
 		}
 	}
 	}
 
 
 	var MemoryReservation int64
 	var MemoryReservation int64
-	if *flMemoryReservation != "" {
-		MemoryReservation, err = units.RAMInBytes(*flMemoryReservation)
+	if *copts.flMemoryReservation != "" {
+		MemoryReservation, err = units.RAMInBytes(*copts.flMemoryReservation)
 		if err != nil {
 		if err != nil {
-			return nil, nil, nil, cmd, err
+			return nil, nil, nil, err
 		}
 		}
 	}
 	}
 
 
 	var memorySwap int64
 	var memorySwap int64
-	if *flMemorySwap != "" {
-		if *flMemorySwap == "-1" {
+	if *copts.flMemorySwap != "" {
+		if *copts.flMemorySwap == "-1" {
 			memorySwap = -1
 			memorySwap = -1
 		} else {
 		} else {
-			memorySwap, err = units.RAMInBytes(*flMemorySwap)
+			memorySwap, err = units.RAMInBytes(*copts.flMemorySwap)
 			if err != nil {
 			if err != nil {
-				return nil, nil, nil, cmd, err
+				return nil, nil, nil, err
 			}
 			}
 		}
 		}
 	}
 	}
 
 
 	var KernelMemory int64
 	var KernelMemory int64
-	if *flKernelMemory != "" {
-		KernelMemory, err = units.RAMInBytes(*flKernelMemory)
+	if *copts.flKernelMemory != "" {
+		KernelMemory, err = units.RAMInBytes(*copts.flKernelMemory)
 		if err != nil {
 		if err != nil {
-			return nil, nil, nil, cmd, err
+			return nil, nil, nil, err
 		}
 		}
 	}
 	}
 
 
-	swappiness := *flSwappiness
+	swappiness := *copts.flSwappiness
 	if swappiness != -1 && (swappiness < 0 || swappiness > 100) {
 	if swappiness != -1 && (swappiness < 0 || swappiness > 100) {
-		return nil, nil, nil, cmd, fmt.Errorf("invalid value: %d. Valid memory swappiness range is 0-100", swappiness)
+		return nil, nil, nil, fmt.Errorf("invalid value: %d. Valid memory swappiness range is 0-100", swappiness)
 	}
 	}
 
 
 	var shmSize int64
 	var shmSize int64
-	if *flShmSize != "" {
-		shmSize, err = units.RAMInBytes(*flShmSize)
+	if *copts.flShmSize != "" {
+		shmSize, err = units.RAMInBytes(*copts.flShmSize)
 		if err != nil {
 		if err != nil {
-			return nil, nil, nil, cmd, err
+			return nil, nil, nil, err
 		}
 		}
 	}
 	}
 
 
 	// TODO FIXME units.RAMInBytes should have a uint64 version
 	// TODO FIXME units.RAMInBytes should have a uint64 version
 	var maxIOBandwidth int64
 	var maxIOBandwidth int64
-	if *flIOMaxBandwidth != "" {
-		maxIOBandwidth, err = units.RAMInBytes(*flIOMaxBandwidth)
+	if *copts.flIOMaxBandwidth != "" {
+		maxIOBandwidth, err = units.RAMInBytes(*copts.flIOMaxBandwidth)
 		if err != nil {
 		if err != nil {
-			return nil, nil, nil, cmd, err
+			return nil, nil, nil, err
 		}
 		}
 		if maxIOBandwidth < 0 {
 		if maxIOBandwidth < 0 {
-			return nil, nil, nil, cmd, fmt.Errorf("invalid value: %s. Maximum IO Bandwidth must be positive", *flIOMaxBandwidth)
+			return nil, nil, nil, fmt.Errorf("invalid value: %s. Maximum IO Bandwidth must be positive", *copts.flIOMaxBandwidth)
 		}
 		}
 	}
 	}
 
 
 	var binds []string
 	var binds []string
 	// add any bind targets to the list of container volumes
 	// add any bind targets to the list of container volumes
-	for bind := range flVolumes.GetMap() {
+	for bind := range copts.flVolumes.GetMap() {
 		if arr := volumeSplitN(bind, 2); len(arr) > 1 {
 		if arr := volumeSplitN(bind, 2); len(arr) > 1 {
-			// after creating the bind mount we want to delete it from the flVolumes values because
+			// after creating the bind mount we want to delete it from the copts.flVolumes values because
 			// we do not want bind mounts being committed to image configs
 			// we do not want bind mounts being committed to image configs
 			binds = append(binds, bind)
 			binds = append(binds, bind)
-			flVolumes.Delete(bind)
+			copts.flVolumes.Delete(bind)
 		}
 		}
 	}
 	}
 
 
 	// Can't evaluate options passed into --tmpfs until we actually mount
 	// Can't evaluate options passed into --tmpfs until we actually mount
 	tmpfs := make(map[string]string)
 	tmpfs := make(map[string]string)
-	for _, t := range flTmpfs.GetAll() {
+	for _, t := range copts.flTmpfs.GetAll() {
 		if arr := strings.SplitN(t, ":", 2); len(arr) > 1 {
 		if arr := strings.SplitN(t, ":", 2); len(arr) > 1 {
 			if _, _, err := mount.ParseTmpfsOptions(arr[1]); err != nil {
 			if _, _, err := mount.ParseTmpfsOptions(arr[1]); err != nil {
-				return nil, nil, nil, cmd, err
+				return nil, nil, nil, err
 			}
 			}
 			tmpfs[arr[0]] = arr[1]
 			tmpfs[arr[0]] = arr[1]
 		} else {
 		} else {
@@ -254,27 +339,25 @@ func Parse(cmd *flag.FlagSet, args []string) (*container.Config, *container.Host
 	}
 	}
 
 
 	var (
 	var (
-		parsedArgs = cmd.Args()
 		runCmd     strslice.StrSlice
 		runCmd     strslice.StrSlice
 		entrypoint strslice.StrSlice
 		entrypoint strslice.StrSlice
-		image      = cmd.Arg(0)
 	)
 	)
-	if len(parsedArgs) > 1 {
-		runCmd = strslice.StrSlice(parsedArgs[1:])
+	if len(copts.Args) > 0 {
+		runCmd = strslice.StrSlice(copts.Args)
 	}
 	}
-	if *flEntrypoint != "" {
-		entrypoint = strslice.StrSlice{*flEntrypoint}
+	if *copts.flEntrypoint != "" {
+		entrypoint = strslice.StrSlice{*copts.flEntrypoint}
 	}
 	}
 
 
-	ports, portBindings, err := nat.ParsePortSpecs(flPublish.GetAll())
+	ports, portBindings, err := nat.ParsePortSpecs(copts.flPublish.GetAll())
 	if err != nil {
 	if err != nil {
-		return nil, nil, nil, cmd, err
+		return nil, nil, nil, err
 	}
 	}
 
 
 	// Merge in exposed ports to the map of published ports
 	// Merge in exposed ports to the map of published ports
-	for _, e := range flExpose.GetAll() {
+	for _, e := range copts.flExpose.GetAll() {
 		if strings.Contains(e, ":") {
 		if strings.Contains(e, ":") {
-			return nil, nil, nil, cmd, fmt.Errorf("invalid port format for --expose: %s", e)
+			return nil, nil, nil, fmt.Errorf("invalid port format for --expose: %s", e)
 		}
 		}
 		//support two formats for expose, original format <portnum>/[<proto>] or <startport-endport>/[<proto>]
 		//support two formats for expose, original format <portnum>/[<proto>] or <startport-endport>/[<proto>]
 		proto, port := nat.SplitProtoPort(e)
 		proto, port := nat.SplitProtoPort(e)
@@ -282,12 +365,12 @@ func Parse(cmd *flag.FlagSet, args []string) (*container.Config, *container.Host
 		//if expose a port, the start and end port are the same
 		//if expose a port, the start and end port are the same
 		start, end, err := nat.ParsePortRange(port)
 		start, end, err := nat.ParsePortRange(port)
 		if err != nil {
 		if err != nil {
-			return nil, nil, nil, cmd, fmt.Errorf("invalid range format for --expose: %s, error: %s", e, err)
+			return nil, nil, nil, fmt.Errorf("invalid range format for --expose: %s, error: %s", e, err)
 		}
 		}
 		for i := start; i <= end; i++ {
 		for i := start; i <= end; i++ {
 			p, err := nat.NewPort(proto, strconv.FormatUint(i, 10))
 			p, err := nat.NewPort(proto, strconv.FormatUint(i, 10))
 			if err != nil {
 			if err != nil {
-				return nil, nil, nil, cmd, err
+				return nil, nil, nil, err
 			}
 			}
 			if _, exists := ports[p]; !exists {
 			if _, exists := ports[p]; !exists {
 				ports[p] = struct{}{}
 				ports[p] = struct{}{}
@@ -297,64 +380,64 @@ func Parse(cmd *flag.FlagSet, args []string) (*container.Config, *container.Host
 
 
 	// parse device mappings
 	// parse device mappings
 	deviceMappings := []container.DeviceMapping{}
 	deviceMappings := []container.DeviceMapping{}
-	for _, device := range flDevices.GetAll() {
+	for _, device := range copts.flDevices.GetAll() {
 		deviceMapping, err := ParseDevice(device)
 		deviceMapping, err := ParseDevice(device)
 		if err != nil {
 		if err != nil {
-			return nil, nil, nil, cmd, err
+			return nil, nil, nil, err
 		}
 		}
 		deviceMappings = append(deviceMappings, deviceMapping)
 		deviceMappings = append(deviceMappings, deviceMapping)
 	}
 	}
 
 
 	// collect all the environment variables for the container
 	// collect all the environment variables for the container
-	envVariables, err := readKVStrings(flEnvFile.GetAll(), flEnv.GetAll())
+	envVariables, err := readKVStrings(copts.flEnvFile.GetAll(), copts.flEnv.GetAll())
 	if err != nil {
 	if err != nil {
-		return nil, nil, nil, cmd, err
+		return nil, nil, nil, err
 	}
 	}
 
 
 	// collect all the labels for the container
 	// collect all the labels for the container
-	labels, err := readKVStrings(flLabelsFile.GetAll(), flLabels.GetAll())
+	labels, err := readKVStrings(copts.flLabelsFile.GetAll(), copts.flLabels.GetAll())
 	if err != nil {
 	if err != nil {
-		return nil, nil, nil, cmd, err
+		return nil, nil, nil, err
 	}
 	}
 
 
-	ipcMode := container.IpcMode(*flIpcMode)
+	ipcMode := container.IpcMode(*copts.flIpcMode)
 	if !ipcMode.Valid() {
 	if !ipcMode.Valid() {
-		return nil, nil, nil, cmd, fmt.Errorf("--ipc: invalid IPC mode")
+		return nil, nil, nil, fmt.Errorf("--ipc: invalid IPC mode")
 	}
 	}
 
 
-	pidMode := container.PidMode(*flPidMode)
+	pidMode := container.PidMode(*copts.flPidMode)
 	if !pidMode.Valid() {
 	if !pidMode.Valid() {
-		return nil, nil, nil, cmd, fmt.Errorf("--pid: invalid PID mode")
+		return nil, nil, nil, fmt.Errorf("--pid: invalid PID mode")
 	}
 	}
 
 
-	utsMode := container.UTSMode(*flUTSMode)
+	utsMode := container.UTSMode(*copts.flUTSMode)
 	if !utsMode.Valid() {
 	if !utsMode.Valid() {
-		return nil, nil, nil, cmd, fmt.Errorf("--uts: invalid UTS mode")
+		return nil, nil, nil, fmt.Errorf("--uts: invalid UTS mode")
 	}
 	}
 
 
-	usernsMode := container.UsernsMode(*flUsernsMode)
+	usernsMode := container.UsernsMode(*copts.flUsernsMode)
 	if !usernsMode.Valid() {
 	if !usernsMode.Valid() {
-		return nil, nil, nil, cmd, fmt.Errorf("--userns: invalid USER mode")
+		return nil, nil, nil, fmt.Errorf("--userns: invalid USER mode")
 	}
 	}
 
 
-	restartPolicy, err := ParseRestartPolicy(*flRestartPolicy)
+	restartPolicy, err := ParseRestartPolicy(*copts.flRestartPolicy)
 	if err != nil {
 	if err != nil {
-		return nil, nil, nil, cmd, err
+		return nil, nil, nil, err
 	}
 	}
 
 
-	loggingOpts, err := parseLoggingOpts(*flLoggingDriver, flLoggingOpts.GetAll())
+	loggingOpts, err := parseLoggingOpts(*copts.flLoggingDriver, copts.flLoggingOpts.GetAll())
 	if err != nil {
 	if err != nil {
-		return nil, nil, nil, cmd, err
+		return nil, nil, nil, err
 	}
 	}
 
 
-	securityOpts, err := parseSecurityOpts(flSecurityOpt.GetAll())
+	securityOpts, err := parseSecurityOpts(copts.flSecurityOpt.GetAll())
 	if err != nil {
 	if err != nil {
-		return nil, nil, nil, cmd, err
+		return nil, nil, nil, err
 	}
 	}
 
 
-	storageOpts, err := parseStorageOpts(flStorageOpt.GetAll())
+	storageOpts, err := parseStorageOpts(copts.flStorageOpt.GetAll())
 	if err != nil {
 	if err != nil {
-		return nil, nil, nil, cmd, err
+		return nil, nil, nil, err
 	}
 	}
 
 
 	// Healthcheck
 	// Healthcheck
@@ -391,96 +474,96 @@ func Parse(cmd *flag.FlagSet, args []string) (*container.Config, *container.Host
 	}
 	}
 
 
 	resources := container.Resources{
 	resources := container.Resources{
-		CgroupParent:         *flCgroupParent,
+		CgroupParent:         *copts.flCgroupParent,
 		Memory:               flMemory,
 		Memory:               flMemory,
 		MemoryReservation:    MemoryReservation,
 		MemoryReservation:    MemoryReservation,
 		MemorySwap:           memorySwap,
 		MemorySwap:           memorySwap,
-		MemorySwappiness:     flSwappiness,
+		MemorySwappiness:     copts.flSwappiness,
 		KernelMemory:         KernelMemory,
 		KernelMemory:         KernelMemory,
-		OomKillDisable:       flOomKillDisable,
-		CPUPercent:           *flCPUPercent,
-		CPUShares:            *flCPUShares,
-		CPUPeriod:            *flCPUPeriod,
-		CpusetCpus:           *flCpusetCpus,
-		CpusetMems:           *flCpusetMems,
-		CPUQuota:             *flCPUQuota,
-		PidsLimit:            *flPidsLimit,
-		BlkioWeight:          *flBlkioWeight,
-		BlkioWeightDevice:    flBlkioWeightDevice.GetList(),
-		BlkioDeviceReadBps:   flDeviceReadBps.GetList(),
-		BlkioDeviceWriteBps:  flDeviceWriteBps.GetList(),
-		BlkioDeviceReadIOps:  flDeviceReadIOps.GetList(),
-		BlkioDeviceWriteIOps: flDeviceWriteIOps.GetList(),
-		IOMaximumIOps:        *flIOMaxIOps,
+		OomKillDisable:       copts.flOomKillDisable,
+		CPUPercent:           *copts.flCPUPercent,
+		CPUShares:            *copts.flCPUShares,
+		CPUPeriod:            *copts.flCPUPeriod,
+		CpusetCpus:           *copts.flCpusetCpus,
+		CpusetMems:           *copts.flCpusetMems,
+		CPUQuota:             *copts.flCPUQuota,
+		PidsLimit:            *copts.flPidsLimit,
+		BlkioWeight:          *copts.flBlkioWeight,
+		BlkioWeightDevice:    copts.flBlkioWeightDevice.GetList(),
+		BlkioDeviceReadBps:   copts.flDeviceReadBps.GetList(),
+		BlkioDeviceWriteBps:  copts.flDeviceWriteBps.GetList(),
+		BlkioDeviceReadIOps:  copts.flDeviceReadIOps.GetList(),
+		BlkioDeviceWriteIOps: copts.flDeviceWriteIOps.GetList(),
+		IOMaximumIOps:        *copts.flIOMaxIOps,
 		IOMaximumBandwidth:   uint64(maxIOBandwidth),
 		IOMaximumBandwidth:   uint64(maxIOBandwidth),
-		Ulimits:              flUlimits.GetList(),
+		Ulimits:              copts.flUlimits.GetList(),
 		Devices:              deviceMappings,
 		Devices:              deviceMappings,
 	}
 	}
 
 
 	config := &container.Config{
 	config := &container.Config{
-		Hostname:     *flHostname,
+		Hostname:     *copts.flHostname,
 		ExposedPorts: ports,
 		ExposedPorts: ports,
-		User:         *flUser,
-		Tty:          *flTty,
+		User:         *copts.flUser,
+		Tty:          *copts.flTty,
 		// TODO: deprecated, it comes from -n, --networking
 		// TODO: deprecated, it comes from -n, --networking
 		// it's still needed internally to set the network to disabled
 		// it's still needed internally to set the network to disabled
 		// if e.g. bridge is none in daemon opts, and in inspect
 		// if e.g. bridge is none in daemon opts, and in inspect
 		NetworkDisabled: false,
 		NetworkDisabled: false,
-		OpenStdin:       *flStdin,
+		OpenStdin:       *copts.flStdin,
 		AttachStdin:     attachStdin,
 		AttachStdin:     attachStdin,
 		AttachStdout:    attachStdout,
 		AttachStdout:    attachStdout,
 		AttachStderr:    attachStderr,
 		AttachStderr:    attachStderr,
 		Env:             envVariables,
 		Env:             envVariables,
 		Cmd:             runCmd,
 		Cmd:             runCmd,
-		Image:           image,
-		Volumes:         flVolumes.GetMap(),
-		MacAddress:      *flMacAddress,
+		Image:           copts.Image,
+		Volumes:         copts.flVolumes.GetMap(),
+		MacAddress:      *copts.flMacAddress,
 		Entrypoint:      entrypoint,
 		Entrypoint:      entrypoint,
-		WorkingDir:      *flWorkingDir,
+		WorkingDir:      *copts.flWorkingDir,
 		Labels:          ConvertKVStringsToMap(labels),
 		Labels:          ConvertKVStringsToMap(labels),
 		Healthcheck:     healthConfig,
 		Healthcheck:     healthConfig,
 	}
 	}
-	if cmd.IsSet("-stop-signal") {
-		config.StopSignal = *flStopSignal
+	if flags.Changed("stop-signal") {
+		config.StopSignal = *copts.flStopSignal
 	}
 	}
 
 
 	hostConfig := &container.HostConfig{
 	hostConfig := &container.HostConfig{
 		Binds:           binds,
 		Binds:           binds,
-		ContainerIDFile: *flContainerIDFile,
-		OomScoreAdj:     *flOomScoreAdj,
-		Privileged:      *flPrivileged,
+		ContainerIDFile: *copts.flContainerIDFile,
+		OomScoreAdj:     *copts.flOomScoreAdj,
+		Privileged:      *copts.flPrivileged,
 		PortBindings:    portBindings,
 		PortBindings:    portBindings,
-		Links:           flLinks.GetAll(),
-		PublishAllPorts: *flPublishAll,
+		Links:           copts.flLinks.GetAll(),
+		PublishAllPorts: *copts.flPublishAll,
 		// Make sure the dns fields are never nil.
 		// Make sure the dns fields are never nil.
 		// New containers don't ever have those fields nil,
 		// New containers don't ever have those fields nil,
 		// but pre created containers can still have those nil values.
 		// but pre created containers can still have those nil values.
 		// See https://github.com/docker/docker/pull/17779
 		// See https://github.com/docker/docker/pull/17779
 		// for a more detailed explanation on why we don't want that.
 		// for a more detailed explanation on why we don't want that.
-		DNS:            flDNS.GetAllOrEmpty(),
-		DNSSearch:      flDNSSearch.GetAllOrEmpty(),
-		DNSOptions:     flDNSOptions.GetAllOrEmpty(),
-		ExtraHosts:     flExtraHosts.GetAll(),
-		VolumesFrom:    flVolumesFrom.GetAll(),
-		NetworkMode:    container.NetworkMode(*flNetMode),
+		DNS:            copts.flDNS.GetAllOrEmpty(),
+		DNSSearch:      copts.flDNSSearch.GetAllOrEmpty(),
+		DNSOptions:     copts.flDNSOptions.GetAllOrEmpty(),
+		ExtraHosts:     copts.flExtraHosts.GetAll(),
+		VolumesFrom:    copts.flVolumesFrom.GetAll(),
+		NetworkMode:    container.NetworkMode(*copts.flNetMode),
 		IpcMode:        ipcMode,
 		IpcMode:        ipcMode,
 		PidMode:        pidMode,
 		PidMode:        pidMode,
 		UTSMode:        utsMode,
 		UTSMode:        utsMode,
 		UsernsMode:     usernsMode,
 		UsernsMode:     usernsMode,
-		CapAdd:         strslice.StrSlice(flCapAdd.GetAll()),
-		CapDrop:        strslice.StrSlice(flCapDrop.GetAll()),
-		GroupAdd:       flGroupAdd.GetAll(),
+		CapAdd:         strslice.StrSlice(copts.flCapAdd.GetAll()),
+		CapDrop:        strslice.StrSlice(copts.flCapDrop.GetAll()),
+		GroupAdd:       copts.flGroupAdd.GetAll(),
 		RestartPolicy:  restartPolicy,
 		RestartPolicy:  restartPolicy,
 		SecurityOpt:    securityOpts,
 		SecurityOpt:    securityOpts,
 		StorageOpt:     storageOpts,
 		StorageOpt:     storageOpts,
-		ReadonlyRootfs: *flReadonlyRootfs,
-		LogConfig:      container.LogConfig{Type: *flLoggingDriver, Config: loggingOpts},
-		VolumeDriver:   *flVolumeDriver,
-		Isolation:      container.Isolation(*flIsolation),
+		ReadonlyRootfs: *copts.flReadonlyRootfs,
+		LogConfig:      container.LogConfig{Type: *copts.flLoggingDriver, Config: loggingOpts},
+		VolumeDriver:   *copts.flVolumeDriver,
+		Isolation:      container.Isolation(*copts.flIsolation),
 		ShmSize:        shmSize,
 		ShmSize:        shmSize,
 		Resources:      resources,
 		Resources:      resources,
 		Tmpfs:          tmpfs,
 		Tmpfs:          tmpfs,
-		Sysctls:        flSysctls.GetAll(),
+		Sysctls:        copts.flSysctls.GetAll(),
 	}
 	}
 
 
 	// When allocating stdin in attached mode, close stdin at client disconnect
 	// When allocating stdin in attached mode, close stdin at client disconnect
@@ -492,11 +575,11 @@ func Parse(cmd *flag.FlagSet, args []string) (*container.Config, *container.Host
 		EndpointsConfig: make(map[string]*networktypes.EndpointSettings),
 		EndpointsConfig: make(map[string]*networktypes.EndpointSettings),
 	}
 	}
 
 
-	if *flIPv4Address != "" || *flIPv6Address != "" {
+	if *copts.flIPv4Address != "" || *copts.flIPv6Address != "" {
 		networkingConfig.EndpointsConfig[string(hostConfig.NetworkMode)] = &networktypes.EndpointSettings{
 		networkingConfig.EndpointsConfig[string(hostConfig.NetworkMode)] = &networktypes.EndpointSettings{
 			IPAMConfig: &networktypes.EndpointIPAMConfig{
 			IPAMConfig: &networktypes.EndpointIPAMConfig{
-				IPv4Address: *flIPv4Address,
-				IPv6Address: *flIPv6Address,
+				IPv4Address: *copts.flIPv4Address,
+				IPv6Address: *copts.flIPv6Address,
 			},
 			},
 		}
 		}
 	}
 	}
@@ -511,17 +594,17 @@ func Parse(cmd *flag.FlagSet, args []string) (*container.Config, *container.Host
 		networkingConfig.EndpointsConfig[string(hostConfig.NetworkMode)] = epConfig
 		networkingConfig.EndpointsConfig[string(hostConfig.NetworkMode)] = epConfig
 	}
 	}
 
 
-	if flAliases.Len() > 0 {
+	if copts.flAliases.Len() > 0 {
 		epConfig := networkingConfig.EndpointsConfig[string(hostConfig.NetworkMode)]
 		epConfig := networkingConfig.EndpointsConfig[string(hostConfig.NetworkMode)]
 		if epConfig == nil {
 		if epConfig == nil {
 			epConfig = &networktypes.EndpointSettings{}
 			epConfig = &networktypes.EndpointSettings{}
 		}
 		}
-		epConfig.Aliases = make([]string, flAliases.Len())
-		copy(epConfig.Aliases, flAliases.GetAll())
+		epConfig.Aliases = make([]string, copts.flAliases.Len())
+		copy(epConfig.Aliases, copts.flAliases.GetAll())
 		networkingConfig.EndpointsConfig[string(hostConfig.NetworkMode)] = epConfig
 		networkingConfig.EndpointsConfig[string(hostConfig.NetworkMode)] = epConfig
 	}
 	}
 
 
-	return config, hostConfig, networkingConfig, cmd, nil
+	return config, hostConfig, networkingConfig, nil
 }
 }
 
 
 // reads a file of line terminated key=value pairs, and overrides any keys
 // reads a file of line terminated key=value pairs, and overrides any keys

+ 38 - 33
runconfig/opts/parse_test.go

@@ -11,22 +11,27 @@ import (
 	"testing"
 	"testing"
 	"time"
 	"time"
 
 
-	flag "github.com/docker/docker/pkg/mflag"
 	"github.com/docker/docker/runconfig"
 	"github.com/docker/docker/runconfig"
 	"github.com/docker/engine-api/types/container"
 	"github.com/docker/engine-api/types/container"
 	networktypes "github.com/docker/engine-api/types/network"
 	networktypes "github.com/docker/engine-api/types/network"
 	"github.com/docker/go-connections/nat"
 	"github.com/docker/go-connections/nat"
+	"github.com/spf13/pflag"
 )
 )
 
 
-func parseRun(args []string) (*container.Config, *container.HostConfig, *networktypes.NetworkingConfig, *flag.FlagSet, error) {
-	cmd := flag.NewFlagSet("run", flag.ContinueOnError)
-	cmd.SetOutput(ioutil.Discard)
-	cmd.Usage = nil
-	return Parse(cmd, args)
+// TODO: drop FlagSet from return value
+func parseRun(args []string) (*container.Config, *container.HostConfig, *networktypes.NetworkingConfig, error) {
+	flags := pflag.NewFlagSet("run", pflag.ContinueOnError)
+	flags.SetOutput(ioutil.Discard)
+	flags.Usage = nil
+	copts := AddFlags(flags)
+	if err := flags.Parse(args); err != nil {
+		return nil, nil, nil, err
+	}
+	return Parse(flags, copts)
 }
 }
 
 
 func parse(t *testing.T, args string) (*container.Config, *container.HostConfig, error) {
 func parse(t *testing.T, args string) (*container.Config, *container.HostConfig, error) {
-	config, hostConfig, _, _, err := parseRun(strings.Split(args+" ubuntu bash", " "))
+	config, hostConfig, _, err := parseRun(strings.Split(args+" ubuntu bash", " "))
 	return config, hostConfig, err
 	return config, hostConfig, err
 }
 }
 
 
@@ -351,7 +356,7 @@ func setupPlatformVolume(u []string, w []string) ([]string, string) {
 func TestParseWithMacAddress(t *testing.T) {
 func TestParseWithMacAddress(t *testing.T) {
 	invalidMacAddress := "--mac-address=invalidMacAddress"
 	invalidMacAddress := "--mac-address=invalidMacAddress"
 	validMacAddress := "--mac-address=92:d0:c6:0a:29:33"
 	validMacAddress := "--mac-address=92:d0:c6:0a:29:33"
-	if _, _, _, _, err := parseRun([]string{invalidMacAddress, "img", "cmd"}); err != nil && err.Error() != "invalidMacAddress is not a valid mac address" {
+	if _, _, _, err := parseRun([]string{invalidMacAddress, "img", "cmd"}); err != nil && err.Error() != "invalidMacAddress is not a valid mac address" {
 		t.Fatalf("Expected an error with %v mac-address, got %v", invalidMacAddress, err)
 		t.Fatalf("Expected an error with %v mac-address, got %v", invalidMacAddress, err)
 	}
 	}
 	if config, _ := mustParse(t, validMacAddress); config.MacAddress != "92:d0:c6:0a:29:33" {
 	if config, _ := mustParse(t, validMacAddress); config.MacAddress != "92:d0:c6:0a:29:33" {
@@ -362,7 +367,7 @@ func TestParseWithMacAddress(t *testing.T) {
 func TestParseWithMemory(t *testing.T) {
 func TestParseWithMemory(t *testing.T) {
 	invalidMemory := "--memory=invalid"
 	invalidMemory := "--memory=invalid"
 	validMemory := "--memory=1G"
 	validMemory := "--memory=1G"
-	if _, _, _, _, err := parseRun([]string{invalidMemory, "img", "cmd"}); err != nil && err.Error() != "invalid size: 'invalid'" {
+	if _, _, _, err := parseRun([]string{invalidMemory, "img", "cmd"}); err != nil && err.Error() != "invalid size: 'invalid'" {
 		t.Fatalf("Expected an error with '%v' Memory, got '%v'", invalidMemory, err)
 		t.Fatalf("Expected an error with '%v' Memory, got '%v'", invalidMemory, err)
 	}
 	}
 	if _, hostconfig := mustParse(t, validMemory); hostconfig.Memory != 1073741824 {
 	if _, hostconfig := mustParse(t, validMemory); hostconfig.Memory != 1073741824 {
@@ -374,7 +379,7 @@ func TestParseWithMemorySwap(t *testing.T) {
 	invalidMemory := "--memory-swap=invalid"
 	invalidMemory := "--memory-swap=invalid"
 	validMemory := "--memory-swap=1G"
 	validMemory := "--memory-swap=1G"
 	anotherValidMemory := "--memory-swap=-1"
 	anotherValidMemory := "--memory-swap=-1"
-	if _, _, _, _, err := parseRun([]string{invalidMemory, "img", "cmd"}); err == nil || err.Error() != "invalid size: 'invalid'" {
+	if _, _, _, err := parseRun([]string{invalidMemory, "img", "cmd"}); err == nil || err.Error() != "invalid size: 'invalid'" {
 		t.Fatalf("Expected an error with '%v' MemorySwap, got '%v'", invalidMemory, err)
 		t.Fatalf("Expected an error with '%v' MemorySwap, got '%v'", invalidMemory, err)
 	}
 	}
 	if _, hostconfig := mustParse(t, validMemory); hostconfig.MemorySwap != 1073741824 {
 	if _, hostconfig := mustParse(t, validMemory); hostconfig.MemorySwap != 1073741824 {
@@ -427,12 +432,12 @@ func TestParseWithExpose(t *testing.T) {
 		"8080-8082/tcp": {"8080/tcp", "8081/tcp", "8082/tcp"},
 		"8080-8082/tcp": {"8080/tcp", "8081/tcp", "8082/tcp"},
 	}
 	}
 	for expose, expectedError := range invalids {
 	for expose, expectedError := range invalids {
-		if _, _, _, _, err := parseRun([]string{fmt.Sprintf("--expose=%v", expose), "img", "cmd"}); err == nil || err.Error() != expectedError {
+		if _, _, _, err := parseRun([]string{fmt.Sprintf("--expose=%v", expose), "img", "cmd"}); err == nil || err.Error() != expectedError {
 			t.Fatalf("Expected error '%v' with '--expose=%v', got '%v'", expectedError, expose, err)
 			t.Fatalf("Expected error '%v' with '--expose=%v', got '%v'", expectedError, expose, err)
 		}
 		}
 	}
 	}
 	for expose, exposedPorts := range valids {
 	for expose, exposedPorts := range valids {
-		config, _, _, _, err := parseRun([]string{fmt.Sprintf("--expose=%v", expose), "img", "cmd"})
+		config, _, _, err := parseRun([]string{fmt.Sprintf("--expose=%v", expose), "img", "cmd"})
 		if err != nil {
 		if err != nil {
 			t.Fatal(err)
 			t.Fatal(err)
 		}
 		}
@@ -446,7 +451,7 @@ func TestParseWithExpose(t *testing.T) {
 		}
 		}
 	}
 	}
 	// Merge with actual published port
 	// Merge with actual published port
-	config, _, _, _, err := parseRun([]string{"--publish=80", "--expose=80-81/tcp", "img", "cmd"})
+	config, _, _, err := parseRun([]string{"--publish=80", "--expose=80-81/tcp", "img", "cmd"})
 	if err != nil {
 	if err != nil {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
@@ -485,7 +490,7 @@ func TestParseDevice(t *testing.T) {
 		},
 		},
 	}
 	}
 	for device, deviceMapping := range valids {
 	for device, deviceMapping := range valids {
-		_, hostconfig, _, _, err := parseRun([]string{fmt.Sprintf("--device=%v", device), "img", "cmd"})
+		_, hostconfig, _, err := parseRun([]string{fmt.Sprintf("--device=%v", device), "img", "cmd"})
 		if err != nil {
 		if err != nil {
 			t.Fatal(err)
 			t.Fatal(err)
 		}
 		}
@@ -501,11 +506,11 @@ func TestParseDevice(t *testing.T) {
 
 
 func TestParseModes(t *testing.T) {
 func TestParseModes(t *testing.T) {
 	// ipc ko
 	// ipc ko
-	if _, _, _, _, err := parseRun([]string{"--ipc=container:", "img", "cmd"}); err == nil || err.Error() != "--ipc: invalid IPC mode" {
+	if _, _, _, err := parseRun([]string{"--ipc=container:", "img", "cmd"}); err == nil || err.Error() != "--ipc: invalid IPC mode" {
 		t.Fatalf("Expected an error with message '--ipc: invalid IPC mode', got %v", err)
 		t.Fatalf("Expected an error with message '--ipc: invalid IPC mode', got %v", err)
 	}
 	}
 	// ipc ok
 	// ipc ok
-	_, hostconfig, _, _, err := parseRun([]string{"--ipc=host", "img", "cmd"})
+	_, hostconfig, _, err := parseRun([]string{"--ipc=host", "img", "cmd"})
 	if err != nil {
 	if err != nil {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
@@ -513,11 +518,11 @@ func TestParseModes(t *testing.T) {
 		t.Fatalf("Expected a valid IpcMode, got %v", hostconfig.IpcMode)
 		t.Fatalf("Expected a valid IpcMode, got %v", hostconfig.IpcMode)
 	}
 	}
 	// pid ko
 	// pid ko
-	if _, _, _, _, err := parseRun([]string{"--pid=container:", "img", "cmd"}); err == nil || err.Error() != "--pid: invalid PID mode" {
+	if _, _, _, err := parseRun([]string{"--pid=container:", "img", "cmd"}); err == nil || err.Error() != "--pid: invalid PID mode" {
 		t.Fatalf("Expected an error with message '--pid: invalid PID mode', got %v", err)
 		t.Fatalf("Expected an error with message '--pid: invalid PID mode', got %v", err)
 	}
 	}
 	// pid ok
 	// pid ok
-	_, hostconfig, _, _, err = parseRun([]string{"--pid=host", "img", "cmd"})
+	_, hostconfig, _, err = parseRun([]string{"--pid=host", "img", "cmd"})
 	if err != nil {
 	if err != nil {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
@@ -525,11 +530,11 @@ func TestParseModes(t *testing.T) {
 		t.Fatalf("Expected a valid PidMode, got %v", hostconfig.PidMode)
 		t.Fatalf("Expected a valid PidMode, got %v", hostconfig.PidMode)
 	}
 	}
 	// uts ko
 	// uts ko
-	if _, _, _, _, err := parseRun([]string{"--uts=container:", "img", "cmd"}); err == nil || err.Error() != "--uts: invalid UTS mode" {
+	if _, _, _, err := parseRun([]string{"--uts=container:", "img", "cmd"}); err == nil || err.Error() != "--uts: invalid UTS mode" {
 		t.Fatalf("Expected an error with message '--uts: invalid UTS mode', got %v", err)
 		t.Fatalf("Expected an error with message '--uts: invalid UTS mode', got %v", err)
 	}
 	}
 	// uts ok
 	// uts ok
-	_, hostconfig, _, _, err = parseRun([]string{"--uts=host", "img", "cmd"})
+	_, hostconfig, _, err = parseRun([]string{"--uts=host", "img", "cmd"})
 	if err != nil {
 	if err != nil {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
@@ -537,11 +542,11 @@ func TestParseModes(t *testing.T) {
 		t.Fatalf("Expected a valid UTSMode, got %v", hostconfig.UTSMode)
 		t.Fatalf("Expected a valid UTSMode, got %v", hostconfig.UTSMode)
 	}
 	}
 	// shm-size ko
 	// shm-size ko
-	if _, _, _, _, err = parseRun([]string{"--shm-size=a128m", "img", "cmd"}); err == nil || err.Error() != "invalid size: 'a128m'" {
+	if _, _, _, err = parseRun([]string{"--shm-size=a128m", "img", "cmd"}); err == nil || err.Error() != "invalid size: 'a128m'" {
 		t.Fatalf("Expected an error with message 'invalid size: a128m', got %v", err)
 		t.Fatalf("Expected an error with message 'invalid size: a128m', got %v", err)
 	}
 	}
 	// shm-size ok
 	// shm-size ok
-	_, hostconfig, _, _, err = parseRun([]string{"--shm-size=128m", "img", "cmd"})
+	_, hostconfig, _, err = parseRun([]string{"--shm-size=128m", "img", "cmd"})
 	if err != nil {
 	if err != nil {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
@@ -570,12 +575,12 @@ func TestParseRestartPolicy(t *testing.T) {
 		},
 		},
 	}
 	}
 	for restart, expectedError := range invalids {
 	for restart, expectedError := range invalids {
-		if _, _, _, _, err := parseRun([]string{fmt.Sprintf("--restart=%s", restart), "img", "cmd"}); err == nil || err.Error() != expectedError {
+		if _, _, _, err := parseRun([]string{fmt.Sprintf("--restart=%s", restart), "img", "cmd"}); err == nil || err.Error() != expectedError {
 			t.Fatalf("Expected an error with message '%v' for %v, got %v", expectedError, restart, err)
 			t.Fatalf("Expected an error with message '%v' for %v, got %v", expectedError, restart, err)
 		}
 		}
 	}
 	}
 	for restart, expected := range valids {
 	for restart, expected := range valids {
-		_, hostconfig, _, _, err := parseRun([]string{fmt.Sprintf("--restart=%v", restart), "img", "cmd"})
+		_, hostconfig, _, err := parseRun([]string{fmt.Sprintf("--restart=%v", restart), "img", "cmd"})
 		if err != nil {
 		if err != nil {
 			t.Fatal(err)
 			t.Fatal(err)
 		}
 		}
@@ -626,11 +631,11 @@ func TestParseHealth(t *testing.T) {
 
 
 func TestParseLoggingOpts(t *testing.T) {
 func TestParseLoggingOpts(t *testing.T) {
 	// logging opts ko
 	// logging opts ko
-	if _, _, _, _, err := parseRun([]string{"--log-driver=none", "--log-opt=anything", "img", "cmd"}); err == nil || err.Error() != "invalid logging opts for driver none" {
+	if _, _, _, err := parseRun([]string{"--log-driver=none", "--log-opt=anything", "img", "cmd"}); err == nil || err.Error() != "invalid logging opts for driver none" {
 		t.Fatalf("Expected an error with message 'invalid logging opts for driver none', got %v", err)
 		t.Fatalf("Expected an error with message 'invalid logging opts for driver none', got %v", err)
 	}
 	}
 	// logging opts ok
 	// logging opts ok
-	_, hostconfig, _, _, err := parseRun([]string{"--log-driver=syslog", "--log-opt=something", "img", "cmd"})
+	_, hostconfig, _, err := parseRun([]string{"--log-driver=syslog", "--log-opt=something", "img", "cmd"})
 	if err != nil {
 	if err != nil {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
@@ -645,18 +650,18 @@ func TestParseEnvfileVariables(t *testing.T) {
 		e = "open nonexistent: The system cannot find the file specified."
 		e = "open nonexistent: The system cannot find the file specified."
 	}
 	}
 	// env ko
 	// env ko
-	if _, _, _, _, err := parseRun([]string{"--env-file=nonexistent", "img", "cmd"}); err == nil || err.Error() != e {
+	if _, _, _, err := parseRun([]string{"--env-file=nonexistent", "img", "cmd"}); err == nil || err.Error() != e {
 		t.Fatalf("Expected an error with message '%s', got %v", e, err)
 		t.Fatalf("Expected an error with message '%s', got %v", e, err)
 	}
 	}
 	// env ok
 	// env ok
-	config, _, _, _, err := parseRun([]string{"--env-file=fixtures/valid.env", "img", "cmd"})
+	config, _, _, err := parseRun([]string{"--env-file=fixtures/valid.env", "img", "cmd"})
 	if err != nil {
 	if err != nil {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
 	if len(config.Env) != 1 || config.Env[0] != "ENV1=value1" {
 	if len(config.Env) != 1 || config.Env[0] != "ENV1=value1" {
 		t.Fatalf("Expected a config with [ENV1=value1], got %v", config.Env)
 		t.Fatalf("Expected a config with [ENV1=value1], got %v", config.Env)
 	}
 	}
-	config, _, _, _, err = parseRun([]string{"--env-file=fixtures/valid.env", "--env=ENV2=value2", "img", "cmd"})
+	config, _, _, err = parseRun([]string{"--env-file=fixtures/valid.env", "--env=ENV2=value2", "img", "cmd"})
 	if err != nil {
 	if err != nil {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
@@ -671,18 +676,18 @@ func TestParseLabelfileVariables(t *testing.T) {
 		e = "open nonexistent: The system cannot find the file specified."
 		e = "open nonexistent: The system cannot find the file specified."
 	}
 	}
 	// label ko
 	// label ko
-	if _, _, _, _, err := parseRun([]string{"--label-file=nonexistent", "img", "cmd"}); err == nil || err.Error() != e {
+	if _, _, _, err := parseRun([]string{"--label-file=nonexistent", "img", "cmd"}); err == nil || err.Error() != e {
 		t.Fatalf("Expected an error with message '%s', got %v", e, err)
 		t.Fatalf("Expected an error with message '%s', got %v", e, err)
 	}
 	}
 	// label ok
 	// label ok
-	config, _, _, _, err := parseRun([]string{"--label-file=fixtures/valid.label", "img", "cmd"})
+	config, _, _, err := parseRun([]string{"--label-file=fixtures/valid.label", "img", "cmd"})
 	if err != nil {
 	if err != nil {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
 	if len(config.Labels) != 1 || config.Labels["LABEL1"] != "value1" {
 	if len(config.Labels) != 1 || config.Labels["LABEL1"] != "value1" {
 		t.Fatalf("Expected a config with [LABEL1:value1], got %v", config.Labels)
 		t.Fatalf("Expected a config with [LABEL1:value1], got %v", config.Labels)
 	}
 	}
-	config, _, _, _, err = parseRun([]string{"--label-file=fixtures/valid.label", "--label=LABEL2=value2", "img", "cmd"})
+	config, _, _, err = parseRun([]string{"--label-file=fixtures/valid.label", "--label=LABEL2=value2", "img", "cmd"})
 	if err != nil {
 	if err != nil {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
@@ -692,7 +697,7 @@ func TestParseLabelfileVariables(t *testing.T) {
 }
 }
 
 
 func TestParseEntryPoint(t *testing.T) {
 func TestParseEntryPoint(t *testing.T) {
-	config, _, _, _, err := parseRun([]string{"--entrypoint=anything", "cmd", "img"})
+	config, _, _, err := parseRun([]string{"--entrypoint=anything", "cmd", "img"})
 	if err != nil {
 	if err != nil {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}

+ 5 - 0
runconfig/opts/throttledevice.go

@@ -106,3 +106,8 @@ func (opt *ThrottledeviceOpt) GetList() []*blkiodev.ThrottleDevice {
 
 
 	return throttledevice
 	return throttledevice
 }
 }
+
+// Type returns the option type
+func (opt *ThrottledeviceOpt) Type() string {
+	return "throttled-device"
+}

+ 5 - 0
runconfig/opts/ulimit.go

@@ -50,3 +50,8 @@ func (o *UlimitOpt) GetList() []*units.Ulimit {
 
 
 	return ulimits
 	return ulimits
 }
 }
+
+// Type returns the option type
+func (o *UlimitOpt) Type() string {
+	return "ulimit"
+}

+ 5 - 0
runconfig/opts/weightdevice.go

@@ -82,3 +82,8 @@ func (opt *WeightdeviceOpt) GetList() []*blkiodev.WeightDevice {
 
 
 	return weightdevice
 	return weightdevice
 }
 }
+
+// Type returns the option type
+func (opt *WeightdeviceOpt) Type() string {
+	return "weighted-device"
+}