فهرست منبع

Merge pull request #23253 from vdemeester/carry-pr-23159

Carry #23159 : Use spf13/cobra for `docker run` and `docker create`
Sebastiaan van Stijn 9 سال پیش
والد
کامیت
6b4a46f282

+ 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
 	}
 	}

+ 5 - 5
api/client/build.go

@@ -172,10 +172,10 @@ func (cli *DockerCli) CmdBuild(args ...string) error {
 	ctx := context.Background()
 	ctx := context.Background()
 
 
 	var resolvedTags []*resolvedTag
 	var resolvedTags []*resolvedTag
-	if isTrusted() {
+	if IsTrusted() {
 		// Wrap the tar archive to replace the Dockerfile entry with the rewritten
 		// Wrap the tar archive to replace the Dockerfile entry with the rewritten
 		// Dockerfile which uses trusted pulls.
 		// Dockerfile which uses trusted pulls.
-		buildCtx = replaceDockerfileTarWrapper(ctx, buildCtx, relDockerfile, cli.trustedReference, &resolvedTags)
+		buildCtx = replaceDockerfileTarWrapper(ctx, buildCtx, relDockerfile, cli.TrustedReference, &resolvedTags)
 	}
 	}
 
 
 	// Setup an upload progress bar
 	// Setup an upload progress bar
@@ -269,11 +269,11 @@ func (cli *DockerCli) CmdBuild(args ...string) error {
 		fmt.Fprintf(cli.out, "%s", buildBuff)
 		fmt.Fprintf(cli.out, "%s", buildBuff)
 	}
 	}
 
 
