Browse code

Clean some stuff from runconfig that are cli only…

… or could be in `opts` package. Having `runconfig/opts` and `opts`
doesn't really make sense and make it difficult to know where to put
some code.

Signed-off-by: Vincent Demeester <vincent@sbr.pm>

Vincent Demeester authored on 2016/12/24 04:09:12
Showing 46 changed files
... ...
@@ -18,7 +18,6 @@ import (
18 18
 	apiclient "github.com/docker/docker/client"
19 19
 	"github.com/docker/docker/reference"
20 20
 	"github.com/docker/docker/registry"
21
-	runconfigopts "github.com/docker/docker/runconfig/opts"
22 21
 	"github.com/spf13/cobra"
23 22
 	"github.com/spf13/pflag"
24 23
 )
... ...
@@ -30,7 +29,7 @@ type createOptions struct {
30 30
 // NewCreateCommand creates a new cobra.Command for `docker create`
31 31
 func NewCreateCommand(dockerCli *command.DockerCli) *cobra.Command {
32 32
 	var opts createOptions
33
-	var copts *runconfigopts.ContainerOptions
33
+	var copts *containerOptions
34 34
 
35 35
 	cmd := &cobra.Command{
36 36
 		Use:   "create [OPTIONS] IMAGE [COMMAND] [ARG...]",
... ...
@@ -55,12 +54,12 @@ func NewCreateCommand(dockerCli *command.DockerCli) *cobra.Command {
55 55
 	flags.Bool("help", false, "Print usage")
56 56
 
57 57
 	command.AddTrustedFlags(flags, true)
58
-	copts = runconfigopts.AddFlags(flags)
58
+	copts = addFlags(flags)
59 59
 	return cmd
60 60
 }
61 61
 
62
-func runCreate(dockerCli *command.DockerCli, flags *pflag.FlagSet, opts *createOptions, copts *runconfigopts.ContainerOptions) error {
63
-	config, hostConfig, networkingConfig, err := runconfigopts.Parse(flags, copts)
62
+func runCreate(dockerCli *command.DockerCli, flags *pflag.FlagSet, opts *createOptions, copts *containerOptions) error {
63
+	config, hostConfig, networkingConfig, err := parse(flags, copts)
64 64
 	if err != nil {
65 65
 		reportError(dockerCli.Err(), "create", err.Error(), true)
66 66
 		return cli.StatusError{StatusCode: 125}
... ...
@@ -13,7 +13,6 @@ import (
13 13
 	apiclient "github.com/docker/docker/client"
14 14
 	options "github.com/docker/docker/opts"
15 15
 	"github.com/docker/docker/pkg/promise"
16
-	runconfigopts "github.com/docker/docker/runconfig/opts"
17 16
 	"github.com/spf13/cobra"
18 17
 )
19 18
 
... ...
@@ -30,7 +29,7 @@ type execOptions struct {
30 30
 func newExecOptions() *execOptions {
31 31
 	var values []string
32 32
 	return &execOptions{
33
-		env: options.NewListOptsRef(&values, runconfigopts.ValidateEnv),
33
+		env: options.NewListOptsRef(&values, options.ValidateEnv),
34 34
 	}
35 35
 }
36 36
 
37 37
new file mode 100644
... ...
@@ -0,0 +1,899 @@
0
+package container
1
+
2
+import (
3
+	"bytes"
4
+	"encoding/json"
5
+	"fmt"
6
+	"io/ioutil"
7
+	"path"
8
+	"strconv"
9
+	"strings"
10
+	"time"
11
+
12
+	"github.com/docker/docker/api/types/container"
13
+	networktypes "github.com/docker/docker/api/types/network"
14
+	"github.com/docker/docker/api/types/strslice"
15
+	"github.com/docker/docker/opts"
16
+	"github.com/docker/docker/pkg/signal"
17
+	runconfigopts "github.com/docker/docker/runconfig/opts"
18
+	"github.com/docker/go-connections/nat"
19
+	units "github.com/docker/go-units"
20
+	"github.com/spf13/pflag"
21
+)
22
+
23
+// containerOptions is a data object with all the options for creating a container
24
+type containerOptions struct {
25
+	attach             opts.ListOpts
26
+	volumes            opts.ListOpts
27
+	tmpfs              opts.ListOpts
28
+	blkioWeightDevice  opts.WeightdeviceOpt
29
+	deviceReadBps      opts.ThrottledeviceOpt
30
+	deviceWriteBps     opts.ThrottledeviceOpt
31
+	links              opts.ListOpts
32
+	aliases            opts.ListOpts
33
+	linkLocalIPs       opts.ListOpts
34
+	deviceReadIOps     opts.ThrottledeviceOpt
35
+	deviceWriteIOps    opts.ThrottledeviceOpt
36
+	env                opts.ListOpts
37
+	labels             opts.ListOpts
38
+	devices            opts.ListOpts
39
+	ulimits            *opts.UlimitOpt
40
+	sysctls            *opts.MapOpts
41
+	publish            opts.ListOpts
42
+	expose             opts.ListOpts
43
+	dns                opts.ListOpts
44
+	dnsSearch          opts.ListOpts
45
+	dnsOptions         opts.ListOpts
46
+	extraHosts         opts.ListOpts
47
+	volumesFrom        opts.ListOpts
48
+	envFile            opts.ListOpts
49
+	capAdd             opts.ListOpts
50
+	capDrop            opts.ListOpts
51
+	groupAdd           opts.ListOpts
52
+	securityOpt        opts.ListOpts
53
+	storageOpt         opts.ListOpts
54
+	labelsFile         opts.ListOpts
55
+	loggingOpts        opts.ListOpts
56
+	privileged         bool
57
+	pidMode            string
58
+	utsMode            string
59
+	usernsMode         string
60
+	publishAll         bool
61
+	stdin              bool
62
+	tty                bool
63
+	oomKillDisable     bool
64
+	oomScoreAdj        int
65
+	containerIDFile    string
66
+	entrypoint         string
67
+	hostname           string
68
+	memoryString       string
69
+	memoryReservation  string
70
+	memorySwap         string
71
+	kernelMemory       string
72
+	user               string
73
+	workingDir         string
74
+	cpuCount           int64
75
+	cpuShares          int64
76
+	cpuPercent         int64
77
+	cpuPeriod          int64
78
+	cpuRealtimePeriod  int64
79
+	cpuRealtimeRuntime int64
80
+	cpuQuota           int64
81
+	cpus               opts.NanoCPUs
82
+	cpusetCpus         string
83
+	cpusetMems         string
84
+	blkioWeight        uint16
85
+	ioMaxBandwidth     string
86
+	ioMaxIOps          uint64
87
+	swappiness         int64
88
+	netMode            string
89
+	macAddress         string
90
+	ipv4Address        string
91
+	ipv6Address        string
92
+	ipcMode            string
93
+	pidsLimit          int64
94
+	restartPolicy      string
95
+	readonlyRootfs     bool
96
+	loggingDriver      string
97
+	cgroupParent       string
98
+	volumeDriver       string
99
+	stopSignal         string
100
+	stopTimeout        int
101
+	isolation          string
102
+	shmSize            string
103
+	noHealthcheck      bool
104
+	healthCmd          string
105
+	healthInterval     time.Duration
106
+	healthTimeout      time.Duration
107
+	healthRetries      int
108
+	runtime            string
109
+	autoRemove         bool
110
+	init               bool
111
+	initPath           string
112
+	credentialSpec     string
113
+
114
+	Image string
115
+	Args  []string
116
+}
117
+
118
+// addFlags adds all command line flags that will be used by parse to the FlagSet
119
+func addFlags(flags *pflag.FlagSet) *containerOptions {
120
+	copts := &containerOptions{
121
+		aliases:           opts.NewListOpts(nil),
122
+		attach:            opts.NewListOpts(validateAttach),
123
+		blkioWeightDevice: opts.NewWeightdeviceOpt(opts.ValidateWeightDevice),
124
+		capAdd:            opts.NewListOpts(nil),
125
+		capDrop:           opts.NewListOpts(nil),
126
+		dns:               opts.NewListOpts(opts.ValidateIPAddress),
127
+		dnsOptions:        opts.NewListOpts(nil),
128
+		dnsSearch:         opts.NewListOpts(opts.ValidateDNSSearch),
129
+		deviceReadBps:     opts.NewThrottledeviceOpt(opts.ValidateThrottleBpsDevice),
130
+		deviceReadIOps:    opts.NewThrottledeviceOpt(opts.ValidateThrottleIOpsDevice),
131
+		deviceWriteBps:    opts.NewThrottledeviceOpt(opts.ValidateThrottleBpsDevice),
132
+		deviceWriteIOps:   opts.NewThrottledeviceOpt(opts.ValidateThrottleIOpsDevice),
133
+		devices:           opts.NewListOpts(validateDevice),
134
+		env:               opts.NewListOpts(opts.ValidateEnv),
135
+		envFile:           opts.NewListOpts(nil),
136
+		expose:            opts.NewListOpts(nil),
137
+		extraHosts:        opts.NewListOpts(opts.ValidateExtraHost),
138
+		groupAdd:          opts.NewListOpts(nil),
139
+		labels:            opts.NewListOpts(opts.ValidateEnv),
140
+		labelsFile:        opts.NewListOpts(nil),
141
+		linkLocalIPs:      opts.NewListOpts(nil),
142
+		links:             opts.NewListOpts(opts.ValidateLink),
143
+		loggingOpts:       opts.NewListOpts(nil),
144
+		publish:           opts.NewListOpts(nil),
145
+		securityOpt:       opts.NewListOpts(nil),
146
+		storageOpt:        opts.NewListOpts(nil),
147
+		sysctls:           opts.NewMapOpts(nil, opts.ValidateSysctl),
148
+		tmpfs:             opts.NewListOpts(nil),
149
+		ulimits:           opts.NewUlimitOpt(nil),
150
+		volumes:           opts.NewListOpts(nil),
151
+		volumesFrom:       opts.NewListOpts(nil),
152
+	}
153
+
154
+	// General purpose flags
155
+	flags.VarP(&copts.attach, "attach", "a", "Attach to STDIN, STDOUT or STDERR")
156
+	flags.Var(&copts.devices, "device", "Add a host device to the container")
157
+	flags.VarP(&copts.env, "env", "e", "Set environment variables")
158
+	flags.Var(&copts.envFile, "env-file", "Read in a file of environment variables")
159
+	flags.StringVar(&copts.entrypoint, "entrypoint", "", "Overwrite the default ENTRYPOINT of the image")
160
+	flags.Var(&copts.groupAdd, "group-add", "Add additional groups to join")
161
+	flags.StringVarP(&copts.hostname, "hostname", "h", "", "Container host name")
162
+	flags.BoolVarP(&copts.stdin, "interactive", "i", false, "Keep STDIN open even if not attached")
163
+	flags.VarP(&copts.labels, "label", "l", "Set meta data on a container")
164
+	flags.Var(&copts.labelsFile, "label-file", "Read in a line delimited file of labels")
165
+	flags.BoolVar(&copts.readonlyRootfs, "read-only", false, "Mount the container's root filesystem as read only")
166
+	flags.StringVar(&copts.restartPolicy, "restart", "no", "Restart policy to apply when a container exits")
167
+	flags.StringVar(&copts.stopSignal, "stop-signal", signal.DefaultStopSignal, fmt.Sprintf("Signal to stop a container, %v by default", signal.DefaultStopSignal))
168
+	flags.IntVar(&copts.stopTimeout, "stop-timeout", 0, "Timeout (in seconds) to stop a container")
169
+	flags.SetAnnotation("stop-timeout", "version", []string{"1.25"})
170
+	flags.Var(copts.sysctls, "sysctl", "Sysctl options")
171
+	flags.BoolVarP(&copts.tty, "tty", "t", false, "Allocate a pseudo-TTY")
172
+	flags.Var(copts.ulimits, "ulimit", "Ulimit options")
173
+	flags.StringVarP(&copts.user, "user", "u", "", "Username or UID (format: <name|uid>[:<group|gid>])")
174
+	flags.StringVarP(&copts.workingDir, "workdir", "w", "", "Working directory inside the container")
175
+	flags.BoolVar(&copts.autoRemove, "rm", false, "Automatically remove the container when it exits")
176
+
177
+	// Security
178
+	flags.Var(&copts.capAdd, "cap-add", "Add Linux capabilities")
179
+	flags.Var(&copts.capDrop, "cap-drop", "Drop Linux capabilities")
180
+	flags.BoolVar(&copts.privileged, "privileged", false, "Give extended privileges to this container")
181
+	flags.Var(&copts.securityOpt, "security-opt", "Security Options")
182
+	flags.StringVar(&copts.usernsMode, "userns", "", "User namespace to use")
183
+	flags.StringVar(&copts.credentialSpec, "credentialspec", "", "Credential spec for managed service account (Windows only)")
184
+
185
+	// Network and port publishing flag
186
+	flags.Var(&copts.extraHosts, "add-host", "Add a custom host-to-IP mapping (host:ip)")
187
+	flags.Var(&copts.dns, "dns", "Set custom DNS servers")
188
+	// We allow for both "--dns-opt" and "--dns-option", although the latter is the recommended way.
189
+	// This is to be consistent with service create/update
190
+	flags.Var(&copts.dnsOptions, "dns-opt", "Set DNS options")
191
+	flags.Var(&copts.dnsOptions, "dns-option", "Set DNS options")
192
+	flags.MarkHidden("dns-opt")
193
+	flags.Var(&copts.dnsSearch, "dns-search", "Set custom DNS search domains")
194
+	flags.Var(&copts.expose, "expose", "Expose a port or a range of ports")
195
+	flags.StringVar(&copts.ipv4Address, "ip", "", "Container IPv4 address (e.g. 172.30.100.104)")
196
+	flags.StringVar(&copts.ipv6Address, "ip6", "", "Container IPv6 address (e.g. 2001:db8::33)")
197
+	flags.Var(&copts.links, "link", "Add link to another container")
198
+	flags.Var(&copts.linkLocalIPs, "link-local-ip", "Container IPv4/IPv6 link-local addresses")
199
+	flags.StringVar(&copts.macAddress, "mac-address", "", "Container MAC address (e.g. 92:d0:c6:0a:29:33)")
200
+	flags.VarP(&copts.publish, "publish", "p", "Publish a container's port(s) to the host")
201
+	flags.BoolVarP(&copts.publishAll, "publish-all", "P", false, "Publish all exposed ports to random ports")
202
+	// We allow for both "--net" and "--network", although the latter is the recommended way.
203
+	flags.StringVar(&copts.netMode, "net", "default", "Connect a container to a network")
204
+	flags.StringVar(&copts.netMode, "network", "default", "Connect a container to a network")
205
+	flags.MarkHidden("net")
206
+	// We allow for both "--net-alias" and "--network-alias", although the latter is the recommended way.
207
+	flags.Var(&copts.aliases, "net-alias", "Add network-scoped alias for the container")
208
+	flags.Var(&copts.aliases, "network-alias", "Add network-scoped alias for the container")
209
+	flags.MarkHidden("net-alias")
210
+
211
+	// Logging and storage
212
+	flags.StringVar(&copts.loggingDriver, "log-driver", "", "Logging driver for the container")
213
+	flags.StringVar(&copts.volumeDriver, "volume-driver", "", "Optional volume driver for the container")
214
+	flags.Var(&copts.loggingOpts, "log-opt", "Log driver options")
215
+	flags.Var(&copts.storageOpt, "storage-opt", "Storage driver options for the container")
216
+	flags.Var(&copts.tmpfs, "tmpfs", "Mount a tmpfs directory")
217
+	flags.Var(&copts.volumesFrom, "volumes-from", "Mount volumes from the specified container(s)")
218
+	flags.VarP(&copts.volumes, "volume", "v", "Bind mount a volume")
219
+
220
+	// Health-checking
221
+	flags.StringVar(&copts.healthCmd, "health-cmd", "", "Command to run to check health")
222
+	flags.DurationVar(&copts.healthInterval, "health-interval", 0, "Time between running the check (ns|us|ms|s|m|h) (default 0s)")
223
+	flags.IntVar(&copts.healthRetries, "health-retries", 0, "Consecutive failures needed to report unhealthy")
224
+	flags.DurationVar(&copts.healthTimeout, "health-timeout", 0, "Maximum time to allow one check to run (ns|us|ms|s|m|h) (default 0s)")
225
+	flags.BoolVar(&copts.noHealthcheck, "no-healthcheck", false, "Disable any container-specified HEALTHCHECK")
226
+
227
+	// Resource management
228
+	flags.Uint16Var(&copts.blkioWeight, "blkio-weight", 0, "Block IO (relative weight), between 10 and 1000, or 0 to disable (default 0)")
229
+	flags.Var(&copts.blkioWeightDevice, "blkio-weight-device", "Block IO weight (relative device weight)")
230
+	flags.StringVar(&copts.containerIDFile, "cidfile", "", "Write the container ID to the file")
231
+	flags.StringVar(&copts.cpusetCpus, "cpuset-cpus", "", "CPUs in which to allow execution (0-3, 0,1)")
232
+	flags.StringVar(&copts.cpusetMems, "cpuset-mems", "", "MEMs in which to allow execution (0-3, 0,1)")
233
+	flags.Int64Var(&copts.cpuCount, "cpu-count", 0, "CPU count (Windows only)")
234
+	flags.Int64Var(&copts.cpuPercent, "cpu-percent", 0, "CPU percent (Windows only)")
235
+	flags.Int64Var(&copts.cpuPeriod, "cpu-period", 0, "Limit CPU CFS (Completely Fair Scheduler) period")
236
+	flags.Int64Var(&copts.cpuQuota, "cpu-quota", 0, "Limit CPU CFS (Completely Fair Scheduler) quota")
237
+	flags.Int64Var(&copts.cpuRealtimePeriod, "cpu-rt-period", 0, "Limit CPU real-time period in microseconds")
238
+	flags.Int64Var(&copts.cpuRealtimeRuntime, "cpu-rt-runtime", 0, "Limit CPU real-time runtime in microseconds")
239
+	flags.Int64VarP(&copts.cpuShares, "cpu-shares", "c", 0, "CPU shares (relative weight)")
240
+	flags.Var(&copts.cpus, "cpus", "Number of CPUs")
241
+	flags.Var(&copts.deviceReadBps, "device-read-bps", "Limit read rate (bytes per second) from a device")
242
+	flags.Var(&copts.deviceReadIOps, "device-read-iops", "Limit read rate (IO per second) from a device")
243
+	flags.Var(&copts.deviceWriteBps, "device-write-bps", "Limit write rate (bytes per second) to a device")
244
+	flags.Var(&copts.deviceWriteIOps, "device-write-iops", "Limit write rate (IO per second) to a device")
245
+	flags.StringVar(&copts.ioMaxBandwidth, "io-maxbandwidth", "", "Maximum IO bandwidth limit for the system drive (Windows only)")
246
+	flags.Uint64Var(&copts.ioMaxIOps, "io-maxiops", 0, "Maximum IOps limit for the system drive (Windows only)")
247
+	flags.StringVar(&copts.kernelMemory, "kernel-memory", "", "Kernel memory limit")
248
+	flags.StringVarP(&copts.memoryString, "memory", "m", "", "Memory limit")
249
+	flags.StringVar(&copts.memoryReservation, "memory-reservation", "", "Memory soft limit")
250
+	flags.StringVar(&copts.memorySwap, "memory-swap", "", "Swap limit equal to memory plus swap: '-1' to enable unlimited swap")
251
+	flags.Int64Var(&copts.swappiness, "memory-swappiness", -1, "Tune container memory swappiness (0 to 100)")
252
+	flags.BoolVar(&copts.oomKillDisable, "oom-kill-disable", false, "Disable OOM Killer")
253
+	flags.IntVar(&copts.oomScoreAdj, "oom-score-adj", 0, "Tune host's OOM preferences (-1000 to 1000)")
254
+	flags.Int64Var(&copts.pidsLimit, "pids-limit", 0, "Tune container pids limit (set -1 for unlimited)")
255
+
256
+	// Low-level execution (cgroups, namespaces, ...)
257
+	flags.StringVar(&copts.cgroupParent, "cgroup-parent", "", "Optional parent cgroup for the container")
258
+	flags.StringVar(&copts.ipcMode, "ipc", "", "IPC namespace to use")
259
+	flags.StringVar(&copts.isolation, "isolation", "", "Container isolation technology")
260
+	flags.StringVar(&copts.pidMode, "pid", "", "PID namespace to use")
261
+	flags.StringVar(&copts.shmSize, "shm-size", "", "Size of /dev/shm, default value is 64MB")
262
+	flags.StringVar(&copts.utsMode, "uts", "", "UTS namespace to use")
263
+	flags.StringVar(&copts.runtime, "runtime", "", "Runtime to use for this container")
264
+
265
+	flags.BoolVar(&copts.init, "init", false, "Run an init inside the container that forwards signals and reaps processes")
266
+	flags.StringVar(&copts.initPath, "init-path", "", "Path to the docker-init binary")
267
+	return copts
268
+}
269
+
270
+// parse parses the args for the specified command and generates a Config,
271
+// a HostConfig and returns them with the specified command.
272
+// If the specified args are not valid, it will return an error.
273
+func parse(flags *pflag.FlagSet, copts *containerOptions) (*container.Config, *container.HostConfig, *networktypes.NetworkingConfig, error) {
274
+	var (
275
+		attachStdin  = copts.attach.Get("stdin")
276
+		attachStdout = copts.attach.Get("stdout")
277
+		attachStderr = copts.attach.Get("stderr")
278
+	)
279
+
280
+	// Validate the input mac address
281
+	if copts.macAddress != "" {
282
+		if _, err := opts.ValidateMACAddress(copts.macAddress); err != nil {
283
+			return nil, nil, nil, fmt.Errorf("%s is not a valid mac address", copts.macAddress)
284
+		}
285
+	}
286
+	if copts.stdin {
287
+		attachStdin = true
288
+	}
289
+	// If -a is not set, attach to stdout and stderr
290
+	if copts.attach.Len() == 0 {
291
+		attachStdout = true
292
+		attachStderr = true
293
+	}
294
+
295
+	var err error
296
+
297
+	var memory int64
298
+	if copts.memoryString != "" {
299
+		memory, err = units.RAMInBytes(copts.memoryString)
300
+		if err != nil {
301
+			return nil, nil, nil, err
302
+		}
303
+	}
304
+
305
+	var memoryReservation int64
306
+	if copts.memoryReservation != "" {
307
+		memoryReservation, err = units.RAMInBytes(copts.memoryReservation)
308
+		if err != nil {
309
+			return nil, nil, nil, err
310
+		}
311
+	}
312
+
313
+	var memorySwap int64
314
+	if copts.memorySwap != "" {
315
+		if copts.memorySwap == "-1" {
316
+			memorySwap = -1
317
+		} else {
318
+			memorySwap, err = units.RAMInBytes(copts.memorySwap)
319
+			if err != nil {
320
+				return nil, nil, nil, err
321
+			}
322
+		}
323
+	}
324
+
325
+	var kernelMemory int64
326
+	if copts.kernelMemory != "" {
327
+		kernelMemory, err = units.RAMInBytes(copts.kernelMemory)
328
+		if err != nil {
329
+			return nil, nil, nil, err
330
+		}
331
+	}
332
+
333
+	swappiness := copts.swappiness
334
+	if swappiness != -1 && (swappiness < 0 || swappiness > 100) {
335
+		return nil, nil, nil, fmt.Errorf("invalid value: %d. Valid memory swappiness range is 0-100", swappiness)
336
+	}
337
+
338
+	var shmSize int64
339
+	if copts.shmSize != "" {
340
+		shmSize, err = units.RAMInBytes(copts.shmSize)
341
+		if err != nil {
342
+			return nil, nil, nil, err
343
+		}
344
+	}
345
+
346
+	// TODO FIXME units.RAMInBytes should have a uint64 version
347
+	var maxIOBandwidth int64
348
+	if copts.ioMaxBandwidth != "" {
349
+		maxIOBandwidth, err = units.RAMInBytes(copts.ioMaxBandwidth)
350
+		if err != nil {
351
+			return nil, nil, nil, err
352
+		}
353
+		if maxIOBandwidth < 0 {
354
+			return nil, nil, nil, fmt.Errorf("invalid value: %s. Maximum IO Bandwidth must be positive", copts.ioMaxBandwidth)
355
+		}
356
+	}
357
+
358
+	var binds []string
359
+	volumes := copts.volumes.GetMap()
360
+	// add any bind targets to the list of container volumes
361
+	for bind := range copts.volumes.GetMap() {
362
+		if arr := volumeSplitN(bind, 2); len(arr) > 1 {
363
+			// after creating the bind mount we want to delete it from the copts.volumes values because
364
+			// we do not want bind mounts being committed to image configs
365
+			binds = append(binds, bind)
366
+			// We should delete from the map (`volumes`) here, as deleting from copts.volumes will not work if
367
+			// there are duplicates entries.
368
+			delete(volumes, bind)
369
+		}
370
+	}
371
+
372
+	// Can't evaluate options passed into --tmpfs until we actually mount
373
+	tmpfs := make(map[string]string)
374
+	for _, t := range copts.tmpfs.GetAll() {
375
+		if arr := strings.SplitN(t, ":", 2); len(arr) > 1 {
376
+			tmpfs[arr[0]] = arr[1]
377
+		} else {
378
+			tmpfs[arr[0]] = ""
379
+		}
380
+	}
381
+
382
+	var (
383
+		runCmd     strslice.StrSlice
384
+		entrypoint strslice.StrSlice
385
+	)
386
+
387
+	if len(copts.Args) > 0 {
388
+		runCmd = strslice.StrSlice(copts.Args)
389
+	}
390
+
391
+	if copts.entrypoint != "" {
392
+		entrypoint = strslice.StrSlice{copts.entrypoint}
393
+	} else if flags.Changed("entrypoint") {
394
+		// if `--entrypoint=` is parsed then Entrypoint is reset
395
+		entrypoint = []string{""}
396
+	}
397
+
398
+	ports, portBindings, err := nat.ParsePortSpecs(copts.publish.GetAll())
399
+	if err != nil {
400
+		return nil, nil, nil, err
401
+	}
402
+
403
+	// Merge in exposed ports to the map of published ports
404
+	for _, e := range copts.expose.GetAll() {
405
+		if strings.Contains(e, ":") {
406
+			return nil, nil, nil, fmt.Errorf("invalid port format for --expose: %s", e)
407
+		}
408
+		//support two formats for expose, original format <portnum>/[<proto>] or <startport-endport>/[<proto>]
409
+		proto, port := nat.SplitProtoPort(e)
410
+		//parse the start and end port and create a sequence of ports to expose
411
+		//if expose a port, the start and end port are the same
412
+		start, end, err := nat.ParsePortRange(port)
413
+		if err != nil {
414
+			return nil, nil, nil, fmt.Errorf("invalid range format for --expose: %s, error: %s", e, err)
415
+		}
416
+		for i := start; i <= end; i++ {
417
+			p, err := nat.NewPort(proto, strconv.FormatUint(i, 10))
418
+			if err != nil {
419
+				return nil, nil, nil, err
420
+			}
421
+			if _, exists := ports[p]; !exists {
422
+				ports[p] = struct{}{}
423
+			}
424
+		}
425
+	}
426
+
427
+	// parse device mappings
428
+	deviceMappings := []container.DeviceMapping{}
429
+	for _, device := range copts.devices.GetAll() {
430
+		deviceMapping, err := parseDevice(device)
431
+		if err != nil {
432
+			return nil, nil, nil, err
433
+		}
434
+		deviceMappings = append(deviceMappings, deviceMapping)
435
+	}
436
+
437
+	// collect all the environment variables for the container
438
+	envVariables, err := runconfigopts.ReadKVStrings(copts.envFile.GetAll(), copts.env.GetAll())
439
+	if err != nil {
440
+		return nil, nil, nil, err
441
+	}
442
+
443
+	// collect all the labels for the container
444
+	labels, err := runconfigopts.ReadKVStrings(copts.labelsFile.GetAll(), copts.labels.GetAll())
445
+	if err != nil {
446
+		return nil, nil, nil, err
447
+	}
448
+
449
+	ipcMode := container.IpcMode(copts.ipcMode)
450
+	if !ipcMode.Valid() {
451
+		return nil, nil, nil, fmt.Errorf("--ipc: invalid IPC mode")
452
+	}
453
+
454
+	pidMode := container.PidMode(copts.pidMode)
455
+	if !pidMode.Valid() {
456
+		return nil, nil, nil, fmt.Errorf("--pid: invalid PID mode")
457
+	}
458
+
459
+	utsMode := container.UTSMode(copts.utsMode)
460
+	if !utsMode.Valid() {
461
+		return nil, nil, nil, fmt.Errorf("--uts: invalid UTS mode")
462
+	}
463
+
464
+	usernsMode := container.UsernsMode(copts.usernsMode)
465
+	if !usernsMode.Valid() {
466
+		return nil, nil, nil, fmt.Errorf("--userns: invalid USER mode")
467
+	}
468
+
469
+	restartPolicy, err := runconfigopts.ParseRestartPolicy(copts.restartPolicy)
470
+	if err != nil {
471
+		return nil, nil, nil, err
472
+	}
473
+
474
+	loggingOpts, err := parseLoggingOpts(copts.loggingDriver, copts.loggingOpts.GetAll())
475
+	if err != nil {
476
+		return nil, nil, nil, err
477
+	}
478
+
479
+	securityOpts, err := parseSecurityOpts(copts.securityOpt.GetAll())
480
+	if err != nil {
481
+		return nil, nil, nil, err
482
+	}
483
+
484
+	storageOpts, err := parseStorageOpts(copts.storageOpt.GetAll())
485
+	if err != nil {
486
+		return nil, nil, nil, err
487
+	}
488
+
489
+	// Healthcheck
490
+	var healthConfig *container.HealthConfig
491
+	haveHealthSettings := copts.healthCmd != "" ||
492
+		copts.healthInterval != 0 ||
493
+		copts.healthTimeout != 0 ||
494
+		copts.healthRetries != 0
495
+	if copts.noHealthcheck {
496
+		if haveHealthSettings {
497
+			return nil, nil, nil, fmt.Errorf("--no-healthcheck conflicts with --health-* options")
498
+		}
499
+		test := strslice.StrSlice{"NONE"}
500
+		healthConfig = &container.HealthConfig{Test: test}
501
+	} else if haveHealthSettings {
502
+		var probe strslice.StrSlice
503
+		if copts.healthCmd != "" {
504
+			args := []string{"CMD-SHELL", copts.healthCmd}
505
+			probe = strslice.StrSlice(args)
506
+		}
507
+		if copts.healthInterval < 0 {
508
+			return nil, nil, nil, fmt.Errorf("--health-interval cannot be negative")
509
+		}
510
+		if copts.healthTimeout < 0 {
511
+			return nil, nil, nil, fmt.Errorf("--health-timeout cannot be negative")
512
+		}
513
+
514
+		healthConfig = &container.HealthConfig{
515
+			Test:     probe,
516
+			Interval: copts.healthInterval,
517
+			Timeout:  copts.healthTimeout,
518
+			Retries:  copts.healthRetries,
519
+		}
520
+	}
521
+
522
+	resources := container.Resources{
523
+		CgroupParent:         copts.cgroupParent,
524
+		Memory:               memory,
525
+		MemoryReservation:    memoryReservation,
526
+		MemorySwap:           memorySwap,
527
+		MemorySwappiness:     &copts.swappiness,
528
+		KernelMemory:         kernelMemory,
529
+		OomKillDisable:       &copts.oomKillDisable,
530
+		NanoCPUs:             copts.cpus.Value(),
531
+		CPUCount:             copts.cpuCount,
532
+		CPUPercent:           copts.cpuPercent,
533
+		CPUShares:            copts.cpuShares,
534
+		CPUPeriod:            copts.cpuPeriod,
535
+		CpusetCpus:           copts.cpusetCpus,
536
+		CpusetMems:           copts.cpusetMems,
537
+		CPUQuota:             copts.cpuQuota,
538
+		CPURealtimePeriod:    copts.cpuRealtimePeriod,
539
+		CPURealtimeRuntime:   copts.cpuRealtimeRuntime,
540
+		PidsLimit:            copts.pidsLimit,
541
+		BlkioWeight:          copts.blkioWeight,
542
+		BlkioWeightDevice:    copts.blkioWeightDevice.GetList(),
543
+		BlkioDeviceReadBps:   copts.deviceReadBps.GetList(),
544
+		BlkioDeviceWriteBps:  copts.deviceWriteBps.GetList(),
545
+		BlkioDeviceReadIOps:  copts.deviceReadIOps.GetList(),
546
+		BlkioDeviceWriteIOps: copts.deviceWriteIOps.GetList(),
547
+		IOMaximumIOps:        copts.ioMaxIOps,
548
+		IOMaximumBandwidth:   uint64(maxIOBandwidth),
549
+		Ulimits:              copts.ulimits.GetList(),
550
+		Devices:              deviceMappings,
551
+	}
552
+
553
+	config := &container.Config{
554
+		Hostname:     copts.hostname,
555
+		ExposedPorts: ports,
556
+		User:         copts.user,
557
+		Tty:          copts.tty,
558
+		// TODO: deprecated, it comes from -n, --networking
559
+		// it's still needed internally to set the network to disabled
560
+		// if e.g. bridge is none in daemon opts, and in inspect
561
+		NetworkDisabled: false,
562
+		OpenStdin:       copts.stdin,
563
+		AttachStdin:     attachStdin,
564
+		AttachStdout:    attachStdout,
565
+		AttachStderr:    attachStderr,
566
+		Env:             envVariables,
567
+		Cmd:             runCmd,
568
+		Image:           copts.Image,
569
+		Volumes:         volumes,
570
+		MacAddress:      copts.macAddress,
571
+		Entrypoint:      entrypoint,
572
+		WorkingDir:      copts.workingDir,
573
+		Labels:          runconfigopts.ConvertKVStringsToMap(labels),
574
+		Healthcheck:     healthConfig,
575
+	}
576
+	if flags.Changed("stop-signal") {
577
+		config.StopSignal = copts.stopSignal
578
+	}
579
+	if flags.Changed("stop-timeout") {
580
+		config.StopTimeout = &copts.stopTimeout
581
+	}
582
+
583
+	hostConfig := &container.HostConfig{
584
+		Binds:           binds,
585
+		ContainerIDFile: copts.containerIDFile,
586
+		OomScoreAdj:     copts.oomScoreAdj,
587
+		AutoRemove:      copts.autoRemove,
588
+		Privileged:      copts.privileged,
589
+		PortBindings:    portBindings,
590
+		Links:           copts.links.GetAll(),
591
+		PublishAllPorts: copts.publishAll,
592
+		// Make sure the dns fields are never nil.
593
+		// New containers don't ever have those fields nil,
594
+		// but pre created containers can still have those nil values.
595
+		// See https://github.com/docker/docker/pull/17779
596
+		// for a more detailed explanation on why we don't want that.
597
+		DNS:            copts.dns.GetAllOrEmpty(),
598
+		DNSSearch:      copts.dnsSearch.GetAllOrEmpty(),
599
+		DNSOptions:     copts.dnsOptions.GetAllOrEmpty(),
600
+		ExtraHosts:     copts.extraHosts.GetAll(),
601
+		VolumesFrom:    copts.volumesFrom.GetAll(),
602
+		NetworkMode:    container.NetworkMode(copts.netMode),
603
+		IpcMode:        ipcMode,
604
+		PidMode:        pidMode,
605
+		UTSMode:        utsMode,
606
+		UsernsMode:     usernsMode,
607
+		CapAdd:         strslice.StrSlice(copts.capAdd.GetAll()),
608
+		CapDrop:        strslice.StrSlice(copts.capDrop.GetAll()),
609
+		GroupAdd:       copts.groupAdd.GetAll(),
610
+		RestartPolicy:  restartPolicy,
611
+		SecurityOpt:    securityOpts,
612
+		StorageOpt:     storageOpts,
613
+		ReadonlyRootfs: copts.readonlyRootfs,
614
+		LogConfig:      container.LogConfig{Type: copts.loggingDriver, Config: loggingOpts},
615
+		VolumeDriver:   copts.volumeDriver,
616
+		Isolation:      container.Isolation(copts.isolation),
617
+		ShmSize:        shmSize,
618
+		Resources:      resources,
619
+		Tmpfs:          tmpfs,
620
+		Sysctls:        copts.sysctls.GetAll(),
621
+		Runtime:        copts.runtime,
622
+	}
623
+
624
+	// only set this value if the user provided the flag, else it should default to nil
625
+	if flags.Changed("init") {
626
+		hostConfig.Init = &copts.init
627
+	}
628
+
629
+	// When allocating stdin in attached mode, close stdin at client disconnect
630
+	if config.OpenStdin && config.AttachStdin {
631
+		config.StdinOnce = true
632
+	}
633
+
634
+	networkingConfig := &networktypes.NetworkingConfig{
635
+		EndpointsConfig: make(map[string]*networktypes.EndpointSettings),
636
+	}
637
+
638
+	if copts.ipv4Address != "" || copts.ipv6Address != "" || copts.linkLocalIPs.Len() > 0 {
639
+		epConfig := &networktypes.EndpointSettings{}
640
+		networkingConfig.EndpointsConfig[string(hostConfig.NetworkMode)] = epConfig
641
+
642
+		epConfig.IPAMConfig = &networktypes.EndpointIPAMConfig{
643
+			IPv4Address: copts.ipv4Address,
644
+			IPv6Address: copts.ipv6Address,
645
+		}
646
+
647
+		if copts.linkLocalIPs.Len() > 0 {
648
+			epConfig.IPAMConfig.LinkLocalIPs = make([]string, copts.linkLocalIPs.Len())
649
+			copy(epConfig.IPAMConfig.LinkLocalIPs, copts.linkLocalIPs.GetAll())
650
+		}
651
+	}
652
+
653
+	if hostConfig.NetworkMode.IsUserDefined() && len(hostConfig.Links) > 0 {
654
+		epConfig := networkingConfig.EndpointsConfig[string(hostConfig.NetworkMode)]
655
+		if epConfig == nil {
656
+			epConfig = &networktypes.EndpointSettings{}
657
+		}
658
+		epConfig.Links = make([]string, len(hostConfig.Links))
659
+		copy(epConfig.Links, hostConfig.Links)
660
+		networkingConfig.EndpointsConfig[string(hostConfig.NetworkMode)] = epConfig
661
+	}
662
+
663
+	if copts.aliases.Len() > 0 {
664
+		epConfig := networkingConfig.EndpointsConfig[string(hostConfig.NetworkMode)]
665
+		if epConfig == nil {
666
+			epConfig = &networktypes.EndpointSettings{}
667
+		}
668
+		epConfig.Aliases = make([]string, copts.aliases.Len())
669
+		copy(epConfig.Aliases, copts.aliases.GetAll())
670
+		networkingConfig.EndpointsConfig[string(hostConfig.NetworkMode)] = epConfig
671
+	}
672
+
673
+	return config, hostConfig, networkingConfig, nil
674
+}
675
+
676
+func parseLoggingOpts(loggingDriver string, loggingOpts []string) (map[string]string, error) {
677
+	loggingOptsMap := runconfigopts.ConvertKVStringsToMap(loggingOpts)
678
+	if loggingDriver == "none" && len(loggingOpts) > 0 {
679
+		return map[string]string{}, fmt.Errorf("invalid logging opts for driver %s", loggingDriver)
680
+	}
681
+	return loggingOptsMap, nil
682
+}
683
+
684
+// takes a local seccomp daemon, reads the file contents for sending to the daemon
685
+func parseSecurityOpts(securityOpts []string) ([]string, error) {
686
+	for key, opt := range securityOpts {
687
+		con := strings.SplitN(opt, "=", 2)
688
+		if len(con) == 1 && con[0] != "no-new-privileges" {
689
+			if strings.Contains(opt, ":") {
690
+				con = strings.SplitN(opt, ":", 2)
691
+			} else {
692
+				return securityOpts, fmt.Errorf("Invalid --security-opt: %q", opt)
693
+			}
694
+		}
695
+		if con[0] == "seccomp" && con[1] != "unconfined" {
696
+			f, err := ioutil.ReadFile(con[1])
697
+			if err != nil {
698
+				return securityOpts, fmt.Errorf("opening seccomp profile (%s) failed: %v", con[1], err)
699
+			}
700
+			b := bytes.NewBuffer(nil)
701
+			if err := json.Compact(b, f); err != nil {
702
+				return securityOpts, fmt.Errorf("compacting json for seccomp profile (%s) failed: %v", con[1], err)
703
+			}
704
+			securityOpts[key] = fmt.Sprintf("seccomp=%s", b.Bytes())
705
+		}
706
+	}
707
+
708
+	return securityOpts, nil
709
+}
710
+
711
+// parses storage options per container into a map
712
+func parseStorageOpts(storageOpts []string) (map[string]string, error) {
713
+	m := make(map[string]string)
714
+	for _, option := range storageOpts {
715
+		if strings.Contains(option, "=") {
716
+			opt := strings.SplitN(option, "=", 2)
717
+			m[opt[0]] = opt[1]
718
+		} else {
719
+			return nil, fmt.Errorf("invalid storage option")
720
+		}
721
+	}
722
+	return m, nil
723
+}
724
+
725
+// parseDevice parses a device mapping string to a container.DeviceMapping struct
726
+func parseDevice(device string) (container.DeviceMapping, error) {
727
+	src := ""
728
+	dst := ""
729
+	permissions := "rwm"
730
+	arr := strings.Split(device, ":")
731
+	switch len(arr) {
732
+	case 3:
733
+		permissions = arr[2]
734
+		fallthrough
735
+	case 2:
736
+		if validDeviceMode(arr[1]) {
737
+			permissions = arr[1]
738
+		} else {
739
+			dst = arr[1]
740
+		}
741
+		fallthrough
742
+	case 1:
743
+		src = arr[0]
744
+	default:
745
+		return container.DeviceMapping{}, fmt.Errorf("invalid device specification: %s", device)
746
+	}
747
+
748
+	if dst == "" {
749
+		dst = src
750
+	}
751
+
752
+	deviceMapping := container.DeviceMapping{
753
+		PathOnHost:        src,
754
+		PathInContainer:   dst,
755
+		CgroupPermissions: permissions,
756
+	}
757
+	return deviceMapping, nil
758
+}
759
+
760
+// validDeviceMode checks if the mode for device is valid or not.
761
+// Valid mode is a composition of r (read), w (write), and m (mknod).
762
+func validDeviceMode(mode string) bool {
763
+	var legalDeviceMode = map[rune]bool{
764
+		'r': true,
765
+		'w': true,
766
+		'm': true,
767
+	}
768
+	if mode == "" {
769
+		return false
770
+	}
771
+	for _, c := range mode {
772
+		if !legalDeviceMode[c] {
773
+			return false
774
+		}
775
+		legalDeviceMode[c] = false
776
+	}
777
+	return true
778
+}
779
+
780
+// validateDevice validates a path for devices
781
+// It will make sure 'val' is in the form:
782
+//    [host-dir:]container-path[:mode]
783
+// It also validates the device mode.
784
+func validateDevice(val string) (string, error) {
785
+	return validatePath(val, validDeviceMode)
786
+}
787
+
788
+func validatePath(val string, validator func(string) bool) (string, error) {
789
+	var containerPath string
790
+	var mode string
791
+
792
+	if strings.Count(val, ":") > 2 {
793
+		return val, fmt.Errorf("bad format for path: %s", val)
794
+	}
795
+
796
+	split := strings.SplitN(val, ":", 3)
797
+	if split[0] == "" {
798
+		return val, fmt.Errorf("bad format for path: %s", val)
799
+	}
800
+	switch len(split) {
801
+	case 1:
802
+		containerPath = split[0]
803
+		val = path.Clean(containerPath)
804
+	case 2:
805
+		if isValid := validator(split[1]); isValid {
806
+			containerPath = split[0]
807
+			mode = split[1]
808
+			val = fmt.Sprintf("%s:%s", path.Clean(containerPath), mode)
809
+		} else {
810
+			containerPath = split[1]
811
+			val = fmt.Sprintf("%s:%s", split[0], path.Clean(containerPath))
812
+		}
813
+	case 3:
814
+		containerPath = split[1]
815
+		mode = split[2]
816
+		if isValid := validator(split[2]); !isValid {
817
+			return val, fmt.Errorf("bad mode specified: %s", mode)
818
+		}
819
+		val = fmt.Sprintf("%s:%s:%s", split[0], containerPath, mode)
820
+	}
821
+
822
+	if !path.IsAbs(containerPath) {
823
+		return val, fmt.Errorf("%s is not an absolute path", containerPath)
824
+	}
825
+	return val, nil
826
+}
827
+
828
+// volumeSplitN splits raw into a maximum of n parts, separated by a separator colon.
829
+// A separator colon is the last `:` character in the regex `[:\\]?[a-zA-Z]:` (note `\\` is `\` escaped).
830
+// In Windows driver letter appears in two situations:
831
+// a. `^[a-zA-Z]:` (A colon followed  by `^[a-zA-Z]:` is OK as colon is the separator in volume option)
832
+// b. A string in the format like `\\?\C:\Windows\...` (UNC).
833
+// Therefore, a driver letter can only follow either a `:` or `\\`
834
+// This allows to correctly split strings such as `C:\foo:D:\:rw` or `/tmp/q:/foo`.
835
+func volumeSplitN(raw string, n int) []string {
836
+	var array []string
837
+	if len(raw) == 0 || raw[0] == ':' {
838
+		// invalid
839
+		return nil
840
+	}
841
+	// numberOfParts counts the number of parts separated by a separator colon
842
+	numberOfParts := 0
843
+	// left represents the left-most cursor in raw, updated at every `:` character considered as a separator.
844
+	left := 0
845
+	// right represents the right-most cursor in raw incremented with the loop. Note this
846
+	// starts at index 1 as index 0 is already handle above as a special case.
847
+	for right := 1; right < len(raw); right++ {
848
+		// stop parsing if reached maximum number of parts
849
+		if n >= 0 && numberOfParts >= n {
850
+			break
851
+		}
852
+		if raw[right] != ':' {
853
+			continue
854
+		}
855
+		potentialDriveLetter := raw[right-1]
856
+		if (potentialDriveLetter >= 'A' && potentialDriveLetter <= 'Z') || (potentialDriveLetter >= 'a' && potentialDriveLetter <= 'z') {
857
+			if right > 1 {
858
+				beforePotentialDriveLetter := raw[right-2]
859
+				// Only `:` or `\\` are checked (`/` could fall into the case of `/tmp/q:/foo`)
860
+				if beforePotentialDriveLetter != ':' && beforePotentialDriveLetter != '\\' {
861
+					// e.g. `C:` is not preceded by any delimiter, therefore it was not a drive letter but a path ending with `C:`.
862
+					array = append(array, raw[left:right])
863
+					left = right + 1
864
+					numberOfParts++
865
+				}
866
+				// else, `C:` is considered as a drive letter and not as a delimiter, so we continue parsing.
867
+			}
868
+			// if right == 1, then `C:` is the beginning of the raw string, therefore `:` is again not considered a delimiter and we continue parsing.
869
+		} else {
870
+			// if `:` is not preceded by a potential drive letter, then consider it as a delimiter.
871
+			array = append(array, raw[left:right])
872
+			left = right + 1
873
+			numberOfParts++
874
+		}
875
+	}
876
+	// need to take care of the last part
877
+	if left < len(raw) {
878
+		if n >= 0 && numberOfParts >= n {
879
+			// if the maximum number of parts is reached, just append the rest to the last part
880
+			// left-1 is at the last `:` that needs to be included since not considered a separator.
881
+			array[n-1] += raw[left-1:]
882
+		} else {
883
+			array = append(array, raw[left:])
884
+		}
885
+	}
886
+	return array
887
+}
888
+
889
+// validateAttach validates that the specified string is a valid attach option.
890
+func validateAttach(val string) (string, error) {
891
+	s := strings.ToLower(val)
892
+	for _, str := range []string{"stdin", "stdout", "stderr"} {
893
+		if s == str {
894
+			return s, nil
895
+		}
896
+	}
897
+	return val, fmt.Errorf("valid streams are STDIN, STDOUT and STDERR")
898
+}
0 899
new file mode 100644
... ...
@@ -0,0 +1,857 @@
0
+package container
1
+
2
+import (
3
+	"bytes"
4
+	"encoding/json"
5
+	"fmt"
6
+	"io/ioutil"
7
+	"os"
8
+	"runtime"
9
+	"strings"
10
+	"testing"
11
+	"time"
12
+
13
+	"github.com/docker/docker/api/types/container"
14
+	networktypes "github.com/docker/docker/api/types/network"
15
+	"github.com/docker/docker/runconfig"
16
+	"github.com/docker/go-connections/nat"
17
+	"github.com/spf13/pflag"
18
+)
19
+
20
+func TestValidateAttach(t *testing.T) {
21
+	valid := []string{
22
+		"stdin",
23
+		"stdout",
24
+		"stderr",
25
+		"STDIN",
26
+		"STDOUT",
27
+		"STDERR",
28
+	}
29
+	if _, err := validateAttach("invalid"); err == nil {
30
+		t.Fatalf("Expected error with [valid streams are STDIN, STDOUT and STDERR], got nothing")
31
+	}
32
+
33
+	for _, attach := range valid {
34
+		value, err := validateAttach(attach)
35
+		if err != nil {
36
+			t.Fatal(err)
37
+		}
38
+		if value != strings.ToLower(attach) {
39
+			t.Fatalf("Expected [%v], got [%v]", attach, value)
40
+		}
41
+	}
42
+}
43
+
44
+func parseRun(args []string) (*container.Config, *container.HostConfig, *networktypes.NetworkingConfig, error) {
45
+	flags := pflag.NewFlagSet("run", pflag.ContinueOnError)
46
+	flags.SetOutput(ioutil.Discard)
47
+	flags.Usage = nil
48
+	copts := addFlags(flags)
49
+	if err := flags.Parse(args); err != nil {
50
+		return nil, nil, nil, err
51
+	}
52
+	return parse(flags, copts)
53
+}
54
+
55
+func parsetest(t *testing.T, args string) (*container.Config, *container.HostConfig, error) {
56
+	config, hostConfig, _, err := parseRun(strings.Split(args+" ubuntu bash", " "))
57
+	return config, hostConfig, err
58
+}
59
+
60
+func mustParse(t *testing.T, args string) (*container.Config, *container.HostConfig) {
61
+	config, hostConfig, err := parsetest(t, args)
62
+	if err != nil {
63
+		t.Fatal(err)
64
+	}
65
+	return config, hostConfig
66
+}
67
+
68
+func TestParseRunLinks(t *testing.T) {
69
+	if _, hostConfig := mustParse(t, "--link a:b"); len(hostConfig.Links) == 0 || hostConfig.Links[0] != "a:b" {
70
+		t.Fatalf("Error parsing links. Expected []string{\"a:b\"}, received: %v", hostConfig.Links)
71
+	}
72
+	if _, hostConfig := mustParse(t, "--link a:b --link c:d"); len(hostConfig.Links) < 2 || hostConfig.Links[0] != "a:b" || hostConfig.Links[1] != "c:d" {
73
+		t.Fatalf("Error parsing links. Expected []string{\"a:b\", \"c:d\"}, received: %v", hostConfig.Links)
74
+	}
75
+	if _, hostConfig := mustParse(t, ""); len(hostConfig.Links) != 0 {
76
+		t.Fatalf("Error parsing links. No link expected, received: %v", hostConfig.Links)
77
+	}
78
+}
79
+
80
+func TestParseRunAttach(t *testing.T) {
81
+	if config, _ := mustParse(t, "-a stdin"); !config.AttachStdin || config.AttachStdout || config.AttachStderr {
82
+		t.Fatalf("Error parsing attach flags. Expect only Stdin enabled. Received: in: %v, out: %v, err: %v", config.AttachStdin, config.AttachStdout, config.AttachStderr)
83
+	}
84
+	if config, _ := mustParse(t, "-a stdin -a stdout"); !config.AttachStdin || !config.AttachStdout || config.AttachStderr {
85
+		t.Fatalf("Error parsing attach flags. Expect only Stdin and Stdout enabled. Received: in: %v, out: %v, err: %v", config.AttachStdin, config.AttachStdout, config.AttachStderr)
86
+	}
87
+	if config, _ := mustParse(t, "-a stdin -a stdout -a stderr"); !config.AttachStdin || !config.AttachStdout || !config.AttachStderr {
88
+		t.Fatalf("Error parsing attach flags. Expect all attach enabled. Received: in: %v, out: %v, err: %v", config.AttachStdin, config.AttachStdout, config.AttachStderr)
89
+	}
90
+	if config, _ := mustParse(t, ""); config.AttachStdin || !config.AttachStdout || !config.AttachStderr {
91
+		t.Fatalf("Error parsing attach flags. Expect Stdin disabled. Received: in: %v, out: %v, err: %v", config.AttachStdin, config.AttachStdout, config.AttachStderr)
92
+	}
93
+	if config, _ := mustParse(t, "-i"); !config.AttachStdin || !config.AttachStdout || !config.AttachStderr {
94
+		t.Fatalf("Error parsing attach flags. Expect Stdin enabled. Received: in: %v, out: %v, err: %v", config.AttachStdin, config.AttachStdout, config.AttachStderr)
95
+	}
96
+
97
+	if _, _, err := parsetest(t, "-a"); err == nil {
98
+		t.Fatalf("Error parsing attach flags, `-a` should be an error but is not")
99
+	}
100
+	if _, _, err := parsetest(t, "-a invalid"); err == nil {
101
+		t.Fatalf("Error parsing attach flags, `-a invalid` should be an error but is not")
102
+	}
103
+	if _, _, err := parsetest(t, "-a invalid -a stdout"); err == nil {
104
+		t.Fatalf("Error parsing attach flags, `-a stdout -a invalid` should be an error but is not")
105
+	}
106
+	if _, _, err := parsetest(t, "-a stdout -a stderr -d"); err == nil {
107
+		t.Fatalf("Error parsing attach flags, `-a stdout -a stderr -d` should be an error but is not")
108
+	}
109
+	if _, _, err := parsetest(t, "-a stdin -d"); err == nil {
110
+		t.Fatalf("Error parsing attach flags, `-a stdin -d` should be an error but is not")
111
+	}
112
+	if _, _, err := parsetest(t, "-a stdout -d"); err == nil {
113
+		t.Fatalf("Error parsing attach flags, `-a stdout -d` should be an error but is not")
114
+	}
115
+	if _, _, err := parsetest(t, "-a stderr -d"); err == nil {
116
+		t.Fatalf("Error parsing attach flags, `-a stderr -d` should be an error but is not")
117
+	}
118
+	if _, _, err := parsetest(t, "-d --rm"); err == nil {
119
+		t.Fatalf("Error parsing attach flags, `-d --rm` should be an error but is not")
120
+	}
121
+}
122
+
123
+func TestParseRunVolumes(t *testing.T) {
124
+
125
+	// A single volume
126
+	arr, tryit := setupPlatformVolume([]string{`/tmp`}, []string{`c:\tmp`})
127
+	if config, hostConfig := mustParse(t, tryit); hostConfig.Binds != nil {
128
+		t.Fatalf("Error parsing volume flags, %q should not mount-bind anything. Received %v", tryit, hostConfig.Binds)
129
+	} else if _, exists := config.Volumes[arr[0]]; !exists {
130
+		t.Fatalf("Error parsing volume flags, %q is missing from volumes. Received %v", tryit, config.Volumes)
131
+	}
132
+
133
+	// Two volumes
134
+	arr, tryit = setupPlatformVolume([]string{`/tmp`, `/var`}, []string{`c:\tmp`, `c:\var`})
135
+	if config, hostConfig := mustParse(t, tryit); hostConfig.Binds != nil {
136
+		t.Fatalf("Error parsing volume flags, %q should not mount-bind anything. Received %v", tryit, hostConfig.Binds)
137
+	} else if _, exists := config.Volumes[arr[0]]; !exists {
138
+		t.Fatalf("Error parsing volume flags, %s is missing from volumes. Received %v", arr[0], config.Volumes)
139
+	} else if _, exists := config.Volumes[arr[1]]; !exists {
140
+		t.Fatalf("Error parsing volume flags, %s is missing from volumes. Received %v", arr[1], config.Volumes)
141
+	}
142
+
143
+	// A single bind-mount
144
+	arr, tryit = setupPlatformVolume([]string{`/hostTmp:/containerTmp`}, []string{os.Getenv("TEMP") + `:c:\containerTmp`})
145
+	if config, hostConfig := mustParse(t, tryit); hostConfig.Binds == nil || hostConfig.Binds[0] != arr[0] {
146
+		t.Fatalf("Error parsing volume flags, %q should mount-bind the path before the colon into the path after the colon. Received %v %v", arr[0], hostConfig.Binds, config.Volumes)
147
+	}
148
+
149
+	// Two bind-mounts.
150
+	arr, tryit = setupPlatformVolume([]string{`/hostTmp:/containerTmp`, `/hostVar:/containerVar`}, []string{os.Getenv("ProgramData") + `:c:\ContainerPD`, os.Getenv("TEMP") + `:c:\containerTmp`})
151
+	if _, hostConfig := mustParse(t, tryit); hostConfig.Binds == nil || compareRandomizedStrings(hostConfig.Binds[0], hostConfig.Binds[1], arr[0], arr[1]) != nil {
152
+		t.Fatalf("Error parsing volume flags, `%s and %s` did not mount-bind correctly. Received %v", arr[0], arr[1], hostConfig.Binds)
153
+	}
154
+
155
+	// Two bind-mounts, first read-only, second read-write.
156
+	// TODO Windows: The Windows version uses read-write as that's the only mode it supports. Can change this post TP4
157
+	arr, tryit = setupPlatformVolume([]string{`/hostTmp:/containerTmp:ro`, `/hostVar:/containerVar:rw`}, []string{os.Getenv("TEMP") + `:c:\containerTmp:rw`, os.Getenv("ProgramData") + `:c:\ContainerPD:rw`})
158
+	if _, hostConfig := mustParse(t, tryit); hostConfig.Binds == nil || compareRandomizedStrings(hostConfig.Binds[0], hostConfig.Binds[1], arr[0], arr[1]) != nil {
159
+		t.Fatalf("Error parsing volume flags, `%s and %s` did not mount-bind correctly. Received %v", arr[0], arr[1], hostConfig.Binds)
160
+	}
161
+
162
+	// Similar to previous test but with alternate modes which are only supported by Linux
163
+	if runtime.GOOS != "windows" {
164
+		arr, tryit = setupPlatformVolume([]string{`/hostTmp:/containerTmp:ro,Z`, `/hostVar:/containerVar:rw,Z`}, []string{})
165
+		if _, hostConfig := mustParse(t, tryit); hostConfig.Binds == nil || compareRandomizedStrings(hostConfig.Binds[0], hostConfig.Binds[1], arr[0], arr[1]) != nil {
166
+			t.Fatalf("Error parsing volume flags, `%s and %s` did not mount-bind correctly. Received %v", arr[0], arr[1], hostConfig.Binds)
167
+		}
168
+
169
+		arr, tryit = setupPlatformVolume([]string{`/hostTmp:/containerTmp:Z`, `/hostVar:/containerVar:z`}, []string{})
170
+		if _, hostConfig := mustParse(t, tryit); hostConfig.Binds == nil || compareRandomizedStrings(hostConfig.Binds[0], hostConfig.Binds[1], arr[0], arr[1]) != nil {
171
+			t.Fatalf("Error parsing volume flags, `%s and %s` did not mount-bind correctly. Received %v", arr[0], arr[1], hostConfig.Binds)
172
+		}
173
+	}
174
+
175
+	// One bind mount and one volume
176
+	arr, tryit = setupPlatformVolume([]string{`/hostTmp:/containerTmp`, `/containerVar`}, []string{os.Getenv("TEMP") + `:c:\containerTmp`, `c:\containerTmp`})
177
+	if config, hostConfig := mustParse(t, tryit); hostConfig.Binds == nil || len(hostConfig.Binds) > 1 || hostConfig.Binds[0] != arr[0] {
178
+		t.Fatalf("Error parsing volume flags, %s and %s should only one and only one bind mount %s. Received %s", arr[0], arr[1], arr[0], hostConfig.Binds)
179
+	} else if _, exists := config.Volumes[arr[1]]; !exists {
180
+		t.Fatalf("Error parsing volume flags %s and %s. %s is missing from volumes. Received %v", arr[0], arr[1], arr[1], config.Volumes)
181
+	}
182
+
183
+	// Root to non-c: drive letter (Windows specific)
184
+	if runtime.GOOS == "windows" {
185
+		arr, tryit = setupPlatformVolume([]string{}, []string{os.Getenv("SystemDrive") + `\:d:`})
186
+		if config, hostConfig := mustParse(t, tryit); hostConfig.Binds == nil || len(hostConfig.Binds) > 1 || hostConfig.Binds[0] != arr[0] || len(config.Volumes) != 0 {
187
+			t.Fatalf("Error parsing %s. Should have a single bind mount and no volumes", arr[0])
188
+		}
189
+	}
190
+
191
+}
192
+
193
+// setupPlatformVolume takes two arrays of volume specs - a Unix style
194
+// spec and a Windows style spec. Depending on the platform being unit tested,
195
+// it returns one of them, along with a volume string that would be passed
196
+// on the docker CLI (e.g. -v /bar -v /foo).
197
+func setupPlatformVolume(u []string, w []string) ([]string, string) {
198
+	var a []string
199
+	if runtime.GOOS == "windows" {
200
+		a = w
201
+	} else {
202
+		a = u
203
+	}
204
+	s := ""
205
+	for _, v := range a {
206
+		s = s + "-v " + v + " "
207
+	}
208
+	return a, s
209
+}
210
+
211
+// check if (a == c && b == d) || (a == d && b == c)
212
+// because maps are randomized
213
+func compareRandomizedStrings(a, b, c, d string) error {
214
+	if a == c && b == d {
215
+		return nil
216
+	}
217
+	if a == d && b == c {
218
+		return nil
219
+	}
220
+	return fmt.Errorf("strings don't match")
221
+}
222
+
223
+// Simple parse with MacAddress validation
224
+func TestParseWithMacAddress(t *testing.T) {
225
+	invalidMacAddress := "--mac-address=invalidMacAddress"
226
+	validMacAddress := "--mac-address=92:d0:c6:0a:29:33"
227
+	if _, _, _, err := parseRun([]string{invalidMacAddress, "img", "cmd"}); err != nil && err.Error() != "invalidMacAddress is not a valid mac address" {
228
+		t.Fatalf("Expected an error with %v mac-address, got %v", invalidMacAddress, err)
229
+	}
230
+	if config, _ := mustParse(t, validMacAddress); config.MacAddress != "92:d0:c6:0a:29:33" {
231
+		t.Fatalf("Expected the config to have '92:d0:c6:0a:29:33' as MacAddress, got '%v'", config.MacAddress)
232
+	}
233
+}
234
+
235
+func TestParseWithMemory(t *testing.T) {
236
+	invalidMemory := "--memory=invalid"
237
+	validMemory := "--memory=1G"
238
+	if _, _, _, err := parseRun([]string{invalidMemory, "img", "cmd"}); err != nil && err.Error() != "invalid size: 'invalid'" {
239
+		t.Fatalf("Expected an error with '%v' Memory, got '%v'", invalidMemory, err)
240
+	}
241
+	if _, hostconfig := mustParse(t, validMemory); hostconfig.Memory != 1073741824 {
242
+		t.Fatalf("Expected the config to have '1G' as Memory, got '%v'", hostconfig.Memory)
243
+	}
244
+}
245
+
246
+func TestParseWithMemorySwap(t *testing.T) {
247
+	invalidMemory := "--memory-swap=invalid"
248
+	validMemory := "--memory-swap=1G"
249
+	anotherValidMemory := "--memory-swap=-1"
250
+	if _, _, _, err := parseRun([]string{invalidMemory, "img", "cmd"}); err == nil || err.Error() != "invalid size: 'invalid'" {
251
+		t.Fatalf("Expected an error with '%v' MemorySwap, got '%v'", invalidMemory, err)
252
+	}
253
+	if _, hostconfig := mustParse(t, validMemory); hostconfig.MemorySwap != 1073741824 {
254
+		t.Fatalf("Expected the config to have '1073741824' as MemorySwap, got '%v'", hostconfig.MemorySwap)
255
+	}
256
+	if _, hostconfig := mustParse(t, anotherValidMemory); hostconfig.MemorySwap != -1 {
257
+		t.Fatalf("Expected the config to have '-1' as MemorySwap, got '%v'", hostconfig.MemorySwap)
258
+	}
259
+}
260
+
261
+func TestParseHostname(t *testing.T) {
262
+	validHostnames := map[string]string{
263
+		"hostname":    "hostname",
264
+		"host-name":   "host-name",
265
+		"hostname123": "hostname123",
266
+		"123hostname": "123hostname",
267
+		"hostname-of-63-bytes-long-should-be-valid-and-without-any-error": "hostname-of-63-bytes-long-should-be-valid-and-without-any-error",
268
+	}
269
+	hostnameWithDomain := "--hostname=hostname.domainname"
270
+	hostnameWithDomainTld := "--hostname=hostname.domainname.tld"
271
+	for hostname, expectedHostname := range validHostnames {
272
+		if config, _ := mustParse(t, fmt.Sprintf("--hostname=%s", hostname)); config.Hostname != expectedHostname {
273
+			t.Fatalf("Expected the config to have 'hostname' as hostname, got '%v'", config.Hostname)
274
+		}
275
+	}
276
+	if config, _ := mustParse(t, hostnameWithDomain); config.Hostname != "hostname.domainname" && config.Domainname != "" {
277
+		t.Fatalf("Expected the config to have 'hostname' as hostname.domainname, got '%v'", config.Hostname)
278
+	}
279
+	if config, _ := mustParse(t, hostnameWithDomainTld); config.Hostname != "hostname.domainname.tld" && config.Domainname != "" {
280
+		t.Fatalf("Expected the config to have 'hostname' as hostname.domainname.tld, got '%v'", config.Hostname)
281
+	}
282
+}
283
+
284
+func TestParseWithExpose(t *testing.T) {
285
+	invalids := map[string]string{
286
+		":":                   "invalid port format for --expose: :",
287
+		"8080:9090":           "invalid port format for --expose: 8080:9090",
288
+		"/tcp":                "invalid range format for --expose: /tcp, error: Empty string specified for ports.",
289
+		"/udp":                "invalid range format for --expose: /udp, error: Empty string specified for ports.",
290
+		"NaN/tcp":             `invalid range format for --expose: NaN/tcp, error: strconv.ParseUint: parsing "NaN": invalid syntax`,
291
+		"NaN-NaN/tcp":         `invalid range format for --expose: NaN-NaN/tcp, error: strconv.ParseUint: parsing "NaN": invalid syntax`,
292
+		"8080-NaN/tcp":        `invalid range format for --expose: 8080-NaN/tcp, error: strconv.ParseUint: parsing "NaN": invalid syntax`,
293
+		"1234567890-8080/tcp": `invalid range format for --expose: 1234567890-8080/tcp, error: strconv.ParseUint: parsing "1234567890": value out of range`,
294
+	}
295
+	valids := map[string][]nat.Port{
296
+		"8080/tcp":      {"8080/tcp"},
297
+		"8080/udp":      {"8080/udp"},
298
+		"8080/ncp":      {"8080/ncp"},
299
+		"8080-8080/udp": {"8080/udp"},
300
+		"8080-8082/tcp": {"8080/tcp", "8081/tcp", "8082/tcp"},
301
+	}
302
+	for expose, expectedError := range invalids {
303
+		if _, _, _, err := parseRun([]string{fmt.Sprintf("--expose=%v", expose), "img", "cmd"}); err == nil || err.Error() != expectedError {
304
+			t.Fatalf("Expected error '%v' with '--expose=%v', got '%v'", expectedError, expose, err)
305
+		}
306
+	}
307
+	for expose, exposedPorts := range valids {
308
+		config, _, _, err := parseRun([]string{fmt.Sprintf("--expose=%v", expose), "img", "cmd"})
309
+		if err != nil {
310
+			t.Fatal(err)
311
+		}
312
+		if len(config.ExposedPorts) != len(exposedPorts) {
313
+			t.Fatalf("Expected %v exposed port, got %v", len(exposedPorts), len(config.ExposedPorts))
314
+		}
315
+		for _, port := range exposedPorts {
316
+			if _, ok := config.ExposedPorts[port]; !ok {
317
+				t.Fatalf("Expected %v, got %v", exposedPorts, config.ExposedPorts)
318
+			}
319
+		}
320
+	}
321
+	// Merge with actual published port
322
+	config, _, _, err := parseRun([]string{"--publish=80", "--expose=80-81/tcp", "img", "cmd"})
323
+	if err != nil {
324
+		t.Fatal(err)
325
+	}
326
+	if len(config.ExposedPorts) != 2 {
327
+		t.Fatalf("Expected 2 exposed ports, got %v", config.ExposedPorts)
328
+	}
329
+	ports := []nat.Port{"80/tcp", "81/tcp"}
330
+	for _, port := range ports {
331
+		if _, ok := config.ExposedPorts[port]; !ok {
332
+			t.Fatalf("Expected %v, got %v", ports, config.ExposedPorts)
333
+		}
334
+	}
335
+}
336
+
337
+func TestParseDevice(t *testing.T) {
338
+	valids := map[string]container.DeviceMapping{
339
+		"/dev/snd": {
340
+			PathOnHost:        "/dev/snd",
341
+			PathInContainer:   "/dev/snd",
342
+			CgroupPermissions: "rwm",
343
+		},
344
+		"/dev/snd:rw": {
345
+			PathOnHost:        "/dev/snd",
346
+			PathInContainer:   "/dev/snd",
347
+			CgroupPermissions: "rw",
348
+		},
349
+		"/dev/snd:/something": {
350
+			PathOnHost:        "/dev/snd",
351
+			PathInContainer:   "/something",
352
+			CgroupPermissions: "rwm",
353
+		},
354
+		"/dev/snd:/something:rw": {
355
+			PathOnHost:        "/dev/snd",
356
+			PathInContainer:   "/something",
357
+			CgroupPermissions: "rw",
358
+		},
359
+	}
360
+	for device, deviceMapping := range valids {
361
+		_, hostconfig, _, err := parseRun([]string{fmt.Sprintf("--device=%v", device), "img", "cmd"})
362
+		if err != nil {
363
+			t.Fatal(err)
364
+		}
365
+		if len(hostconfig.Devices) != 1 {
366
+			t.Fatalf("Expected 1 devices, got %v", hostconfig.Devices)
367
+		}
368
+		if hostconfig.Devices[0] != deviceMapping {
369
+			t.Fatalf("Expected %v, got %v", deviceMapping, hostconfig.Devices)
370
+		}
371
+	}
372
+
373
+}
374
+
375
+func TestParseModes(t *testing.T) {
376
+	// ipc ko
377
+	if _, _, _, err := parseRun([]string{"--ipc=container:", "img", "cmd"}); err == nil || err.Error() != "--ipc: invalid IPC mode" {
378
+		t.Fatalf("Expected an error with message '--ipc: invalid IPC mode', got %v", err)
379
+	}
380
+	// ipc ok
381
+	_, hostconfig, _, err := parseRun([]string{"--ipc=host", "img", "cmd"})
382
+	if err != nil {
383
+		t.Fatal(err)
384
+	}
385
+	if !hostconfig.IpcMode.Valid() {
386
+		t.Fatalf("Expected a valid IpcMode, got %v", hostconfig.IpcMode)
387
+	}
388
+	// pid ko
389
+	if _, _, _, err := parseRun([]string{"--pid=container:", "img", "cmd"}); err == nil || err.Error() != "--pid: invalid PID mode" {
390
+		t.Fatalf("Expected an error with message '--pid: invalid PID mode', got %v", err)
391
+	}
392
+	// pid ok
393
+	_, hostconfig, _, err = parseRun([]string{"--pid=host", "img", "cmd"})
394
+	if err != nil {
395
+		t.Fatal(err)
396
+	}
397
+	if !hostconfig.PidMode.Valid() {
398
+		t.Fatalf("Expected a valid PidMode, got %v", hostconfig.PidMode)
399
+	}
400
+	// uts ko
401
+	if _, _, _, err := parseRun([]string{"--uts=container:", "img", "cmd"}); err == nil || err.Error() != "--uts: invalid UTS mode" {
402
+		t.Fatalf("Expected an error with message '--uts: invalid UTS mode', got %v", err)
403
+	}
404
+	// uts ok
405
+	_, hostconfig, _, err = parseRun([]string{"--uts=host", "img", "cmd"})
406
+	if err != nil {
407
+		t.Fatal(err)
408
+	}
409
+	if !hostconfig.UTSMode.Valid() {
410
+		t.Fatalf("Expected a valid UTSMode, got %v", hostconfig.UTSMode)
411
+	}
412
+	// shm-size ko
413
+	if _, _, _, err = parseRun([]string{"--shm-size=a128m", "img", "cmd"}); err == nil || err.Error() != "invalid size: 'a128m'" {
414
+		t.Fatalf("Expected an error with message 'invalid size: a128m', got %v", err)
415
+	}
416
+	// shm-size ok
417
+	_, hostconfig, _, err = parseRun([]string{"--shm-size=128m", "img", "cmd"})
418
+	if err != nil {
419
+		t.Fatal(err)
420
+	}
421
+	if hostconfig.ShmSize != 134217728 {
422
+		t.Fatalf("Expected a valid ShmSize, got %d", hostconfig.ShmSize)
423
+	}
424
+}
425
+
426
+func TestParseRestartPolicy(t *testing.T) {
427
+	invalids := map[string]string{
428
+		"always:2:3":         "invalid restart policy format",
429
+		"on-failure:invalid": "maximum retry count must be an integer",
430
+	}
431
+	valids := map[string]container.RestartPolicy{
432
+		"": {},
433
+		"always": {
434
+			Name:              "always",
435
+			MaximumRetryCount: 0,
436
+		},
437
+		"on-failure:1": {
438
+			Name:              "on-failure",
439
+			MaximumRetryCount: 1,
440
+		},
441
+	}
442
+	for restart, expectedError := range invalids {
443
+		if _, _, _, err := parseRun([]string{fmt.Sprintf("--restart=%s", restart), "img", "cmd"}); err == nil || err.Error() != expectedError {
444
+			t.Fatalf("Expected an error with message '%v' for %v, got %v", expectedError, restart, err)
445
+		}
446
+	}
447
+	for restart, expected := range valids {
448
+		_, hostconfig, _, err := parseRun([]string{fmt.Sprintf("--restart=%v", restart), "img", "cmd"})
449
+		if err != nil {
450
+			t.Fatal(err)
451
+		}
452
+		if hostconfig.RestartPolicy != expected {
453
+			t.Fatalf("Expected %v, got %v", expected, hostconfig.RestartPolicy)
454
+		}
455
+	}
456
+}
457
+
458
+func TestParseHealth(t *testing.T) {
459
+	checkOk := func(args ...string) *container.HealthConfig {
460
+		config, _, _, err := parseRun(args)
461
+		if err != nil {
462
+			t.Fatalf("%#v: %v", args, err)
463
+		}
464
+		return config.Healthcheck
465
+	}
466
+	checkError := func(expected string, args ...string) {
467
+		config, _, _, err := parseRun(args)
468
+		if err == nil {
469
+			t.Fatalf("Expected error, but got %#v", config)
470
+		}
471
+		if err.Error() != expected {
472
+			t.Fatalf("Expected %#v, got %#v", expected, err)
473
+		}
474
+	}
475
+	health := checkOk("--no-healthcheck", "img", "cmd")
476
+	if health == nil || len(health.Test) != 1 || health.Test[0] != "NONE" {
477
+		t.Fatalf("--no-healthcheck failed: %#v", health)
478
+	}
479
+
480
+	health = checkOk("--health-cmd=/check.sh -q", "img", "cmd")
481
+	if len(health.Test) != 2 || health.Test[0] != "CMD-SHELL" || health.Test[1] != "/check.sh -q" {
482
+		t.Fatalf("--health-cmd: got %#v", health.Test)
483
+	}
484
+	if health.Timeout != 0 {
485
+		t.Fatalf("--health-cmd: timeout = %f", health.Timeout)
486
+	}
487
+
488
+	checkError("--no-healthcheck conflicts with --health-* options",
489
+		"--no-healthcheck", "--health-cmd=/check.sh -q", "img", "cmd")
490
+
491
+	health = checkOk("--health-timeout=2s", "--health-retries=3", "--health-interval=4.5s", "img", "cmd")
492
+	if health.Timeout != 2*time.Second || health.Retries != 3 || health.Interval != 4500*time.Millisecond {
493
+		t.Fatalf("--health-*: got %#v", health)
494
+	}
495
+}
496
+
497
+func TestParseLoggingOpts(t *testing.T) {
498
+	// logging opts ko
499
+	if _, _, _, err := parseRun([]string{"--log-driver=none", "--log-opt=anything", "img", "cmd"}); err == nil || err.Error() != "invalid logging opts for driver none" {
500
+		t.Fatalf("Expected an error with message 'invalid logging opts for driver none', got %v", err)
501
+	}
502
+	// logging opts ok
503
+	_, hostconfig, _, err := parseRun([]string{"--log-driver=syslog", "--log-opt=something", "img", "cmd"})
504
+	if err != nil {
505
+		t.Fatal(err)
506
+	}
507
+	if hostconfig.LogConfig.Type != "syslog" || len(hostconfig.LogConfig.Config) != 1 {
508
+		t.Fatalf("Expected a 'syslog' LogConfig with one config, got %v", hostconfig.RestartPolicy)
509
+	}
510
+}
511
+
512
+func TestParseEnvfileVariables(t *testing.T) {
513
+	e := "open nonexistent: no such file or directory"
514
+	if runtime.GOOS == "windows" {
515
+		e = "open nonexistent: The system cannot find the file specified."
516
+	}
517
+	// env ko
518
+	if _, _, _, err := parseRun([]string{"--env-file=nonexistent", "img", "cmd"}); err == nil || err.Error() != e {
519
+		t.Fatalf("Expected an error with message '%s', got %v", e, err)
520
+	}
521
+	// env ok
522
+	config, _, _, err := parseRun([]string{"--env-file=testdata/valid.env", "img", "cmd"})
523
+	if err != nil {
524
+		t.Fatal(err)
525
+	}
526
+	if len(config.Env) != 1 || config.Env[0] != "ENV1=value1" {
527
+		t.Fatalf("Expected a config with [ENV1=value1], got %v", config.Env)
528
+	}
529
+	config, _, _, err = parseRun([]string{"--env-file=testdata/valid.env", "--env=ENV2=value2", "img", "cmd"})
530
+	if err != nil {
531
+		t.Fatal(err)
532
+	}
533
+	if len(config.Env) != 2 || config.Env[0] != "ENV1=value1" || config.Env[1] != "ENV2=value2" {
534
+		t.Fatalf("Expected a config with [ENV1=value1 ENV2=value2], got %v", config.Env)
535
+	}
536
+}
537
+
538
+func TestParseEnvfileVariablesWithBOMUnicode(t *testing.T) {
539
+	// UTF8 with BOM
540
+	config, _, _, err := parseRun([]string{"--env-file=testdata/utf8.env", "img", "cmd"})
541
+	if err != nil {
542
+		t.Fatal(err)
543
+	}
544
+	env := []string{"FOO=BAR", "HELLO=" + string([]byte{0xe6, 0x82, 0xa8, 0xe5, 0xa5, 0xbd}), "BAR=FOO"}
545
+	if len(config.Env) != len(env) {
546
+		t.Fatalf("Expected a config with %d env variables, got %v: %v", len(env), len(config.Env), config.Env)
547
+	}
548
+	for i, v := range env {
549
+		if config.Env[i] != v {
550
+			t.Fatalf("Expected a config with [%s], got %v", v, []byte(config.Env[i]))
551
+		}
552
+	}
553
+
554
+	// UTF16 with BOM
555
+	e := "contains invalid utf8 bytes at line"
556
+	if _, _, _, err := parseRun([]string{"--env-file=testdata/utf16.env", "img", "cmd"}); err == nil || !strings.Contains(err.Error(), e) {
557
+		t.Fatalf("Expected an error with message '%s', got %v", e, err)
558
+	}
559
+	// UTF16BE with BOM
560
+	if _, _, _, err := parseRun([]string{"--env-file=testdata/utf16be.env", "img", "cmd"}); err == nil || !strings.Contains(err.Error(), e) {
561
+		t.Fatalf("Expected an error with message '%s', got %v", e, err)
562
+	}
563
+}
564
+
565
+func TestParseLabelfileVariables(t *testing.T) {
566
+	e := "open nonexistent: no such file or directory"
567
+	if runtime.GOOS == "windows" {
568
+		e = "open nonexistent: The system cannot find the file specified."
569
+	}
570
+	// label ko
571
+	if _, _, _, err := parseRun([]string{"--label-file=nonexistent", "img", "cmd"}); err == nil || err.Error() != e {
572
+		t.Fatalf("Expected an error with message '%s', got %v", e, err)
573
+	}
574
+	// label ok
575
+	config, _, _, err := parseRun([]string{"--label-file=testdata/valid.label", "img", "cmd"})
576
+	if err != nil {
577
+		t.Fatal(err)
578
+	}
579
+	if len(config.Labels) != 1 || config.Labels["LABEL1"] != "value1" {
580
+		t.Fatalf("Expected a config with [LABEL1:value1], got %v", config.Labels)
581
+	}
582
+	config, _, _, err = parseRun([]string{"--label-file=testdata/valid.label", "--label=LABEL2=value2", "img", "cmd"})
583
+	if err != nil {
584
+		t.Fatal(err)
585
+	}
586
+	if len(config.Labels) != 2 || config.Labels["LABEL1"] != "value1" || config.Labels["LABEL2"] != "value2" {
587
+		t.Fatalf("Expected a config with [LABEL1:value1 LABEL2:value2], got %v", config.Labels)
588
+	}
589
+}
590
+
591
+func TestParseEntryPoint(t *testing.T) {
592
+	config, _, _, err := parseRun([]string{"--entrypoint=anything", "cmd", "img"})
593
+	if err != nil {
594
+		t.Fatal(err)
595
+	}
596
+	if len(config.Entrypoint) != 1 && config.Entrypoint[0] != "anything" {
597
+		t.Fatalf("Expected entrypoint 'anything', got %v", config.Entrypoint)
598
+	}
599
+}
600
+
601
+// This tests the cases for binds which are generated through
602
+// DecodeContainerConfig rather than Parse()
603
+func TestDecodeContainerConfigVolumes(t *testing.T) {
604
+
605
+	// Root to root
606
+	bindsOrVols, _ := setupPlatformVolume([]string{`/:/`}, []string{os.Getenv("SystemDrive") + `\:c:\`})
607
+	if _, _, err := callDecodeContainerConfig(nil, bindsOrVols); err == nil {
608
+		t.Fatalf("binds %v should have failed", bindsOrVols)
609
+	}
610
+	if _, _, err := callDecodeContainerConfig(bindsOrVols, nil); err == nil {
611
+		t.Fatalf("volume %v should have failed", bindsOrVols)
612
+	}
613
+
614
+	// No destination path
615
+	bindsOrVols, _ = setupPlatformVolume([]string{`/tmp:`}, []string{os.Getenv("TEMP") + `\:`})
616
+	if _, _, err := callDecodeContainerConfig(nil, bindsOrVols); err == nil {
617
+		t.Fatalf("binds %v should have failed", bindsOrVols)
618
+	}
619
+	if _, _, err := callDecodeContainerConfig(bindsOrVols, nil); err == nil {
620
+		t.Fatalf("volume %v should have failed", bindsOrVols)
621
+	}
622
+
623
+	//	// No destination path or mode
624
+	bindsOrVols, _ = setupPlatformVolume([]string{`/tmp::`}, []string{os.Getenv("TEMP") + `\::`})
625
+	if _, _, err := callDecodeContainerConfig(nil, bindsOrVols); err == nil {
626
+		t.Fatalf("binds %v should have failed", bindsOrVols)
627
+	}
628
+	if _, _, err := callDecodeContainerConfig(bindsOrVols, nil); err == nil {
629
+		t.Fatalf("volume %v should have failed", bindsOrVols)
630
+	}
631
+
632
+	// A whole lot of nothing
633
+	bindsOrVols = []string{`:`}
634
+	if _, _, err := callDecodeContainerConfig(nil, bindsOrVols); err == nil {
635
+		t.Fatalf("binds %v should have failed", bindsOrVols)
636
+	}
637
+	if _, _, err := callDecodeContainerConfig(bindsOrVols, nil); err == nil {
638
+		t.Fatalf("volume %v should have failed", bindsOrVols)
639
+	}
640
+
641
+	// A whole lot of nothing with no mode
642
+	bindsOrVols = []string{`::`}
643
+	if _, _, err := callDecodeContainerConfig(nil, bindsOrVols); err == nil {
644
+		t.Fatalf("binds %v should have failed", bindsOrVols)
645
+	}
646
+	if _, _, err := callDecodeContainerConfig(bindsOrVols, nil); err == nil {
647
+		t.Fatalf("volume %v should have failed", bindsOrVols)
648
+	}
649
+
650
+	// Too much including an invalid mode
651
+	wTmp := os.Getenv("TEMP")
652
+	bindsOrVols, _ = setupPlatformVolume([]string{`/tmp:/tmp:/tmp:/tmp`}, []string{wTmp + ":" + wTmp + ":" + wTmp + ":" + wTmp})
653
+	if _, _, err := callDecodeContainerConfig(nil, bindsOrVols); err == nil {
654
+		t.Fatalf("binds %v should have failed", bindsOrVols)
655
+	}
656
+	if _, _, err := callDecodeContainerConfig(bindsOrVols, nil); err == nil {
657
+		t.Fatalf("volume %v should have failed", bindsOrVols)
658
+	}
659
+
660
+	// Windows specific error tests
661
+	if runtime.GOOS == "windows" {
662
+		// Volume which does not include a drive letter
663
+		bindsOrVols = []string{`\tmp`}
664
+		if _, _, err := callDecodeContainerConfig(nil, bindsOrVols); err == nil {
665
+			t.Fatalf("binds %v should have failed", bindsOrVols)
666
+		}
667
+		if _, _, err := callDecodeContainerConfig(bindsOrVols, nil); err == nil {
668
+			t.Fatalf("volume %v should have failed", bindsOrVols)
669
+		}
670
+
671
+		// Root to C-Drive
672
+		bindsOrVols = []string{os.Getenv("SystemDrive") + `\:c:`}
673
+		if _, _, err := callDecodeContainerConfig(nil, bindsOrVols); err == nil {
674
+			t.Fatalf("binds %v should have failed", bindsOrVols)
675
+		}
676
+		if _, _, err := callDecodeContainerConfig(bindsOrVols, nil); err == nil {
677
+			t.Fatalf("volume %v should have failed", bindsOrVols)
678
+		}
679
+
680
+		// Container path that does not include a drive letter
681
+		bindsOrVols = []string{`c:\windows:\somewhere`}
682
+		if _, _, err := callDecodeContainerConfig(nil, bindsOrVols); err == nil {
683
+			t.Fatalf("binds %v should have failed", bindsOrVols)
684
+		}
685
+		if _, _, err := callDecodeContainerConfig(bindsOrVols, nil); err == nil {
686
+			t.Fatalf("volume %v should have failed", bindsOrVols)
687
+		}
688
+	}
689
+
690
+	// Linux-specific error tests
691
+	if runtime.GOOS != "windows" {
692
+		// Just root
693
+		bindsOrVols = []string{`/`}
694
+		if _, _, err := callDecodeContainerConfig(nil, bindsOrVols); err == nil {
695
+			t.Fatalf("binds %v should have failed", bindsOrVols)
696
+		}
697
+		if _, _, err := callDecodeContainerConfig(bindsOrVols, nil); err == nil {
698
+			t.Fatalf("volume %v should have failed", bindsOrVols)
699
+		}
700
+
701
+		// A single volume that looks like a bind mount passed in Volumes.
702
+		// This should be handled as a bind mount, not a volume.
703
+		vols := []string{`/foo:/bar`}
704
+		if config, hostConfig, err := callDecodeContainerConfig(vols, nil); err != nil {
705
+			t.Fatal("Volume /foo:/bar should have succeeded as a volume name")
706
+		} else if hostConfig.Binds != nil {
707
+			t.Fatalf("Error parsing volume flags, /foo:/bar should not mount-bind anything. Received %v", hostConfig.Binds)
708
+		} else if _, exists := config.Volumes[vols[0]]; !exists {
709
+			t.Fatalf("Error parsing volume flags, /foo:/bar is missing from volumes. Received %v", config.Volumes)
710
+		}
711
+
712
+	}
713
+}
714
+
715
+// callDecodeContainerConfig is a utility function used by TestDecodeContainerConfigVolumes
716
+// to call DecodeContainerConfig. It effectively does what a client would
717
+// do when calling the daemon by constructing a JSON stream of a
718
+// ContainerConfigWrapper which is populated by the set of volume specs
719
+// passed into it. It returns a config and a hostconfig which can be
720
+// validated to ensure DecodeContainerConfig has manipulated the structures
721
+// correctly.
722
+func callDecodeContainerConfig(volumes []string, binds []string) (*container.Config, *container.HostConfig, error) {
723
+	var (
724
+		b   []byte
725
+		err error
726
+		c   *container.Config
727
+		h   *container.HostConfig
728
+	)
729
+	w := runconfig.ContainerConfigWrapper{
730
+		Config: &container.Config{
731
+			Volumes: map[string]struct{}{},
732
+		},
733
+		HostConfig: &container.HostConfig{
734
+			NetworkMode: "none",
735
+			Binds:       binds,
736
+		},
737
+	}
738
+	for _, v := range volumes {
739
+		w.Config.Volumes[v] = struct{}{}
740
+	}
741
+	if b, err = json.Marshal(w); err != nil {
742
+		return nil, nil, fmt.Errorf("Error on marshal %s", err.Error())
743
+	}
744
+	c, h, _, err = runconfig.DecodeContainerConfig(bytes.NewReader(b))
745
+	if err != nil {
746
+		return nil, nil, fmt.Errorf("Error parsing %s: %v", string(b), err)
747
+	}
748
+	if c == nil || h == nil {
749
+		return nil, nil, fmt.Errorf("Empty config or hostconfig")
750
+	}
751
+
752
+	return c, h, err
753
+}
754
+
755
+func TestVolumeSplitN(t *testing.T) {
756
+	for _, x := range []struct {
757
+		input    string
758
+		n        int
759
+		expected []string
760
+	}{
761
+		{`C:\foo:d:`, -1, []string{`C:\foo`, `d:`}},
762
+		{`:C:\foo:d:`, -1, nil},
763
+		{`/foo:/bar:ro`, 3, []string{`/foo`, `/bar`, `ro`}},
764
+		{`/foo:/bar:ro`, 2, []string{`/foo`, `/bar:ro`}},
765
+		{`C:\foo\:/foo`, -1, []string{`C:\foo\`, `/foo`}},
766
+
767
+		{`d:\`, -1, []string{`d:\`}},
768
+		{`d:`, -1, []string{`d:`}},
769
+		{`d:\path`, -1, []string{`d:\path`}},
770
+		{`d:\path with space`, -1, []string{`d:\path with space`}},
771
+		{`d:\pathandmode:rw`, -1, []string{`d:\pathandmode`, `rw`}},
772
+		{`c:\:d:\`, -1, []string{`c:\`, `d:\`}},
773
+		{`c:\windows\:d:`, -1, []string{`c:\windows\`, `d:`}},
774
+		{`c:\windows:d:\s p a c e`, -1, []string{`c:\windows`, `d:\s p a c e`}},
775
+		{`c:\windows:d:\s p a c e:RW`, -1, []string{`c:\windows`, `d:\s p a c e`, `RW`}},
776
+		{`c:\program files:d:\s p a c e i n h o s t d i r`, -1, []string{`c:\program files`, `d:\s p a c e i n h o s t d i r`}},
777
+		{`0123456789name:d:`, -1, []string{`0123456789name`, `d:`}},
778
+		{`MiXeDcAsEnAmE:d:`, -1, []string{`MiXeDcAsEnAmE`, `d:`}},
779
+		{`name:D:`, -1, []string{`name`, `D:`}},
780
+		{`name:D::rW`, -1, []string{`name`, `D:`, `rW`}},
781
+		{`name:D::RW`, -1, []string{`name`, `D:`, `RW`}},
782
+		{`c:/:d:/forward/slashes/are/good/too`, -1, []string{`c:/`, `d:/forward/slashes/are/good/too`}},
783
+		{`c:\Windows`, -1, []string{`c:\Windows`}},
784
+		{`c:\Program Files (x86)`, -1, []string{`c:\Program Files (x86)`}},
785
+
786
+		{``, -1, nil},
787
+		{`.`, -1, []string{`.`}},
788
+		{`..\`, -1, []string{`..\`}},
789
+		{`c:\:..\`, -1, []string{`c:\`, `..\`}},
790
+		{`c:\:d:\:xyzzy`, -1, []string{`c:\`, `d:\`, `xyzzy`}},
791
+
792
+		// Cover directories with one-character name
793
+		{`/tmp/x/y:/foo/x/y`, -1, []string{`/tmp/x/y`, `/foo/x/y`}},
794
+	} {
795
+		res := volumeSplitN(x.input, x.n)
796
+		if len(res) < len(x.expected) {
797
+			t.Fatalf("input: %v, expected: %v, got: %v", x.input, x.expected, res)
798
+		}
799
+		for i, e := range res {
800
+			if e != x.expected[i] {
801
+				t.Fatalf("input: %v, expected: %v, got: %v", x.input, x.expected, res)
802
+			}
803
+		}
804
+	}
805
+}
806
+
807
+func TestValidateDevice(t *testing.T) {
808
+	valid := []string{
809
+		"/home",
810
+		"/home:/home",
811
+		"/home:/something/else",
812
+		"/with space",
813
+		"/home:/with space",
814
+		"relative:/absolute-path",
815
+		"hostPath:/containerPath:r",
816
+		"/hostPath:/containerPath:rw",
817
+		"/hostPath:/containerPath:mrw",
818
+	}
819
+	invalid := map[string]string{
820
+		"":        "bad format for path: ",
821
+		"./":      "./ is not an absolute path",
822
+		"../":     "../ is not an absolute path",
823
+		"/:../":   "../ is not an absolute path",
824
+		"/:path":  "path is not an absolute path",
825
+		":":       "bad format for path: :",
826
+		"/tmp:":   " is not an absolute path",
827
+		":test":   "bad format for path: :test",
828
+		":/test":  "bad format for path: :/test",
829
+		"tmp:":    " is not an absolute path",
830
+		":test:":  "bad format for path: :test:",
831
+		"::":      "bad format for path: ::",
832
+		":::":     "bad format for path: :::",
833
+		"/tmp:::": "bad format for path: /tmp:::",
834
+		":/tmp::": "bad format for path: :/tmp::",
835
+		"path:ro": "ro is not an absolute path",
836
+		"path:rr": "rr is not an absolute path",
837
+		"a:/b:ro": "bad mode specified: ro",
838
+		"a:/b:rr": "bad mode specified: rr",
839
+	}
840
+
841
+	for _, path := range valid {
842
+		if _, err := validateDevice(path); err != nil {
843
+			t.Fatalf("ValidateDevice(`%q`) should succeed: error %q", path, err)
844
+		}
845
+	}
846
+
847
+	for path, expectedError := range invalid {
848
+		if _, err := validateDevice(path); err == nil {
849
+			t.Fatalf("ValidateDevice(`%q`) should have failed validation", path)
850
+		} else {
851
+			if err.Error() != expectedError {
852
+				t.Fatalf("ValidateDevice(`%q`) error should contain %q, got %q", path, expectedError, err.Error())
853
+			}
854
+		}
855
+	}
856
+}
... ...
@@ -18,7 +18,6 @@ import (
18 18
 	opttypes "github.com/docker/docker/opts"
19 19
 	"github.com/docker/docker/pkg/promise"
20 20
 	"github.com/docker/docker/pkg/signal"
21
-	runconfigopts "github.com/docker/docker/runconfig/opts"
22 21
 	"github.com/docker/libnetwork/resolvconf/dns"
23 22
 	"github.com/spf13/cobra"
24 23
 	"github.com/spf13/pflag"
... ...
@@ -34,7 +33,7 @@ type runOptions struct {
34 34
 // NewRunCommand create a new `docker run` command
35 35
 func NewRunCommand(dockerCli *command.DockerCli) *cobra.Command {
36 36
 	var opts runOptions
37
-	var copts *runconfigopts.ContainerOptions
37
+	var copts *containerOptions
38 38
 
39 39
 	cmd := &cobra.Command{
40 40
 		Use:   "run [OPTIONS] IMAGE [COMMAND] [ARG...]",
... ...
@@ -63,11 +62,11 @@ func NewRunCommand(dockerCli *command.DockerCli) *cobra.Command {
63 63
 	flags.Bool("help", false, "Print usage")
64 64
 
65 65
 	command.AddTrustedFlags(flags, true)
66
-	copts = runconfigopts.AddFlags(flags)
66
+	copts = addFlags(flags)
67 67
 	return cmd
68 68
 }
69 69
 
70
-func runRun(dockerCli *command.DockerCli, flags *pflag.FlagSet, opts *runOptions, copts *runconfigopts.ContainerOptions) error {
70
+func runRun(dockerCli *command.DockerCli, flags *pflag.FlagSet, opts *runOptions, copts *containerOptions) error {
71 71
 	stdout, stderr, stdin := dockerCli.Out(), dockerCli.Err(), dockerCli.In()
72 72
 	client := dockerCli.Client()
73 73
 	// TODO: pass this as an argument
... ...
@@ -79,9 +78,9 @@ func runRun(dockerCli *command.DockerCli, flags *pflag.FlagSet, opts *runOptions
79 79
 		ErrConflictRestartPolicyAndAutoRemove = fmt.Errorf("Conflicting options: --restart and --rm")
80 80
 	)
81 81
 
82
-	config, hostConfig, networkingConfig, err := runconfigopts.Parse(flags, copts)
82
+	config, hostConfig, networkingConfig, err := parse(flags, copts)
83 83
 
84
-	// just in case the Parse does not exit
84
+	// just in case the parse does not exit
85 85
 	if err != nil {
86 86
 		reportError(stderr, cmdPath, err.Error(), true)
87 87
 		return cli.StatusError{StatusCode: 125}
88 88
new file mode 100755
89 89
Binary files /dev/null and b/cli/command/container/testdata/utf16.env differ
90 90
new file mode 100755
91 91
Binary files /dev/null and b/cli/command/container/testdata/utf16be.env differ
92 92
new file mode 100755
... ...
@@ -0,0 +1,3 @@
0
+FOO=BAR
1
+HELLO=您好
2
+BAR=FOO
0 3
\ No newline at end of file
1 4
new file mode 100644
... ...
@@ -0,0 +1 @@
0
+ENV1=value1
0 1
new file mode 100644
... ...
@@ -0,0 +1 @@
0
+LABEL1=value1
... ...
@@ -11,8 +11,6 @@ import (
11 11
 	"regexp"
12 12
 	"runtime"
13 13
 
14
-	"golang.org/x/net/context"
15
-
16 14
 	"github.com/docker/docker/api"
17 15
 	"github.com/docker/docker/api/types"
18 16
 	"github.com/docker/docker/api/types/container"
... ...
@@ -31,6 +29,7 @@ import (
31 31
 	runconfigopts "github.com/docker/docker/runconfig/opts"
32 32
 	"github.com/docker/go-units"
33 33
 	"github.com/spf13/cobra"
34
+	"golang.org/x/net/context"
34 35
 )
35 36
 
36 37
 type buildOptions struct {
... ...
@@ -39,7 +38,7 @@ type buildOptions struct {
39 39
 	tags           opts.ListOpts
40 40
 	labels         opts.ListOpts
41 41
 	buildArgs      opts.ListOpts
42
-	ulimits        *runconfigopts.UlimitOpt
42
+	ulimits        *opts.UlimitOpt
43 43
 	memory         string
44 44
 	memorySwap     string
45 45
 	shmSize        string
... ...
@@ -67,9 +66,9 @@ func NewBuildCommand(dockerCli *command.DockerCli) *cobra.Command {
67 67
 	ulimits := make(map[string]*units.Ulimit)
68 68
 	options := buildOptions{
69 69
 		tags:      opts.NewListOpts(validateTag),
70
-		buildArgs: opts.NewListOpts(runconfigopts.ValidateEnv),
71
-		ulimits:   runconfigopts.NewUlimitOpt(&ulimits),
72
-		labels:    opts.NewListOpts(runconfigopts.ValidateEnv),
70
+		buildArgs: opts.NewListOpts(opts.ValidateEnv),
71
+		ulimits:   opts.NewUlimitOpt(&ulimits),
72
+		labels:    opts.NewListOpts(opts.ValidateEnv),
73 73
 	}
74 74
 
75 75
 	cmd := &cobra.Command{
... ...
@@ -7,7 +7,6 @@ import (
7 7
 	"github.com/docker/docker/cli"
8 8
 	"github.com/docker/docker/cli/command"
9 9
 	"github.com/docker/docker/opts"
10
-	runconfigopts "github.com/docker/docker/runconfig/opts"
11 10
 	"github.com/spf13/cobra"
12 11
 )
13 12
 
... ...
@@ -23,7 +22,7 @@ type connectOptions struct {
23 23
 
24 24
 func newConnectCommand(dockerCli *command.DockerCli) *cobra.Command {
25 25
 	opts := connectOptions{
26
-		links: opts.NewListOpts(runconfigopts.ValidateLink),
26
+		links: opts.NewListOpts(opts.ValidateLink),
27 27
 	}
28 28
 
29 29
 	cmd := &cobra.Command{
... ...
@@ -36,7 +36,7 @@ type createOptions struct {
36 36
 func newCreateCommand(dockerCli *command.DockerCli) *cobra.Command {
37 37
 	opts := createOptions{
38 38
 		driverOpts: *opts.NewMapOpts(nil, nil),
39
-		labels:     opts.NewListOpts(runconfigopts.ValidateEnv),
39
+		labels:     opts.NewListOpts(opts.ValidateEnv),
40 40
 		ipamAux:    *opts.NewMapOpts(nil, nil),
41 41
 		ipamOpt:    *opts.NewMapOpts(nil, nil),
42 42
 	}
... ...
@@ -23,7 +23,7 @@ type createOptions struct {
23 23
 
24 24
 func newSecretCreateCommand(dockerCli *command.DockerCli) *cobra.Command {
25 25
 	createOpts := createOptions{
26
-		labels: opts.NewListOpts(runconfigopts.ValidateEnv),
26
+		labels: opts.NewListOpts(opts.ValidateEnv),
27 27
 	}
28 28
 
29 29
 	cmd := &cobra.Command{
... ...
@@ -304,7 +304,7 @@ type logDriverOptions struct {
304 304
 }
305 305
 
306 306
 func newLogDriverOptions() logDriverOptions {
307
-	return logDriverOptions{opts: opts.NewListOpts(runconfigopts.ValidateEnv)}
307
+	return logDriverOptions{opts: opts.NewListOpts(opts.ValidateEnv)}
308 308
 }
309 309
 
310 310
 func (ldo *logDriverOptions) toLogDriver() *swarm.Driver {
... ...
@@ -426,17 +426,17 @@ type serviceOptions struct {
426 426
 
427 427
 func newServiceOptions() *serviceOptions {
428 428
 	return &serviceOptions{
429
-		labels:          opts.NewListOpts(runconfigopts.ValidateEnv),
429
+		labels:          opts.NewListOpts(opts.ValidateEnv),
430 430
 		constraints:     opts.NewListOpts(nil),
431
-		containerLabels: opts.NewListOpts(runconfigopts.ValidateEnv),
432
-		env:             opts.NewListOpts(runconfigopts.ValidateEnv),
431
+		containerLabels: opts.NewListOpts(opts.ValidateEnv),
432
+		env:             opts.NewListOpts(opts.ValidateEnv),
433 433
 		envFile:         opts.NewListOpts(nil),
434 434
 		groups:          opts.NewListOpts(nil),
435 435
 		logDriver:       newLogDriverOptions(),
436 436
 		dns:             opts.NewListOpts(opts.ValidateIPAddress),
437 437
 		dnsOption:       opts.NewListOpts(nil),
438 438
 		dnsSearch:       opts.NewListOpts(opts.ValidateDNSSearch),
439
-		hosts:           opts.NewListOpts(runconfigopts.ValidateExtraHost),
439
+		hosts:           opts.NewListOpts(opts.ValidateExtraHost),
440 440
 		networks:        opts.NewListOpts(nil),
441 441
 	}
442 442
 }
... ...
@@ -3,14 +3,13 @@ package volume
3 3
 import (
4 4
 	"fmt"
5 5
 
6
-	"golang.org/x/net/context"
7
-
8 6
 	volumetypes "github.com/docker/docker/api/types/volume"
9 7
 	"github.com/docker/docker/cli"
10 8
 	"github.com/docker/docker/cli/command"
11 9
 	"github.com/docker/docker/opts"
12 10
 	runconfigopts "github.com/docker/docker/runconfig/opts"
13 11
 	"github.com/spf13/cobra"
12
+	"golang.org/x/net/context"
14 13
 )
15 14
 
16 15
 type createOptions struct {
... ...
@@ -23,7 +22,7 @@ type createOptions struct {
23 23
 func newCreateCommand(dockerCli *command.DockerCli) *cobra.Command {
24 24
 	opts := createOptions{
25 25
 		driverOpts: *opts.NewMapOpts(nil, nil),
26
-		labels:     opts.NewListOpts(runconfigopts.ValidateEnv),
26
+		labels:     opts.NewListOpts(opts.ValidateEnv),
27 27
 	}
28 28
 
29 29
 	cmd := &cobra.Command{
... ...
@@ -28,6 +28,7 @@ import (
28 28
 	"github.com/docker/docker/image"
29 29
 	"github.com/docker/docker/layer"
30 30
 	"github.com/docker/docker/libcontainerd"
31
+	"github.com/docker/docker/opts"
31 32
 	"github.com/docker/docker/pkg/idtools"
32 33
 	"github.com/docker/docker/pkg/ioutils"
33 34
 	"github.com/docker/docker/pkg/promise"
... ...
@@ -35,7 +36,6 @@ import (
35 35
 	"github.com/docker/docker/pkg/symlink"
36 36
 	"github.com/docker/docker/restartmanager"
37 37
 	"github.com/docker/docker/runconfig"
38
-	runconfigopts "github.com/docker/docker/runconfig/opts"
39 38
 	"github.com/docker/docker/volume"
40 39
 	"github.com/docker/go-connections/nat"
41 40
 	"github.com/docker/libnetwork"
... ...
@@ -815,7 +815,7 @@ func (container *Container) BuildJoinOptions(n libnetwork.Network) ([]libnetwork
815 815
 	var joinOptions []libnetwork.EndpointOption
816 816
 	if epConfig, ok := container.NetworkSettings.Networks[n.Name()]; ok {
817 817
 		for _, str := range epConfig.Links {
818
-			name, alias, err := runconfigopts.ParseLink(str)
818
+			name, alias, err := opts.ParseLink(str)
819 819
 			if err != nil {
820 820
 				return nil, err
821 821
 			}
... ...
@@ -7,7 +7,6 @@ import (
7 7
 
8 8
 	"github.com/docker/docker/api/types"
9 9
 	"github.com/docker/docker/opts"
10
-	runconfigopts "github.com/docker/docker/runconfig/opts"
11 10
 	"github.com/spf13/pflag"
12 11
 )
13 12
 
... ...
@@ -41,7 +40,7 @@ func (config *Config) InstallCommonUnixFlags(flags *pflag.FlagSet) {
41 41
 	flags.Var(opts.NewIPOpt(&config.bridgeConfig.DefaultGatewayIPv6, ""), "default-gateway-v6", "Container default gateway IPv6 address")
42 42
 	flags.BoolVar(&config.bridgeConfig.InterContainerCommunication, "icc", true, "Enable inter-container communication")
43 43
 	flags.Var(opts.NewIPOpt(&config.bridgeConfig.DefaultIP, "0.0.0.0"), "ip", "Default IP when binding container ports")
44
-	flags.Var(runconfigopts.NewNamedRuntimeOpt("runtimes", &config.Runtimes, stockRuntimeName), "add-runtime", "Register an additional OCI compatible runtime")
44
+	flags.Var(opts.NewNamedRuntimeOpt("runtimes", &config.Runtimes, stockRuntimeName), "add-runtime", "Register an additional OCI compatible runtime")
45 45
 	flags.StringVar(&config.DefaultRuntime, "default-runtime", stockRuntimeName, "Default OCI runtime for containers")
46 46
 
47 47
 }
... ...
@@ -5,7 +5,7 @@ package daemon
5 5
 import (
6 6
 	"fmt"
7 7
 
8
-	runconfigopts "github.com/docker/docker/runconfig/opts"
8
+	"github.com/docker/docker/opts"
9 9
 	units "github.com/docker/go-units"
10 10
 	"github.com/spf13/pflag"
11 11
 )
... ...
@@ -68,7 +68,7 @@ func (config *Config) InstallFlags(flags *pflag.FlagSet) {
68 68
 
69 69
 	// Then platform-specific install flags
70 70
 	flags.BoolVar(&config.EnableSelinuxSupport, "selinux-enabled", false, "Enable selinux support")
71
-	flags.Var(runconfigopts.NewUlimitOpt(&config.Ulimits), "default-ulimit", "Default ulimits for containers")
71
+	flags.Var(opts.NewUlimitOpt(&config.Ulimits), "default-ulimit", "Default ulimits for containers")
72 72
 	flags.BoolVar(&config.bridgeConfig.EnableIPTables, "iptables", true, "Enable addition of iptables rules")
73 73
 	flags.BoolVar(&config.bridgeConfig.EnableIPForward, "ip-forward", true, "Enable net.ipv4.ip_forward")
74 74
 	flags.BoolVar(&config.bridgeConfig.EnableIPMasq, "ip-masq", true, "Enable IP masquerading")
... ...
@@ -12,10 +12,10 @@ import (
12 12
 	"github.com/docker/docker/container"
13 13
 	"github.com/docker/docker/daemon/network"
14 14
 	"github.com/docker/docker/image"
15
+	"github.com/docker/docker/opts"
15 16
 	"github.com/docker/docker/pkg/signal"
16 17
 	"github.com/docker/docker/pkg/system"
17 18
 	"github.com/docker/docker/pkg/truncindex"
18
-	"github.com/docker/docker/runconfig/opts"
19 19
 	"github.com/docker/go-connections/nat"
20 20
 )
21 21
 
... ...
@@ -23,12 +23,12 @@ import (
23 23
 	containertypes "github.com/docker/docker/api/types/container"
24 24
 	"github.com/docker/docker/container"
25 25
 	"github.com/docker/docker/image"
26
+	"github.com/docker/docker/opts"
26 27
 	"github.com/docker/docker/pkg/idtools"
27 28
 	"github.com/docker/docker/pkg/parsers"
28 29
 	"github.com/docker/docker/pkg/parsers/kernel"
29 30
 	"github.com/docker/docker/pkg/sysinfo"
30 31
 	"github.com/docker/docker/runconfig"
31
-	runconfigopts "github.com/docker/docker/runconfig/opts"
32 32
 	"github.com/docker/libnetwork"
33 33
 	nwconfig "github.com/docker/libnetwork/config"
34 34
 	"github.com/docker/libnetwork/drivers/bridge"
... ...
@@ -1098,7 +1098,7 @@ func (daemon *Daemon) registerLinks(container *container.Container, hostConfig *
1098 1098
 	}
1099 1099
 
1100 1100
 	for _, l := range hostConfig.Links {
1101
-		name, alias, err := runconfigopts.ParseLink(l)
1101
+		name, alias, err := opts.ParseLink(l)
1102 1102
 		if err != nil {
1103 1103
 			return err
1104 1104
 		}
1105 1105
new file mode 100644
... ...
@@ -0,0 +1,46 @@
0
+package opts
1
+
2
+import (
3
+	"fmt"
4
+	"os"
5
+	"runtime"
6
+	"strings"
7
+)
8
+
9
+// ValidateEnv validates an environment variable and returns it.
10
+// If no value is specified, it returns the current value using os.Getenv.
11
+//
12
+// As on ParseEnvFile and related to #16585, environment variable names
13
+// are not validate what so ever, it's up to application inside docker
14
+// to validate them or not.
15
+//
16
+// The only validation here is to check if name is empty, per #25099
17
+func ValidateEnv(val string) (string, error) {
18
+	arr := strings.Split(val, "=")
19
+	if arr[0] == "" {
20
+		return "", fmt.Errorf("invalid environment variable: %s", val)
21
+	}
22
+	if len(arr) > 1 {
23
+		return val, nil
24
+	}
25
+	if !doesEnvExist(val) {
26
+		return val, nil
27
+	}
28
+	return fmt.Sprintf("%s=%s", val, os.Getenv(val)), nil
29
+}
30
+
31
+func doesEnvExist(name string) bool {
32
+	for _, entry := range os.Environ() {
33
+		parts := strings.SplitN(entry, "=", 2)
34
+		if runtime.GOOS == "windows" {
35
+			// Environment variable are case-insensitive on Windows. PaTh, path and PATH are equivalent.
36
+			if strings.EqualFold(parts[0], name) {
37
+				return true
38
+			}
39
+		}
40
+		if parts[0] == name {
41
+			return true
42
+		}
43
+	}
44
+	return false
45
+}
0 46
new file mode 100644
... ...
@@ -0,0 +1,42 @@
0
+package opts
1
+
2
+import (
3
+	"fmt"
4
+	"os"
5
+	"runtime"
6
+	"testing"
7
+)
8
+
9
+func TestValidateEnv(t *testing.T) {
10
+	valids := map[string]string{
11
+		"a":                   "a",
12
+		"something":           "something",
13
+		"_=a":                 "_=a",
14
+		"env1=value1":         "env1=value1",
15
+		"_env1=value1":        "_env1=value1",
16
+		"env2=value2=value3":  "env2=value2=value3",
17
+		"env3=abc!qwe":        "env3=abc!qwe",
18
+		"env_4=value 4":       "env_4=value 4",
19
+		"PATH":                fmt.Sprintf("PATH=%v", os.Getenv("PATH")),
20
+		"PATH=something":      "PATH=something",
21
+		"asd!qwe":             "asd!qwe",
22
+		"1asd":                "1asd",
23
+		"123":                 "123",
24
+		"some space":          "some space",
25
+		"  some space before": "  some space before",
26
+		"some space after  ":  "some space after  ",
27
+	}
28
+	// Environment variables are case in-sensitive on Windows
29
+	if runtime.GOOS == "windows" {
30
+		valids["PaTh"] = fmt.Sprintf("PaTh=%v", os.Getenv("PATH"))
31
+	}
32
+	for value, expected := range valids {
33
+		actual, err := ValidateEnv(value)
34
+		if err != nil {
35
+			t.Fatal(err)
36
+		}
37
+		if actual != expected {
38
+			t.Fatalf("Expected [%v], got [%v]", expected, actual)
39
+		}
40
+	}
41
+}
... ...
@@ -149,3 +149,17 @@ func ParseTCPAddr(tryAddr string, defaultAddr string) (string, error) {
149 149
 
150 150
 	return fmt.Sprintf("tcp://%s%s", net.JoinHostPort(host, port), u.Path), nil
151 151
 }
152
+
153
+// ValidateExtraHost validates that the specified string is a valid extrahost and returns it.
154
+// ExtraHost is in the form of name:ip where the ip has to be a valid ip (IPv4 or IPv6).
155
+func ValidateExtraHost(val string) (string, error) {
156
+	// allow for IPv6 addresses in extra hosts by only splitting on first ":"
157
+	arr := strings.SplitN(val, ":", 2)
158
+	if len(arr) != 2 || len(arr[0]) == 0 {
159
+		return "", fmt.Errorf("bad format for add-host: %q", val)
160
+	}
161
+	if _, err := ValidateIPAddress(arr[1]); err != nil {
162
+		return "", fmt.Errorf("invalid IP address in add-host: %q", arr[1])
163
+	}
164
+	return val, nil
165
+}
... ...
@@ -2,6 +2,7 @@ package opts
2 2
 
3 3
 import (
4 4
 	"fmt"
5
+	"strings"
5 6
 	"testing"
6 7
 )
7 8
 
... ...
@@ -146,3 +147,35 @@ func TestParseInvalidUnixAddrInvalid(t *testing.T) {
146 146
 		t.Fatalf("Expected an %v, got %v", v, "unix:///var/run/docker.sock")
147 147
 	}
148 148
 }
149
+
150
+func TestValidateExtraHosts(t *testing.T) {
151
+	valid := []string{
152
+		`myhost:192.168.0.1`,
153
+		`thathost:10.0.2.1`,
154
+		`anipv6host:2003:ab34:e::1`,
155
+		`ipv6local:::1`,
156
+	}
157
+
158
+	invalid := map[string]string{
159
+		`myhost:192.notanipaddress.1`:  `invalid IP`,
160
+		`thathost-nosemicolon10.0.0.1`: `bad format`,
161
+		`anipv6host:::::1`:             `invalid IP`,
162
+		`ipv6local:::0::`:              `invalid IP`,
163
+	}
164
+
165
+	for _, extrahost := range valid {
166
+		if _, err := ValidateExtraHost(extrahost); err != nil {
167
+			t.Fatalf("ValidateExtraHost(`"+extrahost+"`) should succeed: error %v", err)
168
+		}
169
+	}
170
+
171
+	for extraHost, expectedError := range invalid {
172
+		if _, err := ValidateExtraHost(extraHost); err == nil {
173
+			t.Fatalf("ValidateExtraHost(`%q`) should have failed validation", extraHost)
174
+		} else {
175
+			if !strings.Contains(err.Error(), expectedError) {
176
+				t.Fatalf("ValidateExtraHost(`%q`) error should contain %q", extraHost, expectedError)
177
+			}
178
+		}
179
+	}
180
+}
... ...
@@ -4,6 +4,7 @@ import (
4 4
 	"fmt"
5 5
 	"math/big"
6 6
 	"net"
7
+	"path"
7 8
 	"regexp"
8 9
 	"strings"
9 10
 
... ...
@@ -231,6 +232,15 @@ func ValidateIPAddress(val string) (string, error) {
231 231
 	return "", fmt.Errorf("%s is not an ip address", val)
232 232
 }
233 233
 
234
+// ValidateMACAddress validates a MAC address.
235
+func ValidateMACAddress(val string) (string, error) {
236
+	_, err := net.ParseMAC(strings.TrimSpace(val))
237
+	if err != nil {
238
+		return "", err
239
+	}
240
+	return val, nil
241
+}
242
+
234 243
 // ValidateDNSSearch validates domain for resolvconf search configuration.
235 244
 // A zero length domain is represented by a dot (.).
236 245
 func ValidateDNSSearch(val string) (string, error) {
... ...
@@ -364,3 +374,31 @@ func ParseCPUs(value string) (int64, error) {
364 364
 	}
365 365
 	return nano.Num().Int64(), nil
366 366
 }
367
+
368
+// ParseLink parses and validates the specified string as a link format (name:alias)
369
+func ParseLink(val string) (string, string, error) {
370
+	if val == "" {
371
+		return "", "", fmt.Errorf("empty string specified for links")
372
+	}
373
+	arr := strings.Split(val, ":")
374
+	if len(arr) > 2 {
375
+		return "", "", fmt.Errorf("bad format for links: %s", val)
376
+	}
377
+	if len(arr) == 1 {
378
+		return val, val, nil
379
+	}
380
+	// This is kept because we can actually get a HostConfig with links
381
+	// from an already created container and the format is not `foo:bar`
382
+	// but `/foo:/c1/bar`
383
+	if strings.HasPrefix(arr[0], "/") {
384
+		_, alias := path.Split(arr[1])
385
+		return arr[0][1:], alias, nil
386
+	}
387
+	return arr[0], arr[1], nil
388
+}
389
+
390
+// ValidateLink validates that the specified string has a valid link format (containerName:alias).
391
+func ValidateLink(val string) (string, error) {
392
+	_, _, err := ParseLink(val)
393
+	return val, err
394
+}
... ...
@@ -230,3 +230,78 @@ func TestNamedMapOpts(t *testing.T) {
230 230
 		t.Errorf("expected map-size to be in the values, got %v", tmpMap)
231 231
 	}
232 232
 }
233
+
234
+func TestValidateMACAddress(t *testing.T) {
235
+	if _, err := ValidateMACAddress(`92:d0:c6:0a:29:33`); err != nil {
236
+		t.Fatalf("ValidateMACAddress(`92:d0:c6:0a:29:33`) got %s", err)
237
+	}
238
+
239
+	if _, err := ValidateMACAddress(`92:d0:c6:0a:33`); err == nil {
240
+		t.Fatalf("ValidateMACAddress(`92:d0:c6:0a:33`) succeeded; expected failure on invalid MAC")
241
+	}
242
+
243
+	if _, err := ValidateMACAddress(`random invalid string`); err == nil {
244
+		t.Fatalf("ValidateMACAddress(`random invalid string`) succeeded; expected failure on invalid MAC")
245
+	}
246
+}
247
+
248
+func TestValidateLink(t *testing.T) {
249
+	valid := []string{
250
+		"name",
251
+		"dcdfbe62ecd0:alias",
252
+		"7a67485460b7642516a4ad82ecefe7f57d0c4916f530561b71a50a3f9c4e33da",
253
+		"angry_torvalds:linus",
254
+	}
255
+	invalid := map[string]string{
256
+		"":               "empty string specified for links",
257
+		"too:much:of:it": "bad format for links: too:much:of:it",
258
+	}
259
+
260
+	for _, link := range valid {
261
+		if _, err := ValidateLink(link); err != nil {
262
+			t.Fatalf("ValidateLink(`%q`) should succeed: error %q", link, err)
263
+		}
264
+	}
265
+
266
+	for link, expectedError := range invalid {
267
+		if _, err := ValidateLink(link); err == nil {
268
+			t.Fatalf("ValidateLink(`%q`) should have failed validation", link)
269
+		} else {
270
+			if !strings.Contains(err.Error(), expectedError) {
271
+				t.Fatalf("ValidateLink(`%q`) error should contain %q", link, expectedError)
272
+			}
273
+		}
274
+	}
275
+}
276
+
277
+func TestParseLink(t *testing.T) {
278
+	name, alias, err := ParseLink("name:alias")
279
+	if err != nil {
280
+		t.Fatalf("Expected not to error out on a valid name:alias format but got: %v", err)
281
+	}
282
+	if name != "name" {
283
+		t.Fatalf("Link name should have been name, got %s instead", name)
284
+	}
285
+	if alias != "alias" {
286
+		t.Fatalf("Link alias should have been alias, got %s instead", alias)
287
+	}
288
+	// short format definition
289
+	name, alias, err = ParseLink("name")
290
+	if err != nil {
291
+		t.Fatalf("Expected not to error out on a valid name only format but got: %v", err)
292
+	}
293
+	if name != "name" {
294
+		t.Fatalf("Link name should have been name, got %s instead", name)
295
+	}
296
+	if alias != "name" {
297
+		t.Fatalf("Link alias should have been name, got %s instead", alias)
298
+	}
299
+	// empty string link definition is not allowed
300
+	if _, _, err := ParseLink(""); err == nil || !strings.Contains(err.Error(), "empty string specified for links") {
301
+		t.Fatalf("Expected error 'empty string specified for links' but got: %v", err)
302
+	}
303
+	// more than two colons are not allowed
304
+	if _, _, err := ParseLink("link:alias:wrong"); err == nil || !strings.Contains(err.Error(), "bad format for links: link:alias:wrong") {
305
+		t.Fatalf("Expected error 'bad format for links: link:alias:wrong' but got: %v", err)
306
+	}
307
+}
233 308
new file mode 100644
... ...
@@ -0,0 +1,79 @@
0
+package opts
1
+
2
+import (
3
+	"fmt"
4
+	"strings"
5
+
6
+	"github.com/docker/docker/api/types"
7
+)
8
+
9
+// RuntimeOpt defines a map of Runtimes
10
+type RuntimeOpt struct {
11
+	name             string
12
+	stockRuntimeName string
13
+	values           *map[string]types.Runtime
14
+}
15
+
16
+// NewNamedRuntimeOpt creates a new RuntimeOpt
17
+func NewNamedRuntimeOpt(name string, ref *map[string]types.Runtime, stockRuntime string) *RuntimeOpt {
18
+	if ref == nil {
19
+		ref = &map[string]types.Runtime{}
20
+	}
21
+	return &RuntimeOpt{name: name, values: ref, stockRuntimeName: stockRuntime}
22
+}
23
+
24
+// Name returns the name of the NamedListOpts in the configuration.
25
+func (o *RuntimeOpt) Name() string {
26
+	return o.name
27
+}
28
+
29
+// Set validates and updates the list of Runtimes
30
+func (o *RuntimeOpt) Set(val string) error {
31
+	parts := strings.SplitN(val, "=", 2)
32
+	if len(parts) != 2 {
33
+		return fmt.Errorf("invalid runtime argument: %s", val)
34
+	}
35
+
36
+	parts[0] = strings.TrimSpace(parts[0])
37
+	parts[1] = strings.TrimSpace(parts[1])
38
+	if parts[0] == "" || parts[1] == "" {
39
+		return fmt.Errorf("invalid runtime argument: %s", val)
40
+	}
41
+
42
+	parts[0] = strings.ToLower(parts[0])
43
+	if parts[0] == o.stockRuntimeName {
44
+		return fmt.Errorf("runtime name '%s' is reserved", o.stockRuntimeName)
45
+	}
46
+
47
+	if _, ok := (*o.values)[parts[0]]; ok {
48
+		return fmt.Errorf("runtime '%s' was already defined", parts[0])
49
+	}
50
+
51
+	(*o.values)[parts[0]] = types.Runtime{Path: parts[1]}
52
+
53
+	return nil
54
+}
55
+
56
+// String returns Runtime values as a string.
57
+func (o *RuntimeOpt) String() string {
58
+	var out []string
59
+	for k := range *o.values {
60
+		out = append(out, k)
61
+	}
62
+
63
+	return fmt.Sprintf("%v", out)
64
+}
65
+
66
+// GetMap returns a map of Runtimes (name: path)
67
+func (o *RuntimeOpt) GetMap() map[string]types.Runtime {
68
+	if o.values != nil {
69
+		return *o.values
70
+	}
71
+
72
+	return map[string]types.Runtime{}
73
+}
74
+
75
+// Type returns the type of the option
76
+func (o *RuntimeOpt) Type() string {
77
+	return "runtime"
78
+}
0 79
new file mode 100644
... ...
@@ -0,0 +1,111 @@
0
+package opts
1
+
2
+import (
3
+	"fmt"
4
+	"strconv"
5
+	"strings"
6
+
7
+	"github.com/docker/docker/api/types/blkiodev"
8
+	"github.com/docker/go-units"
9
+)
10
+
11
+// ValidatorThrottleFctType defines a validator function that returns a validated struct and/or an error.
12
+type ValidatorThrottleFctType func(val string) (*blkiodev.ThrottleDevice, error)
13
+
14
+// ValidateThrottleBpsDevice validates that the specified string has a valid device-rate format.
15
+func ValidateThrottleBpsDevice(val string) (*blkiodev.ThrottleDevice, error) {
16
+	split := strings.SplitN(val, ":", 2)
17
+	if len(split) != 2 {
18
+		return nil, fmt.Errorf("bad format: %s", val)
19
+	}
20
+	if !strings.HasPrefix(split[0], "/dev/") {
21
+		return nil, fmt.Errorf("bad format for device path: %s", val)
22
+	}
23
+	rate, err := units.RAMInBytes(split[1])
24
+	if err != nil {
25
+		return nil, fmt.Errorf("invalid rate for device: %s. The correct format is <device-path>:<number>[<unit>]. Number must be a positive integer. Unit is optional and can be kb, mb, or gb", val)
26
+	}
27
+	if rate < 0 {
28
+		return nil, fmt.Errorf("invalid rate for device: %s. The correct format is <device-path>:<number>[<unit>]. Number must be a positive integer. Unit is optional and can be kb, mb, or gb", val)
29
+	}
30
+
31
+	return &blkiodev.ThrottleDevice{
32
+		Path: split[0],
33
+		Rate: uint64(rate),
34
+	}, nil
35
+}
36
+
37
+// ValidateThrottleIOpsDevice validates that the specified string has a valid device-rate format.
38
+func ValidateThrottleIOpsDevice(val string) (*blkiodev.ThrottleDevice, error) {
39
+	split := strings.SplitN(val, ":", 2)
40
+	if len(split) != 2 {
41
+		return nil, fmt.Errorf("bad format: %s", val)
42
+	}
43
+	if !strings.HasPrefix(split[0], "/dev/") {
44
+		return nil, fmt.Errorf("bad format for device path: %s", val)
45
+	}
46
+	rate, err := strconv.ParseUint(split[1], 10, 64)
47
+	if err != nil {
48
+		return nil, fmt.Errorf("invalid rate for device: %s. The correct format is <device-path>:<number>. Number must be a positive integer", val)
49
+	}
50
+	if rate < 0 {
51
+		return nil, fmt.Errorf("invalid rate for device: %s. The correct format is <device-path>:<number>. Number must be a positive integer", val)
52
+	}
53
+
54
+	return &blkiodev.ThrottleDevice{
55
+		Path: split[0],
56
+		Rate: uint64(rate),
57
+	}, nil
58
+}
59
+
60
+// ThrottledeviceOpt defines a map of ThrottleDevices
61
+type ThrottledeviceOpt struct {
62
+	values    []*blkiodev.ThrottleDevice
63
+	validator ValidatorThrottleFctType
64
+}
65
+
66
+// NewThrottledeviceOpt creates a new ThrottledeviceOpt
67
+func NewThrottledeviceOpt(validator ValidatorThrottleFctType) ThrottledeviceOpt {
68
+	values := []*blkiodev.ThrottleDevice{}
69
+	return ThrottledeviceOpt{
70
+		values:    values,
71
+		validator: validator,
72
+	}
73
+}
74
+
75
+// Set validates a ThrottleDevice and sets its name as a key in ThrottledeviceOpt
76
+func (opt *ThrottledeviceOpt) Set(val string) error {
77
+	var value *blkiodev.ThrottleDevice
78
+	if opt.validator != nil {
79
+		v, err := opt.validator(val)
80
+		if err != nil {
81
+			return err
82
+		}
83
+		value = v
84
+	}
85
+	(opt.values) = append((opt.values), value)
86
+	return nil
87
+}
88
+
89
+// String returns ThrottledeviceOpt values as a string.
90
+func (opt *ThrottledeviceOpt) String() string {
91
+	var out []string
92
+	for _, v := range opt.values {
93
+		out = append(out, v.String())
94
+	}
95
+
96
+	return fmt.Sprintf("%v", out)
97
+}
98
+
99
+// GetList returns a slice of pointers to ThrottleDevices.
100
+func (opt *ThrottledeviceOpt) GetList() []*blkiodev.ThrottleDevice {
101
+	var throttledevice []*blkiodev.ThrottleDevice
102
+	throttledevice = append(throttledevice, opt.values...)
103
+
104
+	return throttledevice
105
+}
106
+
107
+// Type returns the option type
108
+func (opt *ThrottledeviceOpt) Type() string {
109
+	return "throttled-device"
110
+}
0 111
new file mode 100644
... ...
@@ -0,0 +1,57 @@
0
+package opts
1
+
2
+import (
3
+	"fmt"
4
+
5
+	"github.com/docker/go-units"
6
+)
7
+
8
+// UlimitOpt defines a map of Ulimits
9
+type UlimitOpt struct {
10
+	values *map[string]*units.Ulimit
11
+}
12
+
13
+// NewUlimitOpt creates a new UlimitOpt
14
+func NewUlimitOpt(ref *map[string]*units.Ulimit) *UlimitOpt {
15
+	if ref == nil {
16
+		ref = &map[string]*units.Ulimit{}
17
+	}
18
+	return &UlimitOpt{ref}
19
+}
20
+
21
+// Set validates a Ulimit and sets its name as a key in UlimitOpt
22
+func (o *UlimitOpt) Set(val string) error {
23
+	l, err := units.ParseUlimit(val)
24
+	if err != nil {
25
+		return err
26
+	}
27
+
28
+	(*o.values)[l.Name] = l
29
+
30
+	return nil
31
+}
32
+
33
+// String returns Ulimit values as a string.
34
+func (o *UlimitOpt) String() string {
35
+	var out []string
36
+	for _, v := range *o.values {
37
+		out = append(out, v.String())
38
+	}
39
+
40
+	return fmt.Sprintf("%v", out)
41
+}
42
+
43
+// GetList returns a slice of pointers to Ulimits.
44
+func (o *UlimitOpt) GetList() []*units.Ulimit {
45
+	var ulimits []*units.Ulimit
46
+	for _, v := range *o.values {
47
+		ulimits = append(ulimits, v)
48
+	}
49
+
50
+	return ulimits
51
+}
52
+
53
+// Type returns the option type
54
+func (o *UlimitOpt) Type() string {
55
+	return "ulimit"
56
+}
0 57
new file mode 100644
... ...
@@ -0,0 +1,42 @@
0
+package opts
1
+
2
+import (
3
+	"testing"
4
+
5
+	"github.com/docker/go-units"
6
+)
7
+
8
+func TestUlimitOpt(t *testing.T) {
9
+	ulimitMap := map[string]*units.Ulimit{
10
+		"nofile": {"nofile", 1024, 512},
11
+	}
12
+
13
+	ulimitOpt := NewUlimitOpt(&ulimitMap)
14
+
15
+	expected := "[nofile=512:1024]"
16
+	if ulimitOpt.String() != expected {
17
+		t.Fatalf("Expected %v, got %v", expected, ulimitOpt)
18
+	}
19
+
20
+	// Valid ulimit append to opts
21
+	if err := ulimitOpt.Set("core=1024:1024"); err != nil {
22
+		t.Fatal(err)
23
+	}
24
+
25
+	// Invalid ulimit type returns an error and do not append to opts
26
+	if err := ulimitOpt.Set("notavalidtype=1024:1024"); err == nil {
27
+		t.Fatalf("Expected error on invalid ulimit type")
28
+	}
29
+	expected = "[nofile=512:1024 core=1024:1024]"
30
+	expected2 := "[core=1024:1024 nofile=512:1024]"
31
+	result := ulimitOpt.String()
32
+	if result != expected && result != expected2 {
33
+		t.Fatalf("Expected %v or %v, got %v", expected, expected2, ulimitOpt)
34
+	}
35
+
36
+	// And test GetList
37
+	ulimits := ulimitOpt.GetList()
38
+	if len(ulimits) != 2 {
39
+		t.Fatalf("Expected a ulimit list of 2, got %v", ulimits)
40
+	}
41
+}
0 42
new file mode 100644
... ...
@@ -0,0 +1,89 @@
0
+package opts
1
+
2
+import (
3
+	"fmt"
4
+	"strconv"
5
+	"strings"
6
+
7
+	"github.com/docker/docker/api/types/blkiodev"
8
+)
9
+
10
+// ValidatorWeightFctType defines a validator function that returns a validated struct and/or an error.
11
+type ValidatorWeightFctType func(val string) (*blkiodev.WeightDevice, error)
12
+
13
+// ValidateWeightDevice validates that the specified string has a valid device-weight format.
14
+func ValidateWeightDevice(val string) (*blkiodev.WeightDevice, error) {
15
+	split := strings.SplitN(val, ":", 2)
16
+	if len(split) != 2 {
17
+		return nil, fmt.Errorf("bad format: %s", val)
18
+	}
19
+	if !strings.HasPrefix(split[0], "/dev/") {
20
+		return nil, fmt.Errorf("bad format for device path: %s", val)
21
+	}
22
+	weight, err := strconv.ParseUint(split[1], 10, 0)
23
+	if err != nil {
24
+		return nil, fmt.Errorf("invalid weight for device: %s", val)
25
+	}
26
+	if weight > 0 && (weight < 10 || weight > 1000) {
27
+		return nil, fmt.Errorf("invalid weight for device: %s", val)
28
+	}
29
+
30
+	return &blkiodev.WeightDevice{
31
+		Path:   split[0],
32
+		Weight: uint16(weight),
33
+	}, nil
34
+}
35
+
36
+// WeightdeviceOpt defines a map of WeightDevices
37
+type WeightdeviceOpt struct {
38
+	values    []*blkiodev.WeightDevice
39
+	validator ValidatorWeightFctType
40
+}
41
+
42
+// NewWeightdeviceOpt creates a new WeightdeviceOpt
43
+func NewWeightdeviceOpt(validator ValidatorWeightFctType) WeightdeviceOpt {
44
+	values := []*blkiodev.WeightDevice{}
45
+	return WeightdeviceOpt{
46
+		values:    values,
47
+		validator: validator,
48
+	}
49
+}
50
+
51
+// Set validates a WeightDevice and sets its name as a key in WeightdeviceOpt
52
+func (opt *WeightdeviceOpt) Set(val string) error {
53
+	var value *blkiodev.WeightDevice
54
+	if opt.validator != nil {
55
+		v, err := opt.validator(val)
56
+		if err != nil {
57
+			return err
58
+		}
59
+		value = v
60
+	}
61
+	(opt.values) = append((opt.values), value)
62
+	return nil
63
+}
64
+
65
+// String returns WeightdeviceOpt values as a string.
66
+func (opt *WeightdeviceOpt) String() string {
67
+	var out []string
68
+	for _, v := range opt.values {
69
+		out = append(out, v.String())
70
+	}
71
+
72
+	return fmt.Sprintf("%v", out)
73
+}
74
+
75
+// GetList returns a slice of pointers to WeightDevices.
76
+func (opt *WeightdeviceOpt) GetList() []*blkiodev.WeightDevice {
77
+	var weightdevice []*blkiodev.WeightDevice
78
+	for _, v := range opt.values {
79
+		weightdevice = append(weightdevice, v)
80
+	}
81
+
82
+	return weightdevice
83
+}
84
+
85
+// Type returns the option type
86
+func (opt *WeightdeviceOpt) Type() string {
87
+	return "weighted-device"
88
+}
0 89
deleted file mode 100755
1 90
Binary files a/runconfig/opts/fixtures/utf16.env and /dev/null differ
2 91
deleted file mode 100755
3 92
Binary files a/runconfig/opts/fixtures/utf16be.env and /dev/null differ
4 93
deleted file mode 100755
... ...
@@ -1,3 +0,0 @@
1
-FOO=BAR
2
-HELLO=您好
3
-BAR=FOO
4 1
\ No newline at end of file
5 2
deleted file mode 100644
... ...
@@ -1 +0,0 @@
1
-ENV1=value1
2 1
deleted file mode 100644
... ...
@@ -1 +0,0 @@
1
-LABEL1=value1
2 1
deleted file mode 100644
... ...
@@ -1,83 +0,0 @@
1
-package opts
2
-
3
-import (
4
-	"fmt"
5
-	"net"
6
-	"os"
7
-	"runtime"
8
-	"strings"
9
-
10
-	fopts "github.com/docker/docker/opts"
11
-)
12
-
13
-// ValidateAttach validates that the specified string is a valid attach option.
14
-func ValidateAttach(val string) (string, error) {
15
-	s := strings.ToLower(val)
16
-	for _, str := range []string{"stdin", "stdout", "stderr"} {
17
-		if s == str {
18
-			return s, nil
19
-		}
20
-	}
21
-	return val, fmt.Errorf("valid streams are STDIN, STDOUT and STDERR")
22
-}
23
-
24
-// ValidateEnv validates an environment variable and returns it.
25
-// If no value is specified, it returns the current value using os.Getenv.
26
-//
27
-// As on ParseEnvFile and related to #16585, environment variable names
28
-// are not validate what so ever, it's up to application inside docker
29
-// to validate them or not.
30
-//
31
-// The only validation here is to check if name is empty, per #25099
32
-func ValidateEnv(val string) (string, error) {
33
-	arr := strings.Split(val, "=")
34
-	if arr[0] == "" {
35
-		return "", fmt.Errorf("invalid environment variable: %s", val)
36
-	}
37
-	if len(arr) > 1 {
38
-		return val, nil
39
-	}
40
-	if !doesEnvExist(val) {
41
-		return val, nil
42
-	}
43
-	return fmt.Sprintf("%s=%s", val, os.Getenv(val)), nil
44
-}
45
-
46
-func doesEnvExist(name string) bool {
47
-	for _, entry := range os.Environ() {
48
-		parts := strings.SplitN(entry, "=", 2)
49
-		if runtime.GOOS == "windows" {
50
-			// Environment variable are case-insensitive on Windows. PaTh, path and PATH are equivalent.
51
-			if strings.EqualFold(parts[0], name) {
52
-				return true
53
-			}
54
-		}
55
-		if parts[0] == name {
56
-			return true
57
-		}
58
-	}
59
-	return false
60
-}
61
-
62
-// ValidateExtraHost validates that the specified string is a valid extrahost and returns it.
63
-// ExtraHost is in the form of name:ip where the ip has to be a valid ip (IPv4 or IPv6).
64
-func ValidateExtraHost(val string) (string, error) {
65
-	// allow for IPv6 addresses in extra hosts by only splitting on first ":"
66
-	arr := strings.SplitN(val, ":", 2)
67
-	if len(arr) != 2 || len(arr[0]) == 0 {
68
-		return "", fmt.Errorf("bad format for add-host: %q", val)
69
-	}
70
-	if _, err := fopts.ValidateIPAddress(arr[1]); err != nil {
71
-		return "", fmt.Errorf("invalid IP address in add-host: %q", arr[1])
72
-	}
73
-	return val, nil
74
-}
75
-
76
-// ValidateMACAddress validates a MAC address.
77
-func ValidateMACAddress(val string) (string, error) {
78
-	_, err := net.ParseMAC(strings.TrimSpace(val))
79
-	if err != nil {
80
-		return "", err
81
-	}
82
-	return val, nil
83
-}
84 1
deleted file mode 100644
... ...
@@ -1,113 +0,0 @@
1
-package opts
2
-
3
-import (
4
-	"fmt"
5
-	"os"
6
-	"runtime"
7
-	"strings"
8
-	"testing"
9
-)
10
-
11
-func TestValidateAttach(t *testing.T) {
12
-	valid := []string{
13
-		"stdin",
14
-		"stdout",
15
-		"stderr",
16
-		"STDIN",
17
-		"STDOUT",
18
-		"STDERR",
19
-	}
20
-	if _, err := ValidateAttach("invalid"); err == nil {
21
-		t.Fatalf("Expected error with [valid streams are STDIN, STDOUT and STDERR], got nothing")
22
-	}
23
-
24
-	for _, attach := range valid {
25
-		value, err := ValidateAttach(attach)
26
-		if err != nil {
27
-			t.Fatal(err)
28
-		}
29
-		if value != strings.ToLower(attach) {
30
-			t.Fatalf("Expected [%v], got [%v]", attach, value)
31
-		}
32
-	}
33
-}
34
-
35
-func TestValidateEnv(t *testing.T) {
36
-	valids := map[string]string{
37
-		"a":                   "a",
38
-		"something":           "something",
39
-		"_=a":                 "_=a",
40
-		"env1=value1":         "env1=value1",
41
-		"_env1=value1":        "_env1=value1",
42
-		"env2=value2=value3":  "env2=value2=value3",
43
-		"env3=abc!qwe":        "env3=abc!qwe",
44
-		"env_4=value 4":       "env_4=value 4",
45
-		"PATH":                fmt.Sprintf("PATH=%v", os.Getenv("PATH")),
46
-		"PATH=something":      "PATH=something",
47
-		"asd!qwe":             "asd!qwe",
48
-		"1asd":                "1asd",
49
-		"123":                 "123",
50
-		"some space":          "some space",
51
-		"  some space before": "  some space before",
52
-		"some space after  ":  "some space after  ",
53
-	}
54
-	// Environment variables are case in-sensitive on Windows
55
-	if runtime.GOOS == "windows" {
56
-		valids["PaTh"] = fmt.Sprintf("PaTh=%v", os.Getenv("PATH"))
57
-	}
58
-	for value, expected := range valids {
59
-		actual, err := ValidateEnv(value)
60
-		if err != nil {
61
-			t.Fatal(err)
62
-		}
63
-		if actual != expected {
64
-			t.Fatalf("Expected [%v], got [%v]", expected, actual)
65
-		}
66
-	}
67
-}
68
-
69
-func TestValidateExtraHosts(t *testing.T) {
70
-	valid := []string{
71
-		`myhost:192.168.0.1`,
72
-		`thathost:10.0.2.1`,
73
-		`anipv6host:2003:ab34:e::1`,
74
-		`ipv6local:::1`,
75
-	}
76
-
77
-	invalid := map[string]string{
78
-		`myhost:192.notanipaddress.1`:  `invalid IP`,
79
-		`thathost-nosemicolon10.0.0.1`: `bad format`,
80
-		`anipv6host:::::1`:             `invalid IP`,
81
-		`ipv6local:::0::`:              `invalid IP`,
82
-	}
83
-
84
-	for _, extrahost := range valid {
85
-		if _, err := ValidateExtraHost(extrahost); err != nil {
86
-			t.Fatalf("ValidateExtraHost(`"+extrahost+"`) should succeed: error %v", err)
87
-		}
88
-	}
89
-
90
-	for extraHost, expectedError := range invalid {
91
-		if _, err := ValidateExtraHost(extraHost); err == nil {
92
-			t.Fatalf("ValidateExtraHost(`%q`) should have failed validation", extraHost)
93
-		} else {
94
-			if !strings.Contains(err.Error(), expectedError) {
95
-				t.Fatalf("ValidateExtraHost(`%q`) error should contain %q", extraHost, expectedError)
96
-			}
97
-		}
98
-	}
99
-}
100
-
101
-func TestValidateMACAddress(t *testing.T) {
102
-	if _, err := ValidateMACAddress(`92:d0:c6:0a:29:33`); err != nil {
103
-		t.Fatalf("ValidateMACAddress(`92:d0:c6:0a:29:33`) got %s", err)
104
-	}
105
-
106
-	if _, err := ValidateMACAddress(`92:d0:c6:0a:33`); err == nil {
107
-		t.Fatalf("ValidateMACAddress(`92:d0:c6:0a:33`) succeeded; expected failure on invalid MAC")
108
-	}
109
-
110
-	if _, err := ValidateMACAddress(`random invalid string`); err == nil {
111
-		t.Fatalf("ValidateMACAddress(`random invalid string`) succeeded; expected failure on invalid MAC")
112
-	}
113
-}
... ...
@@ -1,678 +1,13 @@
1 1
 package opts
2 2
 
3 3
 import (
4
-	"bytes"
5
-	"encoding/json"
6 4
 	"fmt"
7
-	"io/ioutil"
8
-	"path"
9 5
 	"strconv"
10 6
 	"strings"
11
-	"time"
12 7
 
13 8
 	"github.com/docker/docker/api/types/container"
14
-	networktypes "github.com/docker/docker/api/types/network"
15
-	"github.com/docker/docker/api/types/strslice"
16
-	"github.com/docker/docker/opts"
17
-	"github.com/docker/docker/pkg/signal"
18
-	"github.com/docker/go-connections/nat"
19
-	units "github.com/docker/go-units"
20
-	"github.com/spf13/pflag"
21 9
 )
22 10
 
23
-// ContainerOptions is a data object with all the options for creating a container
24
-type ContainerOptions struct {
25
-	attach             opts.ListOpts
26
-	volumes            opts.ListOpts
27
-	tmpfs              opts.ListOpts
28
-	blkioWeightDevice  WeightdeviceOpt
29
-	deviceReadBps      ThrottledeviceOpt
30
-	deviceWriteBps     ThrottledeviceOpt
31
-	links              opts.ListOpts
32
-	aliases            opts.ListOpts
33
-	linkLocalIPs       opts.ListOpts
34
-	deviceReadIOps     ThrottledeviceOpt
35
-	deviceWriteIOps    ThrottledeviceOpt
36
-	env                opts.ListOpts
37
-	labels             opts.ListOpts
38
-	devices            opts.ListOpts
39
-	ulimits            *UlimitOpt
40
-	sysctls            *opts.MapOpts
41
-	publish            opts.ListOpts
42
-	expose             opts.ListOpts
43
-	dns                opts.ListOpts
44
-	dnsSearch          opts.ListOpts
45
-	dnsOptions         opts.ListOpts
46
-	extraHosts         opts.ListOpts
47
-	volumesFrom        opts.ListOpts
48
-	envFile            opts.ListOpts
49
-	capAdd             opts.ListOpts
50
-	capDrop            opts.ListOpts
51
-	groupAdd           opts.ListOpts
52
-	securityOpt        opts.ListOpts
53
-	storageOpt         opts.ListOpts
54
-	labelsFile         opts.ListOpts
55
-	loggingOpts        opts.ListOpts
56
-	privileged         bool
57
-	pidMode            string
58
-	utsMode            string
59
-	usernsMode         string
60
-	publishAll         bool
61
-	stdin              bool
62
-	tty                bool
63
-	oomKillDisable     bool
64
-	oomScoreAdj        int
65
-	containerIDFile    string
66
-	entrypoint         string
67
-	hostname           string
68
-	memoryString       string
69
-	memoryReservation  string
70
-	memorySwap         string
71
-	kernelMemory       string
72
-	user               string
73
-	workingDir         string
74
-	cpuCount           int64
75
-	cpuShares          int64
76
-	cpuPercent         int64
77
-	cpuPeriod          int64
78
-	cpuRealtimePeriod  int64
79
-	cpuRealtimeRuntime int64
80
-	cpuQuota           int64
81
-	cpus               opts.NanoCPUs
82
-	cpusetCpus         string
83
-	cpusetMems         string
84
-	blkioWeight        uint16
85
-	ioMaxBandwidth     string
86
-	ioMaxIOps          uint64
87
-	swappiness         int64
88
-	netMode            string
89
-	macAddress         string
90
-	ipv4Address        string
91
-	ipv6Address        string
92
-	ipcMode            string
93
-	pidsLimit          int64
94
-	restartPolicy      string
95
-	readonlyRootfs     bool
96
-	loggingDriver      string
97
-	cgroupParent       string
98
-	volumeDriver       string
99
-	stopSignal         string
100
-	stopTimeout        int
101
-	isolation          string
102
-	shmSize            string
103
-	noHealthcheck      bool
104
-	healthCmd          string
105
-	healthInterval     time.Duration
106
-	healthTimeout      time.Duration
107
-	healthRetries      int
108
-	runtime            string
109
-	autoRemove         bool
110
-	init               bool
111
-	initPath           string
112
-	credentialSpec     string
113
-
114
-	Image string
115
-	Args  []string
116
-}
117
-
118
-// AddFlags adds all command line flags that will be used by Parse to the FlagSet
119
-func AddFlags(flags *pflag.FlagSet) *ContainerOptions {
120
-	copts := &ContainerOptions{
121
-		aliases:           opts.NewListOpts(nil),
122
-		attach:            opts.NewListOpts(ValidateAttach),
123
-		blkioWeightDevice: NewWeightdeviceOpt(ValidateWeightDevice),
124
-		capAdd:            opts.NewListOpts(nil),
125
-		capDrop:           opts.NewListOpts(nil),
126
-		dns:               opts.NewListOpts(opts.ValidateIPAddress),
127
-		dnsOptions:        opts.NewListOpts(nil),
128
-		dnsSearch:         opts.NewListOpts(opts.ValidateDNSSearch),
129
-		deviceReadBps:     NewThrottledeviceOpt(ValidateThrottleBpsDevice),
130
-		deviceReadIOps:    NewThrottledeviceOpt(ValidateThrottleIOpsDevice),
131
-		deviceWriteBps:    NewThrottledeviceOpt(ValidateThrottleBpsDevice),
132
-		deviceWriteIOps:   NewThrottledeviceOpt(ValidateThrottleIOpsDevice),
133
-		devices:           opts.NewListOpts(ValidateDevice),
134
-		env:               opts.NewListOpts(ValidateEnv),
135
-		envFile:           opts.NewListOpts(nil),
136
-		expose:            opts.NewListOpts(nil),
137
-		extraHosts:        opts.NewListOpts(ValidateExtraHost),
138
-		groupAdd:          opts.NewListOpts(nil),
139
-		labels:            opts.NewListOpts(ValidateEnv),
140
-		labelsFile:        opts.NewListOpts(nil),
141
-		linkLocalIPs:      opts.NewListOpts(nil),
142
-		links:             opts.NewListOpts(ValidateLink),
143
-		loggingOpts:       opts.NewListOpts(nil),
144
-		publish:           opts.NewListOpts(nil),
145
-		securityOpt:       opts.NewListOpts(nil),
146
-		storageOpt:        opts.NewListOpts(nil),
147
-		sysctls:           opts.NewMapOpts(nil, opts.ValidateSysctl),
148
-		tmpfs:             opts.NewListOpts(nil),
149
-		ulimits:           NewUlimitOpt(nil),
150
-		volumes:           opts.NewListOpts(nil),
151
-		volumesFrom:       opts.NewListOpts(nil),
152
-	}
153
-
154
-	// General purpose flags
155
-	flags.VarP(&copts.attach, "attach", "a", "Attach to STDIN, STDOUT or STDERR")
156
-	flags.Var(&copts.devices, "device", "Add a host device to the container")
157
-	flags.VarP(&copts.env, "env", "e", "Set environment variables")
158
-	flags.Var(&copts.envFile, "env-file", "Read in a file of environment variables")
159
-	flags.StringVar(&copts.entrypoint, "entrypoint", "", "Overwrite the default ENTRYPOINT of the image")
160
-	flags.Var(&copts.groupAdd, "group-add", "Add additional groups to join")
161
-	flags.StringVarP(&copts.hostname, "hostname", "h", "", "Container host name")
162
-	flags.BoolVarP(&copts.stdin, "interactive", "i", false, "Keep STDIN open even if not attached")
163
-	flags.VarP(&copts.labels, "label", "l", "Set meta data on a container")
164
-	flags.Var(&copts.labelsFile, "label-file", "Read in a line delimited file of labels")
165
-	flags.BoolVar(&copts.readonlyRootfs, "read-only", false, "Mount the container's root filesystem as read only")
166
-	flags.StringVar(&copts.restartPolicy, "restart", "no", "Restart policy to apply when a container exits")
167
-	flags.StringVar(&copts.stopSignal, "stop-signal", signal.DefaultStopSignal, fmt.Sprintf("Signal to stop a container, %v by default", signal.DefaultStopSignal))
168
-	flags.IntVar(&copts.stopTimeout, "stop-timeout", 0, "Timeout (in seconds) to stop a container")
169
-	flags.SetAnnotation("stop-timeout", "version", []string{"1.25"})
170
-	flags.Var(copts.sysctls, "sysctl", "Sysctl options")
171
-	flags.BoolVarP(&copts.tty, "tty", "t", false, "Allocate a pseudo-TTY")
172
-	flags.Var(copts.ulimits, "ulimit", "Ulimit options")
173
-	flags.StringVarP(&copts.user, "user", "u", "", "Username or UID (format: <name|uid>[:<group|gid>])")
174
-	flags.StringVarP(&copts.workingDir, "workdir", "w", "", "Working directory inside the container")
175
-	flags.BoolVar(&copts.autoRemove, "rm", false, "Automatically remove the container when it exits")
176
-
177
-	// Security
178
-	flags.Var(&copts.capAdd, "cap-add", "Add Linux capabilities")
179
-	flags.Var(&copts.capDrop, "cap-drop", "Drop Linux capabilities")
180
-	flags.BoolVar(&copts.privileged, "privileged", false, "Give extended privileges to this container")
181
-	flags.Var(&copts.securityOpt, "security-opt", "Security Options")
182
-	flags.StringVar(&copts.usernsMode, "userns", "", "User namespace to use")
183
-	flags.StringVar(&copts.credentialSpec, "credentialspec", "", "Credential spec for managed service account (Windows only)")
184
-
185
-	// Network and port publishing flag
186
-	flags.Var(&copts.extraHosts, "add-host", "Add a custom host-to-IP mapping (host:ip)")
187
-	flags.Var(&copts.dns, "dns", "Set custom DNS servers")
188
-	// We allow for both "--dns-opt" and "--dns-option", although the latter is the recommended way.
189
-	// This is to be consistent with service create/update
190
-	flags.Var(&copts.dnsOptions, "dns-opt", "Set DNS options")
191
-	flags.Var(&copts.dnsOptions, "dns-option", "Set DNS options")
192
-	flags.MarkHidden("dns-opt")
193
-	flags.Var(&copts.dnsSearch, "dns-search", "Set custom DNS search domains")
194
-	flags.Var(&copts.expose, "expose", "Expose a port or a range of ports")
195
-	flags.StringVar(&copts.ipv4Address, "ip", "", "Container IPv4 address (e.g. 172.30.100.104)")
196
-	flags.StringVar(&copts.ipv6Address, "ip6", "", "Container IPv6 address (e.g. 2001:db8::33)")
197
-	flags.Var(&copts.links, "link", "Add link to another container")
198
-	flags.Var(&copts.linkLocalIPs, "link-local-ip", "Container IPv4/IPv6 link-local addresses")
199
-	flags.StringVar(&copts.macAddress, "mac-address", "", "Container MAC address (e.g. 92:d0:c6:0a:29:33)")
200
-	flags.VarP(&copts.publish, "publish", "p", "Publish a container's port(s) to the host")
201
-	flags.BoolVarP(&copts.publishAll, "publish-all", "P", false, "Publish all exposed ports to random ports")
202
-	// We allow for both "--net" and "--network", although the latter is the recommended way.
203
-	flags.StringVar(&copts.netMode, "net", "default", "Connect a container to a network")
204
-	flags.StringVar(&copts.netMode, "network", "default", "Connect a container to a network")
205
-	flags.MarkHidden("net")
206
-	// We allow for both "--net-alias" and "--network-alias", although the latter is the recommended way.
207
-	flags.Var(&copts.aliases, "net-alias", "Add network-scoped alias for the container")
208
-	flags.Var(&copts.aliases, "network-alias", "Add network-scoped alias for the container")
209
-	flags.MarkHidden("net-alias")
210
-
211
-	// Logging and storage
212
-	flags.StringVar(&copts.loggingDriver, "log-driver", "", "Logging driver for the container")
213
-	flags.StringVar(&copts.volumeDriver, "volume-driver", "", "Optional volume driver for the container")
214
-	flags.Var(&copts.loggingOpts, "log-opt", "Log driver options")
215
-	flags.Var(&copts.storageOpt, "storage-opt", "Storage driver options for the container")
216
-	flags.Var(&copts.tmpfs, "tmpfs", "Mount a tmpfs directory")
217
-	flags.Var(&copts.volumesFrom, "volumes-from", "Mount volumes from the specified container(s)")
218
-	flags.VarP(&copts.volumes, "volume", "v", "Bind mount a volume")
219
-
220
-	// Health-checking
221
-	flags.StringVar(&copts.healthCmd, "health-cmd", "", "Command to run to check health")
222
-	flags.DurationVar(&copts.healthInterval, "health-interval", 0, "Time between running the check (ns|us|ms|s|m|h) (default 0s)")
223
-	flags.IntVar(&copts.healthRetries, "health-retries", 0, "Consecutive failures needed to report unhealthy")
224
-	flags.DurationVar(&copts.healthTimeout, "health-timeout", 0, "Maximum time to allow one check to run (ns|us|ms|s|m|h) (default 0s)")
225
-	flags.BoolVar(&copts.noHealthcheck, "no-healthcheck", false, "Disable any container-specified HEALTHCHECK")
226
-
227
-	// Resource management
228
-	flags.Uint16Var(&copts.blkioWeight, "blkio-weight", 0, "Block IO (relative weight), between 10 and 1000, or 0 to disable (default 0)")
229
-	flags.Var(&copts.blkioWeightDevice, "blkio-weight-device", "Block IO weight (relative device weight)")
230
-	flags.StringVar(&copts.containerIDFile, "cidfile", "", "Write the container ID to the file")
231
-	flags.StringVar(&copts.cpusetCpus, "cpuset-cpus", "", "CPUs in which to allow execution (0-3, 0,1)")
232
-	flags.StringVar(&copts.cpusetMems, "cpuset-mems", "", "MEMs in which to allow execution (0-3, 0,1)")
233
-	flags.Int64Var(&copts.cpuCount, "cpu-count", 0, "CPU count (Windows only)")
234
-	flags.Int64Var(&copts.cpuPercent, "cpu-percent", 0, "CPU percent (Windows only)")
235
-	flags.Int64Var(&copts.cpuPeriod, "cpu-period", 0, "Limit CPU CFS (Completely Fair Scheduler) period")
236
-	flags.Int64Var(&copts.cpuQuota, "cpu-quota", 0, "Limit CPU CFS (Completely Fair Scheduler) quota")
237
-	flags.Int64Var(&copts.cpuRealtimePeriod, "cpu-rt-period", 0, "Limit CPU real-time period in microseconds")
238
-	flags.Int64Var(&copts.cpuRealtimeRuntime, "cpu-rt-runtime", 0, "Limit CPU real-time runtime in microseconds")
239
-	flags.Int64VarP(&copts.cpuShares, "cpu-shares", "c", 0, "CPU shares (relative weight)")
240
-	flags.Var(&copts.cpus, "cpus", "Number of CPUs")
241
-	flags.Var(&copts.deviceReadBps, "device-read-bps", "Limit read rate (bytes per second) from a device")
242
-	flags.Var(&copts.deviceReadIOps, "device-read-iops", "Limit read rate (IO per second) from a device")
243
-	flags.Var(&copts.deviceWriteBps, "device-write-bps", "Limit write rate (bytes per second) to a device")
244
-	flags.Var(&copts.deviceWriteIOps, "device-write-iops", "Limit write rate (IO per second) to a device")
245
-	flags.StringVar(&copts.ioMaxBandwidth, "io-maxbandwidth", "", "Maximum IO bandwidth limit for the system drive (Windows only)")
246
-	flags.Uint64Var(&copts.ioMaxIOps, "io-maxiops", 0, "Maximum IOps limit for the system drive (Windows only)")
247
-	flags.StringVar(&copts.kernelMemory, "kernel-memory", "", "Kernel memory limit")
248
-	flags.StringVarP(&copts.memoryString, "memory", "m", "", "Memory limit")
249
-	flags.StringVar(&copts.memoryReservation, "memory-reservation", "", "Memory soft limit")
250
-	flags.StringVar(&copts.memorySwap, "memory-swap", "", "Swap limit equal to memory plus swap: '-1' to enable unlimited swap")
251
-	flags.Int64Var(&copts.swappiness, "memory-swappiness", -1, "Tune container memory swappiness (0 to 100)")
252
-	flags.BoolVar(&copts.oomKillDisable, "oom-kill-disable", false, "Disable OOM Killer")
253
-	flags.IntVar(&copts.oomScoreAdj, "oom-score-adj", 0, "Tune host's OOM preferences (-1000 to 1000)")
254
-	flags.Int64Var(&copts.pidsLimit, "pids-limit", 0, "Tune container pids limit (set -1 for unlimited)")
255
-
256
-	// Low-level execution (cgroups, namespaces, ...)
257
-	flags.StringVar(&copts.cgroupParent, "cgroup-parent", "", "Optional parent cgroup for the container")
258
-	flags.StringVar(&copts.ipcMode, "ipc", "", "IPC namespace to use")
259
-	flags.StringVar(&copts.isolation, "isolation", "", "Container isolation technology")
260
-	flags.StringVar(&copts.pidMode, "pid", "", "PID namespace to use")
261
-	flags.StringVar(&copts.shmSize, "shm-size", "", "Size of /dev/shm, default value is 64MB")
262
-	flags.StringVar(&copts.utsMode, "uts", "", "UTS namespace to use")
263
-	flags.StringVar(&copts.runtime, "runtime", "", "Runtime to use for this container")
264
-
265
-	flags.BoolVar(&copts.init, "init", false, "Run an init inside the container that forwards signals and reaps processes")
266
-	flags.StringVar(&copts.initPath, "init-path", "", "Path to the docker-init binary")
267
-	return copts
268
-}
269
-
270
-// Parse parses the args for the specified command and generates a Config,
271
-// a HostConfig and returns them with the specified command.
272
-// If the specified args are not valid, it will return an error.
273
-func Parse(flags *pflag.FlagSet, copts *ContainerOptions) (*container.Config, *container.HostConfig, *networktypes.NetworkingConfig, error) {
274
-	var (
275
-		attachStdin  = copts.attach.Get("stdin")
276
-		attachStdout = copts.attach.Get("stdout")
277
-		attachStderr = copts.attach.Get("stderr")
278
-	)
279
-
280
-	// Validate the input mac address
281
-	if copts.macAddress != "" {
282
-		if _, err := ValidateMACAddress(copts.macAddress); err != nil {
283
-			return nil, nil, nil, fmt.Errorf("%s is not a valid mac address", copts.macAddress)
284
-		}
285
-	}
286
-	if copts.stdin {
287
-		attachStdin = true
288
-	}
289
-	// If -a is not set, attach to stdout and stderr
290
-	if copts.attach.Len() == 0 {
291
-		attachStdout = true
292
-		attachStderr = true
293
-	}
294
-
295
-	var err error
296
-
297
-	var memory int64
298
-	if copts.memoryString != "" {
299
-		memory, err = units.RAMInBytes(copts.memoryString)
300
-		if err != nil {
301
-			return nil, nil, nil, err
302
-		}
303
-	}
304
-
305
-	var memoryReservation int64
306
-	if copts.memoryReservation != "" {
307
-		memoryReservation, err = units.RAMInBytes(copts.memoryReservation)
308
-		if err != nil {
309
-			return nil, nil, nil, err
310
-		}
311
-	}
312
-
313
-	var memorySwap int64
314
-	if copts.memorySwap != "" {
315
-		if copts.memorySwap == "-1" {
316
-			memorySwap = -1
317
-		} else {
318
-			memorySwap, err = units.RAMInBytes(copts.memorySwap)
319
-			if err != nil {
320
-				return nil, nil, nil, err
321
-			}
322
-		}
323
-	}
324
-
325
-	var kernelMemory int64
326
-	if copts.kernelMemory != "" {
327
-		kernelMemory, err = units.RAMInBytes(copts.kernelMemory)
328
-		if err != nil {
329
-			return nil, nil, nil, err
330
-		}
331
-	}
332
-
333
-	swappiness := copts.swappiness
334
-	if swappiness != -1 && (swappiness < 0 || swappiness > 100) {
335
-		return nil, nil, nil, fmt.Errorf("invalid value: %d. Valid memory swappiness range is 0-100", swappiness)
336
-	}
337
-
338
-	var shmSize int64
339
-	if copts.shmSize != "" {
340
-		shmSize, err = units.RAMInBytes(copts.shmSize)
341
-		if err != nil {
342
-			return nil, nil, nil, err
343
-		}
344
-	}
345
-
346
-	// TODO FIXME units.RAMInBytes should have a uint64 version
347
-	var maxIOBandwidth int64
348
-	if copts.ioMaxBandwidth != "" {
349
-		maxIOBandwidth, err = units.RAMInBytes(copts.ioMaxBandwidth)
350
-		if err != nil {
351
-			return nil, nil, nil, err
352
-		}
353
-		if maxIOBandwidth < 0 {
354
-			return nil, nil, nil, fmt.Errorf("invalid value: %s. Maximum IO Bandwidth must be positive", copts.ioMaxBandwidth)
355
-		}
356
-	}
357
-
358
-	var binds []string
359
-	volumes := copts.volumes.GetMap()
360
-	// add any bind targets to the list of container volumes
361
-	for bind := range copts.volumes.GetMap() {
362
-		if arr := volumeSplitN(bind, 2); len(arr) > 1 {
363
-			// after creating the bind mount we want to delete it from the copts.volumes values because
364
-			// we do not want bind mounts being committed to image configs
365
-			binds = append(binds, bind)
366
-			// We should delete from the map (`volumes`) here, as deleting from copts.volumes will not work if
367
-			// there are duplicates entries.
368
-			delete(volumes, bind)
369
-		}
370
-	}
371
-
372
-	// Can't evaluate options passed into --tmpfs until we actually mount
373
-	tmpfs := make(map[string]string)
374
-	for _, t := range copts.tmpfs.GetAll() {
375
-		if arr := strings.SplitN(t, ":", 2); len(arr) > 1 {
376
-			tmpfs[arr[0]] = arr[1]
377
-		} else {
378
-			tmpfs[arr[0]] = ""
379
-		}
380
-	}
381
-
382
-	var (
383
-		runCmd     strslice.StrSlice
384
-		entrypoint strslice.StrSlice
385
-	)
386
-
387
-	if len(copts.Args) > 0 {
388
-		runCmd = strslice.StrSlice(copts.Args)
389
-	}
390
-
391
-	if copts.entrypoint != "" {
392
-		entrypoint = strslice.StrSlice{copts.entrypoint}
393
-	} else if flags.Changed("entrypoint") {
394
-		// if `--entrypoint=` is parsed then Entrypoint is reset
395
-		entrypoint = []string{""}
396
-	}
397
-
398
-	ports, portBindings, err := nat.ParsePortSpecs(copts.publish.GetAll())
399
-	if err != nil {
400
-		return nil, nil, nil, err
401
-	}
402
-
403
-	// Merge in exposed ports to the map of published ports
404
-	for _, e := range copts.expose.GetAll() {
405
-		if strings.Contains(e, ":") {
406
-			return nil, nil, nil, fmt.Errorf("invalid port format for --expose: %s", e)
407
-		}
408
-		//support two formats for expose, original format <portnum>/[<proto>] or <startport-endport>/[<proto>]
409
-		proto, port := nat.SplitProtoPort(e)
410
-		//parse the start and end port and create a sequence of ports to expose
411
-		//if expose a port, the start and end port are the same
412
-		start, end, err := nat.ParsePortRange(port)
413
-		if err != nil {
414
-			return nil, nil, nil, fmt.Errorf("invalid range format for --expose: %s, error: %s", e, err)
415
-		}
416
-		for i := start; i <= end; i++ {
417
-			p, err := nat.NewPort(proto, strconv.FormatUint(i, 10))
418
-			if err != nil {
419
-				return nil, nil, nil, err
420
-			}
421
-			if _, exists := ports[p]; !exists {
422
-				ports[p] = struct{}{}
423
-			}
424
-		}
425
-	}
426
-
427
-	// parse device mappings
428
-	deviceMappings := []container.DeviceMapping{}
429
-	for _, device := range copts.devices.GetAll() {
430
-		deviceMapping, err := ParseDevice(device)
431
-		if err != nil {
432
-			return nil, nil, nil, err
433
-		}
434
-		deviceMappings = append(deviceMappings, deviceMapping)
435
-	}
436
-
437
-	// collect all the environment variables for the container
438
-	envVariables, err := ReadKVStrings(copts.envFile.GetAll(), copts.env.GetAll())
439
-	if err != nil {
440
-		return nil, nil, nil, err
441
-	}
442
-
443
-	// collect all the labels for the container
444
-	labels, err := ReadKVStrings(copts.labelsFile.GetAll(), copts.labels.GetAll())
445
-	if err != nil {
446
-		return nil, nil, nil, err
447
-	}
448
-
449
-	ipcMode := container.IpcMode(copts.ipcMode)
450
-	if !ipcMode.Valid() {
451
-		return nil, nil, nil, fmt.Errorf("--ipc: invalid IPC mode")
452
-	}
453
-
454
-	pidMode := container.PidMode(copts.pidMode)
455
-	if !pidMode.Valid() {
456
-		return nil, nil, nil, fmt.Errorf("--pid: invalid PID mode")
457
-	}
458
-
459
-	utsMode := container.UTSMode(copts.utsMode)
460
-	if !utsMode.Valid() {
461
-		return nil, nil, nil, fmt.Errorf("--uts: invalid UTS mode")
462
-	}
463
-
464
-	usernsMode := container.UsernsMode(copts.usernsMode)
465
-	if !usernsMode.Valid() {
466
-		return nil, nil, nil, fmt.Errorf("--userns: invalid USER mode")
467
-	}
468
-
469
-	restartPolicy, err := ParseRestartPolicy(copts.restartPolicy)
470
-	if err != nil {
471
-		return nil, nil, nil, err
472
-	}
473
-
474
-	loggingOpts, err := parseLoggingOpts(copts.loggingDriver, copts.loggingOpts.GetAll())
475
-	if err != nil {
476
-		return nil, nil, nil, err
477
-	}
478
-
479
-	securityOpts, err := parseSecurityOpts(copts.securityOpt.GetAll())
480
-	if err != nil {
481
-		return nil, nil, nil, err
482
-	}
483
-
484
-	storageOpts, err := parseStorageOpts(copts.storageOpt.GetAll())
485
-	if err != nil {
486
-		return nil, nil, nil, err
487
-	}
488
-
489
-	// Healthcheck
490
-	var healthConfig *container.HealthConfig
491
-	haveHealthSettings := copts.healthCmd != "" ||
492
-		copts.healthInterval != 0 ||
493
-		copts.healthTimeout != 0 ||
494
-		copts.healthRetries != 0
495
-	if copts.noHealthcheck {
496
-		if haveHealthSettings {
497
-			return nil, nil, nil, fmt.Errorf("--no-healthcheck conflicts with --health-* options")
498
-		}
499
-		test := strslice.StrSlice{"NONE"}
500
-		healthConfig = &container.HealthConfig{Test: test}
501
-	} else if haveHealthSettings {
502
-		var probe strslice.StrSlice
503
-		if copts.healthCmd != "" {
504
-			args := []string{"CMD-SHELL", copts.healthCmd}
505
-			probe = strslice.StrSlice(args)
506
-		}
507
-		if copts.healthInterval < 0 {
508
-			return nil, nil, nil, fmt.Errorf("--health-interval cannot be negative")
509
-		}
510
-		if copts.healthTimeout < 0 {
511
-			return nil, nil, nil, fmt.Errorf("--health-timeout cannot be negative")
512
-		}
513
-
514
-		healthConfig = &container.HealthConfig{
515
-			Test:     probe,
516
-			Interval: copts.healthInterval,
517
-			Timeout:  copts.healthTimeout,
518
-			Retries:  copts.healthRetries,
519
-		}
520
-	}
521
-
522
-	resources := container.Resources{
523
-		CgroupParent:         copts.cgroupParent,
524
-		Memory:               memory,
525
-		MemoryReservation:    memoryReservation,
526
-		MemorySwap:           memorySwap,
527
-		MemorySwappiness:     &copts.swappiness,
528
-		KernelMemory:         kernelMemory,
529
-		OomKillDisable:       &copts.oomKillDisable,
530
-		NanoCPUs:             copts.cpus.Value(),
531
-		CPUCount:             copts.cpuCount,
532
-		CPUPercent:           copts.cpuPercent,
533
-		CPUShares:            copts.cpuShares,
534
-		CPUPeriod:            copts.cpuPeriod,
535
-		CpusetCpus:           copts.cpusetCpus,
536
-		CpusetMems:           copts.cpusetMems,
537
-		CPUQuota:             copts.cpuQuota,
538
-		CPURealtimePeriod:    copts.cpuRealtimePeriod,
539
-		CPURealtimeRuntime:   copts.cpuRealtimeRuntime,
540
-		PidsLimit:            copts.pidsLimit,
541
-		BlkioWeight:          copts.blkioWeight,
542
-		BlkioWeightDevice:    copts.blkioWeightDevice.GetList(),
543
-		BlkioDeviceReadBps:   copts.deviceReadBps.GetList(),
544
-		BlkioDeviceWriteBps:  copts.deviceWriteBps.GetList(),
545
-		BlkioDeviceReadIOps:  copts.deviceReadIOps.GetList(),
546
-		BlkioDeviceWriteIOps: copts.deviceWriteIOps.GetList(),
547
-		IOMaximumIOps:        copts.ioMaxIOps,
548
-		IOMaximumBandwidth:   uint64(maxIOBandwidth),
549
-		Ulimits:              copts.ulimits.GetList(),
550
-		Devices:              deviceMappings,
551
-	}
552
-
553
-	config := &container.Config{
554
-		Hostname:     copts.hostname,
555
-		ExposedPorts: ports,
556
-		User:         copts.user,
557
-		Tty:          copts.tty,
558
-		// TODO: deprecated, it comes from -n, --networking
559
-		// it's still needed internally to set the network to disabled
560
-		// if e.g. bridge is none in daemon opts, and in inspect
561
-		NetworkDisabled: false,
562
-		OpenStdin:       copts.stdin,
563
-		AttachStdin:     attachStdin,
564
-		AttachStdout:    attachStdout,
565
-		AttachStderr:    attachStderr,
566
-		Env:             envVariables,
567
-		Cmd:             runCmd,
568
-		Image:           copts.Image,
569
-		Volumes:         volumes,
570
-		MacAddress:      copts.macAddress,
571
-		Entrypoint:      entrypoint,
572
-		WorkingDir:      copts.workingDir,
573
-		Labels:          ConvertKVStringsToMap(labels),
574
-		Healthcheck:     healthConfig,
575
-	}
576
-	if flags.Changed("stop-signal") {
577
-		config.StopSignal = copts.stopSignal
578
-	}
579
-	if flags.Changed("stop-timeout") {
580
-		config.StopTimeout = &copts.stopTimeout
581
-	}
582
-
583
-	hostConfig := &container.HostConfig{
584
-		Binds:           binds,
585
-		ContainerIDFile: copts.containerIDFile,
586
-		OomScoreAdj:     copts.oomScoreAdj,
587
-		AutoRemove:      copts.autoRemove,
588
-		Privileged:      copts.privileged,
589
-		PortBindings:    portBindings,
590
-		Links:           copts.links.GetAll(),
591
-		PublishAllPorts: copts.publishAll,
592
-		// Make sure the dns fields are never nil.
593
-		// New containers don't ever have those fields nil,
594
-		// but pre created containers can still have those nil values.
595
-		// See https://github.com/docker/docker/pull/17779
596
-		// for a more detailed explanation on why we don't want that.
597
-		DNS:            copts.dns.GetAllOrEmpty(),
598
-		DNSSearch:      copts.dnsSearch.GetAllOrEmpty(),
599
-		DNSOptions:     copts.dnsOptions.GetAllOrEmpty(),
600
-		ExtraHosts:     copts.extraHosts.GetAll(),
601
-		VolumesFrom:    copts.volumesFrom.GetAll(),
602
-		NetworkMode:    container.NetworkMode(copts.netMode),
603
-		IpcMode:        ipcMode,
604
-		PidMode:        pidMode,
605
-		UTSMode:        utsMode,
606
-		UsernsMode:     usernsMode,
607
-		CapAdd:         strslice.StrSlice(copts.capAdd.GetAll()),
608
-		CapDrop:        strslice.StrSlice(copts.capDrop.GetAll()),
609
-		GroupAdd:       copts.groupAdd.GetAll(),
610
-		RestartPolicy:  restartPolicy,
611
-		SecurityOpt:    securityOpts,
612
-		StorageOpt:     storageOpts,
613
-		ReadonlyRootfs: copts.readonlyRootfs,
614
-		LogConfig:      container.LogConfig{Type: copts.loggingDriver, Config: loggingOpts},
615
-		VolumeDriver:   copts.volumeDriver,
616
-		Isolation:      container.Isolation(copts.isolation),
617
-		ShmSize:        shmSize,
618
-		Resources:      resources,
619
-		Tmpfs:          tmpfs,
620
-		Sysctls:        copts.sysctls.GetAll(),
621
-		Runtime:        copts.runtime,
622
-	}
623
-
624
-	// only set this value if the user provided the flag, else it should default to nil
625
-	if flags.Changed("init") {
626
-		hostConfig.Init = &copts.init
627
-	}
628
-
629
-	// When allocating stdin in attached mode, close stdin at client disconnect
630
-	if config.OpenStdin && config.AttachStdin {
631
-		config.StdinOnce = true
632
-	}
633
-
634
-	networkingConfig := &networktypes.NetworkingConfig{
635
-		EndpointsConfig: make(map[string]*networktypes.EndpointSettings),
636
-	}
637
-
638
-	if copts.ipv4Address != "" || copts.ipv6Address != "" || copts.linkLocalIPs.Len() > 0 {
639
-		epConfig := &networktypes.EndpointSettings{}
640
-		networkingConfig.EndpointsConfig[string(hostConfig.NetworkMode)] = epConfig
641
-
642
-		epConfig.IPAMConfig = &networktypes.EndpointIPAMConfig{
643
-			IPv4Address: copts.ipv4Address,
644
-			IPv6Address: copts.ipv6Address,
645
-		}
646
-
647
-		if copts.linkLocalIPs.Len() > 0 {
648
-			epConfig.IPAMConfig.LinkLocalIPs = make([]string, copts.linkLocalIPs.Len())
649
-			copy(epConfig.IPAMConfig.LinkLocalIPs, copts.linkLocalIPs.GetAll())
650
-		}
651
-	}
652
-
653
-	if hostConfig.NetworkMode.IsUserDefined() && len(hostConfig.Links) > 0 {
654
-		epConfig := networkingConfig.EndpointsConfig[string(hostConfig.NetworkMode)]
655
-		if epConfig == nil {
656
-			epConfig = &networktypes.EndpointSettings{}
657
-		}
658
-		epConfig.Links = make([]string, len(hostConfig.Links))
659
-		copy(epConfig.Links, hostConfig.Links)
660
-		networkingConfig.EndpointsConfig[string(hostConfig.NetworkMode)] = epConfig
661
-	}
662
-
663
-	if copts.aliases.Len() > 0 {
664
-		epConfig := networkingConfig.EndpointsConfig[string(hostConfig.NetworkMode)]
665
-		if epConfig == nil {
666
-			epConfig = &networktypes.EndpointSettings{}
667
-		}
668
-		epConfig.Aliases = make([]string, copts.aliases.Len())
669
-		copy(epConfig.Aliases, copts.aliases.GetAll())
670
-		networkingConfig.EndpointsConfig[string(hostConfig.NetworkMode)] = epConfig
671
-	}
672
-
673
-	return config, hostConfig, networkingConfig, nil
674
-}
675
-
676 11
 // ReadKVStrings reads a file of line terminated key=value pairs, and overrides any keys
677 12
 // present in the file with additional pairs specified in the override parameter
678 13
 func ReadKVStrings(files []string, override []string) ([]string, error) {
... ...
@@ -724,55 +59,6 @@ func ConvertKVStringsToMapWithNil(values []string) map[string]*string {
724 724
 	return result
725 725
 }
726 726
 
727
-func parseLoggingOpts(loggingDriver string, loggingOpts []string) (map[string]string, error) {
728
-	loggingOptsMap := ConvertKVStringsToMap(loggingOpts)
729
-	if loggingDriver == "none" && len(loggingOpts) > 0 {
730
-		return map[string]string{}, fmt.Errorf("invalid logging opts for driver %s", loggingDriver)
731
-	}
732
-	return loggingOptsMap, nil
733
-}
734
-
735
-// takes a local seccomp daemon, reads the file contents for sending to the daemon
736
-func parseSecurityOpts(securityOpts []string) ([]string, error) {
737
-	for key, opt := range securityOpts {
738
-		con := strings.SplitN(opt, "=", 2)
739
-		if len(con) == 1 && con[0] != "no-new-privileges" {
740
-			if strings.Contains(opt, ":") {
741
-				con = strings.SplitN(opt, ":", 2)
742
-			} else {
743
-				return securityOpts, fmt.Errorf("Invalid --security-opt: %q", opt)
744
-			}
745
-		}
746
-		if con[0] == "seccomp" && con[1] != "unconfined" {
747
-			f, err := ioutil.ReadFile(con[1])
748
-			if err != nil {
749
-				return securityOpts, fmt.Errorf("opening seccomp profile (%s) failed: %v", con[1], err)
750
-			}
751
-			b := bytes.NewBuffer(nil)
752
-			if err := json.Compact(b, f); err != nil {
753
-				return securityOpts, fmt.Errorf("compacting json for seccomp profile (%s) failed: %v", con[1], err)
754
-			}
755
-			securityOpts[key] = fmt.Sprintf("seccomp=%s", b.Bytes())
756
-		}
757
-	}
758
-
759
-	return securityOpts, nil
760
-}
761
-
762
-// parses storage options per container into a map
763
-func parseStorageOpts(storageOpts []string) (map[string]string, error) {
764
-	m := make(map[string]string)
765
-	for _, option := range storageOpts {
766
-		if strings.Contains(option, "=") {
767
-			opt := strings.SplitN(option, "=", 2)
768
-			m[opt[0]] = opt[1]
769
-		} else {
770
-			return nil, fmt.Errorf("invalid storage option")
771
-		}
772
-	}
773
-	return m, nil
774
-}
775
-
776 727
 // ParseRestartPolicy returns the parsed policy or an error indicating what is incorrect
777 728
 func ParseRestartPolicy(policy string) (container.RestartPolicy, error) {
778 729
 	p := container.RestartPolicy{}
... ...
@@ -799,195 +85,3 @@ func ParseRestartPolicy(policy string) (container.RestartPolicy, error) {
799 799
 
800 800
 	return p, nil
801 801
 }
802
-
803
-// ParseDevice parses a device mapping string to a container.DeviceMapping struct
804
-func ParseDevice(device string) (container.DeviceMapping, error) {
805
-	src := ""
806
-	dst := ""
807
-	permissions := "rwm"
808
-	arr := strings.Split(device, ":")
809
-	switch len(arr) {
810
-	case 3:
811
-		permissions = arr[2]
812
-		fallthrough
813
-	case 2:
814
-		if ValidDeviceMode(arr[1]) {
815
-			permissions = arr[1]
816
-		} else {
817
-			dst = arr[1]
818
-		}
819
-		fallthrough
820
-	case 1:
821
-		src = arr[0]
822
-	default:
823
-		return container.DeviceMapping{}, fmt.Errorf("invalid device specification: %s", device)
824
-	}
825
-
826
-	if dst == "" {
827
-		dst = src
828
-	}
829
-
830
-	deviceMapping := container.DeviceMapping{
831
-		PathOnHost:        src,
832
-		PathInContainer:   dst,
833
-		CgroupPermissions: permissions,
834
-	}
835
-	return deviceMapping, nil
836
-}
837
-
838
-// ParseLink parses and validates the specified string as a link format (name:alias)
839
-func ParseLink(val string) (string, string, error) {
840
-	if val == "" {
841
-		return "", "", fmt.Errorf("empty string specified for links")
842
-	}
843
-	arr := strings.Split(val, ":")
844
-	if len(arr) > 2 {
845
-		return "", "", fmt.Errorf("bad format for links: %s", val)
846
-	}
847
-	if len(arr) == 1 {
848
-		return val, val, nil
849
-	}
850
-	// This is kept because we can actually get a HostConfig with links
851
-	// from an already created container and the format is not `foo:bar`
852
-	// but `/foo:/c1/bar`
853
-	if strings.HasPrefix(arr[0], "/") {
854
-		_, alias := path.Split(arr[1])
855
-		return arr[0][1:], alias, nil
856
-	}
857
-	return arr[0], arr[1], nil
858
-}
859
-
860
-// ValidateLink validates that the specified string has a valid link format (containerName:alias).
861
-func ValidateLink(val string) (string, error) {
862
-	_, _, err := ParseLink(val)
863
-	return val, err
864
-}
865
-
866
-// ValidDeviceMode checks if the mode for device is valid or not.
867
-// Valid mode is a composition of r (read), w (write), and m (mknod).
868
-func ValidDeviceMode(mode string) bool {
869
-	var legalDeviceMode = map[rune]bool{
870
-		'r': true,
871
-		'w': true,
872
-		'm': true,
873
-	}
874
-	if mode == "" {
875
-		return false
876
-	}
877
-	for _, c := range mode {
878
-		if !legalDeviceMode[c] {
879
-			return false
880
-		}
881
-		legalDeviceMode[c] = false
882
-	}
883
-	return true
884
-}
885
-
886
-// ValidateDevice validates a path for devices
887
-// It will make sure 'val' is in the form:
888
-//    [host-dir:]container-path[:mode]
889
-// It also validates the device mode.
890
-func ValidateDevice(val string) (string, error) {
891
-	return validatePath(val, ValidDeviceMode)
892
-}
893
-
894
-func validatePath(val string, validator func(string) bool) (string, error) {
895
-	var containerPath string
896
-	var mode string
897
-
898
-	if strings.Count(val, ":") > 2 {
899
-		return val, fmt.Errorf("bad format for path: %s", val)
900
-	}
901
-
902
-	split := strings.SplitN(val, ":", 3)
903
-	if split[0] == "" {
904
-		return val, fmt.Errorf("bad format for path: %s", val)
905
-	}
906
-	switch len(split) {
907
-	case 1:
908
-		containerPath = split[0]
909
-		val = path.Clean(containerPath)
910
-	case 2:
911
-		if isValid := validator(split[1]); isValid {
912
-			containerPath = split[0]
913
-			mode = split[1]
914
-			val = fmt.Sprintf("%s:%s", path.Clean(containerPath), mode)
915
-		} else {
916
-			containerPath = split[1]
917
-			val = fmt.Sprintf("%s:%s", split[0], path.Clean(containerPath))
918
-		}
919
-	case 3:
920
-		containerPath = split[1]
921
-		mode = split[2]
922
-		if isValid := validator(split[2]); !isValid {
923
-			return val, fmt.Errorf("bad mode specified: %s", mode)
924
-		}
925
-		val = fmt.Sprintf("%s:%s:%s", split[0], containerPath, mode)
926
-	}
927
-
928
-	if !path.IsAbs(containerPath) {
929
-		return val, fmt.Errorf("%s is not an absolute path", containerPath)
930
-	}
931
-	return val, nil
932
-}
933
-
934
-// volumeSplitN splits raw into a maximum of n parts, separated by a separator colon.
935
-// A separator colon is the last `:` character in the regex `[:\\]?[a-zA-Z]:` (note `\\` is `\` escaped).
936
-// In Windows driver letter appears in two situations:
937
-// a. `^[a-zA-Z]:` (A colon followed  by `^[a-zA-Z]:` is OK as colon is the separator in volume option)
938
-// b. A string in the format like `\\?\C:\Windows\...` (UNC).
939
-// Therefore, a driver letter can only follow either a `:` or `\\`
940
-// This allows to correctly split strings such as `C:\foo:D:\:rw` or `/tmp/q:/foo`.
941
-func volumeSplitN(raw string, n int) []string {
942
-	var array []string
943
-	if len(raw) == 0 || raw[0] == ':' {
944
-		// invalid
945
-		return nil
946
-	}
947
-	// numberOfParts counts the number of parts separated by a separator colon
948
-	numberOfParts := 0
949
-	// left represents the left-most cursor in raw, updated at every `:` character considered as a separator.
950
-	left := 0
951
-	// right represents the right-most cursor in raw incremented with the loop. Note this
952
-	// starts at index 1 as index 0 is already handle above as a special case.
953
-	for right := 1; right < len(raw); right++ {
954
-		// stop parsing if reached maximum number of parts
955
-		if n >= 0 && numberOfParts >= n {
956
-			break
957
-		}
958
-		if raw[right] != ':' {
959
-			continue
960
-		}
961
-		potentialDriveLetter := raw[right-1]
962
-		if (potentialDriveLetter >= 'A' && potentialDriveLetter <= 'Z') || (potentialDriveLetter >= 'a' && potentialDriveLetter <= 'z') {
963
-			if right > 1 {
964
-				beforePotentialDriveLetter := raw[right-2]
965
-				// Only `:` or `\\` are checked (`/` could fall into the case of `/tmp/q:/foo`)
966
-				if beforePotentialDriveLetter != ':' && beforePotentialDriveLetter != '\\' {
967
-					// e.g. `C:` is not preceded by any delimiter, therefore it was not a drive letter but a path ending with `C:`.
968
-					array = append(array, raw[left:right])
969
-					left = right + 1
970
-					numberOfParts++
971
-				}
972
-				// else, `C:` is considered as a drive letter and not as a delimiter, so we continue parsing.
973
-			}
974
-			// if right == 1, then `C:` is the beginning of the raw string, therefore `:` is again not considered a delimiter and we continue parsing.
975
-		} else {
976
-			// if `:` is not preceded by a potential drive letter, then consider it as a delimiter.
977
-			array = append(array, raw[left:right])
978
-			left = right + 1
979
-			numberOfParts++
980
-		}
981
-	}
982
-	// need to take care of the last part
983
-	if left < len(raw) {
984
-		if n >= 0 && numberOfParts >= n {
985
-			// if the maximum number of parts is reached, just append the rest to the last part
986
-			// left-1 is at the last `:` that needs to be included since not considered a separator.
987
-			array[n-1] += raw[left-1:]
988
-		} else {
989
-			array = append(array, raw[left:])
990
-		}
991
-	}
992
-	return array
993
-}
994 802
deleted file mode 100644
... ...
@@ -1,894 +0,0 @@
1
-package opts
2
-
3
-import (
4
-	"bytes"
5
-	"encoding/json"
6
-	"fmt"
7
-	"io/ioutil"
8
-	"os"
9
-	"runtime"
10
-	"strings"
11
-	"testing"
12
-	"time"
13
-
14
-	"github.com/docker/docker/api/types/container"
15
-	networktypes "github.com/docker/docker/api/types/network"
16
-	"github.com/docker/docker/runconfig"
17
-	"github.com/docker/go-connections/nat"
18
-	"github.com/spf13/pflag"
19
-)
20
-
21
-func parseRun(args []string) (*container.Config, *container.HostConfig, *networktypes.NetworkingConfig, error) {
22
-	flags := pflag.NewFlagSet("run", pflag.ContinueOnError)
23
-	flags.SetOutput(ioutil.Discard)
24
-	flags.Usage = nil
25
-	copts := AddFlags(flags)
26
-	if err := flags.Parse(args); err != nil {
27
-		return nil, nil, nil, err
28
-	}
29
-	return Parse(flags, copts)
30
-}
31
-
32
-func parse(t *testing.T, args string) (*container.Config, *container.HostConfig, error) {
33
-	config, hostConfig, _, err := parseRun(strings.Split(args+" ubuntu bash", " "))
34
-	return config, hostConfig, err
35
-}
36
-
37
-func mustParse(t *testing.T, args string) (*container.Config, *container.HostConfig) {
38
-	config, hostConfig, err := parse(t, args)
39
-	if err != nil {
40
-		t.Fatal(err)
41
-	}
42
-	return config, hostConfig
43
-}
44
-
45
-func TestParseRunLinks(t *testing.T) {
46
-	if _, hostConfig := mustParse(t, "--link a:b"); len(hostConfig.Links) == 0 || hostConfig.Links[0] != "a:b" {
47
-		t.Fatalf("Error parsing links. Expected []string{\"a:b\"}, received: %v", hostConfig.Links)
48
-	}
49
-	if _, hostConfig := mustParse(t, "--link a:b --link c:d"); len(hostConfig.Links) < 2 || hostConfig.Links[0] != "a:b" || hostConfig.Links[1] != "c:d" {
50
-		t.Fatalf("Error parsing links. Expected []string{\"a:b\", \"c:d\"}, received: %v", hostConfig.Links)
51
-	}
52
-	if _, hostConfig := mustParse(t, ""); len(hostConfig.Links) != 0 {
53
-		t.Fatalf("Error parsing links. No link expected, received: %v", hostConfig.Links)
54
-	}
55
-}
56
-
57
-func TestParseRunAttach(t *testing.T) {
58
-	if config, _ := mustParse(t, "-a stdin"); !config.AttachStdin || config.AttachStdout || config.AttachStderr {
59
-		t.Fatalf("Error parsing attach flags. Expect only Stdin enabled. Received: in: %v, out: %v, err: %v", config.AttachStdin, config.AttachStdout, config.AttachStderr)
60
-	}
61
-	if config, _ := mustParse(t, "-a stdin -a stdout"); !config.AttachStdin || !config.AttachStdout || config.AttachStderr {
62
-		t.Fatalf("Error parsing attach flags. Expect only Stdin and Stdout enabled. Received: in: %v, out: %v, err: %v", config.AttachStdin, config.AttachStdout, config.AttachStderr)
63
-	}
64
-	if config, _ := mustParse(t, "-a stdin -a stdout -a stderr"); !config.AttachStdin || !config.AttachStdout || !config.AttachStderr {
65
-		t.Fatalf("Error parsing attach flags. Expect all attach enabled. Received: in: %v, out: %v, err: %v", config.AttachStdin, config.AttachStdout, config.AttachStderr)
66
-	}
67
-	if config, _ := mustParse(t, ""); config.AttachStdin || !config.AttachStdout || !config.AttachStderr {
68
-		t.Fatalf("Error parsing attach flags. Expect Stdin disabled. Received: in: %v, out: %v, err: %v", config.AttachStdin, config.AttachStdout, config.AttachStderr)
69
-	}
70
-	if config, _ := mustParse(t, "-i"); !config.AttachStdin || !config.AttachStdout || !config.AttachStderr {
71
-		t.Fatalf("Error parsing attach flags. Expect Stdin enabled. Received: in: %v, out: %v, err: %v", config.AttachStdin, config.AttachStdout, config.AttachStderr)
72
-	}
73
-
74
-	if _, _, err := parse(t, "-a"); err == nil {
75
-		t.Fatalf("Error parsing attach flags, `-a` should be an error but is not")
76
-	}
77
-	if _, _, err := parse(t, "-a invalid"); err == nil {
78
-		t.Fatalf("Error parsing attach flags, `-a invalid` should be an error but is not")
79
-	}
80
-	if _, _, err := parse(t, "-a invalid -a stdout"); err == nil {
81
-		t.Fatalf("Error parsing attach flags, `-a stdout -a invalid` should be an error but is not")
82
-	}
83
-	if _, _, err := parse(t, "-a stdout -a stderr -d"); err == nil {
84
-		t.Fatalf("Error parsing attach flags, `-a stdout -a stderr -d` should be an error but is not")
85
-	}
86
-	if _, _, err := parse(t, "-a stdin -d"); err == nil {
87
-		t.Fatalf("Error parsing attach flags, `-a stdin -d` should be an error but is not")
88
-	}
89
-	if _, _, err := parse(t, "-a stdout -d"); err == nil {
90
-		t.Fatalf("Error parsing attach flags, `-a stdout -d` should be an error but is not")
91
-	}
92
-	if _, _, err := parse(t, "-a stderr -d"); err == nil {
93
-		t.Fatalf("Error parsing attach flags, `-a stderr -d` should be an error but is not")
94
-	}
95
-	if _, _, err := parse(t, "-d --rm"); err == nil {
96
-		t.Fatalf("Error parsing attach flags, `-d --rm` should be an error but is not")
97
-	}
98
-}
99
-
100
-func TestParseRunVolumes(t *testing.T) {
101
-
102
-	// A single volume
103
-	arr, tryit := setupPlatformVolume([]string{`/tmp`}, []string{`c:\tmp`})
104
-	if config, hostConfig := mustParse(t, tryit); hostConfig.Binds != nil {
105
-		t.Fatalf("Error parsing volume flags, %q should not mount-bind anything. Received %v", tryit, hostConfig.Binds)
106
-	} else if _, exists := config.Volumes[arr[0]]; !exists {
107
-		t.Fatalf("Error parsing volume flags, %q is missing from volumes. Received %v", tryit, config.Volumes)
108
-	}
109
-
110
-	// Two volumes
111
-	arr, tryit = setupPlatformVolume([]string{`/tmp`, `/var`}, []string{`c:\tmp`, `c:\var`})
112
-	if config, hostConfig := mustParse(t, tryit); hostConfig.Binds != nil {
113
-		t.Fatalf("Error parsing volume flags, %q should not mount-bind anything. Received %v", tryit, hostConfig.Binds)
114
-	} else if _, exists := config.Volumes[arr[0]]; !exists {
115
-		t.Fatalf("Error parsing volume flags, %s is missing from volumes. Received %v", arr[0], config.Volumes)
116
-	} else if _, exists := config.Volumes[arr[1]]; !exists {
117
-		t.Fatalf("Error parsing volume flags, %s is missing from volumes. Received %v", arr[1], config.Volumes)
118
-	}
119
-
120
-	// A single bind-mount
121
-	arr, tryit = setupPlatformVolume([]string{`/hostTmp:/containerTmp`}, []string{os.Getenv("TEMP") + `:c:\containerTmp`})
122
-	if config, hostConfig := mustParse(t, tryit); hostConfig.Binds == nil || hostConfig.Binds[0] != arr[0] {
123
-		t.Fatalf("Error parsing volume flags, %q should mount-bind the path before the colon into the path after the colon. Received %v %v", arr[0], hostConfig.Binds, config.Volumes)
124
-	}
125
-
126
-	// Two bind-mounts.
127
-	arr, tryit = setupPlatformVolume([]string{`/hostTmp:/containerTmp`, `/hostVar:/containerVar`}, []string{os.Getenv("ProgramData") + `:c:\ContainerPD`, os.Getenv("TEMP") + `:c:\containerTmp`})
128
-	if _, hostConfig := mustParse(t, tryit); hostConfig.Binds == nil || compareRandomizedStrings(hostConfig.Binds[0], hostConfig.Binds[1], arr[0], arr[1]) != nil {
129
-		t.Fatalf("Error parsing volume flags, `%s and %s` did not mount-bind correctly. Received %v", arr[0], arr[1], hostConfig.Binds)
130
-	}
131
-
132
-	// Two bind-mounts, first read-only, second read-write.
133
-	// TODO Windows: The Windows version uses read-write as that's the only mode it supports. Can change this post TP4
134
-	arr, tryit = setupPlatformVolume([]string{`/hostTmp:/containerTmp:ro`, `/hostVar:/containerVar:rw`}, []string{os.Getenv("TEMP") + `:c:\containerTmp:rw`, os.Getenv("ProgramData") + `:c:\ContainerPD:rw`})
135
-	if _, hostConfig := mustParse(t, tryit); hostConfig.Binds == nil || compareRandomizedStrings(hostConfig.Binds[0], hostConfig.Binds[1], arr[0], arr[1]) != nil {
136
-		t.Fatalf("Error parsing volume flags, `%s and %s` did not mount-bind correctly. Received %v", arr[0], arr[1], hostConfig.Binds)
137
-	}
138
-
139
-	// Similar to previous test but with alternate modes which are only supported by Linux
140
-	if runtime.GOOS != "windows" {
141
-		arr, tryit = setupPlatformVolume([]string{`/hostTmp:/containerTmp:ro,Z`, `/hostVar:/containerVar:rw,Z`}, []string{})
142
-		if _, hostConfig := mustParse(t, tryit); hostConfig.Binds == nil || compareRandomizedStrings(hostConfig.Binds[0], hostConfig.Binds[1], arr[0], arr[1]) != nil {
143
-			t.Fatalf("Error parsing volume flags, `%s and %s` did not mount-bind correctly. Received %v", arr[0], arr[1], hostConfig.Binds)
144
-		}
145
-
146
-		arr, tryit = setupPlatformVolume([]string{`/hostTmp:/containerTmp:Z`, `/hostVar:/containerVar:z`}, []string{})
147
-		if _, hostConfig := mustParse(t, tryit); hostConfig.Binds == nil || compareRandomizedStrings(hostConfig.Binds[0], hostConfig.Binds[1], arr[0], arr[1]) != nil {
148
-			t.Fatalf("Error parsing volume flags, `%s and %s` did not mount-bind correctly. Received %v", arr[0], arr[1], hostConfig.Binds)
149
-		}
150
-	}
151
-
152
-	// One bind mount and one volume
153
-	arr, tryit = setupPlatformVolume([]string{`/hostTmp:/containerTmp`, `/containerVar`}, []string{os.Getenv("TEMP") + `:c:\containerTmp`, `c:\containerTmp`})
154
-	if config, hostConfig := mustParse(t, tryit); hostConfig.Binds == nil || len(hostConfig.Binds) > 1 || hostConfig.Binds[0] != arr[0] {
155
-		t.Fatalf("Error parsing volume flags, %s and %s should only one and only one bind mount %s. Received %s", arr[0], arr[1], arr[0], hostConfig.Binds)
156
-	} else if _, exists := config.Volumes[arr[1]]; !exists {
157
-		t.Fatalf("Error parsing volume flags %s and %s. %s is missing from volumes. Received %v", arr[0], arr[1], arr[1], config.Volumes)
158
-	}
159
-
160
-	// Root to non-c: drive letter (Windows specific)
161
-	if runtime.GOOS == "windows" {
162
-		arr, tryit = setupPlatformVolume([]string{}, []string{os.Getenv("SystemDrive") + `\:d:`})
163
-		if config, hostConfig := mustParse(t, tryit); hostConfig.Binds == nil || len(hostConfig.Binds) > 1 || hostConfig.Binds[0] != arr[0] || len(config.Volumes) != 0 {
164
-			t.Fatalf("Error parsing %s. Should have a single bind mount and no volumes", arr[0])
165
-		}
166
-	}
167
-
168
-}
169
-
170
-// This tests the cases for binds which are generated through
171
-// DecodeContainerConfig rather than Parse()
172
-func TestDecodeContainerConfigVolumes(t *testing.T) {
173
-
174
-	// Root to root
175
-	bindsOrVols, _ := setupPlatformVolume([]string{`/:/`}, []string{os.Getenv("SystemDrive") + `\:c:\`})
176
-	if _, _, err := callDecodeContainerConfig(nil, bindsOrVols); err == nil {
177
-		t.Fatalf("binds %v should have failed", bindsOrVols)
178
-	}
179
-	if _, _, err := callDecodeContainerConfig(bindsOrVols, nil); err == nil {
180
-		t.Fatalf("volume %v should have failed", bindsOrVols)
181
-	}
182
-
183
-	// No destination path
184
-	bindsOrVols, _ = setupPlatformVolume([]string{`/tmp:`}, []string{os.Getenv("TEMP") + `\:`})
185
-	if _, _, err := callDecodeContainerConfig(nil, bindsOrVols); err == nil {
186
-		t.Fatalf("binds %v should have failed", bindsOrVols)
187
-	}
188
-	if _, _, err := callDecodeContainerConfig(bindsOrVols, nil); err == nil {
189
-		t.Fatalf("volume %v should have failed", bindsOrVols)
190
-	}
191
-
192
-	//	// No destination path or mode
193
-	bindsOrVols, _ = setupPlatformVolume([]string{`/tmp::`}, []string{os.Getenv("TEMP") + `\::`})
194
-	if _, _, err := callDecodeContainerConfig(nil, bindsOrVols); err == nil {
195
-		t.Fatalf("binds %v should have failed", bindsOrVols)
196
-	}
197
-	if _, _, err := callDecodeContainerConfig(bindsOrVols, nil); err == nil {
198
-		t.Fatalf("volume %v should have failed", bindsOrVols)
199
-	}
200
-
201
-	// A whole lot of nothing
202
-	bindsOrVols = []string{`:`}
203
-	if _, _, err := callDecodeContainerConfig(nil, bindsOrVols); err == nil {
204
-		t.Fatalf("binds %v should have failed", bindsOrVols)
205
-	}
206
-	if _, _, err := callDecodeContainerConfig(bindsOrVols, nil); err == nil {
207
-		t.Fatalf("volume %v should have failed", bindsOrVols)
208
-	}
209
-
210
-	// A whole lot of nothing with no mode
211
-	bindsOrVols = []string{`::`}
212
-	if _, _, err := callDecodeContainerConfig(nil, bindsOrVols); err == nil {
213
-		t.Fatalf("binds %v should have failed", bindsOrVols)
214
-	}
215
-	if _, _, err := callDecodeContainerConfig(bindsOrVols, nil); err == nil {
216
-		t.Fatalf("volume %v should have failed", bindsOrVols)
217
-	}
218
-
219
-	// Too much including an invalid mode
220
-	wTmp := os.Getenv("TEMP")
221
-	bindsOrVols, _ = setupPlatformVolume([]string{`/tmp:/tmp:/tmp:/tmp`}, []string{wTmp + ":" + wTmp + ":" + wTmp + ":" + wTmp})
222
-	if _, _, err := callDecodeContainerConfig(nil, bindsOrVols); err == nil {
223
-		t.Fatalf("binds %v should have failed", bindsOrVols)
224
-	}
225
-	if _, _, err := callDecodeContainerConfig(bindsOrVols, nil); err == nil {
226
-		t.Fatalf("volume %v should have failed", bindsOrVols)
227
-	}
228
-
229
-	// Windows specific error tests
230
-	if runtime.GOOS == "windows" {
231
-		// Volume which does not include a drive letter
232
-		bindsOrVols = []string{`\tmp`}
233
-		if _, _, err := callDecodeContainerConfig(nil, bindsOrVols); err == nil {
234
-			t.Fatalf("binds %v should have failed", bindsOrVols)
235
-		}
236
-		if _, _, err := callDecodeContainerConfig(bindsOrVols, nil); err == nil {
237
-			t.Fatalf("volume %v should have failed", bindsOrVols)
238
-		}
239
-
240
-		// Root to C-Drive
241
-		bindsOrVols = []string{os.Getenv("SystemDrive") + `\:c:`}
242
-		if _, _, err := callDecodeContainerConfig(nil, bindsOrVols); err == nil {
243
-			t.Fatalf("binds %v should have failed", bindsOrVols)
244
-		}
245
-		if _, _, err := callDecodeContainerConfig(bindsOrVols, nil); err == nil {
246
-			t.Fatalf("volume %v should have failed", bindsOrVols)
247
-		}
248
-
249
-		// Container path that does not include a drive letter
250
-		bindsOrVols = []string{`c:\windows:\somewhere`}
251
-		if _, _, err := callDecodeContainerConfig(nil, bindsOrVols); err == nil {
252
-			t.Fatalf("binds %v should have failed", bindsOrVols)
253
-		}
254
-		if _, _, err := callDecodeContainerConfig(bindsOrVols, nil); err == nil {
255
-			t.Fatalf("volume %v should have failed", bindsOrVols)
256
-		}
257
-	}
258
-
259
-	// Linux-specific error tests
260
-	if runtime.GOOS != "windows" {
261
-		// Just root
262
-		bindsOrVols = []string{`/`}
263
-		if _, _, err := callDecodeContainerConfig(nil, bindsOrVols); err == nil {
264
-			t.Fatalf("binds %v should have failed", bindsOrVols)
265
-		}
266
-		if _, _, err := callDecodeContainerConfig(bindsOrVols, nil); err == nil {
267
-			t.Fatalf("volume %v should have failed", bindsOrVols)
268
-		}
269
-
270
-		// A single volume that looks like a bind mount passed in Volumes.
271
-		// This should be handled as a bind mount, not a volume.
272
-		vols := []string{`/foo:/bar`}
273
-		if config, hostConfig, err := callDecodeContainerConfig(vols, nil); err != nil {
274
-			t.Fatal("Volume /foo:/bar should have succeeded as a volume name")
275
-		} else if hostConfig.Binds != nil {
276
-			t.Fatalf("Error parsing volume flags, /foo:/bar should not mount-bind anything. Received %v", hostConfig.Binds)
277
-		} else if _, exists := config.Volumes[vols[0]]; !exists {
278
-			t.Fatalf("Error parsing volume flags, /foo:/bar is missing from volumes. Received %v", config.Volumes)
279
-		}
280
-
281
-	}
282
-}
283
-
284
-// callDecodeContainerConfig is a utility function used by TestDecodeContainerConfigVolumes
285
-// to call DecodeContainerConfig. It effectively does what a client would
286
-// do when calling the daemon by constructing a JSON stream of a
287
-// ContainerConfigWrapper which is populated by the set of volume specs
288
-// passed into it. It returns a config and a hostconfig which can be
289
-// validated to ensure DecodeContainerConfig has manipulated the structures
290
-// correctly.
291
-func callDecodeContainerConfig(volumes []string, binds []string) (*container.Config, *container.HostConfig, error) {
292
-	var (
293
-		b   []byte
294
-		err error
295
-		c   *container.Config
296
-		h   *container.HostConfig
297
-	)
298
-	w := runconfig.ContainerConfigWrapper{
299
-		Config: &container.Config{
300
-			Volumes: map[string]struct{}{},
301
-		},
302
-		HostConfig: &container.HostConfig{
303
-			NetworkMode: "none",
304
-			Binds:       binds,
305
-		},
306
-	}
307
-	for _, v := range volumes {
308
-		w.Config.Volumes[v] = struct{}{}
309
-	}
310
-	if b, err = json.Marshal(w); err != nil {
311
-		return nil, nil, fmt.Errorf("Error on marshal %s", err.Error())
312
-	}
313
-	c, h, _, err = runconfig.DecodeContainerConfig(bytes.NewReader(b))
314
-	if err != nil {
315
-		return nil, nil, fmt.Errorf("Error parsing %s: %v", string(b), err)
316
-	}
317
-	if c == nil || h == nil {
318
-		return nil, nil, fmt.Errorf("Empty config or hostconfig")
319
-	}
320
-
321
-	return c, h, err
322
-}
323
-
324
-// check if (a == c && b == d) || (a == d && b == c)
325
-// because maps are randomized
326
-func compareRandomizedStrings(a, b, c, d string) error {
327
-	if a == c && b == d {
328
-		return nil
329
-	}
330
-	if a == d && b == c {
331
-		return nil
332
-	}
333
-	return fmt.Errorf("strings don't match")
334
-}
335
-
336
-// setupPlatformVolume takes two arrays of volume specs - a Unix style
337
-// spec and a Windows style spec. Depending on the platform being unit tested,
338
-// it returns one of them, along with a volume string that would be passed
339
-// on the docker CLI (e.g. -v /bar -v /foo).
340
-func setupPlatformVolume(u []string, w []string) ([]string, string) {
341
-	var a []string
342
-	if runtime.GOOS == "windows" {
343
-		a = w
344
-	} else {
345
-		a = u
346
-	}
347
-	s := ""
348
-	for _, v := range a {
349
-		s = s + "-v " + v + " "
350
-	}
351
-	return a, s
352
-}
353
-
354
-// Simple parse with MacAddress validation
355
-func TestParseWithMacAddress(t *testing.T) {
356
-	invalidMacAddress := "--mac-address=invalidMacAddress"
357
-	validMacAddress := "--mac-address=92:d0:c6:0a:29:33"
358
-	if _, _, _, err := parseRun([]string{invalidMacAddress, "img", "cmd"}); err != nil && err.Error() != "invalidMacAddress is not a valid mac address" {
359
-		t.Fatalf("Expected an error with %v mac-address, got %v", invalidMacAddress, err)
360
-	}
361
-	if config, _ := mustParse(t, validMacAddress); config.MacAddress != "92:d0:c6:0a:29:33" {
362
-		t.Fatalf("Expected the config to have '92:d0:c6:0a:29:33' as MacAddress, got '%v'", config.MacAddress)
363
-	}
364
-}
365
-
366
-func TestParseWithMemory(t *testing.T) {
367
-	invalidMemory := "--memory=invalid"
368
-	validMemory := "--memory=1G"
369
-	if _, _, _, err := parseRun([]string{invalidMemory, "img", "cmd"}); err != nil && err.Error() != "invalid size: 'invalid'" {
370
-		t.Fatalf("Expected an error with '%v' Memory, got '%v'", invalidMemory, err)
371
-	}
372
-	if _, hostconfig := mustParse(t, validMemory); hostconfig.Memory != 1073741824 {
373
-		t.Fatalf("Expected the config to have '1G' as Memory, got '%v'", hostconfig.Memory)
374
-	}
375
-}
376
-
377
-func TestParseWithMemorySwap(t *testing.T) {
378
-	invalidMemory := "--memory-swap=invalid"
379
-	validMemory := "--memory-swap=1G"
380
-	anotherValidMemory := "--memory-swap=-1"
381
-	if _, _, _, err := parseRun([]string{invalidMemory, "img", "cmd"}); err == nil || err.Error() != "invalid size: 'invalid'" {
382
-		t.Fatalf("Expected an error with '%v' MemorySwap, got '%v'", invalidMemory, err)
383
-	}
384
-	if _, hostconfig := mustParse(t, validMemory); hostconfig.MemorySwap != 1073741824 {
385
-		t.Fatalf("Expected the config to have '1073741824' as MemorySwap, got '%v'", hostconfig.MemorySwap)
386
-	}
387
-	if _, hostconfig := mustParse(t, anotherValidMemory); hostconfig.MemorySwap != -1 {
388
-		t.Fatalf("Expected the config to have '-1' as MemorySwap, got '%v'", hostconfig.MemorySwap)
389
-	}
390
-}
391
-
392
-func TestParseHostname(t *testing.T) {
393
-	validHostnames := map[string]string{
394
-		"hostname":    "hostname",
395
-		"host-name":   "host-name",
396
-		"hostname123": "hostname123",
397
-		"123hostname": "123hostname",
398
-		"hostname-of-63-bytes-long-should-be-valid-and-without-any-error": "hostname-of-63-bytes-long-should-be-valid-and-without-any-error",
399
-	}
400
-	hostnameWithDomain := "--hostname=hostname.domainname"
401
-	hostnameWithDomainTld := "--hostname=hostname.domainname.tld"
402
-	for hostname, expectedHostname := range validHostnames {
403
-		if config, _ := mustParse(t, fmt.Sprintf("--hostname=%s", hostname)); config.Hostname != expectedHostname {
404
-			t.Fatalf("Expected the config to have 'hostname' as hostname, got '%v'", config.Hostname)
405
-		}
406
-	}
407
-	if config, _ := mustParse(t, hostnameWithDomain); config.Hostname != "hostname.domainname" && config.Domainname != "" {
408
-		t.Fatalf("Expected the config to have 'hostname' as hostname.domainname, got '%v'", config.Hostname)
409
-	}
410
-	if config, _ := mustParse(t, hostnameWithDomainTld); config.Hostname != "hostname.domainname.tld" && config.Domainname != "" {
411
-		t.Fatalf("Expected the config to have 'hostname' as hostname.domainname.tld, got '%v'", config.Hostname)
412
-	}
413
-}
414
-
415
-func TestParseWithExpose(t *testing.T) {
416
-	invalids := map[string]string{
417
-		":":                   "invalid port format for --expose: :",
418
-		"8080:9090":           "invalid port format for --expose: 8080:9090",
419
-		"/tcp":                "invalid range format for --expose: /tcp, error: Empty string specified for ports.",
420
-		"/udp":                "invalid range format for --expose: /udp, error: Empty string specified for ports.",
421
-		"NaN/tcp":             `invalid range format for --expose: NaN/tcp, error: strconv.ParseUint: parsing "NaN": invalid syntax`,
422
-		"NaN-NaN/tcp":         `invalid range format for --expose: NaN-NaN/tcp, error: strconv.ParseUint: parsing "NaN": invalid syntax`,
423
-		"8080-NaN/tcp":        `invalid range format for --expose: 8080-NaN/tcp, error: strconv.ParseUint: parsing "NaN": invalid syntax`,
424
-		"1234567890-8080/tcp": `invalid range format for --expose: 1234567890-8080/tcp, error: strconv.ParseUint: parsing "1234567890": value out of range`,
425
-	}
426
-	valids := map[string][]nat.Port{
427
-		"8080/tcp":      {"8080/tcp"},
428
-		"8080/udp":      {"8080/udp"},
429
-		"8080/ncp":      {"8080/ncp"},
430
-		"8080-8080/udp": {"8080/udp"},
431
-		"8080-8082/tcp": {"8080/tcp", "8081/tcp", "8082/tcp"},
432
-	}
433
-	for expose, expectedError := range invalids {
434
-		if _, _, _, err := parseRun([]string{fmt.Sprintf("--expose=%v", expose), "img", "cmd"}); err == nil || err.Error() != expectedError {
435
-			t.Fatalf("Expected error '%v' with '--expose=%v', got '%v'", expectedError, expose, err)
436
-		}
437
-	}
438
-	for expose, exposedPorts := range valids {
439
-		config, _, _, err := parseRun([]string{fmt.Sprintf("--expose=%v", expose), "img", "cmd"})
440
-		if err != nil {
441
-			t.Fatal(err)
442
-		}
443
-		if len(config.ExposedPorts) != len(exposedPorts) {
444
-			t.Fatalf("Expected %v exposed port, got %v", len(exposedPorts), len(config.ExposedPorts))
445
-		}
446
-		for _, port := range exposedPorts {
447
-			if _, ok := config.ExposedPorts[port]; !ok {
448
-				t.Fatalf("Expected %v, got %v", exposedPorts, config.ExposedPorts)
449
-			}
450
-		}
451
-	}
452
-	// Merge with actual published port
453
-	config, _, _, err := parseRun([]string{"--publish=80", "--expose=80-81/tcp", "img", "cmd"})
454
-	if err != nil {
455
-		t.Fatal(err)
456
-	}
457
-	if len(config.ExposedPorts) != 2 {
458
-		t.Fatalf("Expected 2 exposed ports, got %v", config.ExposedPorts)
459
-	}
460
-	ports := []nat.Port{"80/tcp", "81/tcp"}
461
-	for _, port := range ports {
462
-		if _, ok := config.ExposedPorts[port]; !ok {
463
-			t.Fatalf("Expected %v, got %v", ports, config.ExposedPorts)
464
-		}
465
-	}
466
-}
467
-
468
-func TestParseDevice(t *testing.T) {
469
-	valids := map[string]container.DeviceMapping{
470
-		"/dev/snd": {
471
-			PathOnHost:        "/dev/snd",
472
-			PathInContainer:   "/dev/snd",
473
-			CgroupPermissions: "rwm",
474
-		},
475
-		"/dev/snd:rw": {
476
-			PathOnHost:        "/dev/snd",
477
-			PathInContainer:   "/dev/snd",
478
-			CgroupPermissions: "rw",
479
-		},
480
-		"/dev/snd:/something": {
481
-			PathOnHost:        "/dev/snd",
482
-			PathInContainer:   "/something",
483
-			CgroupPermissions: "rwm",
484
-		},
485
-		"/dev/snd:/something:rw": {
486
-			PathOnHost:        "/dev/snd",
487
-			PathInContainer:   "/something",
488
-			CgroupPermissions: "rw",
489
-		},
490
-	}
491
-	for device, deviceMapping := range valids {
492
-		_, hostconfig, _, err := parseRun([]string{fmt.Sprintf("--device=%v", device), "img", "cmd"})
493
-		if err != nil {
494
-			t.Fatal(err)
495
-		}
496
-		if len(hostconfig.Devices) != 1 {
497
-			t.Fatalf("Expected 1 devices, got %v", hostconfig.Devices)
498
-		}
499
-		if hostconfig.Devices[0] != deviceMapping {
500
-			t.Fatalf("Expected %v, got %v", deviceMapping, hostconfig.Devices)
501
-		}
502
-	}
503
-
504
-}
505
-
506
-func TestParseModes(t *testing.T) {
507
-	// ipc ko
508
-	if _, _, _, err := parseRun([]string{"--ipc=container:", "img", "cmd"}); err == nil || err.Error() != "--ipc: invalid IPC mode" {
509
-		t.Fatalf("Expected an error with message '--ipc: invalid IPC mode', got %v", err)
510
-	}
511
-	// ipc ok
512
-	_, hostconfig, _, err := parseRun([]string{"--ipc=host", "img", "cmd"})
513
-	if err != nil {
514
-		t.Fatal(err)
515
-	}
516
-	if !hostconfig.IpcMode.Valid() {
517
-		t.Fatalf("Expected a valid IpcMode, got %v", hostconfig.IpcMode)
518
-	}
519
-	// pid ko
520
-	if _, _, _, err := parseRun([]string{"--pid=container:", "img", "cmd"}); err == nil || err.Error() != "--pid: invalid PID mode" {
521
-		t.Fatalf("Expected an error with message '--pid: invalid PID mode', got %v", err)
522
-	}
523
-	// pid ok
524
-	_, hostconfig, _, err = parseRun([]string{"--pid=host", "img", "cmd"})
525
-	if err != nil {
526
-		t.Fatal(err)
527
-	}
528
-	if !hostconfig.PidMode.Valid() {
529
-		t.Fatalf("Expected a valid PidMode, got %v", hostconfig.PidMode)
530
-	}
531
-	// uts ko
532
-	if _, _, _, err := parseRun([]string{"--uts=container:", "img", "cmd"}); err == nil || err.Error() != "--uts: invalid UTS mode" {
533
-		t.Fatalf("Expected an error with message '--uts: invalid UTS mode', got %v", err)
534
-	}
535
-	// uts ok
536
-	_, hostconfig, _, err = parseRun([]string{"--uts=host", "img", "cmd"})
537
-	if err != nil {
538
-		t.Fatal(err)
539
-	}
540
-	if !hostconfig.UTSMode.Valid() {
541
-		t.Fatalf("Expected a valid UTSMode, got %v", hostconfig.UTSMode)
542
-	}
543
-	// shm-size ko
544
-	if _, _, _, err = parseRun([]string{"--shm-size=a128m", "img", "cmd"}); err == nil || err.Error() != "invalid size: 'a128m'" {
545
-		t.Fatalf("Expected an error with message 'invalid size: a128m', got %v", err)
546
-	}
547
-	// shm-size ok
548
-	_, hostconfig, _, err = parseRun([]string{"--shm-size=128m", "img", "cmd"})
549
-	if err != nil {
550
-		t.Fatal(err)
551
-	}
552
-	if hostconfig.ShmSize != 134217728 {
553
-		t.Fatalf("Expected a valid ShmSize, got %d", hostconfig.ShmSize)
554
-	}
555
-}
556
-
557
-func TestParseRestartPolicy(t *testing.T) {
558
-	invalids := map[string]string{
559
-		"always:2:3":         "invalid restart policy format",
560
-		"on-failure:invalid": "maximum retry count must be an integer",
561
-	}
562
-	valids := map[string]container.RestartPolicy{
563
-		"": {},
564
-		"always": {
565
-			Name:              "always",
566
-			MaximumRetryCount: 0,
567
-		},
568
-		"on-failure:1": {
569
-			Name:              "on-failure",
570
-			MaximumRetryCount: 1,
571
-		},
572
-	}
573
-	for restart, expectedError := range invalids {
574
-		if _, _, _, err := parseRun([]string{fmt.Sprintf("--restart=%s", restart), "img", "cmd"}); err == nil || err.Error() != expectedError {
575
-			t.Fatalf("Expected an error with message '%v' for %v, got %v", expectedError, restart, err)
576
-		}
577
-	}
578
-	for restart, expected := range valids {
579
-		_, hostconfig, _, err := parseRun([]string{fmt.Sprintf("--restart=%v", restart), "img", "cmd"})
580
-		if err != nil {
581
-			t.Fatal(err)
582
-		}
583
-		if hostconfig.RestartPolicy != expected {
584
-			t.Fatalf("Expected %v, got %v", expected, hostconfig.RestartPolicy)
585
-		}
586
-	}
587
-}
588
-
589
-func TestParseHealth(t *testing.T) {
590
-	checkOk := func(args ...string) *container.HealthConfig {
591
-		config, _, _, err := parseRun(args)
592
-		if err != nil {
593
-			t.Fatalf("%#v: %v", args, err)
594
-		}
595
-		return config.Healthcheck
596
-	}
597
-	checkError := func(expected string, args ...string) {
598
-		config, _, _, err := parseRun(args)
599
-		if err == nil {
600
-			t.Fatalf("Expected error, but got %#v", config)
601
-		}
602
-		if err.Error() != expected {
603
-			t.Fatalf("Expected %#v, got %#v", expected, err)
604
-		}
605
-	}
606
-	health := checkOk("--no-healthcheck", "img", "cmd")
607
-	if health == nil || len(health.Test) != 1 || health.Test[0] != "NONE" {
608
-		t.Fatalf("--no-healthcheck failed: %#v", health)
609
-	}
610
-
611
-	health = checkOk("--health-cmd=/check.sh -q", "img", "cmd")
612
-	if len(health.Test) != 2 || health.Test[0] != "CMD-SHELL" || health.Test[1] != "/check.sh -q" {
613
-		t.Fatalf("--health-cmd: got %#v", health.Test)
614
-	}
615
-	if health.Timeout != 0 {
616
-		t.Fatalf("--health-cmd: timeout = %f", health.Timeout)
617
-	}
618
-
619
-	checkError("--no-healthcheck conflicts with --health-* options",
620
-		"--no-healthcheck", "--health-cmd=/check.sh -q", "img", "cmd")
621
-
622
-	health = checkOk("--health-timeout=2s", "--health-retries=3", "--health-interval=4.5s", "img", "cmd")
623
-	if health.Timeout != 2*time.Second || health.Retries != 3 || health.Interval != 4500*time.Millisecond {
624
-		t.Fatalf("--health-*: got %#v", health)
625
-	}
626
-}
627
-
628
-func TestParseLoggingOpts(t *testing.T) {
629
-	// logging opts ko
630
-	if _, _, _, err := parseRun([]string{"--log-driver=none", "--log-opt=anything", "img", "cmd"}); err == nil || err.Error() != "invalid logging opts for driver none" {
631
-		t.Fatalf("Expected an error with message 'invalid logging opts for driver none', got %v", err)
632
-	}
633
-	// logging opts ok
634
-	_, hostconfig, _, err := parseRun([]string{"--log-driver=syslog", "--log-opt=something", "img", "cmd"})
635
-	if err != nil {
636
-		t.Fatal(err)
637
-	}
638
-	if hostconfig.LogConfig.Type != "syslog" || len(hostconfig.LogConfig.Config) != 1 {
639
-		t.Fatalf("Expected a 'syslog' LogConfig with one config, got %v", hostconfig.RestartPolicy)
640
-	}
641
-}
642
-
643
-func TestParseEnvfileVariables(t *testing.T) {
644
-	e := "open nonexistent: no such file or directory"
645
-	if runtime.GOOS == "windows" {
646
-		e = "open nonexistent: The system cannot find the file specified."
647
-	}
648
-	// env ko
649
-	if _, _, _, err := parseRun([]string{"--env-file=nonexistent", "img", "cmd"}); err == nil || err.Error() != e {
650
-		t.Fatalf("Expected an error with message '%s', got %v", e, err)
651
-	}
652
-	// env ok
653
-	config, _, _, err := parseRun([]string{"--env-file=fixtures/valid.env", "img", "cmd"})
654
-	if err != nil {
655
-		t.Fatal(err)
656
-	}
657
-	if len(config.Env) != 1 || config.Env[0] != "ENV1=value1" {
658
-		t.Fatalf("Expected a config with [ENV1=value1], got %v", config.Env)
659
-	}
660
-	config, _, _, err = parseRun([]string{"--env-file=fixtures/valid.env", "--env=ENV2=value2", "img", "cmd"})
661
-	if err != nil {
662
-		t.Fatal(err)
663
-	}
664
-	if len(config.Env) != 2 || config.Env[0] != "ENV1=value1" || config.Env[1] != "ENV2=value2" {
665
-		t.Fatalf("Expected a config with [ENV1=value1 ENV2=value2], got %v", config.Env)
666
-	}
667
-}
668
-
669
-func TestParseEnvfileVariablesWithBOMUnicode(t *testing.T) {
670
-	// UTF8 with BOM
671
-	config, _, _, err := parseRun([]string{"--env-file=fixtures/utf8.env", "img", "cmd"})
672
-	if err != nil {
673
-		t.Fatal(err)
674
-	}
675
-	env := []string{"FOO=BAR", "HELLO=" + string([]byte{0xe6, 0x82, 0xa8, 0xe5, 0xa5, 0xbd}), "BAR=FOO"}
676
-	if len(config.Env) != len(env) {
677
-		t.Fatalf("Expected a config with %d env variables, got %v: %v", len(env), len(config.Env), config.Env)
678
-	}
679
-	for i, v := range env {
680
-		if config.Env[i] != v {
681
-			t.Fatalf("Expected a config with [%s], got %v", v, []byte(config.Env[i]))
682
-		}
683
-	}
684
-
685
-	// UTF16 with BOM
686
-	e := "contains invalid utf8 bytes at line"
687
-	if _, _, _, err := parseRun([]string{"--env-file=fixtures/utf16.env", "img", "cmd"}); err == nil || !strings.Contains(err.Error(), e) {
688
-		t.Fatalf("Expected an error with message '%s', got %v", e, err)
689
-	}
690
-	// UTF16BE with BOM
691
-	if _, _, _, err := parseRun([]string{"--env-file=fixtures/utf16be.env", "img", "cmd"}); err == nil || !strings.Contains(err.Error(), e) {
692
-		t.Fatalf("Expected an error with message '%s', got %v", e, err)
693
-	}
694
-}
695
-
696
-func TestParseLabelfileVariables(t *testing.T) {
697
-	e := "open nonexistent: no such file or directory"
698
-	if runtime.GOOS == "windows" {
699
-		e = "open nonexistent: The system cannot find the file specified."
700
-	}
701
-	// label ko
702
-	if _, _, _, err := parseRun([]string{"--label-file=nonexistent", "img", "cmd"}); err == nil || err.Error() != e {
703
-		t.Fatalf("Expected an error with message '%s', got %v", e, err)
704
-	}
705
-	// label ok
706
-	config, _, _, err := parseRun([]string{"--label-file=fixtures/valid.label", "img", "cmd"})
707
-	if err != nil {
708
-		t.Fatal(err)
709
-	}
710
-	if len(config.Labels) != 1 || config.Labels["LABEL1"] != "value1" {
711
-		t.Fatalf("Expected a config with [LABEL1:value1], got %v", config.Labels)
712
-	}
713
-	config, _, _, err = parseRun([]string{"--label-file=fixtures/valid.label", "--label=LABEL2=value2", "img", "cmd"})
714
-	if err != nil {
715
-		t.Fatal(err)
716
-	}
717
-	if len(config.Labels) != 2 || config.Labels["LABEL1"] != "value1" || config.Labels["LABEL2"] != "value2" {
718
-		t.Fatalf("Expected a config with [LABEL1:value1 LABEL2:value2], got %v", config.Labels)
719
-	}
720
-}
721
-
722
-func TestParseEntryPoint(t *testing.T) {
723
-	config, _, _, err := parseRun([]string{"--entrypoint=anything", "cmd", "img"})
724
-	if err != nil {
725
-		t.Fatal(err)
726
-	}
727
-	if len(config.Entrypoint) != 1 && config.Entrypoint[0] != "anything" {
728
-		t.Fatalf("Expected entrypoint 'anything', got %v", config.Entrypoint)
729
-	}
730
-}
731
-
732
-func TestValidateLink(t *testing.T) {
733
-	valid := []string{
734
-		"name",
735
-		"dcdfbe62ecd0:alias",
736
-		"7a67485460b7642516a4ad82ecefe7f57d0c4916f530561b71a50a3f9c4e33da",
737
-		"angry_torvalds:linus",
738
-	}
739
-	invalid := map[string]string{
740
-		"":               "empty string specified for links",
741
-		"too:much:of:it": "bad format for links: too:much:of:it",
742
-	}
743
-
744
-	for _, link := range valid {
745
-		if _, err := ValidateLink(link); err != nil {
746
-			t.Fatalf("ValidateLink(`%q`) should succeed: error %q", link, err)
747
-		}
748
-	}
749
-
750
-	for link, expectedError := range invalid {
751
-		if _, err := ValidateLink(link); err == nil {
752
-			t.Fatalf("ValidateLink(`%q`) should have failed validation", link)
753
-		} else {
754
-			if !strings.Contains(err.Error(), expectedError) {
755
-				t.Fatalf("ValidateLink(`%q`) error should contain %q", link, expectedError)
756
-			}
757
-		}
758
-	}
759
-}
760
-
761
-func TestParseLink(t *testing.T) {
762
-	name, alias, err := ParseLink("name:alias")
763
-	if err != nil {
764
-		t.Fatalf("Expected not to error out on a valid name:alias format but got: %v", err)
765
-	}
766
-	if name != "name" {
767
-		t.Fatalf("Link name should have been name, got %s instead", name)
768
-	}
769
-	if alias != "alias" {
770
-		t.Fatalf("Link alias should have been alias, got %s instead", alias)
771
-	}
772
-	// short format definition
773
-	name, alias, err = ParseLink("name")
774
-	if err != nil {
775
-		t.Fatalf("Expected not to error out on a valid name only format but got: %v", err)
776
-	}
777
-	if name != "name" {
778
-		t.Fatalf("Link name should have been name, got %s instead", name)
779
-	}
780
-	if alias != "name" {
781
-		t.Fatalf("Link alias should have been name, got %s instead", alias)
782
-	}
783
-	// empty string link definition is not allowed
784
-	if _, _, err := ParseLink(""); err == nil || !strings.Contains(err.Error(), "empty string specified for links") {
785
-		t.Fatalf("Expected error 'empty string specified for links' but got: %v", err)
786
-	}
787
-	// more than two colons are not allowed
788
-	if _, _, err := ParseLink("link:alias:wrong"); err == nil || !strings.Contains(err.Error(), "bad format for links: link:alias:wrong") {
789
-		t.Fatalf("Expected error 'bad format for links: link:alias:wrong' but got: %v", err)
790
-	}
791
-}
792
-
793
-func TestValidateDevice(t *testing.T) {
794
-	valid := []string{
795
-		"/home",
796
-		"/home:/home",
797
-		"/home:/something/else",
798
-		"/with space",
799
-		"/home:/with space",
800
-		"relative:/absolute-path",
801
-		"hostPath:/containerPath:r",
802
-		"/hostPath:/containerPath:rw",
803
-		"/hostPath:/containerPath:mrw",
804
-	}
805
-	invalid := map[string]string{
806
-		"":        "bad format for path: ",
807
-		"./":      "./ is not an absolute path",
808
-		"../":     "../ is not an absolute path",
809
-		"/:../":   "../ is not an absolute path",
810
-		"/:path":  "path is not an absolute path",
811
-		":":       "bad format for path: :",
812
-		"/tmp:":   " is not an absolute path",
813
-		":test":   "bad format for path: :test",
814
-		":/test":  "bad format for path: :/test",
815
-		"tmp:":    " is not an absolute path",
816
-		":test:":  "bad format for path: :test:",
817
-		"::":      "bad format for path: ::",
818
-		":::":     "bad format for path: :::",
819
-		"/tmp:::": "bad format for path: /tmp:::",
820
-		":/tmp::": "bad format for path: :/tmp::",
821
-		"path:ro": "ro is not an absolute path",
822
-		"path:rr": "rr is not an absolute path",
823
-		"a:/b:ro": "bad mode specified: ro",
824
-		"a:/b:rr": "bad mode specified: rr",
825
-	}
826
-
827
-	for _, path := range valid {
828
-		if _, err := ValidateDevice(path); err != nil {
829
-			t.Fatalf("ValidateDevice(`%q`) should succeed: error %q", path, err)
830
-		}
831
-	}
832
-
833
-	for path, expectedError := range invalid {
834
-		if _, err := ValidateDevice(path); err == nil {
835
-			t.Fatalf("ValidateDevice(`%q`) should have failed validation", path)
836
-		} else {
837
-			if err.Error() != expectedError {
838
-				t.Fatalf("ValidateDevice(`%q`) error should contain %q, got %q", path, expectedError, err.Error())
839
-			}
840
-		}
841
-	}
842
-}
843
-
844
-func TestVolumeSplitN(t *testing.T) {
845
-	for _, x := range []struct {
846
-		input    string
847
-		n        int
848
-		expected []string
849
-	}{
850
-		{`C:\foo:d:`, -1, []string{`C:\foo`, `d:`}},
851
-		{`:C:\foo:d:`, -1, nil},
852
-		{`/foo:/bar:ro`, 3, []string{`/foo`, `/bar`, `ro`}},
853
-		{`/foo:/bar:ro`, 2, []string{`/foo`, `/bar:ro`}},
854
-		{`C:\foo\:/foo`, -1, []string{`C:\foo\`, `/foo`}},
855
-
856
-		{`d:\`, -1, []string{`d:\`}},
857
-		{`d:`, -1, []string{`d:`}},
858
-		{`d:\path`, -1, []string{`d:\path`}},
859
-		{`d:\path with space`, -1, []string{`d:\path with space`}},
860
-		{`d:\pathandmode:rw`, -1, []string{`d:\pathandmode`, `rw`}},
861
-		{`c:\:d:\`, -1, []string{`c:\`, `d:\`}},
862
-		{`c:\windows\:d:`, -1, []string{`c:\windows\`, `d:`}},
863
-		{`c:\windows:d:\s p a c e`, -1, []string{`c:\windows`, `d:\s p a c e`}},
864
-		{`c:\windows:d:\s p a c e:RW`, -1, []string{`c:\windows`, `d:\s p a c e`, `RW`}},
865
-		{`c:\program files:d:\s p a c e i n h o s t d i r`, -1, []string{`c:\program files`, `d:\s p a c e i n h o s t d i r`}},
866
-		{`0123456789name:d:`, -1, []string{`0123456789name`, `d:`}},
867
-		{`MiXeDcAsEnAmE:d:`, -1, []string{`MiXeDcAsEnAmE`, `d:`}},
868
-		{`name:D:`, -1, []string{`name`, `D:`}},
869
-		{`name:D::rW`, -1, []string{`name`, `D:`, `rW`}},
870
-		{`name:D::RW`, -1, []string{`name`, `D:`, `RW`}},
871
-		{`c:/:d:/forward/slashes/are/good/too`, -1, []string{`c:/`, `d:/forward/slashes/are/good/too`}},
872
-		{`c:\Windows`, -1, []string{`c:\Windows`}},
873
-		{`c:\Program Files (x86)`, -1, []string{`c:\Program Files (x86)`}},
874
-
875
-		{``, -1, nil},
876
-		{`.`, -1, []string{`.`}},
877
-		{`..\`, -1, []string{`..\`}},
878
-		{`c:\:..\`, -1, []string{`c:\`, `..\`}},
879
-		{`c:\:d:\:xyzzy`, -1, []string{`c:\`, `d:\`, `xyzzy`}},
880
-
881
-		// Cover directories with one-character name
882
-		{`/tmp/x/y:/foo/x/y`, -1, []string{`/tmp/x/y`, `/foo/x/y`}},
883
-	} {
884
-		res := volumeSplitN(x.input, x.n)
885
-		if len(res) < len(x.expected) {
886
-			t.Fatalf("input: %v, expected: %v, got: %v", x.input, x.expected, res)
887
-		}
888
-		for i, e := range res {
889
-			if e != x.expected[i] {
890
-				t.Fatalf("input: %v, expected: %v, got: %v", x.input, x.expected, res)
891
-			}
892
-		}
893
-	}
894
-}
895 1
deleted file mode 100644
... ...
@@ -1,79 +0,0 @@
1
-package opts
2
-
3
-import (
4
-	"fmt"
5
-	"strings"
6
-
7
-	"github.com/docker/docker/api/types"
8
-)
9
-
10
-// RuntimeOpt defines a map of Runtimes
11
-type RuntimeOpt struct {
12
-	name             string
13
-	stockRuntimeName string
14
-	values           *map[string]types.Runtime
15
-}
16
-
17
-// NewNamedRuntimeOpt creates a new RuntimeOpt
18
-func NewNamedRuntimeOpt(name string, ref *map[string]types.Runtime, stockRuntime string) *RuntimeOpt {
19
-	if ref == nil {
20
-		ref = &map[string]types.Runtime{}
21
-	}
22
-	return &RuntimeOpt{name: name, values: ref, stockRuntimeName: stockRuntime}
23
-}
24
-
25
-// Name returns the name of the NamedListOpts in the configuration.
26
-func (o *RuntimeOpt) Name() string {
27
-	return o.name
28
-}
29
-
30
-// Set validates and updates the list of Runtimes
31
-func (o *RuntimeOpt) Set(val string) error {
32
-	parts := strings.SplitN(val, "=", 2)
33
-	if len(parts) != 2 {
34
-		return fmt.Errorf("invalid runtime argument: %s", val)
35
-	}
36
-
37
-	parts[0] = strings.TrimSpace(parts[0])
38
-	parts[1] = strings.TrimSpace(parts[1])
39
-	if parts[0] == "" || parts[1] == "" {
40
-		return fmt.Errorf("invalid runtime argument: %s", val)
41
-	}
42
-
43
-	parts[0] = strings.ToLower(parts[0])
44
-	if parts[0] == o.stockRuntimeName {
45
-		return fmt.Errorf("runtime name '%s' is reserved", o.stockRuntimeName)
46
-	}
47
-
48
-	if _, ok := (*o.values)[parts[0]]; ok {
49
-		return fmt.Errorf("runtime '%s' was already defined", parts[0])
50
-	}
51
-
52
-	(*o.values)[parts[0]] = types.Runtime{Path: parts[1]}
53
-
54
-	return nil
55
-}
56
-
57
-// String returns Runtime values as a string.
58
-func (o *RuntimeOpt) String() string {
59
-	var out []string
60
-	for k := range *o.values {
61
-		out = append(out, k)
62
-	}
63
-
64
-	return fmt.Sprintf("%v", out)
65
-}
66
-
67
-// GetMap returns a map of Runtimes (name: path)
68
-func (o *RuntimeOpt) GetMap() map[string]types.Runtime {
69
-	if o.values != nil {
70
-		return *o.values
71
-	}
72
-
73
-	return map[string]types.Runtime{}
74
-}
75
-
76
-// Type returns the type of the option
77
-func (o *RuntimeOpt) Type() string {
78
-	return "runtime"
79
-}
80 1
deleted file mode 100644
... ...
@@ -1,111 +0,0 @@
1
-package opts
2
-
3
-import (
4
-	"fmt"
5
-	"strconv"
6
-	"strings"
7
-
8
-	"github.com/docker/docker/api/types/blkiodev"
9
-	"github.com/docker/go-units"
10
-)
11
-
12
-// ValidatorThrottleFctType defines a validator function that returns a validated struct and/or an error.
13
-type ValidatorThrottleFctType func(val string) (*blkiodev.ThrottleDevice, error)
14
-
15
-// ValidateThrottleBpsDevice validates that the specified string has a valid device-rate format.
16
-func ValidateThrottleBpsDevice(val string) (*blkiodev.ThrottleDevice, error) {
17
-	split := strings.SplitN(val, ":", 2)
18
-	if len(split) != 2 {
19
-		return nil, fmt.Errorf("bad format: %s", val)
20
-	}
21
-	if !strings.HasPrefix(split[0], "/dev/") {
22
-		return nil, fmt.Errorf("bad format for device path: %s", val)
23
-	}
24
-	rate, err := units.RAMInBytes(split[1])
25
-	if err != nil {
26
-		return nil, fmt.Errorf("invalid rate for device: %s. The correct format is <device-path>:<number>[<unit>]. Number must be a positive integer. Unit is optional and can be kb, mb, or gb", val)
27
-	}
28
-	if rate < 0 {
29
-		return nil, fmt.Errorf("invalid rate for device: %s. The correct format is <device-path>:<number>[<unit>]. Number must be a positive integer. Unit is optional and can be kb, mb, or gb", val)
30
-	}
31
-
32
-	return &blkiodev.ThrottleDevice{
33
-		Path: split[0],
34
-		Rate: uint64(rate),
35
-	}, nil
36
-}
37
-
38
-// ValidateThrottleIOpsDevice validates that the specified string has a valid device-rate format.
39
-func ValidateThrottleIOpsDevice(val string) (*blkiodev.ThrottleDevice, error) {
40
-	split := strings.SplitN(val, ":", 2)
41
-	if len(split) != 2 {
42
-		return nil, fmt.Errorf("bad format: %s", val)
43
-	}
44
-	if !strings.HasPrefix(split[0], "/dev/") {
45
-		return nil, fmt.Errorf("bad format for device path: %s", val)
46
-	}
47
-	rate, err := strconv.ParseUint(split[1], 10, 64)
48
-	if err != nil {
49
-		return nil, fmt.Errorf("invalid rate for device: %s. The correct format is <device-path>:<number>. Number must be a positive integer", val)
50
-	}
51
-	if rate < 0 {
52
-		return nil, fmt.Errorf("invalid rate for device: %s. The correct format is <device-path>:<number>. Number must be a positive integer", val)
53
-	}
54
-
55
-	return &blkiodev.ThrottleDevice{
56
-		Path: split[0],
57
-		Rate: uint64(rate),
58
-	}, nil
59
-}
60
-
61
-// ThrottledeviceOpt defines a map of ThrottleDevices
62
-type ThrottledeviceOpt struct {
63
-	values    []*blkiodev.ThrottleDevice
64
-	validator ValidatorThrottleFctType
65
-}
66
-
67
-// NewThrottledeviceOpt creates a new ThrottledeviceOpt
68
-func NewThrottledeviceOpt(validator ValidatorThrottleFctType) ThrottledeviceOpt {
69
-	values := []*blkiodev.ThrottleDevice{}
70
-	return ThrottledeviceOpt{
71
-		values:    values,
72
-		validator: validator,
73
-	}
74
-}
75
-
76
-// Set validates a ThrottleDevice and sets its name as a key in ThrottledeviceOpt
77
-func (opt *ThrottledeviceOpt) Set(val string) error {
78
-	var value *blkiodev.ThrottleDevice
79
-	if opt.validator != nil {
80
-		v, err := opt.validator(val)
81
-		if err != nil {
82
-			return err
83
-		}
84
-		value = v
85
-	}
86
-	(opt.values) = append((opt.values), value)
87
-	return nil
88
-}
89
-
90
-// String returns ThrottledeviceOpt values as a string.
91
-func (opt *ThrottledeviceOpt) String() string {
92
-	var out []string
93
-	for _, v := range opt.values {
94
-		out = append(out, v.String())
95
-	}
96
-
97
-	return fmt.Sprintf("%v", out)
98
-}
99
-
100
-// GetList returns a slice of pointers to ThrottleDevices.
101
-func (opt *ThrottledeviceOpt) GetList() []*blkiodev.ThrottleDevice {
102
-	var throttledevice []*blkiodev.ThrottleDevice
103
-	throttledevice = append(throttledevice, opt.values...)
104
-
105
-	return throttledevice
106
-}
107
-
108
-// Type returns the option type
109
-func (opt *ThrottledeviceOpt) Type() string {
110
-	return "throttled-device"
111
-}
112 1
deleted file mode 100644
... ...
@@ -1,57 +0,0 @@
1
-package opts
2
-
3
-import (
4
-	"fmt"
5
-
6
-	"github.com/docker/go-units"
7
-)
8
-
9
-// UlimitOpt defines a map of Ulimits
10
-type UlimitOpt struct {
11
-	values *map[string]*units.Ulimit
12
-}
13
-
14
-// NewUlimitOpt creates a new UlimitOpt
15
-func NewUlimitOpt(ref *map[string]*units.Ulimit) *UlimitOpt {
16
-	if ref == nil {
17
-		ref = &map[string]*units.Ulimit{}
18
-	}
19
-	return &UlimitOpt{ref}
20
-}
21
-
22
-// Set validates a Ulimit and sets its name as a key in UlimitOpt
23
-func (o *UlimitOpt) Set(val string) error {
24
-	l, err := units.ParseUlimit(val)
25
-	if err != nil {
26
-		return err
27
-	}
28
-
29
-	(*o.values)[l.Name] = l
30
-
31
-	return nil
32
-}
33
-
34
-// String returns Ulimit values as a string.
35
-func (o *UlimitOpt) String() string {
36
-	var out []string
37
-	for _, v := range *o.values {
38
-		out = append(out, v.String())
39
-	}
40
-
41
-	return fmt.Sprintf("%v", out)
42
-}
43
-
44
-// GetList returns a slice of pointers to Ulimits.
45
-func (o *UlimitOpt) GetList() []*units.Ulimit {
46
-	var ulimits []*units.Ulimit
47
-	for _, v := range *o.values {
48
-		ulimits = append(ulimits, v)
49
-	}
50
-
51
-	return ulimits
52
-}
53
-
54
-// Type returns the option type
55
-func (o *UlimitOpt) Type() string {
56
-	return "ulimit"
57
-}
58 1
deleted file mode 100644
... ...
@@ -1,42 +0,0 @@
1
-package opts
2
-
3
-import (
4
-	"testing"
5
-
6
-	"github.com/docker/go-units"
7
-)
8
-
9
-func TestUlimitOpt(t *testing.T) {
10
-	ulimitMap := map[string]*units.Ulimit{
11
-		"nofile": {"nofile", 1024, 512},
12
-	}
13
-
14
-	ulimitOpt := NewUlimitOpt(&ulimitMap)
15
-
16
-	expected := "[nofile=512:1024]"
17
-	if ulimitOpt.String() != expected {
18
-		t.Fatalf("Expected %v, got %v", expected, ulimitOpt)
19
-	}
20
-
21
-	// Valid ulimit append to opts
22
-	if err := ulimitOpt.Set("core=1024:1024"); err != nil {
23
-		t.Fatal(err)
24
-	}
25
-
26
-	// Invalid ulimit type returns an error and do not append to opts
27
-	if err := ulimitOpt.Set("notavalidtype=1024:1024"); err == nil {
28
-		t.Fatalf("Expected error on invalid ulimit type")
29
-	}
30
-	expected = "[nofile=512:1024 core=1024:1024]"
31
-	expected2 := "[core=1024:1024 nofile=512:1024]"
32
-	result := ulimitOpt.String()
33
-	if result != expected && result != expected2 {
34
-		t.Fatalf("Expected %v or %v, got %v", expected, expected2, ulimitOpt)
35
-	}
36
-
37
-	// And test GetList
38
-	ulimits := ulimitOpt.GetList()
39
-	if len(ulimits) != 2 {
40
-		t.Fatalf("Expected a ulimit list of 2, got %v", ulimits)
41
-	}
42
-}
43 1
deleted file mode 100644
... ...
@@ -1,89 +0,0 @@
1
-package opts
2
-
3
-import (
4
-	"fmt"
5
-	"strconv"
6
-	"strings"
7
-
8
-	"github.com/docker/docker/api/types/blkiodev"
9
-)
10
-
11
-// ValidatorWeightFctType defines a validator function that returns a validated struct and/or an error.
12
-type ValidatorWeightFctType func(val string) (*blkiodev.WeightDevice, error)
13
-
14
-// ValidateWeightDevice validates that the specified string has a valid device-weight format.
15
-func ValidateWeightDevice(val string) (*blkiodev.WeightDevice, error) {
16
-	split := strings.SplitN(val, ":", 2)
17
-	if len(split) != 2 {
18
-		return nil, fmt.Errorf("bad format: %s", val)
19
-	}
20
-	if !strings.HasPrefix(split[0], "/dev/") {
21
-		return nil, fmt.Errorf("bad format for device path: %s", val)
22
-	}
23
-	weight, err := strconv.ParseUint(split[1], 10, 0)
24
-	if err != nil {
25
-		return nil, fmt.Errorf("invalid weight for device: %s", val)
26
-	}
27
-	if weight > 0 && (weight < 10 || weight > 1000) {
28
-		return nil, fmt.Errorf("invalid weight for device: %s", val)
29
-	}
30
-
31
-	return &blkiodev.WeightDevice{
32
-		Path:   split[0],
33
-		Weight: uint16(weight),
34
-	}, nil
35
-}
36
-
37
-// WeightdeviceOpt defines a map of WeightDevices
38
-type WeightdeviceOpt struct {
39
-	values    []*blkiodev.WeightDevice
40
-	validator ValidatorWeightFctType
41
-}
42
-
43
-// NewWeightdeviceOpt creates a new WeightdeviceOpt
44
-func NewWeightdeviceOpt(validator ValidatorWeightFctType) WeightdeviceOpt {
45
-	values := []*blkiodev.WeightDevice{}
46
-	return WeightdeviceOpt{
47
-		values:    values,
48
-		validator: validator,
49
-	}
50
-}
51
-
52
-// Set validates a WeightDevice and sets its name as a key in WeightdeviceOpt
53
-func (opt *WeightdeviceOpt) Set(val string) error {
54
-	var value *blkiodev.WeightDevice
55
-	if opt.validator != nil {
56
-		v, err := opt.validator(val)
57
-		if err != nil {
58
-			return err
59
-		}
60
-		value = v
61
-	}
62
-	(opt.values) = append((opt.values), value)
63
-	return nil
64
-}
65
-
66
-// String returns WeightdeviceOpt values as a string.
67
-func (opt *WeightdeviceOpt) String() string {
68
-	var out []string
69
-	for _, v := range opt.values {
70
-		out = append(out, v.String())
71
-	}
72
-
73
-	return fmt.Sprintf("%v", out)
74
-}
75
-
76
-// GetList returns a slice of pointers to WeightDevices.
77
-func (opt *WeightdeviceOpt) GetList() []*blkiodev.WeightDevice {
78
-	var weightdevice []*blkiodev.WeightDevice
79
-	for _, v := range opt.values {
80
-		weightdevice = append(weightdevice, v)
81
-	}
82
-
83
-	return weightdevice
84
-}
85
-
86
-// Type returns the option type
87
-func (opt *WeightdeviceOpt) Type() string {
88
-	return "weighted-device"
89
-}