Browse code

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

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

Signed-off-by: Daniel Nephin <dnephin@docker.com>

Daniel Nephin authored on 2016/06/01 08:49:32
Showing 19 changed files
... ...
@@ -66,7 +66,7 @@ func (cli *DockerCli) CmdAttach(args ...string) error {
66 66
 	}
67 67
 
68 68
 	if *proxy && !c.Config.Tty {
69
-		sigc := cli.forwardAllSignals(ctx, container)
69
+		sigc := cli.ForwardAllSignals(ctx, container)
70 70
 		defer signal.StopCatch(sigc)
71 71
 	}
72 72
 
... ...
@@ -80,20 +80,20 @@ func (cli *DockerCli) CmdAttach(args ...string) error {
80 80
 	defer resp.Close()
81 81
 
82 82
 	if c.Config.Tty && cli.isTerminalOut {
83
-		height, width := cli.getTtySize()
83
+		height, width := cli.GetTtySize()
84 84
 		// To handle the case where a user repeatedly attaches/detaches without resizing their
85 85
 		// terminal, the only way to get the shell prompt to display for attaches 2+ is to artificially
86 86
 		// resize it, then go back to normal. Without this, every attach after the first will
87 87
 		// require the user to manually resize or hit enter.
88 88
 		cli.resizeTtyTo(ctx, cmd.Arg(0), height+1, width+1, false)
89 89
 
90
-		// After the above resizing occurs, the call to monitorTtySize below will handle resetting back
90
+		// After the above resizing occurs, the call to MonitorTtySize below will handle resetting back
91 91
 		// to the actual size.
92
-		if err := cli.monitorTtySize(ctx, cmd.Arg(0), false); err != nil {
92
+		if err := cli.MonitorTtySize(ctx, cmd.Arg(0), false); err != nil {
93 93
 			logrus.Debugf("Error monitoring TTY size: %s", err)
94 94
 		}
95 95
 	}
96
-	if err := cli.holdHijackedConnection(ctx, c.Config.Tty, in, cli.out, cli.err, resp); err != nil {
96
+	if err := cli.HoldHijackedConnection(ctx, c.Config.Tty, in, cli.out, cli.err, resp); err != nil {
97 97
 		return err
98 98
 	}
99 99
 
... ...
@@ -101,7 +101,7 @@ func (cli *DockerCli) CmdAttach(args ...string) error {
101 101
 		return errAttach
102 102
 	}
103 103
 
104
-	_, status, err := cli.getExitCode(ctx, container)
104
+	_, status, err := cli.GetExitCode(ctx, container)
105 105
 	if err != nil {
106 106
 		return err
107 107
 	}
... ...
@@ -75,6 +75,21 @@ func (cli *DockerCli) Err() io.Writer {
75 75
 	return cli.err
76 76
 }
77 77
 
78
+// In returns the reader used for stdin
79
+func (cli *DockerCli) In() io.ReadCloser {
80
+	return cli.in
81
+}
82
+
83
+// ConfigFile returns the ConfigFile
84
+func (cli *DockerCli) ConfigFile() *configfile.ConfigFile {
85
+	return cli.configFile
86
+}
87
+
88
+// IsTerminalOut returns true if the clients stdin is a TTY
89
+func (cli *DockerCli) IsTerminalOut() bool {
90
+	return cli.isTerminalOut
91
+}
92
+
78 93
 // CheckTtyInput checks if we are trying to attach to a container tty
79 94
 // from a non-tty client input stream, and if so, returns an error.
80 95
 func (cli *DockerCli) CheckTtyInput(attachStdin, ttyMode bool) error {
... ...
@@ -38,7 +38,6 @@ func (cli *DockerCli) Command(name string) func(...string) error {
38 38
 		"restart":            cli.CmdRestart,
39 39
 		"rm":                 cli.CmdRm,
40 40
 		"rmi":                cli.CmdRmi,
41
-		"run":                cli.CmdRun,
42 41
 		"save":               cli.CmdSave,
43 42
 		"start":              cli.CmdStart,
44 43
 		"stats":              cli.CmdStats,
45 44
new file mode 100644
... ...
@@ -0,0 +1,325 @@
0
+package container
1
+
2
+import (
3
+	"fmt"
4
+	"io"
5
+	"net/http/httputil"
6
+	"os"
7
+	"runtime"
8
+	"strings"
9
+
10
+	"golang.org/x/net/context"
11
+
12
+	"github.com/Sirupsen/logrus"
13
+	"github.com/docker/docker/api/client"
14
+	"github.com/docker/docker/cli"
15
+	opttypes "github.com/docker/docker/opts"
16
+	"github.com/docker/docker/pkg/promise"
17
+	"github.com/docker/docker/pkg/signal"
18
+	runconfigopts "github.com/docker/docker/runconfig/opts"
19
+	"github.com/docker/engine-api/types"
20
+	"github.com/docker/libnetwork/resolvconf/dns"
21
+	"github.com/spf13/cobra"
22
+	"github.com/spf13/pflag"
23
+)
24
+
25
+const (
26
+	errCmdNotFound          = "not found or does not exist"
27
+	errCmdCouldNotBeInvoked = "could not be invoked"
28
+)
29
+
30
+type runOptions struct {
31
+	autoRemove bool
32
+	detach     bool
33
+	sigProxy   bool
34
+	name       string
35
+	detachKeys string
36
+}
37
+
38
+// NewRunCommand create a new `docker run` command
39
+func NewRunCommand(dockerCli *client.DockerCli) *cobra.Command {
40
+	var opts runOptions
41
+	var copts *runconfigopts.ContainerOptions
42
+
43
+	cmd := &cobra.Command{
44
+		Use:   "run [OPTIONS] IMAGE [COMMAND] [ARG...]",
45
+		Short: "Run a command in a new container",
46
+		Args:  cli.RequiresMinArgs(1),
47
+		RunE: func(cmd *cobra.Command, args []string) error {
48
+			copts.Image = args[0]
49
+			if len(args) > 1 {
50
+				copts.Args = args[1:]
51
+			}
52
+			return runRun(dockerCli, cmd.Flags(), &opts, copts)
53
+		},
54
+	}
55
+
56
+	flags := cmd.Flags()
57
+	flags.SetInterspersed(false)
58
+
59
+	// These are flags not stored in Config/HostConfig
60
+	flags.BoolVar(&opts.autoRemove, "rm", false, "Automatically remove the container when it exits")
61
+	flags.BoolVarP(&opts.detach, "detach", "d", false, "Run container in background and print container ID")
62
+	flags.BoolVar(&opts.sigProxy, "sig-proxy", true, "Proxy received signals to the process")
63
+	flags.StringVar(&opts.name, "name", "", "Assign a name to the container")
64
+	flags.StringVar(&opts.detachKeys, "detach-keys", "", "Override the key sequence for detaching a container")
65
+
66
+	// Add an explicit help that doesn't have a `-h` to prevent the conflict
67
+	// with hostname
68
+	flags.Bool("help", false, "Print usage")
69
+
70
+	client.AddTrustedFlags(flags, true)
71
+	copts = runconfigopts.AddFlags(flags)
72
+	return cmd
73
+}
74
+
75
+func runRun(dockerCli *client.DockerCli, flags *pflag.FlagSet, opts *runOptions, copts *runconfigopts.ContainerOptions) error {
76
+	stdout, stderr, stdin := dockerCli.Out(), dockerCli.Err(), dockerCli.In()
77
+	client := dockerCli.Client()
78
+	// TODO: pass this as an argument
79
+	cmdPath := "run"
80
+
81
+	var (
82
+		flAttach                              *opttypes.ListOpts
83
+		ErrConflictAttachDetach               = fmt.Errorf("Conflicting options: -a and -d")
84
+		ErrConflictRestartPolicyAndAutoRemove = fmt.Errorf("Conflicting options: --restart and --rm")
85
+		ErrConflictDetachAutoRemove           = fmt.Errorf("Conflicting options: --rm and -d")
86
+	)
87
+
88
+	config, hostConfig, networkingConfig, err := runconfigopts.Parse(flags, copts)
89
+
90
+	// just in case the Parse does not exit
91
+	if err != nil {
92
+		reportError(stderr, cmdPath, err.Error(), true)
93
+		os.Exit(125)
94
+	}
95
+
96
+	if hostConfig.OomKillDisable != nil && *hostConfig.OomKillDisable && hostConfig.Memory == 0 {
97
+		fmt.Fprintf(stderr, "WARNING: Disabling the OOM killer on containers without setting a '-m/--memory' limit may be dangerous.\n")
98
+	}
99
+
100
+	if len(hostConfig.DNS) > 0 {
101
+		// check the DNS settings passed via --dns against
102
+		// localhost regexp to warn if they are trying to
103
+		// set a DNS to a localhost address
104
+		for _, dnsIP := range hostConfig.DNS {
105
+			if dns.IsLocalhost(dnsIP) {
106
+				fmt.Fprintf(stderr, "WARNING: Localhost DNS setting (--dns=%s) may fail in containers.\n", dnsIP)
107
+				break
108
+			}
109
+		}
110
+	}
111
+
112
+	config.ArgsEscaped = false
113
+
114
+	if !opts.detach {
115
+		if err := dockerCli.CheckTtyInput(config.AttachStdin, config.Tty); err != nil {
116
+			return err
117
+		}
118
+	} else {
119
+		if fl := flags.Lookup("attach"); fl != nil {
120
+			flAttach = fl.Value.(*opttypes.ListOpts)
121
+			if flAttach.Len() != 0 {
122
+				return ErrConflictAttachDetach
123
+			}
124
+		}
125
+		if opts.autoRemove {
126
+			return ErrConflictDetachAutoRemove
127
+		}
128
+
129
+		config.AttachStdin = false
130
+		config.AttachStdout = false
131
+		config.AttachStderr = false
132
+		config.StdinOnce = false
133
+	}
134
+
135
+	// Disable sigProxy when in TTY mode
136
+	if config.Tty {
137
+		opts.sigProxy = false
138
+	}
139
+
140
+	// Telling the Windows daemon the initial size of the tty during start makes
141
+	// a far better user experience rather than relying on subsequent resizes
142
+	// to cause things to catch up.
143
+	if runtime.GOOS == "windows" {
144
+		hostConfig.ConsoleSize[0], hostConfig.ConsoleSize[1] = dockerCli.GetTtySize()
145
+	}
146
+
147
+	ctx, cancelFun := context.WithCancel(context.Background())
148
+
149
+	createResponse, err := dockerCli.CreateContainer(ctx, config, hostConfig, networkingConfig, hostConfig.ContainerIDFile, opts.name)
150
+	if err != nil {
151
+		reportError(stderr, cmdPath, err.Error(), true)
152
+		return runStartContainerErr(err)
153
+	}
154
+	if opts.sigProxy {
155
+		sigc := dockerCli.ForwardAllSignals(ctx, createResponse.ID)
156
+		defer signal.StopCatch(sigc)
157
+	}
158
+	var (
159
+		waitDisplayID chan struct{}
160
+		errCh         chan error
161
+	)
162
+	if !config.AttachStdout && !config.AttachStderr {
163
+		// Make this asynchronous to allow the client to write to stdin before having to read the ID
164
+		waitDisplayID = make(chan struct{})
165
+		go func() {
166
+			defer close(waitDisplayID)
167
+			fmt.Fprintf(stdout, "%s\n", createResponse.ID)
168
+		}()
169
+	}
170
+	if opts.autoRemove && (hostConfig.RestartPolicy.IsAlways() || hostConfig.RestartPolicy.IsOnFailure()) {
171
+		return ErrConflictRestartPolicyAndAutoRemove
172
+	}
173
+	attach := config.AttachStdin || config.AttachStdout || config.AttachStderr
174
+	if attach {
175
+		var (
176
+			out, cerr io.Writer
177
+			in        io.ReadCloser
178
+		)
179
+		if config.AttachStdin {
180
+			in = stdin
181
+		}
182
+		if config.AttachStdout {
183
+			out = stdout
184
+		}
185
+		if config.AttachStderr {
186
+			if config.Tty {
187
+				cerr = stdout
188
+			} else {
189
+				cerr = stderr
190
+			}
191
+		}
192
+
193
+		if opts.detachKeys != "" {
194
+			dockerCli.ConfigFile().DetachKeys = opts.detachKeys
195
+		}
196
+
197
+		options := types.ContainerAttachOptions{
198
+			Stream:     true,
199
+			Stdin:      config.AttachStdin,
200
+			Stdout:     config.AttachStdout,
201
+			Stderr:     config.AttachStderr,
202
+			DetachKeys: dockerCli.ConfigFile().DetachKeys,
203
+		}
204
+
205
+		resp, errAttach := client.ContainerAttach(ctx, createResponse.ID, options)
206
+		if errAttach != nil && errAttach != httputil.ErrPersistEOF {
207
+			// ContainerAttach returns an ErrPersistEOF (connection closed)
208
+			// means server met an error and put it in Hijacked connection
209
+			// keep the error and read detailed error message from hijacked connection later
210
+			return errAttach
211
+		}
212
+		defer resp.Close()
213
+
214
+		errCh = promise.Go(func() error {
215
+			errHijack := dockerCli.HoldHijackedConnection(ctx, config.Tty, in, out, cerr, resp)
216
+			if errHijack == nil {
217
+				return errAttach
218
+			}
219
+			return errHijack
220
+		})
221
+	}
222
+
223
+	if opts.autoRemove {
224
+		defer func() {
225
+			// Explicitly not sharing the context as it could be "Done" (by calling cancelFun)
226
+			// and thus the container would not be removed.
227
+			if err := dockerCli.RemoveContainer(context.Background(), createResponse.ID, true, false, true); err != nil {
228
+				fmt.Fprintf(stderr, "%v\n", err)
229
+			}
230
+		}()
231
+	}
232
+
233
+	//start the container
234
+	if err := client.ContainerStart(ctx, createResponse.ID, types.ContainerStartOptions{}); err != nil {
235
+		// If we have holdHijackedConnection, we should notify
236
+		// holdHijackedConnection we are going to exit and wait
237
+		// to avoid the terminal are not restored.
238
+		if attach {
239
+			cancelFun()
240
+			<-errCh
241
+		}
242
+
243
+		reportError(stderr, cmdPath, err.Error(), false)
244
+		return runStartContainerErr(err)
245
+	}
246
+
247
+	if (config.AttachStdin || config.AttachStdout || config.AttachStderr) && config.Tty && dockerCli.IsTerminalOut() {
248
+		if err := dockerCli.MonitorTtySize(ctx, createResponse.ID, false); err != nil {
249
+			fmt.Fprintf(stderr, "Error monitoring TTY size: %s\n", err)
250
+		}
251
+	}
252
+
253
+	if errCh != nil {
254
+		if err := <-errCh; err != nil {
255
+			logrus.Debugf("Error hijack: %s", err)
256
+			return err
257
+		}
258
+	}
259
+
260
+	// Detached mode: wait for the id to be displayed and return.
261
+	if !config.AttachStdout && !config.AttachStderr {
262
+		// Detached mode
263
+		<-waitDisplayID
264
+		return nil
265
+	}
266
+
267
+	var status int
268
+
269
+	// Attached mode
270
+	if opts.autoRemove {
271
+		// Autoremove: wait for the container to finish, retrieve
272
+		// the exit code and remove the container
273
+		if status, err = client.ContainerWait(ctx, createResponse.ID); err != nil {
274
+			return runStartContainerErr(err)
275
+		}
276
+		if _, status, err = dockerCli.GetExitCode(ctx, createResponse.ID); err != nil {
277
+			return err
278
+		}
279
+	} else {
280
+		// No Autoremove: Simply retrieve the exit code
281
+		if !config.Tty {
282
+			// In non-TTY mode, we can't detach, so we must wait for container exit
283
+			if status, err = client.ContainerWait(ctx, createResponse.ID); err != nil {
284
+				return err
285
+			}
286
+		} else {
287
+			// In TTY mode, there is a race: if the process dies too slowly, the state could
288
+			// be updated after the getExitCode call and result in the wrong exit code being reported
289
+			if _, status, err = dockerCli.GetExitCode(ctx, createResponse.ID); err != nil {
290
+				return err
291
+			}
292
+		}
293
+	}
294
+	if status != 0 {
295
+		return cli.StatusError{StatusCode: status}
296
+	}
297
+	return nil
298
+}
299
+
300
+// reportError is a utility method that prints a user-friendly message
301
+// containing the error that occurred during parsing and a suggestion to get help
302
+func reportError(stderr io.Writer, name string, str string, withHelp bool) {
303
+	if withHelp {
304
+		str += ".\nSee '" + os.Args[0] + " " + name + " --help'"
305
+	}
306
+	fmt.Fprintf(stderr, "%s: %s.\n", os.Args[0], str)
307
+}
308
+
309
+// if container start fails with 'command not found' error, return 127
310
+// if container start fails with 'command cannot be invoked' error, return 126
311
+// return 125 for generic docker daemon failures
312
+func runStartContainerErr(err error) error {
313
+	trimmedErr := strings.TrimPrefix(err.Error(), "Error response from daemon: ")
314
+	statusError := cli.StatusError{StatusCode: 125}
315
+	if strings.HasPrefix(trimmedErr, "Container command") {
316
+		if strings.Contains(trimmedErr, errCmdNotFound) {
317
+			statusError = cli.StatusError{StatusCode: 127}
318
+		} else if strings.Contains(trimmedErr, errCmdCouldNotBeInvoked) {
319
+			statusError = cli.StatusError{StatusCode: 126}
320
+		}
321
+	}
322
+
323
+	return statusError
324
+}
... ...
@@ -12,7 +12,7 @@ import (
12 12
 	// FIXME migrate to docker/distribution/reference
13 13
 	"github.com/docker/docker/reference"
14 14
 	"github.com/docker/docker/registry"
15
-	runconfigopts "github.com/docker/docker/runconfig/opts"
15
+	//runconfigopts "github.com/docker/docker/runconfig/opts"
16 16
 	"github.com/docker/engine-api/client"
17 17
 	"github.com/docker/engine-api/types"
18 18
 	"github.com/docker/engine-api/types/container"
... ...
@@ -56,6 +56,26 @@ type cidFile struct {
56 56
 	written bool
57 57
 }
58 58
 
59
+func (cid *cidFile) Close() error {
60
+	cid.file.Close()
61
+
62
+	if !cid.written {
63
+		if err := os.Remove(cid.path); err != nil {
64
+			return fmt.Errorf("failed to remove the CID file '%s': %s \n", cid.path, err)
65
+		}
66
+	}
67
+
68
+	return nil
69
+}
70
+
71
+func (cid *cidFile) Write(id string) error {
72
+	if _, err := cid.file.Write([]byte(id)); err != nil {
73
+		return fmt.Errorf("Failed to write the container ID to the file: %s", err)
74
+	}
75
+	cid.written = true
76
+	return nil
77
+}
78
+
59 79
 func newCIDFile(path string) (*cidFile, error) {
60 80
 	if _, err := os.Stat(path); err == nil {
61 81
 		return nil, fmt.Errorf("Container ID file found, make sure the other container isn't running or delete %s", path)
... ...
@@ -69,7 +89,10 @@ func newCIDFile(path string) (*cidFile, error) {
69 69
 	return &cidFile{path: path, file: f}, nil
70 70
 }
71 71
 
72
-func (cli *DockerCli) createContainer(ctx context.Context, config *container.Config, hostConfig *container.HostConfig, networkingConfig *networktypes.NetworkingConfig, cidfile, name string) (*types.ContainerCreateResponse, error) {
72
+// CreateContainer creates a container from a config
73
+// TODO: this can be unexported again once all container commands are under
74
+// api/client/container
75
+func (cli *DockerCli) CreateContainer(ctx context.Context, config *container.Config, hostConfig *container.HostConfig, networkingConfig *networktypes.NetworkingConfig, cidfile, name string) (*types.ContainerCreateResponse, error) {
73 76
 	var containerIDFile *cidFile
74 77
 	if cidfile != "" {
75 78
 		var err error
... ...
@@ -143,25 +166,26 @@ func (cli *DockerCli) CmdCreate(args ...string) error {
143 143
 	cmd := Cli.Subcmd("create", []string{"IMAGE [COMMAND] [ARG...]"}, Cli.DockerCommands["create"].Description, true)
144 144
 	addTrustedFlags(cmd, true)
145 145
 
146
+	// TODO: tmp disable for PoC, convert to cobra and pflag later
146 147
 	// These are flags not stored in Config/HostConfig
147
-	var (
148
-		flName = cmd.String([]string{"-name"}, "", "Assign a name to the container")
149
-	)
150
-
151
-	config, hostConfig, networkingConfig, cmd, err := runconfigopts.Parse(cmd, args)
152
-
153
-	if err != nil {
154
-		cmd.ReportError(err.Error(), true)
155
-		os.Exit(1)
156
-	}
157
-	if config.Image == "" {
158
-		cmd.Usage()
159
-		return nil
160
-	}
161
-	response, err := cli.createContainer(context.Background(), config, hostConfig, networkingConfig, hostConfig.ContainerIDFile, *flName)
162
-	if err != nil {
163
-		return err
164
-	}
165
-	fmt.Fprintf(cli.out, "%s\n", response.ID)
148
+	//	var (
149
+	//		flName = cmd.String([]string{"-name"}, "", "Assign a name to the container")
150
+	//	)
151
+
152
+	//	config, hostConfig, networkingConfig, cmd, err := runconfigopts.Parse(cmd, args)
153
+	//
154
+	//	if err != nil {
155
+	//		cmd.ReportError(err.Error(), true)
156
+	//		os.Exit(1)
157
+	//	}
158
+	//	if config.Image == "" {
159
+	//		cmd.Usage()
160
+	//		return nil
161
+	//	}
162
+	//	response, err := cli.CreateContainer(context.Background(), config, hostConfig, networkingConfig, hostConfig.ContainerIDFile, *flName)
163
+	//	if err != nil {
164
+	//		return err
165
+	//	}
166
+	//	fmt.Fprintf(cli.out, "%s\n", response.ID)
166 167
 	return nil
167 168
 }
... ...
@@ -93,11 +93,11 @@ func (cli *DockerCli) CmdExec(args ...string) error {
93 93
 	}
94 94
 	defer resp.Close()
95 95
 	errCh = promise.Go(func() error {
96
-		return cli.holdHijackedConnection(ctx, execConfig.Tty, in, out, stderr, resp)
96
+		return cli.HoldHijackedConnection(ctx, execConfig.Tty, in, out, stderr, resp)
97 97
 	})
98 98
 
99 99
 	if execConfig.Tty && cli.isTerminalIn {
100
-		if err := cli.monitorTtySize(ctx, execID, true); err != nil {
100
+		if err := cli.MonitorTtySize(ctx, execID, true); err != nil {
101 101
 			fmt.Fprintf(cli.err, "Error monitoring TTY size: %s\n", err)
102 102
 		}
103 103
 	}
... ...
@@ -11,7 +11,8 @@ import (
11 11
 	"github.com/docker/engine-api/types"
12 12
 )
13 13
 
14
-func (cli *DockerCli) holdHijackedConnection(ctx context.Context, tty bool, inputStream io.ReadCloser, outputStream, errorStream io.Writer, resp types.HijackedResponse) error {
14
+// HoldHijackedConnection ... TODO docstring
15
+func (cli *DockerCli) HoldHijackedConnection(ctx context.Context, tty bool, inputStream io.ReadCloser, outputStream, errorStream io.Writer, resp types.HijackedResponse) error {
15 16
 	var (
16 17
 		err         error
17 18
 		restoreOnce sync.Once
... ...
@@ -32,7 +32,7 @@ func (cli *DockerCli) CmdRm(args ...string) error {
32 32
 		}
33 33
 		name = strings.Trim(name, "/")
34 34
 
35
-		if err := cli.removeContainer(ctx, name, *v, *link, *force); err != nil {
35
+		if err := cli.RemoveContainer(ctx, name, *v, *link, *force); err != nil {
36 36
 			errs = append(errs, err.Error())
37 37
 		} else {
38 38
 			fmt.Fprintf(cli.out, "%s\n", name)
... ...
@@ -44,7 +44,10 @@ func (cli *DockerCli) CmdRm(args ...string) error {
44 44
 	return nil
45 45
 }
46 46
 
47
-func (cli *DockerCli) removeContainer(ctx context.Context, container string, removeVolumes, removeLinks, force bool) error {
47
+// RemoveContainer removes a container
48
+// TODO: this can be unexported again once all container commands are under
49
+// api/client/container
50
+func (cli *DockerCli) RemoveContainer(ctx context.Context, container string, removeVolumes, removeLinks, force bool) error {
48 51
 	options := types.ContainerRemoveOptions{
49 52
 		RemoveVolumes: removeVolumes,
50 53
 		RemoveLinks:   removeLinks,
51 54
deleted file mode 100644
... ...
@@ -1,301 +0,0 @@
1
-package client
2
-
3
-import (
4
-	"fmt"
5
-	"io"
6
-	"net/http/httputil"
7
-	"os"
8
-	"runtime"
9
-	"strings"
10
-
11
-	"golang.org/x/net/context"
12
-
13
-	"github.com/Sirupsen/logrus"
14
-	Cli "github.com/docker/docker/cli"
15
-	"github.com/docker/docker/opts"
16
-	"github.com/docker/docker/pkg/promise"
17
-	"github.com/docker/docker/pkg/signal"
18
-	runconfigopts "github.com/docker/docker/runconfig/opts"
19
-	"github.com/docker/engine-api/types"
20
-	"github.com/docker/libnetwork/resolvconf/dns"
21
-)
22
-
23
-const (
24
-	errCmdNotFound          = "not found or does not exist"
25
-	errCmdCouldNotBeInvoked = "could not be invoked"
26
-)
27
-
28
-func (cid *cidFile) Close() error {
29
-	cid.file.Close()
30
-
31
-	if !cid.written {
32
-		if err := os.Remove(cid.path); err != nil {
33
-			return fmt.Errorf("failed to remove the CID file '%s': %s \n", cid.path, err)
34
-		}
35
-	}
36
-
37
-	return nil
38
-}
39
-
40
-func (cid *cidFile) Write(id string) error {
41
-	if _, err := cid.file.Write([]byte(id)); err != nil {
42
-		return fmt.Errorf("Failed to write the container ID to the file: %s", err)
43
-	}
44
-	cid.written = true
45
-	return nil
46
-}
47
-
48
-// if container start fails with 'command not found' error, return 127
49
-// if container start fails with 'command cannot be invoked' error, return 126
50
-// return 125 for generic docker daemon failures
51
-func runStartContainerErr(err error) error {
52
-	trimmedErr := strings.TrimPrefix(err.Error(), "Error response from daemon: ")
53
-	statusError := Cli.StatusError{StatusCode: 125}
54
-	if strings.HasPrefix(trimmedErr, "Container command") {
55
-		if strings.Contains(trimmedErr, errCmdNotFound) {
56
-			statusError = Cli.StatusError{StatusCode: 127}
57
-		} else if strings.Contains(trimmedErr, errCmdCouldNotBeInvoked) {
58
-			statusError = Cli.StatusError{StatusCode: 126}
59
-		}
60
-	}
61
-
62
-	return statusError
63
-}
64
-
65
-// CmdRun runs a command in a new container.
66
-//
67
-// Usage: docker run [OPTIONS] IMAGE [COMMAND] [ARG...]
68
-func (cli *DockerCli) CmdRun(args ...string) error {
69
-	cmd := Cli.Subcmd("run", []string{"IMAGE [COMMAND] [ARG...]"}, Cli.DockerCommands["run"].Description, true)
70
-	addTrustedFlags(cmd, true)
71
-
72
-	// These are flags not stored in Config/HostConfig
73
-	var (
74
-		flAutoRemove = cmd.Bool([]string{"-rm"}, false, "Automatically remove the container when it exits")
75
-		flDetach     = cmd.Bool([]string{"d", "-detach"}, false, "Run container in background and print container ID")
76
-		flSigProxy   = cmd.Bool([]string{"-sig-proxy"}, true, "Proxy received signals to the process")
77
-		flName       = cmd.String([]string{"-name"}, "", "Assign a name to the container")
78
-		flDetachKeys = cmd.String([]string{"-detach-keys"}, "", "Override the key sequence for detaching a container")
79
-		flAttach     *opts.ListOpts
80
-
81
-		ErrConflictAttachDetach               = fmt.Errorf("Conflicting options: -a and -d")
82
-		ErrConflictRestartPolicyAndAutoRemove = fmt.Errorf("Conflicting options: --restart and --rm")
83
-		ErrConflictDetachAutoRemove           = fmt.Errorf("Conflicting options: --rm and -d")
84
-	)
85
-
86
-	config, hostConfig, networkingConfig, cmd, err := runconfigopts.Parse(cmd, args)
87
-
88
-	// just in case the Parse does not exit
89
-	if err != nil {
90
-		cmd.ReportError(err.Error(), true)
91
-		os.Exit(125)
92
-	}
93
-
94
-	if hostConfig.OomKillDisable != nil && *hostConfig.OomKillDisable && hostConfig.Memory == 0 {
95
-		fmt.Fprintf(cli.err, "WARNING: Disabling the OOM killer on containers without setting a '-m/--memory' limit may be dangerous.\n")
96
-	}
97
-
98
-	if len(hostConfig.DNS) > 0 {
99
-		// check the DNS settings passed via --dns against
100
-		// localhost regexp to warn if they are trying to
101
-		// set a DNS to a localhost address
102
-		for _, dnsIP := range hostConfig.DNS {
103
-			if dns.IsLocalhost(dnsIP) {
104
-				fmt.Fprintf(cli.err, "WARNING: Localhost DNS setting (--dns=%s) may fail in containers.\n", dnsIP)
105
-				break
106
-			}
107
-		}
108
-	}
109
-	if config.Image == "" {
110
-		cmd.Usage()
111
-		return nil
112
-	}
113
-
114
-	config.ArgsEscaped = false
115
-
116
-	if !*flDetach {
117
-		if err := cli.CheckTtyInput(config.AttachStdin, config.Tty); err != nil {
118
-			return err
119
-		}
120
-	} else {
121
-		if fl := cmd.Lookup("-attach"); fl != nil {
122
-			flAttach = fl.Value.(*opts.ListOpts)
123
-			if flAttach.Len() != 0 {
124
-				return ErrConflictAttachDetach
125
-			}
126
-		}
127
-		if *flAutoRemove {
128
-			return ErrConflictDetachAutoRemove
129
-		}
130
-
131
-		config.AttachStdin = false
132
-		config.AttachStdout = false
133
-		config.AttachStderr = false
134
-		config.StdinOnce = false
135
-	}
136
-
137
-	// Disable flSigProxy when in TTY mode
138
-	sigProxy := *flSigProxy
139
-	if config.Tty {
140
-		sigProxy = false
141
-	}
142
-
143
-	// Telling the Windows daemon the initial size of the tty during start makes
144
-	// a far better user experience rather than relying on subsequent resizes
145
-	// to cause things to catch up.
146
-	if runtime.GOOS == "windows" {
147
-		hostConfig.ConsoleSize[0], hostConfig.ConsoleSize[1] = cli.getTtySize()
148
-	}
149
-
150
-	ctx, cancelFun := context.WithCancel(context.Background())
151
-
152
-	createResponse, err := cli.createContainer(ctx, config, hostConfig, networkingConfig, hostConfig.ContainerIDFile, *flName)
153
-	if err != nil {
154
-		cmd.ReportError(err.Error(), true)
155
-		return runStartContainerErr(err)
156
-	}
157
-	if sigProxy {
158
-		sigc := cli.forwardAllSignals(ctx, createResponse.ID)
159
-		defer signal.StopCatch(sigc)
160
-	}
161
-	var (
162
-		waitDisplayID chan struct{}
163
-		errCh         chan error
164
-	)
165
-	if !config.AttachStdout && !config.AttachStderr {
166
-		// Make this asynchronous to allow the client to write to stdin before having to read the ID
167
-		waitDisplayID = make(chan struct{})
168
-		go func() {
169
-			defer close(waitDisplayID)
170
-			fmt.Fprintf(cli.out, "%s\n", createResponse.ID)
171
-		}()
172
-	}
173
-	if *flAutoRemove && (hostConfig.RestartPolicy.IsAlways() || hostConfig.RestartPolicy.IsOnFailure()) {
174
-		return ErrConflictRestartPolicyAndAutoRemove
175
-	}
176
-	attach := config.AttachStdin || config.AttachStdout || config.AttachStderr
177
-	if attach {
178
-		var (
179
-			out, stderr io.Writer
180
-			in          io.ReadCloser
181
-		)
182
-		if config.AttachStdin {
183
-			in = cli.in
184
-		}
185
-		if config.AttachStdout {
186
-			out = cli.out
187
-		}
188
-		if config.AttachStderr {
189
-			if config.Tty {
190
-				stderr = cli.out
191
-			} else {
192
-				stderr = cli.err
193
-			}
194
-		}
195
-
196
-		if *flDetachKeys != "" {
197
-			cli.configFile.DetachKeys = *flDetachKeys
198
-		}
199
-
200
-		options := types.ContainerAttachOptions{
201
-			Stream:     true,
202
-			Stdin:      config.AttachStdin,
203
-			Stdout:     config.AttachStdout,
204
-			Stderr:     config.AttachStderr,
205
-			DetachKeys: cli.configFile.DetachKeys,
206
-		}
207
-
208
-		resp, errAttach := cli.client.ContainerAttach(ctx, createResponse.ID, options)
209
-		if errAttach != nil && errAttach != httputil.ErrPersistEOF {
210
-			// ContainerAttach returns an ErrPersistEOF (connection closed)
211
-			// means server met an error and put it in Hijacked connection
212
-			// keep the error and read detailed error message from hijacked connection later
213
-			return errAttach
214
-		}
215
-		defer resp.Close()
216
-
217
-		errCh = promise.Go(func() error {
218
-			errHijack := cli.holdHijackedConnection(ctx, config.Tty, in, out, stderr, resp)
219
-			if errHijack == nil {
220
-				return errAttach
221
-			}
222
-			return errHijack
223
-		})
224
-	}
225
-
226
-	if *flAutoRemove {
227
-		defer func() {
228
-			// Explicitly not sharing the context as it could be "Done" (by calling cancelFun)
229
-			// and thus the container would not be removed.
230
-			if err := cli.removeContainer(context.Background(), createResponse.ID, true, false, true); err != nil {
231
-				fmt.Fprintf(cli.err, "%v\n", err)
232
-			}
233
-		}()
234
-	}
235
-
236
-	//start the container
237
-	if err := cli.client.ContainerStart(ctx, createResponse.ID, types.ContainerStartOptions{}); err != nil {
238
-		// If we have holdHijackedConnection, we should notify
239
-		// holdHijackedConnection we are going to exit and wait
240
-		// to avoid the terminal are not restored.
241
-		if attach {
242
-			cancelFun()
243
-			<-errCh
244
-		}
245
-
246
-		cmd.ReportError(err.Error(), false)
247
-		return runStartContainerErr(err)
248
-	}
249
-
250
-	if (config.AttachStdin || config.AttachStdout || config.AttachStderr) && config.Tty && cli.isTerminalOut {
251
-		if err := cli.monitorTtySize(ctx, createResponse.ID, false); err != nil {
252
-			fmt.Fprintf(cli.err, "Error monitoring TTY size: %s\n", err)
253
-		}
254
-	}
255
-
256
-	if errCh != nil {
257
-		if err := <-errCh; err != nil {
258
-			logrus.Debugf("Error hijack: %s", err)
259
-			return err
260
-		}
261
-	}
262
-
263
-	// Detached mode: wait for the id to be displayed and return.
264
-	if !config.AttachStdout && !config.AttachStderr {
265
-		// Detached mode
266
-		<-waitDisplayID
267
-		return nil
268
-	}
269
-
270
-	var status int
271
-
272
-	// Attached mode
273
-	if *flAutoRemove {
274
-		// Autoremove: wait for the container to finish, retrieve
275
-		// the exit code and remove the container
276
-		if status, err = cli.client.ContainerWait(ctx, createResponse.ID); err != nil {
277
-			return runStartContainerErr(err)
278
-		}
279
-		if _, status, err = cli.getExitCode(ctx, createResponse.ID); err != nil {
280
-			return err
281
-		}
282
-	} else {
283
-		// No Autoremove: Simply retrieve the exit code
284
-		if !config.Tty {
285
-			// In non-TTY mode, we can't detach, so we must wait for container exit
286
-			if status, err = cli.client.ContainerWait(ctx, createResponse.ID); err != nil {
287
-				return err
288
-			}
289
-		} else {
290
-			// In TTY mode, there is a race: if the process dies too slowly, the state could
291
-			// be updated after the getExitCode call and result in the wrong exit code being reported
292
-			if _, status, err = cli.getExitCode(ctx, createResponse.ID); err != nil {
293
-				return err
294
-			}
295
-		}
296
-	}
297
-	if status != 0 {
298
-		return Cli.StatusError{StatusCode: status}
299
-	}
300
-	return nil
301
-}
... ...
@@ -17,7 +17,10 @@ import (
17 17
 	"github.com/docker/engine-api/types"
18 18
 )
19 19
 
20
-func (cli *DockerCli) forwardAllSignals(ctx context.Context, cid string) chan os.Signal {
20
+// ForwardAllSignals forwards signals to the contianer
21
+// TODO: this can be unexported again once all container commands are under
22
+// api/client/container
23
+func (cli *DockerCli) ForwardAllSignals(ctx context.Context, cid string) chan os.Signal {
21 24
 	sigc := make(chan os.Signal, 128)
22 25
 	signal.CatchAll(sigc)
23 26
 	go func() {
... ...
@@ -74,7 +77,7 @@ func (cli *DockerCli) CmdStart(args ...string) error {
74 74
 		}
75 75
 
76 76
 		if !c.Config.Tty {
77
-			sigc := cli.forwardAllSignals(ctx, container)
77
+			sigc := cli.ForwardAllSignals(ctx, container)
78 78
 			defer signal.StopCatch(sigc)
79 79
 		}
80 80
 
... ...
@@ -105,7 +108,7 @@ func (cli *DockerCli) CmdStart(args ...string) error {
105 105
 		}
106 106
 		defer resp.Close()
107 107
 		cErr := promise.Go(func() error {
108
-			errHijack := cli.holdHijackedConnection(ctx, c.Config.Tty, in, cli.out, cli.err, resp)
108
+			errHijack := cli.HoldHijackedConnection(ctx, c.Config.Tty, in, cli.out, cli.err, resp)
109 109
 			if errHijack == nil {
110 110
 				return errAttach
111 111
 			}
... ...
@@ -121,14 +124,14 @@ func (cli *DockerCli) CmdStart(args ...string) error {
121 121
 
122 122
 		// 4. Wait for attachment to break.
123 123
 		if c.Config.Tty && cli.isTerminalOut {
124
-			if err := cli.monitorTtySize(ctx, container, false); err != nil {
124
+			if err := cli.MonitorTtySize(ctx, container, false); err != nil {
125 125
 				fmt.Fprintf(cli.err, "Error monitoring TTY size: %s\n", err)
126 126
 			}
127 127
 		}
128 128
 		if attchErr := <-cErr; attchErr != nil {
129 129
 			return attchErr
130 130
 		}
131
-		_, status, err := cli.getExitCode(ctx, container)
131
+		_, status, err := cli.GetExitCode(ctx, container)
132 132
 		if err != nil {
133 133
 			return err
134 134
 		}
... ...
@@ -37,6 +37,7 @@ import (
37 37
 	"github.com/docker/notary/tuf/data"
38 38
 	"github.com/docker/notary/tuf/signed"
39 39
 	"github.com/docker/notary/tuf/store"
40
+	"github.com/spf13/pflag"
40 41
 )
41 42
 
42 43
 var (
... ...
@@ -44,7 +45,20 @@ var (
44 44
 	untrusted    bool
45 45
 )
46 46
 
47
+// TODO: tmp workaround to get this PoC working, change everything to use
48
+// exported version
47 49
 func addTrustedFlags(fs *flag.FlagSet, verify bool) {
50
+	trusted, message := setupTrustedFlag(verify)
51
+	fs.BoolVar(&untrusted, []string{"-disable-content-trust"}, !trusted, message)
52
+}
53
+
54
+// AddTrustedFlags adds the trust flags to a FlagSet
55
+func AddTrustedFlags(fs *pflag.FlagSet, verify bool) {
56
+	trusted, message := setupTrustedFlag(verify)
57
+	fs.BoolVar(&untrusted, "disable-content-trust", !trusted, message)
58
+}
59
+
60
+func setupTrustedFlag(verify bool) (bool, string) {
48 61
 	var trusted bool
49 62
 	if e := os.Getenv("DOCKER_CONTENT_TRUST"); e != "" {
50 63
 		if t, err := strconv.ParseBool(e); t || err != nil {
... ...
@@ -56,7 +70,7 @@ func addTrustedFlags(fs *flag.FlagSet, verify bool) {
56 56
 	if verify {
57 57
 		message = "Skip image verification"
58 58
 	}
59
-	fs.BoolVar(&untrusted, []string{"-disable-content-trust"}, !trusted, message)
59
+	return trusted, message
60 60
 }
61 61
 
62 62
 func isTrusted() bool {
... ...
@@ -61,7 +61,7 @@ func (cli *DockerCli) RegistryAuthenticationPrivilegedFunc(index *registrytypes.
61 61
 }
62 62
 
63 63
 func (cli *DockerCli) resizeTty(ctx context.Context, id string, isExec bool) {
64
-	height, width := cli.getTtySize()
64
+	height, width := cli.GetTtySize()
65 65
 	cli.resizeTtyTo(ctx, id, height, width, isExec)
66 66
 }
67 67
 
... ...
@@ -87,9 +87,9 @@ func (cli *DockerCli) resizeTtyTo(ctx context.Context, id string, height, width
87 87
 	}
88 88
 }
89 89
 
90
-// getExitCode perform an inspect on the container. It returns
90
+// GetExitCode perform an inspect on the container. It returns
91 91
 // the running state and the exit code.
92
-func (cli *DockerCli) getExitCode(ctx context.Context, containerID string) (bool, int, error) {
92
+func (cli *DockerCli) GetExitCode(ctx context.Context, containerID string) (bool, int, error) {
93 93
 	c, err := cli.client.ContainerInspect(ctx, containerID)
94 94
 	if err != nil {
95 95
 		// If we can't connect, then the daemon probably died.
... ...
@@ -117,15 +117,16 @@ func (cli *DockerCli) getExecExitCode(ctx context.Context, execID string) (bool,
117 117
 	return resp.Running, resp.ExitCode, nil
118 118
 }
119 119
 
120
-func (cli *DockerCli) monitorTtySize(ctx context.Context, id string, isExec bool) error {
120
+// MonitorTtySize updates the container tty size when the terminal tty changes size
121
+func (cli *DockerCli) MonitorTtySize(ctx context.Context, id string, isExec bool) error {
121 122
 	cli.resizeTty(ctx, id, isExec)
122 123
 
123 124
 	if runtime.GOOS == "windows" {
124 125
 		go func() {
125
-			prevH, prevW := cli.getTtySize()
126
+			prevH, prevW := cli.GetTtySize()
126 127
 			for {
127 128
 				time.Sleep(time.Millisecond * 250)
128
-				h, w := cli.getTtySize()
129
+				h, w := cli.GetTtySize()
129 130
 
130 131
 				if prevW != w || prevH != h {
131 132
 					cli.resizeTty(ctx, id, isExec)
... ...
@@ -146,7 +147,8 @@ func (cli *DockerCli) monitorTtySize(ctx context.Context, id string, isExec bool
146 146
 	return nil
147 147
 }
148 148
 
149
-func (cli *DockerCli) getTtySize() (int, int) {
149
+// GetTtySize returns the height and width in characters of the tty
150
+func (cli *DockerCli) GetTtySize() (int, int) {
150 151
 	if !cli.isTerminalOut {
151 152
 		return 0, 0
152 153
 	}
... ...
@@ -4,6 +4,7 @@ import (
4 4
 	"fmt"
5 5
 
6 6
 	"github.com/docker/docker/api/client"
7
+	"github.com/docker/docker/api/client/container"
7 8
 	"github.com/docker/docker/api/client/image"
8 9
 	"github.com/docker/docker/api/client/volume"
9 10
 	"github.com/docker/docker/cli"
... ...
@@ -34,8 +35,9 @@ func NewCobraAdaptor(clientFlags *cliflags.ClientFlags) CobraAdaptor {
34 34
 	rootCmd.SetFlagErrorFunc(flagErrorFunc)
35 35
 	rootCmd.SetOutput(stdout)
36 36
 	rootCmd.AddCommand(
37
-		volume.NewVolumeCommand(dockerCli),
37
+		container.NewRunCommand(dockerCli),
38 38
 		image.NewSearchCommand(dockerCli),
39
+		volume.NewVolumeCommand(dockerCli),
39 40
 	)
40 41
 
41 42
 	rootCmd.PersistentFlags().BoolP("help", "h", false, "Print usage")
... ...
@@ -52,7 +54,7 @@ func NewCobraAdaptor(clientFlags *cliflags.ClientFlags) CobraAdaptor {
52 52
 func (c CobraAdaptor) Usage() []cli.Command {
53 53
 	cmds := []cli.Command{}
54 54
 	for _, cmd := range c.rootCmd.Commands() {
55
-		cmds = append(cmds, cli.Command{Name: cmd.Use, Description: cmd.Short})
55
+		cmds = append(cmds, cli.Command{Name: cmd.Name(), Description: cmd.Short})
56 56
 	}
57 57
 	return cmds
58 58
 }
... ...
@@ -37,7 +37,6 @@ var DockerCommandUsage = []Command{
37 37
 	{"restart", "Restart a container"},
38 38
 	{"rm", "Remove one or more containers"},
39 39
 	{"rmi", "Remove one or more images"},
40
-	{"run", "Run a command in a new container"},
41 40
 	{"save", "Save one or more images to a tar archive"},
42 41
 	{"start", "Start one or more stopped containers"},
43 42
 	{"stats", "Display a live stream of container(s) resource usage statistics"},
... ...
@@ -8,160 +8,245 @@ import (
8 8
 	"path"
9 9
 	"strconv"
10 10
 	"strings"
11
+	"time"
11 12
 
12 13
 	"github.com/docker/docker/opts"
13
-	flag "github.com/docker/docker/pkg/mflag"
14 14
 	"github.com/docker/docker/pkg/mount"
15 15
 	"github.com/docker/docker/pkg/signal"
16 16
 	"github.com/docker/engine-api/types/container"
17 17
 	networktypes "github.com/docker/engine-api/types/network"
18 18
 	"github.com/docker/engine-api/types/strslice"
19 19
 	"github.com/docker/go-connections/nat"
20
-	"github.com/docker/go-units"
20
+	units "github.com/docker/go-units"
21
+	"github.com/spf13/pflag"
21 22
 )
22 23
 
23
-// Parse parses the specified args for the specified command and generates a Config,
24
+// ContainerOptions is a data object with all the options for creating a container
25
+// TODO: remove fl prefix
26
+type ContainerOptions struct {
27
+	flAttach            opts.ListOpts
28
+	flVolumes           opts.ListOpts
29
+	flTmpfs             opts.ListOpts
30
+	flBlkioWeightDevice WeightdeviceOpt
31
+	flDeviceReadBps     ThrottledeviceOpt
32
+	flDeviceWriteBps    ThrottledeviceOpt
33
+	flLinks             opts.ListOpts
34
+	flAliases           opts.ListOpts
35
+	flDeviceReadIOps    ThrottledeviceOpt
36
+	flDeviceWriteIOps   ThrottledeviceOpt
37
+	flEnv               opts.ListOpts
38
+	flLabels            opts.ListOpts
39
+	flDevices           opts.ListOpts
40
+	flUlimits           *UlimitOpt
41
+	flSysctls           *opts.MapOpts
42
+	flPublish           opts.ListOpts
43
+	flExpose            opts.ListOpts
44
+	flDNS               opts.ListOpts
45
+	flDNSSearch         opts.ListOpts
46
+	flDNSOptions        opts.ListOpts
47
+	flExtraHosts        opts.ListOpts
48
+	flVolumesFrom       opts.ListOpts
49
+	flEnvFile           opts.ListOpts
50
+	flCapAdd            opts.ListOpts
51
+	flCapDrop           opts.ListOpts
52
+	flGroupAdd          opts.ListOpts
53
+	flSecurityOpt       opts.ListOpts
54
+	flStorageOpt        opts.ListOpts
55
+	flLabelsFile        opts.ListOpts
56
+	flLoggingOpts       opts.ListOpts
57
+	flPrivileged        *bool
58
+	flPidMode           *string
59
+	flUTSMode           *string
60
+	flUsernsMode        *string
61
+	flPublishAll        *bool
62
+	flStdin             *bool
63
+	flTty               *bool
64
+	flOomKillDisable    *bool
65
+	flOomScoreAdj       *int
66
+	flContainerIDFile   *string
67
+	flEntrypoint        *string
68
+	flHostname          *string
69
+	flMemoryString      *string
70
+	flMemoryReservation *string
71
+	flMemorySwap        *string
72
+	flKernelMemory      *string
73
+	flUser              *string
74
+	flWorkingDir        *string
75
+	flCPUShares         *int64
76
+	flCPUPercent        *int64
77
+	flCPUPeriod         *int64
78
+	flCPUQuota          *int64
79
+	flCpusetCpus        *string
80
+	flCpusetMems        *string
81
+	flBlkioWeight       *uint16
82
+	flIOMaxBandwidth    *string
83
+	flIOMaxIOps         *uint64
84
+	flSwappiness        *int64
85
+	flNetMode           *string
86
+	flMacAddress        *string
87
+	flIPv4Address       *string
88
+	flIPv6Address       *string
89
+	flIpcMode           *string
90
+	flPidsLimit         *int64
91
+	flRestartPolicy     *string
92
+	flReadonlyRootfs    *bool
93
+	flLoggingDriver     *string
94
+	flCgroupParent      *string
95
+	flVolumeDriver      *string
96
+	flStopSignal        *string
97
+	flIsolation         *string
98
+	flShmSize           *string
99
+	flNoHealthcheck     *bool
100
+	flHealthCmd         *string
101
+	flHealthInterval    *time.Duration
102
+	flHealthTimeout     *time.Duration
103
+	flHealthRetries     *int
104
+
105
+	Image string
106
+	Args  []string
107
+}
108
+
109
+// AddFlags adds all command line flags that will be used by Parse to the FlagSet
110
+func AddFlags(flags *pflag.FlagSet) *ContainerOptions {
111
+	copts := &ContainerOptions{
112
+		flAttach:            opts.NewListOpts(ValidateAttach),
113
+		flVolumes:           opts.NewListOpts(nil),
114
+		flTmpfs:             opts.NewListOpts(nil),
115
+		flBlkioWeightDevice: NewWeightdeviceOpt(ValidateWeightDevice),
116
+		flDeviceReadBps:     NewThrottledeviceOpt(ValidateThrottleBpsDevice),
117
+		flDeviceWriteBps:    NewThrottledeviceOpt(ValidateThrottleBpsDevice),
118
+		flLinks:             opts.NewListOpts(ValidateLink),
119
+		flAliases:           opts.NewListOpts(nil),
120
+		flDeviceReadIOps:    NewThrottledeviceOpt(ValidateThrottleIOpsDevice),
121
+		flDeviceWriteIOps:   NewThrottledeviceOpt(ValidateThrottleIOpsDevice),
122
+		flEnv:               opts.NewListOpts(ValidateEnv),
123
+		flLabels:            opts.NewListOpts(ValidateEnv),
124
+		flDevices:           opts.NewListOpts(ValidateDevice),
125
+
126
+		flUlimits: NewUlimitOpt(nil),
127
+		flSysctls: opts.NewMapOpts(nil, opts.ValidateSysctl),
128
+
129
+		flPublish:     opts.NewListOpts(nil),
130
+		flExpose:      opts.NewListOpts(nil),
131
+		flDNS:         opts.NewListOpts(opts.ValidateIPAddress),
132
+		flDNSSearch:   opts.NewListOpts(opts.ValidateDNSSearch),
133
+		flDNSOptions:  opts.NewListOpts(nil),
134
+		flExtraHosts:  opts.NewListOpts(ValidateExtraHost),
135
+		flVolumesFrom: opts.NewListOpts(nil),
136
+		flEnvFile:     opts.NewListOpts(nil),
137
+		flCapAdd:      opts.NewListOpts(nil),
138
+		flCapDrop:     opts.NewListOpts(nil),
139
+		flGroupAdd:    opts.NewListOpts(nil),
140
+		flSecurityOpt: opts.NewListOpts(nil),
141
+		flStorageOpt:  opts.NewListOpts(nil),
142
+		flLabelsFile:  opts.NewListOpts(nil),
143
+		flLoggingOpts: opts.NewListOpts(nil),
144
+
145
+		flPrivileged:        flags.Bool("privileged", false, "Give extended privileges to this container"),
146
+		flPidMode:           flags.String("pid", "", "PID namespace to use"),
147
+		flUTSMode:           flags.String("uts", "", "UTS namespace to use"),
148
+		flUsernsMode:        flags.String("userns", "", "User namespace to use"),
149
+		flPublishAll:        flags.BoolP("publish-all", "P", false, "Publish all exposed ports to random ports"),
150
+		flStdin:             flags.BoolP("interactive", "i", false, "Keep STDIN open even if not attached"),
151
+		flTty:               flags.BoolP("tty", "t", false, "Allocate a pseudo-TTY"),
152
+		flOomKillDisable:    flags.Bool("oom-kill-disable", false, "Disable OOM Killer"),
153
+		flOomScoreAdj:       flags.Int("oom-score-adj", 0, "Tune host's OOM preferences (-1000 to 1000)"),
154
+		flContainerIDFile:   flags.String("cidfile", "", "Write the container ID to the file"),
155
+		flEntrypoint:        flags.String("entrypoint", "", "Overwrite the default ENTRYPOINT of the image"),
156
+		flHostname:          flags.StringP("hostname", "h", "", "Container host name"),
157
+		flMemoryString:      flags.StringP("memory", "m", "", "Memory limit"),
158
+		flMemoryReservation: flags.String("memory-reservation", "", "Memory soft limit"),
159
+		flMemorySwap:        flags.String("memory-swap", "", "Swap limit equal to memory plus swap: '-1' to enable unlimited swap"),
160
+		flKernelMemory:      flags.String("kernel-memory", "", "Kernel memory limit"),
161
+		flUser:              flags.StringP("user", "u", "", "Username or UID (format: <name|uid>[:<group|gid>])"),
162
+		flWorkingDir:        flags.StringP("workdir", "w", "", "Working directory inside the container"),
163
+		flCPUShares:         flags.Int64P("cpu-shares", "c", 0, "CPU shares (relative weight)"),
164
+		flCPUPercent:        flags.Int64("cpu-percent", 0, "CPU percent (Windows only)"),
165
+		flCPUPeriod:         flags.Int64("cpu-period", 0, "Limit CPU CFS (Completely Fair Scheduler) period"),
166
+		flCPUQuota:          flags.Int64("cpu-quota", 0, "Limit CPU CFS (Completely Fair Scheduler) quota"),
167
+		flCpusetCpus:        flags.String("cpuset-cpus", "", "CPUs in which to allow execution (0-3, 0,1)"),
168
+		flCpusetMems:        flags.String("cpuset-mems", "", "MEMs in which to allow execution (0-3, 0,1)"),
169
+		flBlkioWeight:       flags.Uint16("blkio-weight", 0, "Block IO (relative weight), between 10 and 1000"),
170
+		flIOMaxBandwidth:    flags.String("io-maxbandwidth", "", "Maximum IO bandwidth limit for the system drive (Windows only)"),
171
+		flIOMaxIOps:         flags.Uint64("io-maxiops", 0, "Maximum IOps limit for the system drive (Windows only)"),
172
+		flSwappiness:        flags.Int64("memory-swappiness", -1, "Tune container memory swappiness (0 to 100)"),
173
+		flNetMode:           flags.String("net", "default", "Connect a container to a network"),
174
+		flMacAddress:        flags.String("mac-address", "", "Container MAC address (e.g. 92:d0:c6:0a:29:33)"),
175
+		flIPv4Address:       flags.String("ip", "", "Container IPv4 address (e.g. 172.30.100.104)"),
176
+		flIPv6Address:       flags.String("ip6", "", "Container IPv6 address (e.g. 2001:db8::33)"),
177
+		flIpcMode:           flags.String("ipc", "", "IPC namespace to use"),
178
+		flPidsLimit:         flags.Int64("pids-limit", 0, "Tune container pids limit (set -1 for unlimited)"),
179
+		flRestartPolicy:     flags.String("restart", "no", "Restart policy to apply when a container exits"),
180
+		flReadonlyRootfs:    flags.Bool("read-only", false, "Mount the container's root filesystem as read only"),
181
+		flLoggingDriver:     flags.String("log-driver", "", "Logging driver for container"),
182
+		flCgroupParent:      flags.String("cgroup-parent", "", "Optional parent cgroup for the container"),
183
+		flVolumeDriver:      flags.String("volume-driver", "", "Optional volume driver for the container"),
184
+		flStopSignal:        flags.String("stop-signal", signal.DefaultStopSignal, fmt.Sprintf("Signal to stop a container, %v by default", signal.DefaultStopSignal)),
185
+		flIsolation:         flags.String("isolation", "", "Container isolation technology"),
186
+		flShmSize:           flags.String("shm-size", "", "Size of /dev/shm, default value is 64MB"),
187
+		flNoHealthcheck:     cmd.Bool([]string{"-no-healthcheck"}, false, "Disable any container-specified HEALTHCHECK"),
188
+		flHealthCmd:         cmd.String([]string{"-health-cmd"}, "", "Command to run to check health"),
189
+		flHealthInterval:    cmd.Duration([]string{"-health-interval"}, 0, "Time between running the check"),
190
+		flHealthTimeout:     cmd.Duration([]string{"-health-timeout"}, 0, "Maximum time to allow one check to run"),
191
+		flHealthRetries:     cmd.Int([]string{"-health-retries"}, 0, "Consecutive failures needed to report unhealthy"),
192
+	}
193
+
194
+	flags.VarP(&copts.flAttach, "attach", "a", "Attach to STDIN, STDOUT or STDERR")
195
+	flags.Var(&copts.flBlkioWeightDevice, "blkio-weight-device", "Block IO weight (relative device weight)")
196
+	flags.Var(&copts.flDeviceReadBps, "device-read-bps", "Limit read rate (bytes per second) from a device")
197
+	flags.Var(&copts.flDeviceWriteBps, "device-write-bps", "Limit write rate (bytes per second) to a device")
198
+	flags.Var(&copts.flDeviceReadIOps, "device-read-iops", "Limit read rate (IO per second) from a device")
199
+	flags.Var(&copts.flDeviceWriteIOps, "device-write-iops", "Limit write rate (IO per second) to a device")
200
+	flags.VarP(&copts.flVolumes, "volume", "v", "Bind mount a volume")
201
+	flags.Var(&copts.flTmpfs, "tmpfs", "Mount a tmpfs directory")
202
+	flags.Var(&copts.flLinks, "link", "Add link to another container")
203
+	flags.Var(&copts.flAliases, "net-alias", "Add network-scoped alias for the container")
204
+	flags.Var(&copts.flDevices, "device", "Add a host device to the container")
205
+	flags.VarP(&copts.flLabels, "label", "l", "Set meta data on a container")
206
+	flags.Var(&copts.flLabelsFile, "label-file", "Read in a line delimited file of labels")
207
+	flags.VarP(&copts.flEnv, "env", "e", "Set environment variables")
208
+	flags.Var(&copts.flEnvFile, "env-file", "Read in a file of environment variables")
209
+	flags.VarP(&copts.flPublish, "publish", "p", "Publish a container's port(s) to the host")
210
+	flags.Var(&copts.flExpose, "expose", "Expose a port or a range of ports")
211
+	flags.Var(&copts.flDNS, "dns", "Set custom DNS servers")
212
+	flags.Var(&copts.flDNSSearch, "dns-search", "Set custom DNS search domains")
213
+	flags.Var(&copts.flDNSOptions, "dns-opt", "Set DNS options")
214
+	flags.Var(&copts.flExtraHosts, "add-host", "Add a custom host-to-IP mapping (host:ip)")
215
+	flags.Var(&copts.flVolumesFrom, "volumes-from", "Mount volumes from the specified container(s)")
216
+	flags.Var(&copts.flCapAdd, "cap-add", "Add Linux capabilities")
217
+	flags.Var(&copts.flCapDrop, "cap-drop", "Drop Linux capabilities")
218
+	flags.Var(&copts.flGroupAdd, "group-add", "Add additional groups to join")
219
+	flags.Var(&copts.flSecurityOpt, "security-opt", "Security Options")
220
+	flags.Var(&copts.flStorageOpt, "storage-opt", "Set storage driver options per container")
221
+	flags.Var(copts.flUlimits, "ulimit", "Ulimit options")
222
+	flags.Var(copts.flSysctls, "sysctl", "Sysctl options")
223
+	flags.Var(&copts.flLoggingOpts, "log-opt", "Log driver options")
224
+
225
+	return copts
226
+}
227
+
228
+// Parse parses the args for the specified command and generates a Config,
24 229
 // a HostConfig and returns them with the specified command.
25 230
 // If the specified args are not valid, it will return an error.
26
-func Parse(cmd *flag.FlagSet, args []string) (*container.Config, *container.HostConfig, *networktypes.NetworkingConfig, *flag.FlagSet, error) {
27
-	var (
28
-		// FIXME: use utils.ListOpts for attach and volumes?
29
-		flAttach            = opts.NewListOpts(ValidateAttach)
30
-		flVolumes           = opts.NewListOpts(nil)
31
-		flTmpfs             = opts.NewListOpts(nil)
32
-		flBlkioWeightDevice = NewWeightdeviceOpt(ValidateWeightDevice)
33
-		flDeviceReadBps     = NewThrottledeviceOpt(ValidateThrottleBpsDevice)
34
-		flDeviceWriteBps    = NewThrottledeviceOpt(ValidateThrottleBpsDevice)
35
-		flLinks             = opts.NewListOpts(ValidateLink)
36
-		flAliases           = opts.NewListOpts(nil)
37
-		flDeviceReadIOps    = NewThrottledeviceOpt(ValidateThrottleIOpsDevice)
38
-		flDeviceWriteIOps   = NewThrottledeviceOpt(ValidateThrottleIOpsDevice)
39
-		flEnv               = opts.NewListOpts(ValidateEnv)
40
-		flLabels            = opts.NewListOpts(ValidateEnv)
41
-		flDevices           = opts.NewListOpts(ValidateDevice)
42
-
43
-		flUlimits = NewUlimitOpt(nil)
44
-		flSysctls = opts.NewMapOpts(nil, opts.ValidateSysctl)
45
-
46
-		flPublish           = opts.NewListOpts(nil)
47
-		flExpose            = opts.NewListOpts(nil)
48
-		flDNS               = opts.NewListOpts(opts.ValidateIPAddress)
49
-		flDNSSearch         = opts.NewListOpts(opts.ValidateDNSSearch)
50
-		flDNSOptions        = opts.NewListOpts(nil)
51
-		flExtraHosts        = opts.NewListOpts(ValidateExtraHost)
52
-		flVolumesFrom       = opts.NewListOpts(nil)
53
-		flEnvFile           = opts.NewListOpts(nil)
54
-		flCapAdd            = opts.NewListOpts(nil)
55
-		flCapDrop           = opts.NewListOpts(nil)
56
-		flGroupAdd          = opts.NewListOpts(nil)
57
-		flSecurityOpt       = opts.NewListOpts(nil)
58
-		flStorageOpt        = opts.NewListOpts(nil)
59
-		flLabelsFile        = opts.NewListOpts(nil)
60
-		flLoggingOpts       = opts.NewListOpts(nil)
61
-		flPrivileged        = cmd.Bool([]string{"-privileged"}, false, "Give extended privileges to this container")
62
-		flPidMode           = cmd.String([]string{"-pid"}, "", "PID namespace to use")
63
-		flUTSMode           = cmd.String([]string{"-uts"}, "", "UTS namespace to use")
64
-		flUsernsMode        = cmd.String([]string{"-userns"}, "", "User namespace to use")
65
-		flPublishAll        = cmd.Bool([]string{"P", "-publish-all"}, false, "Publish all exposed ports to random ports")
66
-		flStdin             = cmd.Bool([]string{"i", "-interactive"}, false, "Keep STDIN open even if not attached")
67
-		flTty               = cmd.Bool([]string{"t", "-tty"}, false, "Allocate a pseudo-TTY")
68
-		flOomKillDisable    = cmd.Bool([]string{"-oom-kill-disable"}, false, "Disable OOM Killer")
69
-		flOomScoreAdj       = cmd.Int([]string{"-oom-score-adj"}, 0, "Tune host's OOM preferences (-1000 to 1000)")
70
-		flContainerIDFile   = cmd.String([]string{"-cidfile"}, "", "Write the container ID to the file")
71
-		flEntrypoint        = cmd.String([]string{"-entrypoint"}, "", "Overwrite the default ENTRYPOINT of the image")
72
-		flHostname          = cmd.String([]string{"h", "-hostname"}, "", "Container host name")
73
-		flMemoryString      = cmd.String([]string{"m", "-memory"}, "", "Memory limit")
74
-		flMemoryReservation = cmd.String([]string{"-memory-reservation"}, "", "Memory soft limit")
75
-		flMemorySwap        = cmd.String([]string{"-memory-swap"}, "", "Swap limit equal to memory plus swap: '-1' to enable unlimited swap")
76
-		flKernelMemory      = cmd.String([]string{"-kernel-memory"}, "", "Kernel memory limit")
77
-		flUser              = cmd.String([]string{"u", "-user"}, "", "Username or UID (format: <name|uid>[:<group|gid>])")
78
-		flWorkingDir        = cmd.String([]string{"w", "-workdir"}, "", "Working directory inside the container")
79
-		flCPUShares         = cmd.Int64([]string{"c", "-cpu-shares"}, 0, "CPU shares (relative weight)")
80
-		flCPUPercent        = cmd.Int64([]string{"-cpu-percent"}, 0, "CPU percent (Windows only)")
81
-		flCPUPeriod         = cmd.Int64([]string{"-cpu-period"}, 0, "Limit CPU CFS (Completely Fair Scheduler) period")
82
-		flCPUQuota          = cmd.Int64([]string{"-cpu-quota"}, 0, "Limit CPU CFS (Completely Fair Scheduler) quota")
83
-		flCpusetCpus        = cmd.String([]string{"-cpuset-cpus"}, "", "CPUs in which to allow execution (0-3, 0,1)")
84
-		flCpusetMems        = cmd.String([]string{"-cpuset-mems"}, "", "MEMs in which to allow execution (0-3, 0,1)")
85
-		flBlkioWeight       = cmd.Uint16([]string{"-blkio-weight"}, 0, "Block IO (relative weight), between 10 and 1000")
86
-		flIOMaxBandwidth    = cmd.String([]string{"-io-maxbandwidth"}, "", "Maximum IO bandwidth limit for the system drive (Windows only)")
87
-		flIOMaxIOps         = cmd.Uint64([]string{"-io-maxiops"}, 0, "Maximum IOps limit for the system drive (Windows only)")
88
-		flSwappiness        = cmd.Int64([]string{"-memory-swappiness"}, -1, "Tune container memory swappiness (0 to 100)")
89
-		flNetMode           = cmd.String([]string{"-net"}, "default", "Connect a container to a network")
90
-		flMacAddress        = cmd.String([]string{"-mac-address"}, "", "Container MAC address (e.g. 92:d0:c6:0a:29:33)")
91
-		flIPv4Address       = cmd.String([]string{"-ip"}, "", "Container IPv4 address (e.g. 172.30.100.104)")
92
-		flIPv6Address       = cmd.String([]string{"-ip6"}, "", "Container IPv6 address (e.g. 2001:db8::33)")
93
-		flIpcMode           = cmd.String([]string{"-ipc"}, "", "IPC namespace to use")
94
-		flPidsLimit         = cmd.Int64([]string{"-pids-limit"}, 0, "Tune container pids limit (set -1 for unlimited)")
95
-		flRestartPolicy     = cmd.String([]string{"-restart"}, "no", "Restart policy to apply when a container exits")
96
-		flReadonlyRootfs    = cmd.Bool([]string{"-read-only"}, false, "Mount the container's root filesystem as read only")
97
-		flLoggingDriver     = cmd.String([]string{"-log-driver"}, "", "Logging driver for container")
98
-		flCgroupParent      = cmd.String([]string{"-cgroup-parent"}, "", "Optional parent cgroup for the container")
99
-		flVolumeDriver      = cmd.String([]string{"-volume-driver"}, "", "Optional volume driver for the container")
100
-		flStopSignal        = cmd.String([]string{"-stop-signal"}, signal.DefaultStopSignal, fmt.Sprintf("Signal to stop a container, %v by default", signal.DefaultStopSignal))
101
-		flIsolation         = cmd.String([]string{"-isolation"}, "", "Container isolation technology")
102
-		flShmSize           = cmd.String([]string{"-shm-size"}, "", "Size of /dev/shm, default value is 64MB")
103
-		// Healthcheck
104
-		flNoHealthcheck  = cmd.Bool([]string{"-no-healthcheck"}, false, "Disable any container-specified HEALTHCHECK")
105
-		flHealthCmd      = cmd.String([]string{"-health-cmd"}, "", "Command to run to check health")
106
-		flHealthInterval = cmd.Duration([]string{"-health-interval"}, 0, "Time between running the check")
107
-		flHealthTimeout  = cmd.Duration([]string{"-health-timeout"}, 0, "Maximum time to allow one check to run")
108
-		flHealthRetries  = cmd.Int([]string{"-health-retries"}, 0, "Consecutive failures needed to report unhealthy")
109
-	)
110
-
111
-	cmd.Var(&flAttach, []string{"a", "-attach"}, "Attach to STDIN, STDOUT or STDERR")
112
-	cmd.Var(&flBlkioWeightDevice, []string{"-blkio-weight-device"}, "Block IO weight (relative device weight)")
113
-	cmd.Var(&flDeviceReadBps, []string{"-device-read-bps"}, "Limit read rate (bytes per second) from a device")
114
-	cmd.Var(&flDeviceWriteBps, []string{"-device-write-bps"}, "Limit write rate (bytes per second) to a device")
115
-	cmd.Var(&flDeviceReadIOps, []string{"-device-read-iops"}, "Limit read rate (IO per second) from a device")
116
-	cmd.Var(&flDeviceWriteIOps, []string{"-device-write-iops"}, "Limit write rate (IO per second) to a device")
117
-	cmd.Var(&flVolumes, []string{"v", "-volume"}, "Bind mount a volume")
118
-	cmd.Var(&flTmpfs, []string{"-tmpfs"}, "Mount a tmpfs directory")
119
-	cmd.Var(&flLinks, []string{"-link"}, "Add link to another container")
120
-	cmd.Var(&flAliases, []string{"-net-alias"}, "Add network-scoped alias for the container")
121
-	cmd.Var(&flDevices, []string{"-device"}, "Add a host device to the container")
122
-	cmd.Var(&flLabels, []string{"l", "-label"}, "Set meta data on a container")
123
-	cmd.Var(&flLabelsFile, []string{"-label-file"}, "Read in a line delimited file of labels")
124
-	cmd.Var(&flEnv, []string{"e", "-env"}, "Set environment variables")
125
-	cmd.Var(&flEnvFile, []string{"-env-file"}, "Read in a file of environment variables")
126
-	cmd.Var(&flPublish, []string{"p", "-publish"}, "Publish a container's port(s) to the host")
127
-	cmd.Var(&flExpose, []string{"-expose"}, "Expose a port or a range of ports")
128
-	cmd.Var(&flDNS, []string{"-dns"}, "Set custom DNS servers")
129
-	cmd.Var(&flDNSSearch, []string{"-dns-search"}, "Set custom DNS search domains")
130
-	cmd.Var(&flDNSOptions, []string{"-dns-opt"}, "Set DNS options")
131
-	cmd.Var(&flExtraHosts, []string{"-add-host"}, "Add a custom host-to-IP mapping (host:ip)")
132
-	cmd.Var(&flVolumesFrom, []string{"-volumes-from"}, "Mount volumes from the specified container(s)")
133
-	cmd.Var(&flCapAdd, []string{"-cap-add"}, "Add Linux capabilities")
134
-	cmd.Var(&flCapDrop, []string{"-cap-drop"}, "Drop Linux capabilities")
135
-	cmd.Var(&flGroupAdd, []string{"-group-add"}, "Add additional groups to join")
136
-	cmd.Var(&flSecurityOpt, []string{"-security-opt"}, "Security Options")
137
-	cmd.Var(&flStorageOpt, []string{"-storage-opt"}, "Set storage driver options per container")
138
-	cmd.Var(flUlimits, []string{"-ulimit"}, "Ulimit options")
139
-	cmd.Var(flSysctls, []string{"-sysctl"}, "Sysctl options")
140
-	cmd.Var(&flLoggingOpts, []string{"-log-opt"}, "Log driver options")
141
-
142
-	cmd.Require(flag.Min, 1)
143
-
144
-	if err := cmd.ParseFlags(args, true); err != nil {
145
-		return nil, nil, nil, cmd, err
146
-	}
231
+func Parse(flags *pflag.FlagSet, copts *ContainerOptions) (*container.Config, *container.HostConfig, *networktypes.NetworkingConfig, error) {
147 232
 
148 233
 	var (
149
-		attachStdin  = flAttach.Get("stdin")
150
-		attachStdout = flAttach.Get("stdout")
151
-		attachStderr = flAttach.Get("stderr")
234
+		attachStdin  = copts.flAttach.Get("stdin")
235
+		attachStdout = copts.flAttach.Get("stdout")
236
+		attachStderr = copts.flAttach.Get("stderr")
152 237
 	)
153 238
 
154 239
 	// Validate the input mac address
155
-	if *flMacAddress != "" {
156
-		if _, err := ValidateMACAddress(*flMacAddress); err != nil {
157
-			return nil, nil, nil, cmd, fmt.Errorf("%s is not a valid mac address", *flMacAddress)
240
+	if *copts.flMacAddress != "" {
241
+		if _, err := ValidateMACAddress(*copts.flMacAddress); err != nil {
242
+			return nil, nil, nil, fmt.Errorf("%s is not a valid mac address", *copts.flMacAddress)
158 243
 		}
159 244
 	}
160
-	if *flStdin {
245
+	if *copts.flStdin {
161 246
 		attachStdin = true
162 247
 	}
163 248
 	// If -a is not set, attach to stdout and stderr
164
-	if flAttach.Len() == 0 {
249
+	if copts.flAttach.Len() == 0 {
165 250
 		attachStdout = true
166 251
 		attachStderr = true
167 252
 	}
... ...
@@ -169,83 +254,83 @@ func Parse(cmd *flag.FlagSet, args []string) (*container.Config, *container.Host
169 169
 	var err error
170 170
 
171 171
 	var flMemory int64
172
-	if *flMemoryString != "" {
173
-		flMemory, err = units.RAMInBytes(*flMemoryString)
172
+	if *copts.flMemoryString != "" {
173
+		flMemory, err = units.RAMInBytes(*copts.flMemoryString)
174 174
 		if err != nil {
175
-			return nil, nil, nil, cmd, err
175
+			return nil, nil, nil, err
176 176
 		}
177 177
 	}
178 178
 
179 179
 	var MemoryReservation int64
180
-	if *flMemoryReservation != "" {
181
-		MemoryReservation, err = units.RAMInBytes(*flMemoryReservation)
180
+	if *copts.flMemoryReservation != "" {
181
+		MemoryReservation, err = units.RAMInBytes(*copts.flMemoryReservation)
182 182
 		if err != nil {
183
-			return nil, nil, nil, cmd, err
183
+			return nil, nil, nil, err
184 184
 		}
185 185
 	}
186 186
 
187 187
 	var memorySwap int64
188
-	if *flMemorySwap != "" {
189
-		if *flMemorySwap == "-1" {
188
+	if *copts.flMemorySwap != "" {
189
+		if *copts.flMemorySwap == "-1" {
190 190
 			memorySwap = -1
191 191
 		} else {
192
-			memorySwap, err = units.RAMInBytes(*flMemorySwap)
192
+			memorySwap, err = units.RAMInBytes(*copts.flMemorySwap)
193 193
 			if err != nil {
194
-				return nil, nil, nil, cmd, err
194
+				return nil, nil, nil, err
195 195
 			}
196 196
 		}
197 197
 	}
198 198
 
199 199
 	var KernelMemory int64
200
-	if *flKernelMemory != "" {
201
-		KernelMemory, err = units.RAMInBytes(*flKernelMemory)
200
+	if *copts.flKernelMemory != "" {
201
+		KernelMemory, err = units.RAMInBytes(*copts.flKernelMemory)
202 202
 		if err != nil {
203
-			return nil, nil, nil, cmd, err
203
+			return nil, nil, nil, err
204 204
 		}
205 205
 	}
206 206
 
207
-	swappiness := *flSwappiness
207
+	swappiness := *copts.flSwappiness
208 208
 	if swappiness != -1 && (swappiness < 0 || swappiness > 100) {
209
-		return nil, nil, nil, cmd, fmt.Errorf("invalid value: %d. Valid memory swappiness range is 0-100", swappiness)
209
+		return nil, nil, nil, fmt.Errorf("invalid value: %d. Valid memory swappiness range is 0-100", swappiness)
210 210
 	}
211 211
 
212 212
 	var shmSize int64
213
-	if *flShmSize != "" {
214
-		shmSize, err = units.RAMInBytes(*flShmSize)
213
+	if *copts.flShmSize != "" {
214
+		shmSize, err = units.RAMInBytes(*copts.flShmSize)
215 215
 		if err != nil {
216
-			return nil, nil, nil, cmd, err
216
+			return nil, nil, nil, err
217 217
 		}
218 218
 	}
219 219
 
220 220
 	// TODO FIXME units.RAMInBytes should have a uint64 version
221 221
 	var maxIOBandwidth int64
222
-	if *flIOMaxBandwidth != "" {
223
-		maxIOBandwidth, err = units.RAMInBytes(*flIOMaxBandwidth)
222
+	if *copts.flIOMaxBandwidth != "" {
223
+		maxIOBandwidth, err = units.RAMInBytes(*copts.flIOMaxBandwidth)
224 224
 		if err != nil {
225
-			return nil, nil, nil, cmd, err
225
+			return nil, nil, nil, err
226 226
 		}
227 227
 		if maxIOBandwidth < 0 {
228
-			return nil, nil, nil, cmd, fmt.Errorf("invalid value: %s. Maximum IO Bandwidth must be positive", *flIOMaxBandwidth)
228
+			return nil, nil, nil, fmt.Errorf("invalid value: %s. Maximum IO Bandwidth must be positive", *copts.flIOMaxBandwidth)
229 229
 		}
230 230
 	}
231 231
 
232 232
 	var binds []string
233 233
 	// add any bind targets to the list of container volumes
234
-	for bind := range flVolumes.GetMap() {
234
+	for bind := range copts.flVolumes.GetMap() {
235 235
 		if arr := volumeSplitN(bind, 2); len(arr) > 1 {
236
-			// after creating the bind mount we want to delete it from the flVolumes values because
236
+			// after creating the bind mount we want to delete it from the copts.flVolumes values because
237 237
 			// we do not want bind mounts being committed to image configs
238 238
 			binds = append(binds, bind)
239
-			flVolumes.Delete(bind)
239
+			copts.flVolumes.Delete(bind)
240 240
 		}
241 241
 	}
242 242
 
243 243
 	// Can't evaluate options passed into --tmpfs until we actually mount
244 244
 	tmpfs := make(map[string]string)
245
-	for _, t := range flTmpfs.GetAll() {
245
+	for _, t := range copts.flTmpfs.GetAll() {
246 246
 		if arr := strings.SplitN(t, ":", 2); len(arr) > 1 {
247 247
 			if _, _, err := mount.ParseTmpfsOptions(arr[1]); err != nil {
248
-				return nil, nil, nil, cmd, err
248
+				return nil, nil, nil, err
249 249
 			}
250 250
 			tmpfs[arr[0]] = arr[1]
251 251
 		} else {
... ...
@@ -254,27 +339,25 @@ func Parse(cmd *flag.FlagSet, args []string) (*container.Config, *container.Host
254 254
 	}
255 255
 
256 256
 	var (
257
-		parsedArgs = cmd.Args()
258 257
 		runCmd     strslice.StrSlice
259 258
 		entrypoint strslice.StrSlice
260
-		image      = cmd.Arg(0)
261 259
 	)
262
-	if len(parsedArgs) > 1 {
263
-		runCmd = strslice.StrSlice(parsedArgs[1:])
260
+	if len(copts.Args) > 0 {
261
+		runCmd = strslice.StrSlice(copts.Args)
264 262
 	}
265
-	if *flEntrypoint != "" {
266
-		entrypoint = strslice.StrSlice{*flEntrypoint}
263
+	if *copts.flEntrypoint != "" {
264
+		entrypoint = strslice.StrSlice{*copts.flEntrypoint}
267 265
 	}
268 266
 
269
-	ports, portBindings, err := nat.ParsePortSpecs(flPublish.GetAll())
267
+	ports, portBindings, err := nat.ParsePortSpecs(copts.flPublish.GetAll())
270 268
 	if err != nil {
271
-		return nil, nil, nil, cmd, err
269
+		return nil, nil, nil, err
272 270
 	}
273 271
 
274 272
 	// Merge in exposed ports to the map of published ports
275
-	for _, e := range flExpose.GetAll() {
273
+	for _, e := range copts.flExpose.GetAll() {
276 274
 		if strings.Contains(e, ":") {
277
-			return nil, nil, nil, cmd, fmt.Errorf("invalid port format for --expose: %s", e)
275
+			return nil, nil, nil, fmt.Errorf("invalid port format for --expose: %s", e)
278 276
 		}
279 277
 		//support two formats for expose, original format <portnum>/[<proto>] or <startport-endport>/[<proto>]
280 278
 		proto, port := nat.SplitProtoPort(e)
... ...
@@ -282,12 +365,12 @@ func Parse(cmd *flag.FlagSet, args []string) (*container.Config, *container.Host
282 282
 		//if expose a port, the start and end port are the same
283 283
 		start, end, err := nat.ParsePortRange(port)
284 284
 		if err != nil {
285
-			return nil, nil, nil, cmd, fmt.Errorf("invalid range format for --expose: %s, error: %s", e, err)
285
+			return nil, nil, nil, fmt.Errorf("invalid range format for --expose: %s, error: %s", e, err)
286 286
 		}
287 287
 		for i := start; i <= end; i++ {
288 288
 			p, err := nat.NewPort(proto, strconv.FormatUint(i, 10))
289 289
 			if err != nil {
290
-				return nil, nil, nil, cmd, err
290
+				return nil, nil, nil, err
291 291
 			}
292 292
 			if _, exists := ports[p]; !exists {
293 293
 				ports[p] = struct{}{}
... ...
@@ -297,64 +380,64 @@ func Parse(cmd *flag.FlagSet, args []string) (*container.Config, *container.Host
297 297
 
298 298
 	// parse device mappings
299 299
 	deviceMappings := []container.DeviceMapping{}
300
-	for _, device := range flDevices.GetAll() {
300
+	for _, device := range copts.flDevices.GetAll() {
301 301
 		deviceMapping, err := ParseDevice(device)
302 302
 		if err != nil {
303
-			return nil, nil, nil, cmd, err
303
+			return nil, nil, nil, err
304 304
 		}
305 305
 		deviceMappings = append(deviceMappings, deviceMapping)
306 306
 	}
307 307
 
308 308
 	// collect all the environment variables for the container
309
-	envVariables, err := readKVStrings(flEnvFile.GetAll(), flEnv.GetAll())
309
+	envVariables, err := readKVStrings(copts.flEnvFile.GetAll(), copts.flEnv.GetAll())
310 310
 	if err != nil {
311
-		return nil, nil, nil, cmd, err
311
+		return nil, nil, nil, err
312 312
 	}
313 313
 
314 314
 	// collect all the labels for the container
315
-	labels, err := readKVStrings(flLabelsFile.GetAll(), flLabels.GetAll())
315
+	labels, err := readKVStrings(copts.flLabelsFile.GetAll(), copts.flLabels.GetAll())
316 316
 	if err != nil {
317
-		return nil, nil, nil, cmd, err
317
+		return nil, nil, nil, err
318 318
 	}
319 319
 
320
-	ipcMode := container.IpcMode(*flIpcMode)
320
+	ipcMode := container.IpcMode(*copts.flIpcMode)
321 321
 	if !ipcMode.Valid() {
322
-		return nil, nil, nil, cmd, fmt.Errorf("--ipc: invalid IPC mode")
322
+		return nil, nil, nil, fmt.Errorf("--ipc: invalid IPC mode")
323 323
 	}
324 324
 
325
-	pidMode := container.PidMode(*flPidMode)
325
+	pidMode := container.PidMode(*copts.flPidMode)
326 326
 	if !pidMode.Valid() {
327
-		return nil, nil, nil, cmd, fmt.Errorf("--pid: invalid PID mode")
327
+		return nil, nil, nil, fmt.Errorf("--pid: invalid PID mode")
328 328
 	}
329 329
 
330
-	utsMode := container.UTSMode(*flUTSMode)
330
+	utsMode := container.UTSMode(*copts.flUTSMode)
331 331
 	if !utsMode.Valid() {
332
-		return nil, nil, nil, cmd, fmt.Errorf("--uts: invalid UTS mode")
332
+		return nil, nil, nil, fmt.Errorf("--uts: invalid UTS mode")
333 333
 	}
334 334
 
335
-	usernsMode := container.UsernsMode(*flUsernsMode)
335
+	usernsMode := container.UsernsMode(*copts.flUsernsMode)
336 336
 	if !usernsMode.Valid() {
337
-		return nil, nil, nil, cmd, fmt.Errorf("--userns: invalid USER mode")
337
+		return nil, nil, nil, fmt.Errorf("--userns: invalid USER mode")
338 338
 	}
339 339
 
340
-	restartPolicy, err := ParseRestartPolicy(*flRestartPolicy)
340
+	restartPolicy, err := ParseRestartPolicy(*copts.flRestartPolicy)
341 341
 	if err != nil {
342
-		return nil, nil, nil, cmd, err
342
+		return nil, nil, nil, err
343 343
 	}
344 344
 
345
-	loggingOpts, err := parseLoggingOpts(*flLoggingDriver, flLoggingOpts.GetAll())
345
+	loggingOpts, err := parseLoggingOpts(*copts.flLoggingDriver, copts.flLoggingOpts.GetAll())
346 346
 	if err != nil {
347
-		return nil, nil, nil, cmd, err
347
+		return nil, nil, nil, err
348 348
 	}
349 349
 
350
-	securityOpts, err := parseSecurityOpts(flSecurityOpt.GetAll())
350
+	securityOpts, err := parseSecurityOpts(copts.flSecurityOpt.GetAll())
351 351
 	if err != nil {
352
-		return nil, nil, nil, cmd, err
352
+		return nil, nil, nil, err
353 353
 	}
354 354
 
355
-	storageOpts, err := parseStorageOpts(flStorageOpt.GetAll())
355
+	storageOpts, err := parseStorageOpts(copts.flStorageOpt.GetAll())
356 356
 	if err != nil {
357
-		return nil, nil, nil, cmd, err
357
+		return nil, nil, nil, err
358 358
 	}
359 359
 
360 360
 	// Healthcheck
... ...
@@ -391,96 +474,96 @@ func Parse(cmd *flag.FlagSet, args []string) (*container.Config, *container.Host
391 391
 	}
392 392
 
393 393
 	resources := container.Resources{
394
-		CgroupParent:         *flCgroupParent,
394
+		CgroupParent:         *copts.flCgroupParent,
395 395
 		Memory:               flMemory,
396 396
 		MemoryReservation:    MemoryReservation,
397 397
 		MemorySwap:           memorySwap,
398
-		MemorySwappiness:     flSwappiness,
398
+		MemorySwappiness:     copts.flSwappiness,
399 399
 		KernelMemory:         KernelMemory,
400
-		OomKillDisable:       flOomKillDisable,
401
-		CPUPercent:           *flCPUPercent,
402
-		CPUShares:            *flCPUShares,
403
-		CPUPeriod:            *flCPUPeriod,
404
-		CpusetCpus:           *flCpusetCpus,
405
-		CpusetMems:           *flCpusetMems,
406
-		CPUQuota:             *flCPUQuota,
407
-		PidsLimit:            *flPidsLimit,
408
-		BlkioWeight:          *flBlkioWeight,
409
-		BlkioWeightDevice:    flBlkioWeightDevice.GetList(),
410
-		BlkioDeviceReadBps:   flDeviceReadBps.GetList(),
411
-		BlkioDeviceWriteBps:  flDeviceWriteBps.GetList(),
412
-		BlkioDeviceReadIOps:  flDeviceReadIOps.GetList(),
413
-		BlkioDeviceWriteIOps: flDeviceWriteIOps.GetList(),
414
-		IOMaximumIOps:        *flIOMaxIOps,
400
+		OomKillDisable:       copts.flOomKillDisable,
401
+		CPUPercent:           *copts.flCPUPercent,
402
+		CPUShares:            *copts.flCPUShares,
403
+		CPUPeriod:            *copts.flCPUPeriod,
404
+		CpusetCpus:           *copts.flCpusetCpus,
405
+		CpusetMems:           *copts.flCpusetMems,
406
+		CPUQuota:             *copts.flCPUQuota,
407
+		PidsLimit:            *copts.flPidsLimit,
408
+		BlkioWeight:          *copts.flBlkioWeight,
409
+		BlkioWeightDevice:    copts.flBlkioWeightDevice.GetList(),
410
+		BlkioDeviceReadBps:   copts.flDeviceReadBps.GetList(),
411
+		BlkioDeviceWriteBps:  copts.flDeviceWriteBps.GetList(),
412
+		BlkioDeviceReadIOps:  copts.flDeviceReadIOps.GetList(),
413
+		BlkioDeviceWriteIOps: copts.flDeviceWriteIOps.GetList(),
414
+		IOMaximumIOps:        *copts.flIOMaxIOps,
415 415
 		IOMaximumBandwidth:   uint64(maxIOBandwidth),
416
-		Ulimits:              flUlimits.GetList(),
416
+		Ulimits:              copts.flUlimits.GetList(),
417 417
 		Devices:              deviceMappings,
418 418
 	}
419 419
 
420 420
 	config := &container.Config{
421
-		Hostname:     *flHostname,
421
+		Hostname:     *copts.flHostname,
422 422
 		ExposedPorts: ports,
423
-		User:         *flUser,
424
-		Tty:          *flTty,
423
+		User:         *copts.flUser,
424
+		Tty:          *copts.flTty,
425 425
 		// TODO: deprecated, it comes from -n, --networking
426 426
 		// it's still needed internally to set the network to disabled
427 427
 		// if e.g. bridge is none in daemon opts, and in inspect
428 428
 		NetworkDisabled: false,
429
-		OpenStdin:       *flStdin,
429
+		OpenStdin:       *copts.flStdin,
430 430
 		AttachStdin:     attachStdin,
431 431
 		AttachStdout:    attachStdout,
432 432
 		AttachStderr:    attachStderr,
433 433
 		Env:             envVariables,
434 434
 		Cmd:             runCmd,
435
-		Image:           image,
436
-		Volumes:         flVolumes.GetMap(),
437
-		MacAddress:      *flMacAddress,
435
+		Image:           copts.Image,
436
+		Volumes:         copts.flVolumes.GetMap(),
437
+		MacAddress:      *copts.flMacAddress,
438 438
 		Entrypoint:      entrypoint,
439
-		WorkingDir:      *flWorkingDir,
439
+		WorkingDir:      *copts.flWorkingDir,
440 440
 		Labels:          ConvertKVStringsToMap(labels),
441 441
 		Healthcheck:     healthConfig,
442 442
 	}
443
-	if cmd.IsSet("-stop-signal") {
444
-		config.StopSignal = *flStopSignal
443
+	if flags.Changed("stop-signal") {
444
+		config.StopSignal = *copts.flStopSignal
445 445
 	}
446 446
 
447 447
 	hostConfig := &container.HostConfig{
448 448
 		Binds:           binds,
449
-		ContainerIDFile: *flContainerIDFile,
450
-		OomScoreAdj:     *flOomScoreAdj,
451
-		Privileged:      *flPrivileged,
449
+		ContainerIDFile: *copts.flContainerIDFile,
450
+		OomScoreAdj:     *copts.flOomScoreAdj,
451
+		Privileged:      *copts.flPrivileged,
452 452
 		PortBindings:    portBindings,
453
-		Links:           flLinks.GetAll(),
454
-		PublishAllPorts: *flPublishAll,
453
+		Links:           copts.flLinks.GetAll(),
454
+		PublishAllPorts: *copts.flPublishAll,
455 455
 		// Make sure the dns fields are never nil.
456 456
 		// New containers don't ever have those fields nil,
457 457
 		// but pre created containers can still have those nil values.
458 458
 		// See https://github.com/docker/docker/pull/17779
459 459
 		// for a more detailed explanation on why we don't want that.
460
-		DNS:            flDNS.GetAllOrEmpty(),
461
-		DNSSearch:      flDNSSearch.GetAllOrEmpty(),
462
-		DNSOptions:     flDNSOptions.GetAllOrEmpty(),
463
-		ExtraHosts:     flExtraHosts.GetAll(),
464
-		VolumesFrom:    flVolumesFrom.GetAll(),
465
-		NetworkMode:    container.NetworkMode(*flNetMode),
460
+		DNS:            copts.flDNS.GetAllOrEmpty(),
461
+		DNSSearch:      copts.flDNSSearch.GetAllOrEmpty(),
462
+		DNSOptions:     copts.flDNSOptions.GetAllOrEmpty(),
463
+		ExtraHosts:     copts.flExtraHosts.GetAll(),
464
+		VolumesFrom:    copts.flVolumesFrom.GetAll(),
465
+		NetworkMode:    container.NetworkMode(*copts.flNetMode),
466 466
 		IpcMode:        ipcMode,
467 467
 		PidMode:        pidMode,
468 468
 		UTSMode:        utsMode,
469 469
 		UsernsMode:     usernsMode,
470
-		CapAdd:         strslice.StrSlice(flCapAdd.GetAll()),
471
-		CapDrop:        strslice.StrSlice(flCapDrop.GetAll()),
472
-		GroupAdd:       flGroupAdd.GetAll(),
470
+		CapAdd:         strslice.StrSlice(copts.flCapAdd.GetAll()),
471
+		CapDrop:        strslice.StrSlice(copts.flCapDrop.GetAll()),
472
+		GroupAdd:       copts.flGroupAdd.GetAll(),
473 473
 		RestartPolicy:  restartPolicy,
474 474
 		SecurityOpt:    securityOpts,
475 475
 		StorageOpt:     storageOpts,
476
-		ReadonlyRootfs: *flReadonlyRootfs,
477
-		LogConfig:      container.LogConfig{Type: *flLoggingDriver, Config: loggingOpts},
478
-		VolumeDriver:   *flVolumeDriver,
479
-		Isolation:      container.Isolation(*flIsolation),
476
+		ReadonlyRootfs: *copts.flReadonlyRootfs,
477
+		LogConfig:      container.LogConfig{Type: *copts.flLoggingDriver, Config: loggingOpts},
478
+		VolumeDriver:   *copts.flVolumeDriver,
479
+		Isolation:      container.Isolation(*copts.flIsolation),
480 480
 		ShmSize:        shmSize,
481 481
 		Resources:      resources,
482 482
 		Tmpfs:          tmpfs,
483
-		Sysctls:        flSysctls.GetAll(),
483
+		Sysctls:        copts.flSysctls.GetAll(),
484 484
 	}
485 485
 
486 486
 	// 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
492 492
 		EndpointsConfig: make(map[string]*networktypes.EndpointSettings),
493 493
 	}
494 494
 
495
-	if *flIPv4Address != "" || *flIPv6Address != "" {
495
+	if *copts.flIPv4Address != "" || *copts.flIPv6Address != "" {
496 496
 		networkingConfig.EndpointsConfig[string(hostConfig.NetworkMode)] = &networktypes.EndpointSettings{
497 497
 			IPAMConfig: &networktypes.EndpointIPAMConfig{
498
-				IPv4Address: *flIPv4Address,
499
-				IPv6Address: *flIPv6Address,
498
+				IPv4Address: *copts.flIPv4Address,
499
+				IPv6Address: *copts.flIPv6Address,
500 500
 			},
501 501
 		}
502 502
 	}
... ...
@@ -511,17 +594,17 @@ func Parse(cmd *flag.FlagSet, args []string) (*container.Config, *container.Host
511 511
 		networkingConfig.EndpointsConfig[string(hostConfig.NetworkMode)] = epConfig
512 512
 	}
513 513
 
514
-	if flAliases.Len() > 0 {
514
+	if copts.flAliases.Len() > 0 {
515 515
 		epConfig := networkingConfig.EndpointsConfig[string(hostConfig.NetworkMode)]
516 516
 		if epConfig == nil {
517 517
 			epConfig = &networktypes.EndpointSettings{}
518 518
 		}
519
-		epConfig.Aliases = make([]string, flAliases.Len())
520
-		copy(epConfig.Aliases, flAliases.GetAll())
519
+		epConfig.Aliases = make([]string, copts.flAliases.Len())
520
+		copy(epConfig.Aliases, copts.flAliases.GetAll())
521 521
 		networkingConfig.EndpointsConfig[string(hostConfig.NetworkMode)] = epConfig
522 522
 	}
523 523
 
524
-	return config, hostConfig, networkingConfig, cmd, nil
524
+	return config, hostConfig, networkingConfig, nil
525 525
 }
526 526
 
527 527
 // reads a file of line terminated key=value pairs, and overrides any keys
... ...
@@ -11,22 +11,27 @@ import (
11 11
 	"testing"
12 12
 	"time"
13 13
 
14
-	flag "github.com/docker/docker/pkg/mflag"
15 14
 	"github.com/docker/docker/runconfig"
16 15
 	"github.com/docker/engine-api/types/container"
17 16
 	networktypes "github.com/docker/engine-api/types/network"
18 17
 	"github.com/docker/go-connections/nat"
18
+	"github.com/spf13/pflag"
19 19
 )
20 20
 
21
-func parseRun(args []string) (*container.Config, *container.HostConfig, *networktypes.NetworkingConfig, *flag.FlagSet, error) {
22
-	cmd := flag.NewFlagSet("run", flag.ContinueOnError)
23
-	cmd.SetOutput(ioutil.Discard)
24
-	cmd.Usage = nil
25
-	return Parse(cmd, args)
21
+// TODO: drop FlagSet from return value
22
+func parseRun(args []string) (*container.Config, *container.HostConfig, *networktypes.NetworkingConfig, error) {
23
+	flags := pflag.NewFlagSet("run", pflag.ContinueOnError)
24
+	flags.SetOutput(ioutil.Discard)
25
+	flags.Usage = nil
26
+	copts := AddFlags(flags)
27
+	if err := flags.Parse(args); err != nil {
28
+		return nil, nil, nil, err
29
+	}
30
+	return Parse(flags, copts)
26 31
 }
27 32
 
28 33
 func parse(t *testing.T, args string) (*container.Config, *container.HostConfig, error) {
29
-	config, hostConfig, _, _, err := parseRun(strings.Split(args+" ubuntu bash", " "))
34
+	config, hostConfig, _, err := parseRun(strings.Split(args+" ubuntu bash", " "))
30 35
 	return config, hostConfig, err
31 36
 }
32 37
 
... ...
@@ -351,7 +356,7 @@ func setupPlatformVolume(u []string, w []string) ([]string, string) {
351 351
 func TestParseWithMacAddress(t *testing.T) {
352 352
 	invalidMacAddress := "--mac-address=invalidMacAddress"
353 353
 	validMacAddress := "--mac-address=92:d0:c6:0a:29:33"
354
-	if _, _, _, _, err := parseRun([]string{invalidMacAddress, "img", "cmd"}); err != nil && err.Error() != "invalidMacAddress is not a valid mac address" {
354
+	if _, _, _, err := parseRun([]string{invalidMacAddress, "img", "cmd"}); err != nil && err.Error() != "invalidMacAddress is not a valid mac address" {
355 355
 		t.Fatalf("Expected an error with %v mac-address, got %v", invalidMacAddress, err)
356 356
 	}
357 357
 	if config, _ := mustParse(t, validMacAddress); config.MacAddress != "92:d0:c6:0a:29:33" {
... ...
@@ -362,7 +367,7 @@ func TestParseWithMacAddress(t *testing.T) {
362 362
 func TestParseWithMemory(t *testing.T) {
363 363
 	invalidMemory := "--memory=invalid"
364 364
 	validMemory := "--memory=1G"
365
-	if _, _, _, _, err := parseRun([]string{invalidMemory, "img", "cmd"}); err != nil && err.Error() != "invalid size: 'invalid'" {
365
+	if _, _, _, err := parseRun([]string{invalidMemory, "img", "cmd"}); err != nil && err.Error() != "invalid size: 'invalid'" {
366 366
 		t.Fatalf("Expected an error with '%v' Memory, got '%v'", invalidMemory, err)
367 367
 	}
368 368
 	if _, hostconfig := mustParse(t, validMemory); hostconfig.Memory != 1073741824 {
... ...
@@ -374,7 +379,7 @@ func TestParseWithMemorySwap(t *testing.T) {
374 374
 	invalidMemory := "--memory-swap=invalid"
375 375
 	validMemory := "--memory-swap=1G"
376 376
 	anotherValidMemory := "--memory-swap=-1"
377
-	if _, _, _, _, err := parseRun([]string{invalidMemory, "img", "cmd"}); err == nil || err.Error() != "invalid size: 'invalid'" {
377
+	if _, _, _, err := parseRun([]string{invalidMemory, "img", "cmd"}); err == nil || err.Error() != "invalid size: 'invalid'" {
378 378
 		t.Fatalf("Expected an error with '%v' MemorySwap, got '%v'", invalidMemory, err)
379 379
 	}
380 380
 	if _, hostconfig := mustParse(t, validMemory); hostconfig.MemorySwap != 1073741824 {
... ...
@@ -427,12 +432,12 @@ func TestParseWithExpose(t *testing.T) {
427 427
 		"8080-8082/tcp": {"8080/tcp", "8081/tcp", "8082/tcp"},
428 428
 	}
429 429
 	for expose, expectedError := range invalids {
430
-		if _, _, _, _, err := parseRun([]string{fmt.Sprintf("--expose=%v", expose), "img", "cmd"}); err == nil || err.Error() != expectedError {
430
+		if _, _, _, err := parseRun([]string{fmt.Sprintf("--expose=%v", expose), "img", "cmd"}); err == nil || err.Error() != expectedError {
431 431
 			t.Fatalf("Expected error '%v' with '--expose=%v', got '%v'", expectedError, expose, err)
432 432
 		}
433 433
 	}
434 434
 	for expose, exposedPorts := range valids {
435
-		config, _, _, _, err := parseRun([]string{fmt.Sprintf("--expose=%v", expose), "img", "cmd"})
435
+		config, _, _, err := parseRun([]string{fmt.Sprintf("--expose=%v", expose), "img", "cmd"})
436 436
 		if err != nil {
437 437
 			t.Fatal(err)
438 438
 		}
... ...
@@ -446,7 +451,7 @@ func TestParseWithExpose(t *testing.T) {
446 446
 		}
447 447
 	}
448 448
 	// Merge with actual published port
449
-	config, _, _, _, err := parseRun([]string{"--publish=80", "--expose=80-81/tcp", "img", "cmd"})
449
+	config, _, _, err := parseRun([]string{"--publish=80", "--expose=80-81/tcp", "img", "cmd"})
450 450
 	if err != nil {
451 451
 		t.Fatal(err)
452 452
 	}
... ...
@@ -485,7 +490,7 @@ func TestParseDevice(t *testing.T) {
485 485
 		},
486 486
 	}
487 487
 	for device, deviceMapping := range valids {
488
-		_, hostconfig, _, _, err := parseRun([]string{fmt.Sprintf("--device=%v", device), "img", "cmd"})
488
+		_, hostconfig, _, err := parseRun([]string{fmt.Sprintf("--device=%v", device), "img", "cmd"})
489 489
 		if err != nil {
490 490
 			t.Fatal(err)
491 491
 		}
... ...
@@ -501,11 +506,11 @@ func TestParseDevice(t *testing.T) {
501 501
 
502 502
 func TestParseModes(t *testing.T) {
503 503
 	// ipc ko
504
-	if _, _, _, _, err := parseRun([]string{"--ipc=container:", "img", "cmd"}); err == nil || err.Error() != "--ipc: invalid IPC mode" {
504
+	if _, _, _, err := parseRun([]string{"--ipc=container:", "img", "cmd"}); err == nil || err.Error() != "--ipc: invalid IPC mode" {
505 505
 		t.Fatalf("Expected an error with message '--ipc: invalid IPC mode', got %v", err)
506 506
 	}
507 507
 	// ipc ok
508
-	_, hostconfig, _, _, err := parseRun([]string{"--ipc=host", "img", "cmd"})
508
+	_, hostconfig, _, err := parseRun([]string{"--ipc=host", "img", "cmd"})
509 509
 	if err != nil {
510 510
 		t.Fatal(err)
511 511
 	}
... ...
@@ -513,11 +518,11 @@ func TestParseModes(t *testing.T) {
513 513
 		t.Fatalf("Expected a valid IpcMode, got %v", hostconfig.IpcMode)
514 514
 	}
515 515
 	// pid ko
516
-	if _, _, _, _, err := parseRun([]string{"--pid=container:", "img", "cmd"}); err == nil || err.Error() != "--pid: invalid PID mode" {
516
+	if _, _, _, err := parseRun([]string{"--pid=container:", "img", "cmd"}); err == nil || err.Error() != "--pid: invalid PID mode" {
517 517
 		t.Fatalf("Expected an error with message '--pid: invalid PID mode', got %v", err)
518 518
 	}
519 519
 	// pid ok
520
-	_, hostconfig, _, _, err = parseRun([]string{"--pid=host", "img", "cmd"})
520
+	_, hostconfig, _, err = parseRun([]string{"--pid=host", "img", "cmd"})
521 521
 	if err != nil {
522 522
 		t.Fatal(err)
523 523
 	}
... ...
@@ -525,11 +530,11 @@ func TestParseModes(t *testing.T) {
525 525
 		t.Fatalf("Expected a valid PidMode, got %v", hostconfig.PidMode)
526 526
 	}
527 527
 	// uts ko
528
-	if _, _, _, _, err := parseRun([]string{"--uts=container:", "img", "cmd"}); err == nil || err.Error() != "--uts: invalid UTS mode" {
528
+	if _, _, _, err := parseRun([]string{"--uts=container:", "img", "cmd"}); err == nil || err.Error() != "--uts: invalid UTS mode" {
529 529
 		t.Fatalf("Expected an error with message '--uts: invalid UTS mode', got %v", err)
530 530
 	}
531 531
 	// uts ok
532
-	_, hostconfig, _, _, err = parseRun([]string{"--uts=host", "img", "cmd"})
532
+	_, hostconfig, _, err = parseRun([]string{"--uts=host", "img", "cmd"})
533 533
 	if err != nil {
534 534
 		t.Fatal(err)
535 535
 	}
... ...
@@ -537,11 +542,11 @@ func TestParseModes(t *testing.T) {
537 537
 		t.Fatalf("Expected a valid UTSMode, got %v", hostconfig.UTSMode)
538 538
 	}
539 539
 	// shm-size ko
540
-	if _, _, _, _, err = parseRun([]string{"--shm-size=a128m", "img", "cmd"}); err == nil || err.Error() != "invalid size: 'a128m'" {
540
+	if _, _, _, err = parseRun([]string{"--shm-size=a128m", "img", "cmd"}); err == nil || err.Error() != "invalid size: 'a128m'" {
541 541
 		t.Fatalf("Expected an error with message 'invalid size: a128m', got %v", err)
542 542
 	}
543 543
 	// shm-size ok
544
-	_, hostconfig, _, _, err = parseRun([]string{"--shm-size=128m", "img", "cmd"})
544
+	_, hostconfig, _, err = parseRun([]string{"--shm-size=128m", "img", "cmd"})
545 545
 	if err != nil {
546 546
 		t.Fatal(err)
547 547
 	}
... ...
@@ -570,12 +575,12 @@ func TestParseRestartPolicy(t *testing.T) {
570 570
 		},
571 571
 	}
572 572
 	for restart, expectedError := range invalids {
573
-		if _, _, _, _, err := parseRun([]string{fmt.Sprintf("--restart=%s", restart), "img", "cmd"}); err == nil || err.Error() != expectedError {
573
+		if _, _, _, err := parseRun([]string{fmt.Sprintf("--restart=%s", restart), "img", "cmd"}); err == nil || err.Error() != expectedError {
574 574
 			t.Fatalf("Expected an error with message '%v' for %v, got %v", expectedError, restart, err)
575 575
 		}
576 576
 	}
577 577
 	for restart, expected := range valids {
578
-		_, hostconfig, _, _, err := parseRun([]string{fmt.Sprintf("--restart=%v", restart), "img", "cmd"})
578
+		_, hostconfig, _, err := parseRun([]string{fmt.Sprintf("--restart=%v", restart), "img", "cmd"})
579 579
 		if err != nil {
580 580
 			t.Fatal(err)
581 581
 		}
... ...
@@ -626,11 +631,11 @@ func TestParseHealth(t *testing.T) {
626 626
 
627 627
 func TestParseLoggingOpts(t *testing.T) {
628 628
 	// logging opts ko
629
-	if _, _, _, _, err := parseRun([]string{"--log-driver=none", "--log-opt=anything", "img", "cmd"}); err == nil || err.Error() != "invalid logging opts for driver none" {
629
+	if _, _, _, err := parseRun([]string{"--log-driver=none", "--log-opt=anything", "img", "cmd"}); err == nil || err.Error() != "invalid logging opts for driver none" {
630 630
 		t.Fatalf("Expected an error with message 'invalid logging opts for driver none', got %v", err)
631 631
 	}
632 632
 	// logging opts ok
633
-	_, hostconfig, _, _, err := parseRun([]string{"--log-driver=syslog", "--log-opt=something", "img", "cmd"})
633
+	_, hostconfig, _, err := parseRun([]string{"--log-driver=syslog", "--log-opt=something", "img", "cmd"})
634 634
 	if err != nil {
635 635
 		t.Fatal(err)
636 636
 	}
... ...
@@ -645,18 +650,18 @@ func TestParseEnvfileVariables(t *testing.T) {
645 645
 		e = "open nonexistent: The system cannot find the file specified."
646 646
 	}
647 647
 	// env ko
648
-	if _, _, _, _, err := parseRun([]string{"--env-file=nonexistent", "img", "cmd"}); err == nil || err.Error() != e {
648
+	if _, _, _, err := parseRun([]string{"--env-file=nonexistent", "img", "cmd"}); err == nil || err.Error() != e {
649 649
 		t.Fatalf("Expected an error with message '%s', got %v", e, err)
650 650
 	}
651 651
 	// env ok
652
-	config, _, _, _, err := parseRun([]string{"--env-file=fixtures/valid.env", "img", "cmd"})
652
+	config, _, _, err := parseRun([]string{"--env-file=fixtures/valid.env", "img", "cmd"})
653 653
 	if err != nil {
654 654
 		t.Fatal(err)
655 655
 	}
656 656
 	if len(config.Env) != 1 || config.Env[0] != "ENV1=value1" {
657 657
 		t.Fatalf("Expected a config with [ENV1=value1], got %v", config.Env)
658 658
 	}
659
-	config, _, _, _, err = parseRun([]string{"--env-file=fixtures/valid.env", "--env=ENV2=value2", "img", "cmd"})
659
+	config, _, _, err = parseRun([]string{"--env-file=fixtures/valid.env", "--env=ENV2=value2", "img", "cmd"})
660 660
 	if err != nil {
661 661
 		t.Fatal(err)
662 662
 	}
... ...
@@ -671,18 +676,18 @@ func TestParseLabelfileVariables(t *testing.T) {
671 671
 		e = "open nonexistent: The system cannot find the file specified."
672 672
 	}
673 673
 	// label ko
674
-	if _, _, _, _, err := parseRun([]string{"--label-file=nonexistent", "img", "cmd"}); err == nil || err.Error() != e {
674
+	if _, _, _, err := parseRun([]string{"--label-file=nonexistent", "img", "cmd"}); err == nil || err.Error() != e {
675 675
 		t.Fatalf("Expected an error with message '%s', got %v", e, err)
676 676
 	}
677 677
 	// label ok
678
-	config, _, _, _, err := parseRun([]string{"--label-file=fixtures/valid.label", "img", "cmd"})
678
+	config, _, _, err := parseRun([]string{"--label-file=fixtures/valid.label", "img", "cmd"})
679 679
 	if err != nil {
680 680
 		t.Fatal(err)
681 681
 	}
682 682
 	if len(config.Labels) != 1 || config.Labels["LABEL1"] != "value1" {
683 683
 		t.Fatalf("Expected a config with [LABEL1:value1], got %v", config.Labels)
684 684
 	}
685
-	config, _, _, _, err = parseRun([]string{"--label-file=fixtures/valid.label", "--label=LABEL2=value2", "img", "cmd"})
685
+	config, _, _, err = parseRun([]string{"--label-file=fixtures/valid.label", "--label=LABEL2=value2", "img", "cmd"})
686 686
 	if err != nil {
687 687
 		t.Fatal(err)
688 688
 	}
... ...
@@ -692,7 +697,7 @@ func TestParseLabelfileVariables(t *testing.T) {
692 692
 }
693 693
 
694 694
 func TestParseEntryPoint(t *testing.T) {
695
-	config, _, _, _, err := parseRun([]string{"--entrypoint=anything", "cmd", "img"})
695
+	config, _, _, err := parseRun([]string{"--entrypoint=anything", "cmd", "img"})
696 696
 	if err != nil {
697 697
 		t.Fatal(err)
698 698
 	}
... ...
@@ -106,3 +106,8 @@ func (opt *ThrottledeviceOpt) GetList() []*blkiodev.ThrottleDevice {
106 106
 
107 107
 	return throttledevice
108 108
 }
109
+
110
+// Type returns the option type
111
+func (opt *ThrottledeviceOpt) Type() string {
112
+	return "throttled-device"
113
+}
... ...
@@ -50,3 +50,8 @@ func (o *UlimitOpt) GetList() []*units.Ulimit {
50 50
 
51 51
 	return ulimits
52 52
 }
53
+
54
+// Type returns the option type
55
+func (o *UlimitOpt) Type() string {
56
+	return "ulimit"
57
+}
... ...
@@ -82,3 +82,8 @@ func (opt *WeightdeviceOpt) GetList() []*blkiodev.WeightDevice {
82 82
 
83 83
 	return weightdevice
84 84
 }
85
+
86
+// Type returns the option type
87
+func (opt *WeightdeviceOpt) Type() string {
88
+	return "weighted-device"
89
+}