-	if isTrusted() {
+	if IsTrusted() {
 		// Since the build was successful, now we must tag any of the resolved
 		// Since the build was successful, now we must tag any of the resolved
 		// images from the above Dockerfile rewrite.
 		// images from the above Dockerfile rewrite.
 		for _, resolved := range resolvedTags {
 		for _, resolved := range resolvedTags {
-			if err := cli.tagTrusted(ctx, resolved.digestRef, resolved.tagRef); err != nil {
+			if err := cli.TagTrusted(ctx, resolved.digestRef, resolved.tagRef); err != nil {
 				return err
 				return err
 			}
 			}
 		}
 		}
@@ -321,7 +321,7 @@ func rewriteDockerfileFrom(ctx context.Context, dockerfile io.Reader, translator
 				return nil, nil, err
 				return nil, nil, err
 			}
 			}
 			ref = reference.WithDefaultTag(ref)
 			ref = reference.WithDefaultTag(ref)
-			if ref, ok := ref.(reference.NamedTagged); ok && isTrusted() {
+			if ref, ok := ref.(reference.NamedTagged); ok && IsTrusted() {
 				trustedRef, err := translator(ctx, ref)
 				trustedRef, err := translator(ctx, ref)
 				if err != nil {
 				if err != nil {
 					return nil, nil, err
 					return nil, nil, err

+ 20 - 0
api/client/cli.go

@@ -75,6 +75,26 @@ 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
+}
+
+// OutFd returns the fd for the stdout stream
+func (cli *DockerCli) OutFd() uintptr {
+	return cli.outFd
+}
+
 // 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 - 2
api/client/commands.go

@@ -7,7 +7,6 @@ func (cli *DockerCli) Command(name string) func(...string) error {
 		"build":              cli.CmdBuild,
 		"build":              cli.CmdBuild,
 		"commit":             cli.CmdCommit,
 		"commit":             cli.CmdCommit,
 		"cp":                 cli.CmdCp,
 		"cp":                 cli.CmdCp,
-		"create":             cli.CmdCreate,
 		"diff":               cli.CmdDiff,
 		"diff":               cli.CmdDiff,
 		"events":             cli.CmdEvents,
 		"events":             cli.CmdEvents,
 		"exec":               cli.CmdExec,
 		"exec":               cli.CmdExec,
@@ -38,7 +37,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,

+ 216 - 0
api/client/container/create.go

@@ -0,0 +1,216 @@
+package container
+
+import (
+	"fmt"
+	"io"
+	"os"
+
+	"golang.org/x/net/context"
+
+	"github.com/docker/docker/api/client"
+	"github.com/docker/docker/cli"
+	"github.com/docker/docker/pkg/jsonmessage"
+	// FIXME migrate to docker/distribution/reference
+	"github.com/docker/docker/reference"
+	"github.com/docker/docker/registry"
+	runconfigopts "github.com/docker/docker/runconfig/opts"
+	apiclient "github.com/docker/engine-api/client"
+	"github.com/docker/engine-api/types"
+	"github.com/docker/engine-api/types/container"
+	networktypes "github.com/docker/engine-api/types/network"
+	"github.com/spf13/cobra"
+	"github.com/spf13/pflag"
+)
+
+type createOptions struct {
+	name string
+}
+
+// NewCreateCommand creats a new cobra.Command for `docker create`
+func NewCreateCommand(dockerCli *client.DockerCli) *cobra.Command {
+	var opts createOptions
+	var copts *runconfigopts.ContainerOptions
+
+	cmd := &cobra.Command{
+		Use:   "create [OPTIONS] IMAGE [COMMAND] [ARG...]",
+		Short: "Create 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 runCreate(dockerCli, cmd.Flags(), &opts, copts)
+		},
+	}
+	cmd.SetFlagErrorFunc(flagErrorFunc)
+
+	flags := cmd.Flags()
+	flags.StringVar(&opts.name, "name", "", "Assign a name to the 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
+}
+
+func runCreate(dockerCli *client.DockerCli, flags *pflag.FlagSet, opts *createOptions, copts *runconfigopts.ContainerOptions) error {
+	config, hostConfig, networkingConfig, err := runconfigopts.Parse(flags, copts)
+	if err != nil {
+		reportError(dockerCli.Err(), "create", err.Error(), true)
+		return cli.StatusError{StatusCode: 125}
+	}
+	response, err := createContainer(context.Background(), dockerCli, config, hostConfig, networkingConfig, hostConfig.ContainerIDFile, opts.name)
+	if err != nil {
+		return err
+	}
+	fmt.Fprintf(dockerCli.Out(), "%s\n", response.ID)
+	return nil
+}
+
+func pullImage(ctx context.Context, dockerCli *client.DockerCli, image string, out io.Writer) error {
+	ref, err := reference.ParseNamed(image)
+	if err != nil {
+		return err
+	}
+
+	// Resolve the Repository name from fqn to RepositoryInfo
+	repoInfo, err := registry.ParseRepositoryInfo(ref)
+	if err != nil {
+		return err
+	}
+
+	authConfig := dockerCli.ResolveAuthConfig(ctx, repoInfo.Index)
+	encodedAuth, err := client.EncodeAuthToBase64(authConfig)
+	if err != nil {
+		return err
+	}
+
+	options := types.ImageCreateOptions{
+		RegistryAuth: encodedAuth,
+	}
+
+	responseBody, err := dockerCli.Client().ImageCreate(ctx, image, options)
+	if err != nil {
+		return err
+	}
+	defer responseBody.Close()
+
+	return jsonmessage.DisplayJSONMessagesStream(
+		responseBody,
+		out,
+		dockerCli.OutFd(),
+		dockerCli.IsTerminalOut(),
+		nil)
+}
+
+type cidFile struct {
+	path    string
+	file    *os.File
+	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) {
+	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)
+	}
+
+	f, err := os.Create(path)
+	if err != nil {
+		return nil, fmt.Errorf("Failed to create the container ID file: %s", err)
+	}
+
+	return &cidFile{path: path, file: f}, nil
+}
+
+func createContainer(ctx context.Context, dockerCli *client.DockerCli, config *container.Config, hostConfig *container.HostConfig, networkingConfig *networktypes.NetworkingConfig, cidfile, name string) (*types.ContainerCreateResponse, error) {
+	stderr := dockerCli.Err()
+
+	var containerIDFile *cidFile
+	if cidfile != "" {
+		var err error
+		if containerIDFile, err = newCIDFile(cidfile); err != nil {
+			return nil, err
+		}
+		defer containerIDFile.Close()
+	}
+
+	var trustedRef reference.Canonical
+	_, ref, err := reference.ParseIDOrReference(config.Image)
+	if err != nil {
+		return nil, err
+	}
+	if ref != nil {
+		ref = reference.WithDefaultTag(ref)
+
+		if ref, ok := ref.(reference.NamedTagged); ok && client.IsTrusted() {
+			var err error
+			trustedRef, err = dockerCli.TrustedReference(ctx, ref)
+			if err != nil {
+				return nil, err
+			}
+			config.Image = trustedRef.String()
+		}
+	}
+
+	//create the container
+	response, err := dockerCli.Client().ContainerCreate(ctx, config, hostConfig, networkingConfig, name)
+
+	//if image not found try to pull it
+	if err != nil {
+		if apiclient.IsErrImageNotFound(err) && ref != nil {
+			fmt.Fprintf(stderr, "Unable to find image '%s' locally\n", ref.String())
+
+			// we don't want to write to stdout anything apart from container.ID
+			if err = pullImage(ctx, dockerCli, config.Image, stderr); err != nil {
+				return nil, err
+			}
+			if ref, ok := ref.(reference.NamedTagged); ok && trustedRef != nil {
+				if err := dockerCli.TagTrusted(ctx, trustedRef, ref); err != nil {
+					return nil, err
+				}
+			}
+			// Retry
+			var retryErr error
+			response, retryErr = dockerCli.Client().ContainerCreate(ctx, config, hostConfig, networkingConfig, name)
+			if retryErr != nil {
+				return nil, retryErr
+			}
+		} else {
+			return nil, err
+		}
+	}
+
+	for _, warning := range response.Warnings {
+		fmt.Fprintf(stderr, "WARNING: %s\n", warning)
+	}
+	if containerIDFile != nil {
+		if err = containerIDFile.Write(response.ID); err != nil {
+			return nil, err
+		}
+	}
+	return &response, nil
+}

+ 126 - 94
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,82 @@ const (
 	errCmdCouldNotBeInvoked = "could not be invoked"
 	errCmdCouldNotBeInvoked = "could not be invoked"
 )
 )
 
 
-func (cid *cidFile) Close() error {
-	cid.file.Close()
+type runOptions struct {
+	autoRemove bool
+	detach     bool
+	sigProxy   bool
+	name       string
+	detachKeys string
+}
 
 
-	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)
-		}
+// 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)
+		},
 	}
 	}
+	cmd.SetFlagErrorFunc(flagErrorFunc)
 
 
-	return nil
-}
+	flags := cmd.Flags()
+	flags.SetInterspersed(false)
 
 
-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
+	// 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
 }
 }
 
 
-// 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}
-		}
+func flagErrorFunc(cmd *cobra.Command, err error) error {
+	return cli.StatusError{
+		Status:     cli.FlagErrorFunc(cmd, err).Error(),
+		StatusCode: 125,
 	}
 	}
-
-	return statusError
 }
 }
 
 
-// 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)
-		os.Exit(125)
+		reportError(stderr, cmdPath, err.Error(), true)
+		return cli.StatusError{StatusCode: 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 +112,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 +141,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 := createContainer(ctx, dockerCli, 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 +173,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 +208,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 +221,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 +229,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 +249,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 +276,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
+}

+ 0 - 167
api/client/create.go

@@ -1,167 +0,0 @@
-package client
-
-import (
-	"fmt"
-	"io"
-	"os"
-
-	"golang.org/x/net/context"
-
-	Cli "github.com/docker/docker/cli"
-	"github.com/docker/docker/pkg/jsonmessage"
-	// FIXME migrate to docker/distribution/reference
-	"github.com/docker/docker/reference"
-	"github.com/docker/docker/registry"
-	runconfigopts "github.com/docker/docker/runconfig/opts"
-	"github.com/docker/engine-api/client"
-	"github.com/docker/engine-api/types"
-	"github.com/docker/engine-api/types/container"
-	networktypes "github.com/docker/engine-api/types/network"
-)
-
-func (cli *DockerCli) pullImage(ctx context.Context, image string, out io.Writer) error {
-	ref, err := reference.ParseNamed(image)
-	if err != nil {
-		return err
-	}
-
-	// Resolve the Repository name from fqn to RepositoryInfo
-	repoInfo, err := registry.ParseRepositoryInfo(ref)
-	if err != nil {
-		return err
-	}
-
-	authConfig := cli.ResolveAuthConfig(ctx, repoInfo.Index)
-	encodedAuth, err := EncodeAuthToBase64(authConfig)
-	if err != nil {
-		return err
-	}
-
-	options := types.ImageCreateOptions{
-		RegistryAuth: encodedAuth,
-	}
-
-	responseBody, err := cli.client.ImageCreate(ctx, image, options)
-	if err != nil {
-		return err
-	}
-	defer responseBody.Close()
-
-	return jsonmessage.DisplayJSONMessagesStream(responseBody, out, cli.outFd, cli.isTerminalOut, nil)
-}
-
-type cidFile struct {
-	path    string
-	file    *os.File
-	written bool
-}
-
-func newCIDFile(path string) (*cidFile, error) {
-	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)
-	}
-
-	f, err := os.Create(path)
-	if err != nil {
-		return nil, fmt.Errorf("Failed to create the container ID file: %s", err)
-	}
-
-	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) {
-	var containerIDFile *cidFile
-	if cidfile != "" {
-		var err error
-		if containerIDFile, err = newCIDFile(cidfile); err != nil {
-			return nil, err
-		}
-		defer containerIDFile.Close()
-	}
-
-	var trustedRef reference.Canonical
-	_, ref, err := reference.ParseIDOrReference(config.Image)
-	if err != nil {
-		return nil, err
-	}
-	if ref != nil {
-		ref = reference.WithDefaultTag(ref)
-
-		if ref, ok := ref.(reference.NamedTagged); ok && isTrusted() {
-			var err error
-			trustedRef, err = cli.trustedReference(ctx, ref)
-			if err != nil {
-				return nil, err
-			}
-			config.Image = trustedRef.String()
-		}
-	}
-
-	//create the container
-	response, err := cli.client.ContainerCreate(ctx, config, hostConfig, networkingConfig, name)
-
-	//if image not found try to pull it
-	if err != nil {
-		if client.IsErrImageNotFound(err) && ref != nil {
-			fmt.Fprintf(cli.err, "Unable to find image '%s' locally\n", ref.String())
-
-			// we don't want to write to stdout anything apart from container.ID
-			if err = cli.pullImage(ctx, config.Image, cli.err); err != nil {
-				return nil, err
-			}
-			if ref, ok := ref.(reference.NamedTagged); ok && trustedRef != nil {
-				if err := cli.tagTrusted(ctx, trustedRef, ref); err != nil {
-					return nil, err
-				}
-			}
-			// Retry
-			var retryErr error
-			response, retryErr = cli.client.ContainerCreate(ctx, config, hostConfig, networkingConfig, name)
-			if retryErr != nil {
-				return nil, retryErr
-			}
-		} else {
-			return nil, err
-		}
-	}
-
-	for _, warning := range response.Warnings {
-		fmt.Fprintf(cli.err, "WARNING: %s\n", warning)
-	}
-	if containerIDFile != nil {
-		if err = containerIDFile.Write(response.ID); err != nil {
-			return nil, err
-		}
-	}
-	return &response, nil
-}
-
-// CmdCreate creates a new container from a given image.
-//
-// Usage: docker create [OPTIONS] IMAGE [COMMAND] [ARG...]
-func (cli *DockerCli) CmdCreate(args ...string) error {
-	cmd := Cli.Subcmd("create", []string{"IMAGE [COMMAND] [ARG...]"}, Cli.DockerCommands["create"].Description, true)
-	addTrustedFlags(cmd, true)
-
-	// 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)
-	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)
 		}
 		}
 	}
 	}

+ 3 - 1
api/client/hijack.go

@@ -11,7 +11,9 @@ 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 handles copying input to and output from streams to the
+// connection
+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

+ 1 - 1
api/client/pull.go

@@ -60,7 +60,7 @@ func (cli *DockerCli) CmdPull(args ...string) error {
 	authConfig := cli.ResolveAuthConfig(ctx, repoInfo.Index)
 	authConfig := cli.ResolveAuthConfig(ctx, repoInfo.Index)
 	requestPrivilege := cli.RegistryAuthenticationPrivilegedFunc(repoInfo.Index, "pull")
 	requestPrivilege := cli.RegistryAuthenticationPrivilegedFunc(repoInfo.Index, "pull")
 
 
-	if isTrusted() && !registryRef.HasDigest() {
+	if IsTrusted() && !registryRef.HasDigest() {
 		// Check if tag is digest
 		// Check if tag is digest
 		return cli.trustedPull(ctx, repoInfo, registryRef, authConfig, requestPrivilege)
 		return cli.trustedPull(ctx, repoInfo, registryRef, authConfig, requestPrivilege)
 	}
 	}

+ 1 - 1
api/client/push.go

@@ -40,7 +40,7 @@ func (cli *DockerCli) CmdPush(args ...string) error {
 	authConfig := cli.ResolveAuthConfig(ctx, repoInfo.Index)
 	authConfig := cli.ResolveAuthConfig(ctx, repoInfo.Index)
 	requestPrivilege := cli.RegistryAuthenticationPrivilegedFunc(repoInfo.Index, "push")
 	requestPrivilege := cli.RegistryAuthenticationPrivilegedFunc(repoInfo.Index, "push")
 
 
-	if isTrusted() {
+	if IsTrusted() {
 		return cli.trustedPush(ctx, repoInfo, ref, authConfig, requestPrivilege)
 		return cli.trustedPush(ctx, repoInfo, ref, authConfig, requestPrivilege)
 	}
 	}
 
 

+ 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
 		}
 		}

+ 21 - 5
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,19 @@ var (
 	untrusted    bool
 	untrusted    bool
 )
 )
 
 
+// addTrustedFlags is the mflag version of AddTrustedFlags
 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,10 +69,11 @@ 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 {
+// IsTrusted returns true if content trust is enabled
+func IsTrusted() bool {
 	return !untrusted
 	return !untrusted
 }
 }
 
 
@@ -229,7 +243,8 @@ func (cli *DockerCli) getPassphraseRetriever() passphrase.Retriever {
 	}
 	}
 }
 }
 
 
-func (cli *DockerCli) trustedReference(ctx context.Context, ref reference.NamedTagged) (reference.Canonical, error) {
+// TrustedReference returns the canonical trusted reference for an image reference
+func (cli *DockerCli) TrustedReference(ctx context.Context, ref reference.NamedTagged) (reference.Canonical, error) {
 	repoInfo, err := registry.ParseRepositoryInfo(ref)
 	repoInfo, err := registry.ParseRepositoryInfo(ref)
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
@@ -262,7 +277,8 @@ func (cli *DockerCli) trustedReference(ctx context.Context, ref reference.NamedT
 	return reference.WithDigest(ref, r.digest)
 	return reference.WithDigest(ref, r.digest)
 }
 }
 
 
-func (cli *DockerCli) tagTrusted(ctx context.Context, trustedRef reference.Canonical, ref reference.NamedTagged) error {
+// TagTrusted tags a trusted ref
+func (cli *DockerCli) TagTrusted(ctx context.Context, trustedRef reference.Canonical, ref reference.NamedTagged) error {
 	fmt.Fprintf(cli.out, "Tagging %s as %s\n", trustedRef.String(), ref.String())
 	fmt.Fprintf(cli.out, "Tagging %s as %s\n", trustedRef.String(), ref.String())
 
 
 	return cli.client.ImageTag(ctx, trustedRef.String(), ref.String())
 	return cli.client.ImageTag(ctx, trustedRef.String(), ref.String())
@@ -374,7 +390,7 @@ func (cli *DockerCli) trustedPull(ctx context.Context, repoInfo *registry.Reposi
 			if err != nil {
 			if err != nil {
 				return err
 				return err
 			}
 			}
-			if err := cli.tagTrusted(ctx, trustedRef, tagged); err != nil {
+			if err := cli.TagTrusted(ctx, trustedRef, tagged); err != nil {
 				return err
 				return err
 			}
 			}
 		}
 		}

+ 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
 	}
 	}

+ 6 - 19
cli/cobraadaptor/adaptor.go

@@ -1,9 +1,8 @@
 package cobraadaptor
 package cobraadaptor
 
 
 import (
 import (
-	"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"
@@ -31,11 +30,13 @@ func NewCobraAdaptor(clientFlags *cliflags.ClientFlags) CobraAdaptor {
 	}
 	}
 	rootCmd.SetUsageTemplate(usageTemplate)
 	rootCmd.SetUsageTemplate(usageTemplate)
 	rootCmd.SetHelpTemplate(helpTemplate)
 	rootCmd.SetHelpTemplate(helpTemplate)
-	rootCmd.SetFlagErrorFunc(flagErrorFunc)
+	rootCmd.SetFlagErrorFunc(cli.FlagErrorFunc)
 	rootCmd.SetOutput(stdout)
 	rootCmd.SetOutput(stdout)
 	rootCmd.AddCommand(
 	rootCmd.AddCommand(
-		volume.NewVolumeCommand(dockerCli),
+		container.NewCreateCommand(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 +53,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
 }
 }
@@ -76,20 +77,6 @@ func (c CobraAdaptor) Command(name string) func(...string) error {
 	return nil
 	return nil
 }
 }
 
 
-// flagErrorFunc prints an error messages which matches the format of the
-// docker/docker/cli error messages
-func flagErrorFunc(cmd *cobra.Command, err error) error {
-	if err == nil {
-		return err
-	}
-
-	usage := ""
-	if cmd.HasSubCommands() {
-		usage = "\n\n" + cmd.UsageString()
-	}
-	return fmt.Errorf("%s\nSee '%s --help'.%s", err, cmd.CommandPath(), usage)
-}
-
 var usageTemplate = `Usage:	{{if not .HasSubCommands}}{{if .HasLocalFlags}}{{appendIfNotPresent .UseLine "[OPTIONS]"}}{{else}}{{.UseLine}}{{end}}{{end}}{{if .HasSubCommands}}{{ .CommandPath}} COMMAND{{end}}
 var usageTemplate = `Usage:	{{if not .HasSubCommands}}{{if .HasLocalFlags}}{{appendIfNotPresent .UseLine "[OPTIONS]"}}{{else}}{{.UseLine}}{{end}}{{end}}{{if .HasSubCommands}}{{ .CommandPath}} COMMAND{{end}}
 
 
 {{with or .Long .Short }}{{. | trim}}{{end}}{{if gt .Aliases 0}}
 {{with or .Long .Short }}{{. | trim}}{{end}}{{if gt .Aliases 0}}

+ 21 - 0
cli/flagerrors.go

@@ -0,0 +1,21 @@
+package cli
+
+import (
+	"fmt"
+
+	"github.com/spf13/cobra"
+)
+
+// FlagErrorFunc prints an error messages which matches the format of the
+// docker/docker/cli error messages
+func FlagErrorFunc(cmd *cobra.Command, err error) error {
+	if err == nil {
+		return err
+	}
+
+	usage := ""
+	if cmd.HasSubCommands() {
+		usage = "\n\n" + cmd.UsageString()
+	}
+	return fmt.Errorf("%s\nSee '%s --help'.%s", err, cmd.CommandPath(), usage)
+}

+ 0 - 2
cli/usage.go

@@ -12,7 +12,6 @@ var DockerCommandUsage = []Command{
 	{"build", "Build an image from a Dockerfile"},
 	{"build", "Build an image from a Dockerfile"},
 	{"commit", "Create a new image from a container's changes"},
 	{"commit", "Create a new image from a container's changes"},
 	{"cp", "Copy files/folders between a container and the local filesystem"},
 	{"cp", "Copy files/folders between a container and the local filesystem"},
-	{"create", "Create a new container"},
 	{"diff", "Inspect changes on a container's filesystem"},
 	{"diff", "Inspect changes on a container's filesystem"},
 	{"events", "Get real time events from the server"},
 	{"events", "Get real time events from the server"},
 	{"exec", "Run a command in a running container"},
 	{"exec", "Run a command in a running container"},
@@ -37,7 +36,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"},

+ 4 - 0
cmd/docker/docker.go

@@ -73,6 +73,10 @@ func main() {
 		if sterr, ok := err.(cli.StatusError); ok {
 		if sterr, ok := err.(cli.StatusError); ok {
 			if sterr.Status != "" {
 			if sterr.Status != "" {
 				fmt.Fprintln(stderr, sterr.Status)
 				fmt.Fprintln(stderr, sterr.Status)
+			}
+			// StatusError should only be used for errors, and all errors should
+			// have a non-zero exit status, so never exit with 0
+			if sterr.StatusCode == 0 {
 				os.Exit(1)
 				os.Exit(1)
 			}
 			}
 			os.Exit(sterr.StatusCode)
 			os.Exit(sterr.StatusCode)

+ 3 - 3
integration-cli/docker_cli_run_test.go

@@ -1455,7 +1455,7 @@ func (s *DockerSuite) TestRunResolvconfUpdate(c *check.C) {
 	}()
 	}()
 
 
 	//1. test that a restarting container gets an updated resolv.conf
 	//1. test that a restarting container gets an updated resolv.conf
-	dockerCmd(c, "run", "--name='first'", "busybox", "true")
+	dockerCmd(c, "run", "--name=first", "busybox", "true")
 	containerID1, err := getIDByName("first")
 	containerID1, err := getIDByName("first")
 	if err != nil {
 	if err != nil {
 		c.Fatal(err)
 		c.Fatal(err)
@@ -1485,7 +1485,7 @@ func (s *DockerSuite) TestRunResolvconfUpdate(c *check.C) {
 								} */
 								} */
 	//2. test that a restarting container does not receive resolv.conf updates
 	//2. test that a restarting container does not receive resolv.conf updates
 	//   if it modified the container copy of the starting point resolv.conf
 	//   if it modified the container copy of the starting point resolv.conf
-	dockerCmd(c, "run", "--name='second'", "busybox", "sh", "-c", "echo 'search mylittlepony.com' >>/etc/resolv.conf")
+	dockerCmd(c, "run", "--name=second", "busybox", "sh", "-c", "echo 'search mylittlepony.com' >>/etc/resolv.conf")
 	containerID2, err := getIDByName("second")
 	containerID2, err := getIDByName("second")
 	if err != nil {
 	if err != nil {
 		c.Fatal(err)
 		c.Fatal(err)
@@ -1574,7 +1574,7 @@ func (s *DockerSuite) TestRunResolvconfUpdate(c *check.C) {
 	}
 	}
 
 
 	// Run the container so it picks up the old settings
 	// Run the container so it picks up the old settings
-	dockerCmd(c, "run", "--name='third'", "busybox", "true")
+	dockerCmd(c, "run", "--name=third", "busybox", "true")
 	containerID3, err := getIDByName("third")
 	containerID3, err := getIDByName("third")
 	if err != nil {
 	if err != nil {
 		c.Fatal(err)
 		c.Fatal(err)

+ 1 - 1
integration-cli/docker_cli_run_unix_test.go

@@ -894,7 +894,7 @@ func (s *DockerSuite) TestRunSysctls(c *check.C) {
 
 
 	runCmd := exec.Command(dockerBinary, "run", "--sysctl", "kernel.foobar=1", "--name", "test2", "busybox", "cat", "/proc/sys/kernel/foobar")
 	runCmd := exec.Command(dockerBinary, "run", "--sysctl", "kernel.foobar=1", "--name", "test2", "busybox", "cat", "/proc/sys/kernel/foobar")
 	out, _, _ = runCommandWithOutput(runCmd)
 	out, _, _ = runCommandWithOutput(runCmd)
-	if !strings.Contains(out, "invalid value") {
+	if !strings.Contains(out, "invalid argument") {
 		c.Fatalf("expected --sysctl to fail, got %s", out)
 		c.Fatalf("expected --sysctl to fail, got %s", out)
 	}
 	}
 }
 }

+ 345 - 262
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:     flags.Bool("no-healthcheck", false, "Disable any container-specified HEALTHCHECK"),
+		flHealthCmd:         flags.String("health-cmd", "", "Command to run to check health"),
+		flHealthInterval:    flags.Duration("health-interval", 0, "Time between running the check"),
+		flHealthTimeout:     flags.Duration("health-timeout", 0, "Maximum time to allow one check to run"),
+		flHealthRetries:     flags.Int("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,190 +380,190 @@ 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
 	var healthConfig *container.HealthConfig
 	var healthConfig *container.HealthConfig
-	haveHealthSettings := *flHealthCmd != "" ||
-		*flHealthInterval != 0 ||
-		*flHealthTimeout != 0 ||
-		*flHealthRetries != 0
-	if *flNoHealthcheck {
+	haveHealthSettings := *copts.flHealthCmd != "" ||
+		*copts.flHealthInterval != 0 ||
+		*copts.flHealthTimeout != 0 ||
+		*copts.flHealthRetries != 0
+	if *copts.flNoHealthcheck {
 		if haveHealthSettings {
 		if haveHealthSettings {
-			return nil, nil, nil, cmd, fmt.Errorf("--no-healthcheck conflicts with --health-* options")
+			return nil, nil, nil, fmt.Errorf("--no-healthcheck conflicts with --health-* options")
 		}
 		}
 		test := strslice.StrSlice{"NONE"}
 		test := strslice.StrSlice{"NONE"}
 		healthConfig = &container.HealthConfig{Test: test}
 		healthConfig = &container.HealthConfig{Test: test}
 	} else if haveHealthSettings {
 	} else if haveHealthSettings {
 		var probe strslice.StrSlice
 		var probe strslice.StrSlice
-		if *flHealthCmd != "" {
-			args := []string{"CMD-SHELL", *flHealthCmd}
+		if *copts.flHealthCmd != "" {
+			args := []string{"CMD-SHELL", *copts.flHealthCmd}
 			probe = strslice.StrSlice(args)
 			probe = strslice.StrSlice(args)
 		}
 		}
-		if *flHealthInterval < 0 {
-			return nil, nil, nil, cmd, fmt.Errorf("--health-interval cannot be negative")
+		if *copts.flHealthInterval < 0 {
+			return nil, nil, nil, fmt.Errorf("--health-interval cannot be negative")
 		}
 		}
-		if *flHealthTimeout < 0 {
-			return nil, nil, nil, cmd, fmt.Errorf("--health-timeout cannot be negative")
+		if *copts.flHealthTimeout < 0 {
+			return nil, nil, nil, fmt.Errorf("--health-timeout cannot be negative")
 		}
 		}
 
 
 		healthConfig = &container.HealthConfig{
 		healthConfig = &container.HealthConfig{
 			Test:     probe,
 			Test:     probe,
-			Interval: *flHealthInterval,
-			Timeout:  *flHealthTimeout,
-			Retries:  *flHealthRetries,
+			Interval: *copts.flHealthInterval,
+			Timeout:  *copts.flHealthTimeout,
+			Retries:  *copts.flHealthRetries,
 		}
 		}
 	}
 	}
 
 
 	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

+ 39 - 35
runconfig/opts/parse_test.go

@@ -11,22 +11,26 @@ 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)
+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 +355,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 +366,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 +378,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 +431,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 +450,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 +489,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 +505,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 +517,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 +529,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 +541,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 +574,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)
 		}
 		}
@@ -587,14 +591,14 @@ func TestParseRestartPolicy(t *testing.T) {
 
 
 func TestParseHealth(t *testing.T) {
 func TestParseHealth(t *testing.T) {
 	checkOk := func(args ...string) *container.HealthConfig {
 	checkOk := func(args ...string) *container.HealthConfig {
-		config, _, _, _, err := parseRun(args)
+		config, _, _, err := parseRun(args)
 		if err != nil {
 		if err != nil {
 			t.Fatalf("%#v: %v", args, err)
 			t.Fatalf("%#v: %v", args, err)
 		}
 		}
 		return config.Healthcheck
 		return config.Healthcheck
 	}
 	}
 	checkError := func(expected string, args ...string) {
 	checkError := func(expected string, args ...string) {
-		config, _, _, _, err := parseRun(args)
+		config, _, _, err := parseRun(args)
 		if err == nil {
 		if err == nil {
 			t.Fatalf("Expected error, but got %#v", config)
 			t.Fatalf("Expected error, but got %#v", config)
 		}
 		}
@@ -626,11 +630,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 +649,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 +675,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 +696,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"
+}