Browse code

Move the runconfig.Parse() function into the runconfig/opts package.

The parse.go file is used almost exclusively in the client. The few small
functions that are used outside of the client could easily be copied out
when the client is extracted, allowing this runconfig/opts package to
move to the client.

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

Daniel Nephin authored on 2015/12/22 10:05:55
Showing 13 changed files
... ...
@@ -12,7 +12,7 @@ import (
12 12
 	"github.com/docker/docker/pkg/jsonmessage"
13 13
 	"github.com/docker/docker/reference"
14 14
 	"github.com/docker/docker/registry"
15
-	"github.com/docker/docker/runconfig"
15
+	runconfigopts "github.com/docker/docker/runconfig/opts"
16 16
 )
17 17
 
18 18
 func (cli *DockerCli) pullImage(image string) error {
... ...
@@ -156,7 +156,7 @@ func (cli *DockerCli) CmdCreate(args ...string) error {
156 156
 		flName = cmd.String([]string{"-name"}, "", "Assign a name to the container")
157 157
 	)
158 158
 
159
-	config, hostConfig, cmd, err := runconfig.Parse(cmd, args)
159
+	config, hostConfig, cmd, err := runconfigopts.Parse(cmd, args)
160 160
 	if err != nil {
161 161
 		cmd.ReportError(err.Error(), true)
162 162
 		os.Exit(1)
... ...
@@ -14,7 +14,7 @@ import (
14 14
 	"github.com/docker/docker/opts"
15 15
 	"github.com/docker/docker/pkg/promise"
16 16
 	"github.com/docker/docker/pkg/signal"
17
-	"github.com/docker/docker/runconfig"
17
+	runconfigopts "github.com/docker/docker/runconfig/opts"
18 18
 	"github.com/docker/libnetwork/resolvconf/dns"
19 19
 )
20 20
 
... ...
@@ -82,7 +82,7 @@ func (cli *DockerCli) CmdRun(args ...string) error {
82 82
 		ErrConflictDetachAutoRemove           = fmt.Errorf("Conflicting options: --rm and -d")
83 83
 	)
84 84
 
85
-	config, hostConfig, cmd, err := runconfig.Parse(cmd, args)
85
+	config, hostConfig, cmd, err := runconfigopts.Parse(cmd, args)
86 86
 	// just in case the Parse does not exit
87 87
 	if err != nil {
88 88
 		cmd.ReportError(err.Error(), true)
... ...
@@ -25,6 +25,7 @@ import (
25 25
 	"github.com/docker/docker/pkg/signal"
26 26
 	"github.com/docker/docker/pkg/system"
27 27
 	"github.com/docker/docker/runconfig"
28
+	runconfigopts "github.com/docker/docker/runconfig/opts"
28 29
 	"github.com/docker/go-connections/nat"
29 30
 )
30 31
 
... ...
@@ -337,7 +338,7 @@ func run(b *Builder, args []string, attributes map[string]bool, original string)
337 337
 	// of RUN, without leaking it to the final image. It also aids cache
338 338
 	// lookup for same image built with same build time environment.
339 339
 	cmdBuildEnv := []string{}
340
-	configEnv := runconfig.ConvertKVStringsToMap(b.runConfig.Env)
340
+	configEnv := runconfigopts.ConvertKVStringsToMap(b.runConfig.Env)
341 341
 	for key, val := range b.BuildArgs {
342 342
 		if !b.isBuildArgAllowed(key) {
343 343
 			// skip build-args that are not in allowed list, meaning they have
... ...
@@ -23,6 +23,7 @@ import (
23 23
 	"github.com/docker/docker/pkg/sysinfo"
24 24
 	"github.com/docker/docker/reference"
25 25
 	"github.com/docker/docker/runconfig"
26
+	runconfigopts "github.com/docker/docker/runconfig/opts"
26 27
 	"github.com/docker/libnetwork"
27 28
 	nwconfig "github.com/docker/libnetwork/config"
28 29
 	"github.com/docker/libnetwork/drivers/bridge"
... ...
@@ -681,7 +682,7 @@ func (daemon *Daemon) registerLinks(container *container.Container, hostConfig *
681 681
 	}
682 682
 
683 683
 	for _, l := range hostConfig.Links {
684
-		name, alias, err := runconfig.ParseLink(l)
684
+		name, alias, err := runconfigopts.ParseLink(l)
685 685
 		if err != nil {
686 686
 			return err
687 687
 		}
688 688
deleted file mode 100644
... ...
@@ -1,651 +0,0 @@
1
-package runconfig
2
-
3
-import (
4
-	"fmt"
5
-	"path"
6
-	"strconv"
7
-	"strings"
8
-
9
-	"github.com/docker/docker/api/types/container"
10
-	"github.com/docker/docker/api/types/strslice"
11
-	"github.com/docker/docker/opts"
12
-	flag "github.com/docker/docker/pkg/mflag"
13
-	"github.com/docker/docker/pkg/mount"
14
-	"github.com/docker/docker/pkg/signal"
15
-	runconfigopts "github.com/docker/docker/runconfig/opts"
16
-	"github.com/docker/docker/volume"
17
-	"github.com/docker/go-connections/nat"
18
-	"github.com/docker/go-units"
19
-)
20
-
21
-var (
22
-	// ErrConflictContainerNetworkAndLinks conflict between --net=container and links
23
-	ErrConflictContainerNetworkAndLinks = fmt.Errorf("Conflicting options: container type network can't be used with links. This would result in undefined behavior")
24
-	// ErrConflictUserDefinedNetworkAndLinks conflict between --net=<NETWORK> and links
25
-	ErrConflictUserDefinedNetworkAndLinks = fmt.Errorf("Conflicting options: networking can't be used with links. This would result in undefined behavior")
26
-	// ErrConflictSharedNetwork conflict between private and other networks
27
-	ErrConflictSharedNetwork = fmt.Errorf("Container sharing network namespace with another container or host cannot be connected to any other network")
28
-	// ErrConflictHostNetwork conflict from being disconnected from host network or connected to host network.
29
-	ErrConflictHostNetwork = fmt.Errorf("Container cannot be disconnected from host network or connected to host network")
30
-	// ErrConflictNoNetwork conflict between private and other networks
31
-	ErrConflictNoNetwork = fmt.Errorf("Container cannot be connected to multiple networks with one of the networks in private (none) mode")
32
-	// ErrConflictNetworkAndDNS conflict between --dns and the network mode
33
-	ErrConflictNetworkAndDNS = fmt.Errorf("Conflicting options: dns and the network mode")
34
-	// ErrConflictNetworkHostname conflict between the hostname and the network mode
35
-	ErrConflictNetworkHostname = fmt.Errorf("Conflicting options: hostname and the network mode")
36
-	// ErrConflictHostNetworkAndLinks conflict between --net=host and links
37
-	ErrConflictHostNetworkAndLinks = fmt.Errorf("Conflicting options: host type networking can't be used with links. This would result in undefined behavior")
38
-	// ErrConflictContainerNetworkAndMac conflict between the mac address and the network mode
39
-	ErrConflictContainerNetworkAndMac = fmt.Errorf("Conflicting options: mac-address and the network mode")
40
-	// ErrConflictNetworkHosts conflict between add-host and the network mode
41
-	ErrConflictNetworkHosts = fmt.Errorf("Conflicting options: custom host-to-IP mapping and the network mode")
42
-	// ErrConflictNetworkPublishPorts conflict between the publish options and the network mode
43
-	ErrConflictNetworkPublishPorts = fmt.Errorf("Conflicting options: port publishing and the container type network mode")
44
-	// ErrConflictNetworkExposePorts conflict between the expose option and the network mode
45
-	ErrConflictNetworkExposePorts = fmt.Errorf("Conflicting options: port exposing and the container type network mode")
46
-)
47
-
48
-// Parse parses the specified args for the specified command and generates a Config,
49
-// a HostConfig and returns them with the specified command.
50
-// If the specified args are not valid, it will return an error.
51
-func Parse(cmd *flag.FlagSet, args []string) (*container.Config, *container.HostConfig, *flag.FlagSet, error) {
52
-	var (
53
-		// FIXME: use utils.ListOpts for attach and volumes?
54
-		flAttach            = opts.NewListOpts(opts.ValidateAttach)
55
-		flVolumes           = opts.NewListOpts(nil)
56
-		flTmpfs             = opts.NewListOpts(nil)
57
-		flBlkioWeightDevice = runconfigopts.NewWeightdeviceOpt(runconfigopts.ValidateWeightDevice)
58
-		flDeviceReadBps     = runconfigopts.NewThrottledeviceOpt(runconfigopts.ValidateThrottleBpsDevice)
59
-		flDeviceWriteBps    = runconfigopts.NewThrottledeviceOpt(runconfigopts.ValidateThrottleBpsDevice)
60
-		flLinks             = opts.NewListOpts(ValidateLink)
61
-		flDeviceReadIOps    = runconfigopts.NewThrottledeviceOpt(runconfigopts.ValidateThrottleIOpsDevice)
62
-		flDeviceWriteIOps   = runconfigopts.NewThrottledeviceOpt(runconfigopts.ValidateThrottleIOpsDevice)
63
-		flEnv               = opts.NewListOpts(opts.ValidateEnv)
64
-		flLabels            = opts.NewListOpts(opts.ValidateEnv)
65
-		flDevices           = opts.NewListOpts(ValidateDevice)
66
-
67
-		flUlimits = runconfigopts.NewUlimitOpt(nil)
68
-
69
-		flPublish           = opts.NewListOpts(nil)
70
-		flExpose            = opts.NewListOpts(nil)
71
-		flDNS               = opts.NewListOpts(opts.ValidateIPAddress)
72
-		flDNSSearch         = opts.NewListOpts(opts.ValidateDNSSearch)
73
-		flDNSOptions        = opts.NewListOpts(nil)
74
-		flExtraHosts        = opts.NewListOpts(opts.ValidateExtraHost)
75
-		flVolumesFrom       = opts.NewListOpts(nil)
76
-		flEnvFile           = opts.NewListOpts(nil)
77
-		flCapAdd            = opts.NewListOpts(nil)
78
-		flCapDrop           = opts.NewListOpts(nil)
79
-		flGroupAdd          = opts.NewListOpts(nil)
80
-		flSecurityOpt       = opts.NewListOpts(nil)
81
-		flLabelsFile        = opts.NewListOpts(nil)
82
-		flLoggingOpts       = opts.NewListOpts(nil)
83
-		flPrivileged        = cmd.Bool([]string{"-privileged"}, false, "Give extended privileges to this container")
84
-		flPidMode           = cmd.String([]string{"-pid"}, "", "PID namespace to use")
85
-		flUTSMode           = cmd.String([]string{"-uts"}, "", "UTS namespace to use")
86
-		flPublishAll        = cmd.Bool([]string{"P", "-publish-all"}, false, "Publish all exposed ports to random ports")
87
-		flStdin             = cmd.Bool([]string{"i", "-interactive"}, false, "Keep STDIN open even if not attached")
88
-		flTty               = cmd.Bool([]string{"t", "-tty"}, false, "Allocate a pseudo-TTY")
89
-		flOomKillDisable    = cmd.Bool([]string{"-oom-kill-disable"}, false, "Disable OOM Killer")
90
-		flOomScoreAdj       = cmd.Int([]string{"-oom-score-adj"}, 0, "Tune host's OOM preferences (-1000 to 1000)")
91
-		flContainerIDFile   = cmd.String([]string{"-cidfile"}, "", "Write the container ID to the file")
92
-		flEntrypoint        = cmd.String([]string{"-entrypoint"}, "", "Overwrite the default ENTRYPOINT of the image")
93
-		flHostname          = cmd.String([]string{"h", "-hostname"}, "", "Container host name")
94
-		flMemoryString      = cmd.String([]string{"m", "-memory"}, "", "Memory limit")
95
-		flMemoryReservation = cmd.String([]string{"-memory-reservation"}, "", "Memory soft limit")
96
-		flMemorySwap        = cmd.String([]string{"-memory-swap"}, "", "Swap limit equal to memory plus swap: '-1' to enable unlimited swap")
97
-		flKernelMemory      = cmd.String([]string{"-kernel-memory"}, "", "Kernel memory limit")
98
-		flUser              = cmd.String([]string{"u", "-user"}, "", "Username or UID (format: <name|uid>[:<group|gid>])")
99
-		flWorkingDir        = cmd.String([]string{"w", "-workdir"}, "", "Working directory inside the container")
100
-		flCPUShares         = cmd.Int64([]string{"#c", "-cpu-shares"}, 0, "CPU shares (relative weight)")
101
-		flCPUPeriod         = cmd.Int64([]string{"-cpu-period"}, 0, "Limit CPU CFS (Completely Fair Scheduler) period")
102
-		flCPUQuota          = cmd.Int64([]string{"-cpu-quota"}, 0, "Limit CPU CFS (Completely Fair Scheduler) quota")
103
-		flCpusetCpus        = cmd.String([]string{"-cpuset-cpus"}, "", "CPUs in which to allow execution (0-3, 0,1)")
104
-		flCpusetMems        = cmd.String([]string{"-cpuset-mems"}, "", "MEMs in which to allow execution (0-3, 0,1)")
105
-		flBlkioWeight       = cmd.Uint16([]string{"-blkio-weight"}, 0, "Block IO (relative weight), between 10 and 1000")
106
-		flSwappiness        = cmd.Int64([]string{"-memory-swappiness"}, -1, "Tune container memory swappiness (0 to 100)")
107
-		flNetMode           = cmd.String([]string{"-net"}, "default", "Connect a container to a network")
108
-		flMacAddress        = cmd.String([]string{"-mac-address"}, "", "Container MAC address (e.g. 92:d0:c6:0a:29:33)")
109
-		flIpcMode           = cmd.String([]string{"-ipc"}, "", "IPC namespace to use")
110
-		flRestartPolicy     = cmd.String([]string{"-restart"}, "no", "Restart policy to apply when a container exits")
111
-		flReadonlyRootfs    = cmd.Bool([]string{"-read-only"}, false, "Mount the container's root filesystem as read only")
112
-		flLoggingDriver     = cmd.String([]string{"-log-driver"}, "", "Logging driver for container")
113
-		flCgroupParent      = cmd.String([]string{"-cgroup-parent"}, "", "Optional parent cgroup for the container")
114
-		flVolumeDriver      = cmd.String([]string{"-volume-driver"}, "", "Optional volume driver for the container")
115
-		flStopSignal        = cmd.String([]string{"-stop-signal"}, signal.DefaultStopSignal, fmt.Sprintf("Signal to stop a container, %v by default", signal.DefaultStopSignal))
116
-		flIsolation         = cmd.String([]string{"-isolation"}, "", "Container isolation level")
117
-		flShmSize           = cmd.String([]string{"-shm-size"}, "", "Size of /dev/shm, default value is 64MB")
118
-	)
119
-
120
-	cmd.Var(&flAttach, []string{"a", "-attach"}, "Attach to STDIN, STDOUT or STDERR")
121
-	cmd.Var(&flBlkioWeightDevice, []string{"-blkio-weight-device"}, "Block IO weight (relative device weight)")
122
-	cmd.Var(&flDeviceReadBps, []string{"-device-read-bps"}, "Limit read rate (bytes per second) from a device")
123
-	cmd.Var(&flDeviceWriteBps, []string{"-device-write-bps"}, "Limit write rate (bytes per second) to a device")
124
-	cmd.Var(&flDeviceReadIOps, []string{"-device-read-iops"}, "Limit read rate (IO per second) from a device")
125
-	cmd.Var(&flDeviceWriteIOps, []string{"-device-write-iops"}, "Limit write rate (IO per second) to a device")
126
-	cmd.Var(&flVolumes, []string{"v", "-volume"}, "Bind mount a volume")
127
-	cmd.Var(&flTmpfs, []string{"-tmpfs"}, "Mount a tmpfs directory")
128
-	cmd.Var(&flLinks, []string{"-link"}, "Add link to another container")
129
-	cmd.Var(&flDevices, []string{"-device"}, "Add a host device to the container")
130
-	cmd.Var(&flLabels, []string{"l", "-label"}, "Set meta data on a container")
131
-	cmd.Var(&flLabelsFile, []string{"-label-file"}, "Read in a line delimited file of labels")
132
-	cmd.Var(&flEnv, []string{"e", "-env"}, "Set environment variables")
133
-	cmd.Var(&flEnvFile, []string{"-env-file"}, "Read in a file of environment variables")
134
-	cmd.Var(&flPublish, []string{"p", "-publish"}, "Publish a container's port(s) to the host")
135
-	cmd.Var(&flExpose, []string{"-expose"}, "Expose a port or a range of ports")
136
-	cmd.Var(&flDNS, []string{"-dns"}, "Set custom DNS servers")
137
-	cmd.Var(&flDNSSearch, []string{"-dns-search"}, "Set custom DNS search domains")
138
-	cmd.Var(&flDNSOptions, []string{"-dns-opt"}, "Set DNS options")
139
-	cmd.Var(&flExtraHosts, []string{"-add-host"}, "Add a custom host-to-IP mapping (host:ip)")
140
-	cmd.Var(&flVolumesFrom, []string{"-volumes-from"}, "Mount volumes from the specified container(s)")
141
-	cmd.Var(&flCapAdd, []string{"-cap-add"}, "Add Linux capabilities")
142
-	cmd.Var(&flCapDrop, []string{"-cap-drop"}, "Drop Linux capabilities")
143
-	cmd.Var(&flGroupAdd, []string{"-group-add"}, "Add additional groups to join")
144
-	cmd.Var(&flSecurityOpt, []string{"-security-opt"}, "Security Options")
145
-	cmd.Var(flUlimits, []string{"-ulimit"}, "Ulimit options")
146
-	cmd.Var(&flLoggingOpts, []string{"-log-opt"}, "Log driver options")
147
-
148
-	cmd.Require(flag.Min, 1)
149
-
150
-	if err := cmd.ParseFlags(args, true); err != nil {
151
-		return nil, nil, cmd, err
152
-	}
153
-
154
-	var (
155
-		attachStdin  = flAttach.Get("stdin")
156
-		attachStdout = flAttach.Get("stdout")
157
-		attachStderr = flAttach.Get("stderr")
158
-	)
159
-
160
-	// Validate the input mac address
161
-	if *flMacAddress != "" {
162
-		if _, err := opts.ValidateMACAddress(*flMacAddress); err != nil {
163
-			return nil, nil, cmd, fmt.Errorf("%s is not a valid mac address", *flMacAddress)
164
-		}
165
-	}
166
-	if *flStdin {
167
-		attachStdin = true
168
-	}
169
-	// If -a is not set attach to the output stdio
170
-	if flAttach.Len() == 0 {
171
-		attachStdout = true
172
-		attachStderr = true
173
-	}
174
-
175
-	var err error
176
-
177
-	var flMemory int64
178
-	if *flMemoryString != "" {
179
-		flMemory, err = units.RAMInBytes(*flMemoryString)
180
-		if err != nil {
181
-			return nil, nil, cmd, err
182
-		}
183
-	}
184
-
185
-	var MemoryReservation int64
186
-	if *flMemoryReservation != "" {
187
-		MemoryReservation, err = units.RAMInBytes(*flMemoryReservation)
188
-		if err != nil {
189
-			return nil, nil, cmd, err
190
-		}
191
-	}
192
-
193
-	var memorySwap int64
194
-	if *flMemorySwap != "" {
195
-		if *flMemorySwap == "-1" {
196
-			memorySwap = -1
197
-		} else {
198
-			memorySwap, err = units.RAMInBytes(*flMemorySwap)
199
-			if err != nil {
200
-				return nil, nil, cmd, err
201
-			}
202
-		}
203
-	}
204
-
205
-	var KernelMemory int64
206
-	if *flKernelMemory != "" {
207
-		KernelMemory, err = units.RAMInBytes(*flKernelMemory)
208
-		if err != nil {
209
-			return nil, nil, cmd, err
210
-		}
211
-	}
212
-
213
-	swappiness := *flSwappiness
214
-	if swappiness != -1 && (swappiness < 0 || swappiness > 100) {
215
-		return nil, nil, cmd, fmt.Errorf("Invalid value: %d. Valid memory swappiness range is 0-100", swappiness)
216
-	}
217
-
218
-	var parsedShm *int64
219
-	if *flShmSize != "" {
220
-		shmSize, err := units.RAMInBytes(*flShmSize)
221
-		if err != nil {
222
-			return nil, nil, cmd, err
223
-		}
224
-		parsedShm = &shmSize
225
-	}
226
-
227
-	var binds []string
228
-	// add any bind targets to the list of container volumes
229
-	for bind := range flVolumes.GetMap() {
230
-		if arr := volume.SplitN(bind, 2); len(arr) > 1 {
231
-			// after creating the bind mount we want to delete it from the flVolumes values because
232
-			// we do not want bind mounts being committed to image configs
233
-			binds = append(binds, bind)
234
-			flVolumes.Delete(bind)
235
-		}
236
-	}
237
-
238
-	// Can't evaluate options passed into --tmpfs until we actually mount
239
-	tmpfs := make(map[string]string)
240
-	for _, t := range flTmpfs.GetAll() {
241
-		if arr := strings.SplitN(t, ":", 2); len(arr) > 1 {
242
-			if _, _, err := mount.ParseTmpfsOptions(arr[1]); err != nil {
243
-				return nil, nil, cmd, err
244
-			}
245
-			tmpfs[arr[0]] = arr[1]
246
-		} else {
247
-			tmpfs[arr[0]] = ""
248
-		}
249
-	}
250
-
251
-	var (
252
-		parsedArgs = cmd.Args()
253
-		runCmd     *strslice.StrSlice
254
-		entrypoint *strslice.StrSlice
255
-		image      = cmd.Arg(0)
256
-	)
257
-	if len(parsedArgs) > 1 {
258
-		runCmd = strslice.New(parsedArgs[1:]...)
259
-	}
260
-	if *flEntrypoint != "" {
261
-		entrypoint = strslice.New(*flEntrypoint)
262
-	}
263
-
264
-	var (
265
-		domainname string
266
-		hostname   = *flHostname
267
-		parts      = strings.SplitN(hostname, ".", 2)
268
-	)
269
-	if len(parts) > 1 {
270
-		hostname = parts[0]
271
-		domainname = parts[1]
272
-	}
273
-
274
-	ports, portBindings, err := nat.ParsePortSpecs(flPublish.GetAll())
275
-	if err != nil {
276
-		return nil, nil, cmd, err
277
-	}
278
-
279
-	// Merge in exposed ports to the map of published ports
280
-	for _, e := range flExpose.GetAll() {
281
-		if strings.Contains(e, ":") {
282
-			return nil, nil, cmd, fmt.Errorf("Invalid port format for --expose: %s", e)
283
-		}
284
-		//support two formats for expose, original format <portnum>/[<proto>] or <startport-endport>/[<proto>]
285
-		proto, port := nat.SplitProtoPort(e)
286
-		//parse the start and end port and create a sequence of ports to expose
287
-		//if expose a port, the start and end port are the same
288
-		start, end, err := nat.ParsePortRange(port)
289
-		if err != nil {
290
-			return nil, nil, cmd, fmt.Errorf("Invalid range format for --expose: %s, error: %s", e, err)
291
-		}
292
-		for i := start; i <= end; i++ {
293
-			p, err := nat.NewPort(proto, strconv.FormatUint(i, 10))
294
-			if err != nil {
295
-				return nil, nil, cmd, err
296
-			}
297
-			if _, exists := ports[p]; !exists {
298
-				ports[p] = struct{}{}
299
-			}
300
-		}
301
-	}
302
-
303
-	// parse device mappings
304
-	deviceMappings := []container.DeviceMapping{}
305
-	for _, device := range flDevices.GetAll() {
306
-		deviceMapping, err := ParseDevice(device)
307
-		if err != nil {
308
-			return nil, nil, cmd, err
309
-		}
310
-		deviceMappings = append(deviceMappings, deviceMapping)
311
-	}
312
-
313
-	// collect all the environment variables for the container
314
-	envVariables, err := readKVStrings(flEnvFile.GetAll(), flEnv.GetAll())
315
-	if err != nil {
316
-		return nil, nil, cmd, err
317
-	}
318
-
319
-	// collect all the labels for the container
320
-	labels, err := readKVStrings(flLabelsFile.GetAll(), flLabels.GetAll())
321
-	if err != nil {
322
-		return nil, nil, cmd, err
323
-	}
324
-
325
-	ipcMode := container.IpcMode(*flIpcMode)
326
-	if !ipcMode.Valid() {
327
-		return nil, nil, cmd, fmt.Errorf("--ipc: invalid IPC mode")
328
-	}
329
-
330
-	pidMode := container.PidMode(*flPidMode)
331
-	if !pidMode.Valid() {
332
-		return nil, nil, cmd, fmt.Errorf("--pid: invalid PID mode")
333
-	}
334
-
335
-	utsMode := container.UTSMode(*flUTSMode)
336
-	if !utsMode.Valid() {
337
-		return nil, nil, cmd, fmt.Errorf("--uts: invalid UTS mode")
338
-	}
339
-
340
-	restartPolicy, err := ParseRestartPolicy(*flRestartPolicy)
341
-	if err != nil {
342
-		return nil, nil, cmd, err
343
-	}
344
-
345
-	loggingOpts, err := parseLoggingOpts(*flLoggingDriver, flLoggingOpts.GetAll())
346
-	if err != nil {
347
-		return nil, nil, cmd, err
348
-	}
349
-
350
-	resources := container.Resources{
351
-		CgroupParent:         *flCgroupParent,
352
-		Memory:               flMemory,
353
-		MemoryReservation:    MemoryReservation,
354
-		MemorySwap:           memorySwap,
355
-		MemorySwappiness:     flSwappiness,
356
-		KernelMemory:         KernelMemory,
357
-		OomKillDisable:       *flOomKillDisable,
358
-		CPUShares:            *flCPUShares,
359
-		CPUPeriod:            *flCPUPeriod,
360
-		CpusetCpus:           *flCpusetCpus,
361
-		CpusetMems:           *flCpusetMems,
362
-		CPUQuota:             *flCPUQuota,
363
-		BlkioWeight:          *flBlkioWeight,
364
-		BlkioWeightDevice:    flBlkioWeightDevice.GetList(),
365
-		BlkioDeviceReadBps:   flDeviceReadBps.GetList(),
366
-		BlkioDeviceWriteBps:  flDeviceWriteBps.GetList(),
367
-		BlkioDeviceReadIOps:  flDeviceReadIOps.GetList(),
368
-		BlkioDeviceWriteIOps: flDeviceWriteIOps.GetList(),
369
-		Ulimits:              flUlimits.GetList(),
370
-		Devices:              deviceMappings,
371
-	}
372
-
373
-	config := &container.Config{
374
-		Hostname:     hostname,
375
-		Domainname:   domainname,
376
-		ExposedPorts: ports,
377
-		User:         *flUser,
378
-		Tty:          *flTty,
379
-		// TODO: deprecated, it comes from -n, --networking
380
-		// it's still needed internally to set the network to disabled
381
-		// if e.g. bridge is none in daemon opts, and in inspect
382
-		NetworkDisabled: false,
383
-		OpenStdin:       *flStdin,
384
-		AttachStdin:     attachStdin,
385
-		AttachStdout:    attachStdout,
386
-		AttachStderr:    attachStderr,
387
-		Env:             envVariables,
388
-		Cmd:             runCmd,
389
-		Image:           image,
390
-		Volumes:         flVolumes.GetMap(),
391
-		MacAddress:      *flMacAddress,
392
-		Entrypoint:      entrypoint,
393
-		WorkingDir:      *flWorkingDir,
394
-		Labels:          ConvertKVStringsToMap(labels),
395
-		StopSignal:      *flStopSignal,
396
-	}
397
-
398
-	hostConfig := &container.HostConfig{
399
-		Binds:           binds,
400
-		ContainerIDFile: *flContainerIDFile,
401
-		OomScoreAdj:     *flOomScoreAdj,
402
-		Privileged:      *flPrivileged,
403
-		PortBindings:    portBindings,
404
-		Links:           flLinks.GetAll(),
405
-		PublishAllPorts: *flPublishAll,
406
-		// Make sure the dns fields are never nil.
407
-		// New containers don't ever have those fields nil,
408
-		// but pre created containers can still have those nil values.
409
-		// See https://github.com/docker/docker/pull/17779
410
-		// for a more detailed explanation on why we don't want that.
411
-		DNS:            flDNS.GetAllOrEmpty(),
412
-		DNSSearch:      flDNSSearch.GetAllOrEmpty(),
413
-		DNSOptions:     flDNSOptions.GetAllOrEmpty(),
414
-		ExtraHosts:     flExtraHosts.GetAll(),
415
-		VolumesFrom:    flVolumesFrom.GetAll(),
416
-		NetworkMode:    container.NetworkMode(*flNetMode),
417
-		IpcMode:        ipcMode,
418
-		PidMode:        pidMode,
419
-		UTSMode:        utsMode,
420
-		CapAdd:         strslice.New(flCapAdd.GetAll()...),
421
-		CapDrop:        strslice.New(flCapDrop.GetAll()...),
422
-		GroupAdd:       flGroupAdd.GetAll(),
423
-		RestartPolicy:  restartPolicy,
424
-		SecurityOpt:    flSecurityOpt.GetAll(),
425
-		ReadonlyRootfs: *flReadonlyRootfs,
426
-		LogConfig:      container.LogConfig{Type: *flLoggingDriver, Config: loggingOpts},
427
-		VolumeDriver:   *flVolumeDriver,
428
-		Isolation:      container.IsolationLevel(*flIsolation),
429
-		ShmSize:        parsedShm,
430
-		Resources:      resources,
431
-		Tmpfs:          tmpfs,
432
-	}
433
-
434
-	// When allocating stdin in attached mode, close stdin at client disconnect
435
-	if config.OpenStdin && config.AttachStdin {
436
-		config.StdinOnce = true
437
-	}
438
-	return config, hostConfig, cmd, nil
439
-}
440
-
441
-// reads a file of line terminated key=value pairs and override that with override parameter
442
-func readKVStrings(files []string, override []string) ([]string, error) {
443
-	envVariables := []string{}
444
-	for _, ef := range files {
445
-		parsedVars, err := opts.ParseEnvFile(ef)
446
-		if err != nil {
447
-			return nil, err
448
-		}
449
-		envVariables = append(envVariables, parsedVars...)
450
-	}
451
-	// parse the '-e' and '--env' after, to allow override
452
-	envVariables = append(envVariables, override...)
453
-
454
-	return envVariables, nil
455
-}
456
-
457
-// ConvertKVStringsToMap converts ["key=value"] to {"key":"value"}
458
-func ConvertKVStringsToMap(values []string) map[string]string {
459
-	result := make(map[string]string, len(values))
460
-	for _, value := range values {
461
-		kv := strings.SplitN(value, "=", 2)
462
-		if len(kv) == 1 {
463
-			result[kv[0]] = ""
464
-		} else {
465
-			result[kv[0]] = kv[1]
466
-		}
467
-	}
468
-
469
-	return result
470
-}
471
-
472
-func parseLoggingOpts(loggingDriver string, loggingOpts []string) (map[string]string, error) {
473
-	loggingOptsMap := ConvertKVStringsToMap(loggingOpts)
474
-	if loggingDriver == "none" && len(loggingOpts) > 0 {
475
-		return map[string]string{}, fmt.Errorf("Invalid logging opts for driver %s", loggingDriver)
476
-	}
477
-	return loggingOptsMap, nil
478
-}
479
-
480
-// ParseRestartPolicy returns the parsed policy or an error indicating what is incorrect
481
-func ParseRestartPolicy(policy string) (container.RestartPolicy, error) {
482
-	p := container.RestartPolicy{}
483
-
484
-	if policy == "" {
485
-		return p, nil
486
-	}
487
-
488
-	var (
489
-		parts = strings.Split(policy, ":")
490
-		name  = parts[0]
491
-	)
492
-
493
-	p.Name = name
494
-	switch name {
495
-	case "always", "unless-stopped":
496
-		if len(parts) > 1 {
497
-			return p, fmt.Errorf("maximum restart count not valid with restart policy of \"%s\"", name)
498
-		}
499
-	case "no":
500
-		// do nothing
501
-	case "on-failure":
502
-		if len(parts) > 2 {
503
-			return p, fmt.Errorf("restart count format is not valid, usage: 'on-failure:N' or 'on-failure'")
504
-		}
505
-		if len(parts) == 2 {
506
-			count, err := strconv.Atoi(parts[1])
507
-			if err != nil {
508
-				return p, err
509
-			}
510
-
511
-			p.MaximumRetryCount = count
512
-		}
513
-	default:
514
-		return p, fmt.Errorf("invalid restart policy %s", name)
515
-	}
516
-
517
-	return p, nil
518
-}
519
-
520
-// ParseDevice parses a device mapping string to a container.DeviceMapping struct
521
-func ParseDevice(device string) (container.DeviceMapping, error) {
522
-	src := ""
523
-	dst := ""
524
-	permissions := "rwm"
525
-	arr := strings.Split(device, ":")
526
-	switch len(arr) {
527
-	case 3:
528
-		permissions = arr[2]
529
-		fallthrough
530
-	case 2:
531
-		if ValidDeviceMode(arr[1]) {
532
-			permissions = arr[1]
533
-		} else {
534
-			dst = arr[1]
535
-		}
536
-		fallthrough
537
-	case 1:
538
-		src = arr[0]
539
-	default:
540
-		return container.DeviceMapping{}, fmt.Errorf("Invalid device specification: %s", device)
541
-	}
542
-
543
-	if dst == "" {
544
-		dst = src
545
-	}
546
-
547
-	deviceMapping := container.DeviceMapping{
548
-		PathOnHost:        src,
549
-		PathInContainer:   dst,
550
-		CgroupPermissions: permissions,
551
-	}
552
-	return deviceMapping, nil
553
-}
554
-
555
-// ParseLink parses and validates the specified string as a link format (name:alias)
556
-func ParseLink(val string) (string, string, error) {
557
-	if val == "" {
558
-		return "", "", fmt.Errorf("empty string specified for links")
559
-	}
560
-	arr := strings.Split(val, ":")
561
-	if len(arr) > 2 {
562
-		return "", "", fmt.Errorf("bad format for links: %s", val)
563
-	}
564
-	if len(arr) == 1 {
565
-		return val, val, nil
566
-	}
567
-	// This is kept because we can actually get an HostConfig with links
568
-	// from an already created container and the format is not `foo:bar`
569
-	// but `/foo:/c1/bar`
570
-	if strings.HasPrefix(arr[0], "/") {
571
-		_, alias := path.Split(arr[1])
572
-		return arr[0][1:], alias, nil
573
-	}
574
-	return arr[0], arr[1], nil
575
-}
576
-
577
-// ValidateLink validates that the specified string has a valid link format (containerName:alias).
578
-func ValidateLink(val string) (string, error) {
579
-	if _, _, err := ParseLink(val); err != nil {
580
-		return val, err
581
-	}
582
-	return val, nil
583
-}
584
-
585
-// ValidDeviceMode checks if the mode for device is valid or not.
586
-// Valid mode is a composition of r (read), w (write), and m (mknod).
587
-func ValidDeviceMode(mode string) bool {
588
-	var legalDeviceMode = map[rune]bool{
589
-		'r': true,
590
-		'w': true,
591
-		'm': true,
592
-	}
593
-	if mode == "" {
594
-		return false
595
-	}
596
-	for _, c := range mode {
597
-		if !legalDeviceMode[c] {
598
-			return false
599
-		}
600
-		legalDeviceMode[c] = false
601
-	}
602
-	return true
603
-}
604
-
605
-// ValidateDevice validates a path for devices
606
-// It will make sure 'val' is in the form:
607
-//    [host-dir:]container-path[:mode]
608
-// It also validates the device mode.
609
-func ValidateDevice(val string) (string, error) {
610
-	return validatePath(val, ValidDeviceMode)
611
-}
612
-
613
-func validatePath(val string, validator func(string) bool) (string, error) {
614
-	var containerPath string
615
-	var mode string
616
-
617
-	if strings.Count(val, ":") > 2 {
618
-		return val, fmt.Errorf("bad format for path: %s", val)
619
-	}
620
-
621
-	split := strings.SplitN(val, ":", 3)
622
-	if split[0] == "" {
623
-		return val, fmt.Errorf("bad format for path: %s", val)
624
-	}
625
-	switch len(split) {
626
-	case 1:
627
-		containerPath = split[0]
628
-		val = path.Clean(containerPath)
629
-	case 2:
630
-		if isValid := validator(split[1]); isValid {
631
-			containerPath = split[0]
632
-			mode = split[1]
633
-			val = fmt.Sprintf("%s:%s", path.Clean(containerPath), mode)
634
-		} else {
635
-			containerPath = split[1]
636
-			val = fmt.Sprintf("%s:%s", split[0], path.Clean(containerPath))
637
-		}
638
-	case 3:
639
-		containerPath = split[1]
640
-		mode = split[2]
641
-		if isValid := validator(split[2]); !isValid {
642
-			return val, fmt.Errorf("bad mode specified: %s", mode)
643
-		}
644
-		val = fmt.Sprintf("%s:%s:%s", split[0], containerPath, mode)
645
-	}
646
-
647
-	if !path.IsAbs(containerPath) {
648
-		return val, fmt.Errorf("%s is not an absolute path", containerPath)
649
-	}
650
-	return val, nil
651
-}
652 1
deleted file mode 100644
... ...
@@ -1,764 +0,0 @@
1
-package runconfig
2
-
3
-import (
4
-	"bytes"
5
-	"encoding/json"
6
-	"fmt"
7
-	"io/ioutil"
8
-	"os"
9
-	"runtime"
10
-	"strings"
11
-	"testing"
12
-
13
-	"github.com/docker/docker/api/types/container"
14
-	flag "github.com/docker/docker/pkg/mflag"
15
-	"github.com/docker/go-connections/nat"
16
-)
17
-
18
-func parseRun(args []string) (*container.Config, *container.HostConfig, *flag.FlagSet, error) {
19
-	cmd := flag.NewFlagSet("run", flag.ContinueOnError)
20
-	cmd.SetOutput(ioutil.Discard)
21
-	cmd.Usage = nil
22
-	return Parse(cmd, args)
23
-}
24
-
25
-func parse(t *testing.T, args string) (*container.Config, *container.HostConfig, error) {
26
-	config, hostConfig, _, err := parseRun(strings.Split(args+" ubuntu bash", " "))
27
-	return config, hostConfig, err
28
-}
29
-
30
-func mustParse(t *testing.T, args string) (*container.Config, *container.HostConfig) {
31
-	config, hostConfig, err := parse(t, args)
32
-	if err != nil {
33
-		t.Fatal(err)
34
-	}
35
-	return config, hostConfig
36
-}
37
-
38
-func TestParseRunLinks(t *testing.T) {
39
-	if _, hostConfig := mustParse(t, "--link a:b"); len(hostConfig.Links) == 0 || hostConfig.Links[0] != "a:b" {
40
-		t.Fatalf("Error parsing links. Expected []string{\"a:b\"}, received: %v", hostConfig.Links)
41
-	}
42
-	if _, hostConfig := mustParse(t, "--link a:b --link c:d"); len(hostConfig.Links) < 2 || hostConfig.Links[0] != "a:b" || hostConfig.Links[1] != "c:d" {
43
-		t.Fatalf("Error parsing links. Expected []string{\"a:b\", \"c:d\"}, received: %v", hostConfig.Links)
44
-	}
45
-	if _, hostConfig := mustParse(t, ""); len(hostConfig.Links) != 0 {
46
-		t.Fatalf("Error parsing links. No link expected, received: %v", hostConfig.Links)
47
-	}
48
-}
49
-
50
-func TestParseRunAttach(t *testing.T) {
51
-	if config, _ := mustParse(t, "-a stdin"); !config.AttachStdin || config.AttachStdout || config.AttachStderr {
52
-		t.Fatalf("Error parsing attach flags. Expect only Stdin enabled. Received: in: %v, out: %v, err: %v", config.AttachStdin, config.AttachStdout, config.AttachStderr)
53
-	}
54
-	if config, _ := mustParse(t, "-a stdin -a stdout"); !config.AttachStdin || !config.AttachStdout || config.AttachStderr {
55
-		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)
56
-	}
57
-	if config, _ := mustParse(t, "-a stdin -a stdout -a stderr"); !config.AttachStdin || !config.AttachStdout || !config.AttachStderr {
58
-		t.Fatalf("Error parsing attach flags. Expect all attach enabled. Received: in: %v, out: %v, err: %v", config.AttachStdin, config.AttachStdout, config.AttachStderr)
59
-	}
60
-	if config, _ := mustParse(t, ""); config.AttachStdin || !config.AttachStdout || !config.AttachStderr {
61
-		t.Fatalf("Error parsing attach flags. Expect Stdin disabled. Received: in: %v, out: %v, err: %v", config.AttachStdin, config.AttachStdout, config.AttachStderr)
62
-	}
63
-	if config, _ := mustParse(t, "-i"); !config.AttachStdin || !config.AttachStdout || !config.AttachStderr {
64
-		t.Fatalf("Error parsing attach flags. Expect Stdin enabled. Received: in: %v, out: %v, err: %v", config.AttachStdin, config.AttachStdout, config.AttachStderr)
65
-	}
66
-
67
-	if _, _, err := parse(t, "-a"); err == nil {
68
-		t.Fatalf("Error parsing attach flags, `-a` should be an error but is not")
69
-	}
70
-	if _, _, err := parse(t, "-a invalid"); err == nil {
71
-		t.Fatalf("Error parsing attach flags, `-a invalid` should be an error but is not")
72
-	}
73
-	if _, _, err := parse(t, "-a invalid -a stdout"); err == nil {
74
-		t.Fatalf("Error parsing attach flags, `-a stdout -a invalid` should be an error but is not")
75
-	}
76
-	if _, _, err := parse(t, "-a stdout -a stderr -d"); err == nil {
77
-		t.Fatalf("Error parsing attach flags, `-a stdout -a stderr -d` should be an error but is not")
78
-	}
79
-	if _, _, err := parse(t, "-a stdin -d"); err == nil {
80
-		t.Fatalf("Error parsing attach flags, `-a stdin -d` should be an error but is not")
81
-	}
82
-	if _, _, err := parse(t, "-a stdout -d"); err == nil {
83
-		t.Fatalf("Error parsing attach flags, `-a stdout -d` should be an error but is not")
84
-	}
85
-	if _, _, err := parse(t, "-a stderr -d"); err == nil {
86
-		t.Fatalf("Error parsing attach flags, `-a stderr -d` should be an error but is not")
87
-	}
88
-	if _, _, err := parse(t, "-d --rm"); err == nil {
89
-		t.Fatalf("Error parsing attach flags, `-d --rm` should be an error but is not")
90
-	}
91
-}
92
-
93
-func TestParseRunVolumes(t *testing.T) {
94
-
95
-	// A single volume
96
-	arr, tryit := setupPlatformVolume([]string{`/tmp`}, []string{`c:\tmp`})
97
-	if config, hostConfig := mustParse(t, tryit); hostConfig.Binds != nil {
98
-		t.Fatalf("Error parsing volume flags, %q should not mount-bind anything. Received %v", tryit, hostConfig.Binds)
99
-	} else if _, exists := config.Volumes[arr[0]]; !exists {
100
-		t.Fatalf("Error parsing volume flags, %q is missing from volumes. Received %v", tryit, config.Volumes)
101
-	}
102
-
103
-	// Two volumes
104
-	arr, tryit = setupPlatformVolume([]string{`/tmp`, `/var`}, []string{`c:\tmp`, `c:\var`})
105
-	if config, hostConfig := mustParse(t, tryit); hostConfig.Binds != nil {
106
-		t.Fatalf("Error parsing volume flags, %q should not mount-bind anything. Received %v", tryit, hostConfig.Binds)
107
-	} else if _, exists := config.Volumes[arr[0]]; !exists {
108
-		t.Fatalf("Error parsing volume flags, %s is missing from volumes. Received %v", arr[0], config.Volumes)
109
-	} else if _, exists := config.Volumes[arr[1]]; !exists {
110
-		t.Fatalf("Error parsing volume flags, %s is missing from volumes. Received %v", arr[1], config.Volumes)
111
-	}
112
-
113
-	// A single bind-mount
114
-	arr, tryit = setupPlatformVolume([]string{`/hostTmp:/containerTmp`}, []string{os.Getenv("TEMP") + `:c:\containerTmp`})
115
-	if config, hostConfig := mustParse(t, tryit); hostConfig.Binds == nil || hostConfig.Binds[0] != arr[0] {
116
-		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)
117
-	}
118
-
119
-	// Two bind-mounts.
120
-	arr, tryit = setupPlatformVolume([]string{`/hostTmp:/containerTmp`, `/hostVar:/containerVar`}, []string{os.Getenv("ProgramData") + `:c:\ContainerPD`, os.Getenv("TEMP") + `:c:\containerTmp`})
121
-	if _, hostConfig := mustParse(t, tryit); hostConfig.Binds == nil || compareRandomizedStrings(hostConfig.Binds[0], hostConfig.Binds[1], arr[0], arr[1]) != nil {
122
-		t.Fatalf("Error parsing volume flags, `%s and %s` did not mount-bind correctly. Received %v", arr[0], arr[1], hostConfig.Binds)
123
-	}
124
-
125
-	// Two bind-mounts, first read-only, second read-write.
126
-	// TODO Windows: The Windows version uses read-write as that's the only mode it supports. Can change this post TP4
127
-	arr, tryit = setupPlatformVolume([]string{`/hostTmp:/containerTmp:ro`, `/hostVar:/containerVar:rw`}, []string{os.Getenv("TEMP") + `:c:\containerTmp:rw`, os.Getenv("ProgramData") + `:c:\ContainerPD:rw`})
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
-	// Similar to previous test but with alternate modes which are only supported by Linux
133
-	if runtime.GOOS != "windows" {
134
-		arr, tryit = setupPlatformVolume([]string{`/hostTmp:/containerTmp:ro,Z`, `/hostVar:/containerVar:rw,Z`}, []string{})
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
-		arr, tryit = setupPlatformVolume([]string{`/hostTmp:/containerTmp:Z`, `/hostVar:/containerVar:z`}, []string{})
140
-		if _, hostConfig := mustParse(t, tryit); hostConfig.Binds == nil || compareRandomizedStrings(hostConfig.Binds[0], hostConfig.Binds[1], arr[0], arr[1]) != nil {
141
-			t.Fatalf("Error parsing volume flags, `%s and %s` did not mount-bind correctly. Received %v", arr[0], arr[1], hostConfig.Binds)
142
-		}
143
-	}
144
-
145
-	// One bind mount and one volume
146
-	arr, tryit = setupPlatformVolume([]string{`/hostTmp:/containerTmp`, `/containerVar`}, []string{os.Getenv("TEMP") + `:c:\containerTmp`, `c:\containerTmp`})
147
-	if config, hostConfig := mustParse(t, tryit); hostConfig.Binds == nil || len(hostConfig.Binds) > 1 || hostConfig.Binds[0] != arr[0] {
148
-		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)
149
-	} else if _, exists := config.Volumes[arr[1]]; !exists {
150
-		t.Fatalf("Error parsing volume flags %s and %s. %s is missing from volumes. Received %v", arr[0], arr[1], arr[1], config.Volumes)
151
-	}
152
-
153
-	// Root to non-c: drive letter (Windows specific)
154
-	if runtime.GOOS == "windows" {
155
-		arr, tryit = setupPlatformVolume([]string{}, []string{os.Getenv("SystemDrive") + `\:d:`})
156
-		if config, hostConfig := mustParse(t, tryit); hostConfig.Binds == nil || len(hostConfig.Binds) > 1 || hostConfig.Binds[0] != arr[0] || len(config.Volumes) != 0 {
157
-			t.Fatalf("Error parsing %s. Should have a single bind mount and no volumes", arr[0])
158
-		}
159
-	}
160
-
161
-}
162
-
163
-// This tests the cases for binds which are generated through
164
-// DecodeContainerConfig rather than Parse()
165
-func TestDecodeContainerConfigVolumes(t *testing.T) {
166
-
167
-	// Root to root
168
-	bindsOrVols, _ := setupPlatformVolume([]string{`/:/`}, []string{os.Getenv("SystemDrive") + `\:c:\`})
169
-	if _, _, err := callDecodeContainerConfig(nil, bindsOrVols); err == nil {
170
-		t.Fatalf("binds %v should have failed", bindsOrVols)
171
-	}
172
-	if _, _, err := callDecodeContainerConfig(bindsOrVols, nil); err == nil {
173
-		t.Fatalf("volume %v should have failed", bindsOrVols)
174
-	}
175
-
176
-	// No destination path
177
-	bindsOrVols, _ = setupPlatformVolume([]string{`/tmp:`}, []string{os.Getenv("TEMP") + `\:`})
178
-	if _, _, err := callDecodeContainerConfig(nil, bindsOrVols); err == nil {
179
-		t.Fatalf("binds %v should have failed", bindsOrVols)
180
-	}
181
-	if _, _, err := callDecodeContainerConfig(bindsOrVols, nil); err == nil {
182
-		t.Fatalf("binds %v should have failed", bindsOrVols)
183
-	}
184
-
185
-	//	// No destination path or mode
186
-	bindsOrVols, _ = setupPlatformVolume([]string{`/tmp::`}, []string{os.Getenv("TEMP") + `\::`})
187
-	if _, _, err := callDecodeContainerConfig(nil, bindsOrVols); err == nil {
188
-		t.Fatalf("binds %v should have failed", bindsOrVols)
189
-	}
190
-	if _, _, err := callDecodeContainerConfig(bindsOrVols, nil); err == nil {
191
-		t.Fatalf("binds %v should have failed", bindsOrVols)
192
-	}
193
-
194
-	// A whole lot of nothing
195
-	bindsOrVols = []string{`:`}
196
-	if _, _, err := callDecodeContainerConfig(nil, bindsOrVols); err == nil {
197
-		t.Fatalf("binds %v should have failed", bindsOrVols)
198
-	}
199
-	if _, _, err := callDecodeContainerConfig(bindsOrVols, nil); err == nil {
200
-		t.Fatalf("binds %v should have failed", bindsOrVols)
201
-	}
202
-
203
-	// A whole lot of nothing with no mode
204
-	bindsOrVols = []string{`::`}
205
-	if _, _, err := callDecodeContainerConfig(nil, bindsOrVols); err == nil {
206
-		t.Fatalf("binds %v should have failed", bindsOrVols)
207
-	}
208
-	if _, _, err := callDecodeContainerConfig(bindsOrVols, nil); err == nil {
209
-		t.Fatalf("binds %v should have failed", bindsOrVols)
210
-	}
211
-
212
-	// Too much including an invalid mode
213
-	wTmp := os.Getenv("TEMP")
214
-	bindsOrVols, _ = setupPlatformVolume([]string{`/tmp:/tmp:/tmp:/tmp`}, []string{wTmp + ":" + wTmp + ":" + wTmp + ":" + wTmp})
215
-	if _, _, err := callDecodeContainerConfig(nil, bindsOrVols); err == nil {
216
-		t.Fatalf("binds %v should have failed", bindsOrVols)
217
-	}
218
-	if _, _, err := callDecodeContainerConfig(bindsOrVols, nil); err == nil {
219
-		t.Fatalf("binds %v should have failed", bindsOrVols)
220
-	}
221
-
222
-	// Windows specific error tests
223
-	if runtime.GOOS == "windows" {
224
-		// Volume which does not include a drive letter
225
-		bindsOrVols = []string{`\tmp`}
226
-		if _, _, err := callDecodeContainerConfig(nil, bindsOrVols); err == nil {
227
-			t.Fatalf("binds %v should have failed", bindsOrVols)
228
-		}
229
-		if _, _, err := callDecodeContainerConfig(bindsOrVols, nil); err == nil {
230
-			t.Fatalf("binds %v should have failed", bindsOrVols)
231
-		}
232
-
233
-		// Root to C-Drive
234
-		bindsOrVols = []string{os.Getenv("SystemDrive") + `\:c:`}
235
-		if _, _, err := callDecodeContainerConfig(nil, bindsOrVols); err == nil {
236
-			t.Fatalf("binds %v should have failed", bindsOrVols)
237
-		}
238
-		if _, _, err := callDecodeContainerConfig(bindsOrVols, nil); err == nil {
239
-			t.Fatalf("binds %v should have failed", bindsOrVols)
240
-		}
241
-
242
-		// Container path that does not include a drive letter
243
-		bindsOrVols = []string{`c:\windows:\somewhere`}
244
-		if _, _, err := callDecodeContainerConfig(nil, bindsOrVols); err == nil {
245
-			t.Fatalf("binds %v should have failed", bindsOrVols)
246
-		}
247
-		if _, _, err := callDecodeContainerConfig(bindsOrVols, nil); err == nil {
248
-			t.Fatalf("binds %v should have failed", bindsOrVols)
249
-		}
250
-	}
251
-
252
-	// Linux-specific error tests
253
-	if runtime.GOOS != "windows" {
254
-		// Just root
255
-		bindsOrVols = []string{`/`}
256
-		if _, _, err := callDecodeContainerConfig(nil, bindsOrVols); err == nil {
257
-			t.Fatalf("binds %v should have failed", bindsOrVols)
258
-		}
259
-		if _, _, err := callDecodeContainerConfig(bindsOrVols, nil); err == nil {
260
-			t.Fatalf("binds %v should have failed", bindsOrVols)
261
-		}
262
-
263
-		// A single volume that looks like a bind mount passed in Volumes.
264
-		// This should be handled as a bind mount, not a volume.
265
-		vols := []string{`/foo:/bar`}
266
-		if config, hostConfig, err := callDecodeContainerConfig(vols, nil); err != nil {
267
-			t.Fatal("Volume /foo:/bar should have succeeded as a volume name")
268
-		} else if hostConfig.Binds != nil {
269
-			t.Fatalf("Error parsing volume flags, /foo:/bar should not mount-bind anything. Received %v", hostConfig.Binds)
270
-		} else if _, exists := config.Volumes[vols[0]]; !exists {
271
-			t.Fatalf("Error parsing volume flags, /foo:/bar is missing from volumes. Received %v", config.Volumes)
272
-		}
273
-
274
-	}
275
-}
276
-
277
-// callDecodeContainerConfig is a utility function used by TestDecodeContainerConfigVolumes
278
-// to call DecodeContainerConfig. It effectively does what a client would
279
-// do when calling the daemon by constructing a JSON stream of a
280
-// ContainerConfigWrapper which is populated by the set of volume specs
281
-// passed into it. It returns a config and a hostconfig which can be
282
-// validated to ensure DecodeContainerConfig has manipulated the structures
283
-// correctly.
284
-func callDecodeContainerConfig(volumes []string, binds []string) (*container.Config, *container.HostConfig, error) {
285
-	var (
286
-		b   []byte
287
-		err error
288
-		c   *container.Config
289
-		h   *container.HostConfig
290
-	)
291
-	w := ContainerConfigWrapper{
292
-		Config: &container.Config{
293
-			Volumes: map[string]struct{}{},
294
-		},
295
-		HostConfig: &container.HostConfig{
296
-			NetworkMode: "none",
297
-			Binds:       binds,
298
-		},
299
-	}
300
-	for _, v := range volumes {
301
-		w.Config.Volumes[v] = struct{}{}
302
-	}
303
-	if b, err = json.Marshal(w); err != nil {
304
-		return nil, nil, fmt.Errorf("Error on marshal %s", err.Error())
305
-	}
306
-	c, h, err = DecodeContainerConfig(bytes.NewReader(b))
307
-	if err != nil {
308
-		return nil, nil, fmt.Errorf("Error parsing %s: %v", string(b), err)
309
-	}
310
-	if c == nil || h == nil {
311
-		return nil, nil, fmt.Errorf("Empty config or hostconfig")
312
-	}
313
-
314
-	return c, h, err
315
-}
316
-
317
-// check if (a == c && b == d) || (a == d && b == c)
318
-// because maps are randomized
319
-func compareRandomizedStrings(a, b, c, d string) error {
320
-	if a == c && b == d {
321
-		return nil
322
-	}
323
-	if a == d && b == c {
324
-		return nil
325
-	}
326
-	return fmt.Errorf("strings don't match")
327
-}
328
-
329
-// setupPlatformVolume takes two arrays of volume specs - a Unix style
330
-// spec and a Windows style spec. Depending on the platform being unit tested,
331
-// it returns one of them, along with a volume string that would be passed
332
-// on the docker CLI (eg -v /bar -v /foo).
333
-func setupPlatformVolume(u []string, w []string) ([]string, string) {
334
-	var a []string
335
-	if runtime.GOOS == "windows" {
336
-		a = w
337
-	} else {
338
-		a = u
339
-	}
340
-	s := ""
341
-	for _, v := range a {
342
-		s = s + "-v " + v + " "
343
-	}
344
-	return a, s
345
-}
346
-
347
-// Simple parse with MacAddress validation
348
-func TestParseWithMacAddress(t *testing.T) {
349
-	invalidMacAddress := "--mac-address=invalidMacAddress"
350
-	validMacAddress := "--mac-address=92:d0:c6:0a:29:33"
351
-	if _, _, _, err := parseRun([]string{invalidMacAddress, "img", "cmd"}); err != nil && err.Error() != "invalidMacAddress is not a valid mac address" {
352
-		t.Fatalf("Expected an error with %v mac-address, got %v", invalidMacAddress, err)
353
-	}
354
-	if config, _ := mustParse(t, validMacAddress); config.MacAddress != "92:d0:c6:0a:29:33" {
355
-		t.Fatalf("Expected the config to have '92:d0:c6:0a:29:33' as MacAddress, got '%v'", config.MacAddress)
356
-	}
357
-}
358
-
359
-func TestParseWithMemory(t *testing.T) {
360
-	invalidMemory := "--memory=invalid"
361
-	validMemory := "--memory=1G"
362
-	if _, _, _, err := parseRun([]string{invalidMemory, "img", "cmd"}); err != nil && err.Error() != "invalid size: 'invalid'" {
363
-		t.Fatalf("Expected an error with '%v' Memory, got '%v'", invalidMemory, err)
364
-	}
365
-	if _, hostconfig := mustParse(t, validMemory); hostconfig.Memory != 1073741824 {
366
-		t.Fatalf("Expected the config to have '1G' as Memory, got '%v'", hostconfig.Memory)
367
-	}
368
-}
369
-
370
-func TestParseWithMemorySwap(t *testing.T) {
371
-	invalidMemory := "--memory-swap=invalid"
372
-	validMemory := "--memory-swap=1G"
373
-	anotherValidMemory := "--memory-swap=-1"
374
-	if _, _, _, err := parseRun([]string{invalidMemory, "img", "cmd"}); err == nil || err.Error() != "invalid size: 'invalid'" {
375
-		t.Fatalf("Expected an error with '%v' MemorySwap, got '%v'", invalidMemory, err)
376
-	}
377
-	if _, hostconfig := mustParse(t, validMemory); hostconfig.MemorySwap != 1073741824 {
378
-		t.Fatalf("Expected the config to have '1073741824' as MemorySwap, got '%v'", hostconfig.MemorySwap)
379
-	}
380
-	if _, hostconfig := mustParse(t, anotherValidMemory); hostconfig.MemorySwap != -1 {
381
-		t.Fatalf("Expected the config to have '-1' as MemorySwap, got '%v'", hostconfig.MemorySwap)
382
-	}
383
-}
384
-
385
-func TestParseHostname(t *testing.T) {
386
-	hostname := "--hostname=hostname"
387
-	hostnameWithDomain := "--hostname=hostname.domainname"
388
-	hostnameWithDomainTld := "--hostname=hostname.domainname.tld"
389
-	if config, _ := mustParse(t, hostname); config.Hostname != "hostname" && config.Domainname != "" {
390
-		t.Fatalf("Expected the config to have 'hostname' as hostname, got '%v'", config.Hostname)
391
-	}
392
-	if config, _ := mustParse(t, hostnameWithDomain); config.Hostname != "hostname" && config.Domainname != "domainname" {
393
-		t.Fatalf("Expected the config to have 'hostname' as hostname, got '%v'", config.Hostname)
394
-	}
395
-	if config, _ := mustParse(t, hostnameWithDomainTld); config.Hostname != "hostname" && config.Domainname != "domainname.tld" {
396
-		t.Fatalf("Expected the config to have 'hostname' as hostname, got '%v'", config.Hostname)
397
-	}
398
-}
399
-
400
-func TestParseWithExpose(t *testing.T) {
401
-	invalids := map[string]string{
402
-		":":                   "Invalid port format for --expose: :",
403
-		"8080:9090":           "Invalid port format for --expose: 8080:9090",
404
-		"/tcp":                "Invalid range format for --expose: /tcp, error: Empty string specified for ports.",
405
-		"/udp":                "Invalid range format for --expose: /udp, error: Empty string specified for ports.",
406
-		"NaN/tcp":             `Invalid range format for --expose: NaN/tcp, error: strconv.ParseUint: parsing "NaN": invalid syntax`,
407
-		"NaN-NaN/tcp":         `Invalid range format for --expose: NaN-NaN/tcp, error: strconv.ParseUint: parsing "NaN": invalid syntax`,
408
-		"8080-NaN/tcp":        `Invalid range format for --expose: 8080-NaN/tcp, error: strconv.ParseUint: parsing "NaN": invalid syntax`,
409
-		"1234567890-8080/tcp": `Invalid range format for --expose: 1234567890-8080/tcp, error: strconv.ParseUint: parsing "1234567890": value out of range`,
410
-	}
411
-	valids := map[string][]nat.Port{
412
-		"8080/tcp":      {"8080/tcp"},
413
-		"8080/udp":      {"8080/udp"},
414
-		"8080/ncp":      {"8080/ncp"},
415
-		"8080-8080/udp": {"8080/udp"},
416
-		"8080-8082/tcp": {"8080/tcp", "8081/tcp", "8082/tcp"},
417
-	}
418
-	for expose, expectedError := range invalids {
419
-		if _, _, _, err := parseRun([]string{fmt.Sprintf("--expose=%v", expose), "img", "cmd"}); err == nil || err.Error() != expectedError {
420
-			t.Fatalf("Expected error '%v' with '--expose=%v', got '%v'", expectedError, expose, err)
421
-		}
422
-	}
423
-	for expose, exposedPorts := range valids {
424
-		config, _, _, err := parseRun([]string{fmt.Sprintf("--expose=%v", expose), "img", "cmd"})
425
-		if err != nil {
426
-			t.Fatal(err)
427
-		}
428
-		if len(config.ExposedPorts) != len(exposedPorts) {
429
-			t.Fatalf("Expected %v exposed port, got %v", len(exposedPorts), len(config.ExposedPorts))
430
-		}
431
-		for _, port := range exposedPorts {
432
-			if _, ok := config.ExposedPorts[port]; !ok {
433
-				t.Fatalf("Expected %v, got %v", exposedPorts, config.ExposedPorts)
434
-			}
435
-		}
436
-	}
437
-	// Merge with actual published port
438
-	config, _, _, err := parseRun([]string{"--publish=80", "--expose=80-81/tcp", "img", "cmd"})
439
-	if err != nil {
440
-		t.Fatal(err)
441
-	}
442
-	if len(config.ExposedPorts) != 2 {
443
-		t.Fatalf("Expected 2 exposed ports, got %v", config.ExposedPorts)
444
-	}
445
-	ports := []nat.Port{"80/tcp", "81/tcp"}
446
-	for _, port := range ports {
447
-		if _, ok := config.ExposedPorts[port]; !ok {
448
-			t.Fatalf("Expected %v, got %v", ports, config.ExposedPorts)
449
-		}
450
-	}
451
-}
452
-
453
-func TestParseDevice(t *testing.T) {
454
-	valids := map[string]container.DeviceMapping{
455
-		"/dev/snd": {
456
-			PathOnHost:        "/dev/snd",
457
-			PathInContainer:   "/dev/snd",
458
-			CgroupPermissions: "rwm",
459
-		},
460
-		"/dev/snd:rw": {
461
-			PathOnHost:        "/dev/snd",
462
-			PathInContainer:   "/dev/snd",
463
-			CgroupPermissions: "rw",
464
-		},
465
-		"/dev/snd:/something": {
466
-			PathOnHost:        "/dev/snd",
467
-			PathInContainer:   "/something",
468
-			CgroupPermissions: "rwm",
469
-		},
470
-		"/dev/snd:/something:rw": {
471
-			PathOnHost:        "/dev/snd",
472
-			PathInContainer:   "/something",
473
-			CgroupPermissions: "rw",
474
-		},
475
-	}
476
-	for device, deviceMapping := range valids {
477
-		_, hostconfig, _, err := parseRun([]string{fmt.Sprintf("--device=%v", device), "img", "cmd"})
478
-		if err != nil {
479
-			t.Fatal(err)
480
-		}
481
-		if len(hostconfig.Devices) != 1 {
482
-			t.Fatalf("Expected 1 devices, got %v", hostconfig.Devices)
483
-		}
484
-		if hostconfig.Devices[0] != deviceMapping {
485
-			t.Fatalf("Expected %v, got %v", deviceMapping, hostconfig.Devices)
486
-		}
487
-	}
488
-
489
-}
490
-
491
-func TestParseModes(t *testing.T) {
492
-	// ipc ko
493
-	if _, _, _, err := parseRun([]string{"--ipc=container:", "img", "cmd"}); err == nil || err.Error() != "--ipc: invalid IPC mode" {
494
-		t.Fatalf("Expected an error with message '--ipc: invalid IPC mode', got %v", err)
495
-	}
496
-	// ipc ok
497
-	_, hostconfig, _, err := parseRun([]string{"--ipc=host", "img", "cmd"})
498
-	if err != nil {
499
-		t.Fatal(err)
500
-	}
501
-	if !hostconfig.IpcMode.Valid() {
502
-		t.Fatalf("Expected a valid IpcMode, got %v", hostconfig.IpcMode)
503
-	}
504
-	// pid ko
505
-	if _, _, _, err := parseRun([]string{"--pid=container:", "img", "cmd"}); err == nil || err.Error() != "--pid: invalid PID mode" {
506
-		t.Fatalf("Expected an error with message '--pid: invalid PID mode', got %v", err)
507
-	}
508
-	// pid ok
509
-	_, hostconfig, _, err = parseRun([]string{"--pid=host", "img", "cmd"})
510
-	if err != nil {
511
-		t.Fatal(err)
512
-	}
513
-	if !hostconfig.PidMode.Valid() {
514
-		t.Fatalf("Expected a valid PidMode, got %v", hostconfig.PidMode)
515
-	}
516
-	// uts ko
517
-	if _, _, _, err := parseRun([]string{"--uts=container:", "img", "cmd"}); err == nil || err.Error() != "--uts: invalid UTS mode" {
518
-		t.Fatalf("Expected an error with message '--uts: invalid UTS mode', got %v", err)
519
-	}
520
-	// uts ok
521
-	_, hostconfig, _, err = parseRun([]string{"--uts=host", "img", "cmd"})
522
-	if err != nil {
523
-		t.Fatal(err)
524
-	}
525
-	if !hostconfig.UTSMode.Valid() {
526
-		t.Fatalf("Expected a valid UTSMode, got %v", hostconfig.UTSMode)
527
-	}
528
-	// shm-size ko
529
-	if _, _, _, err = parseRun([]string{"--shm-size=a128m", "img", "cmd"}); err == nil || err.Error() != "invalid size: 'a128m'" {
530
-		t.Fatalf("Expected an error with message 'invalid size: a128m', got %v", err)
531
-	}
532
-	// shm-size ok
533
-	_, hostconfig, _, err = parseRun([]string{"--shm-size=128m", "img", "cmd"})
534
-	if err != nil {
535
-		t.Fatal(err)
536
-	}
537
-	if *hostconfig.ShmSize != 134217728 {
538
-		t.Fatalf("Expected a valid ShmSize, got %d", *hostconfig.ShmSize)
539
-	}
540
-}
541
-
542
-func TestParseRestartPolicy(t *testing.T) {
543
-	invalids := map[string]string{
544
-		"something":          "invalid restart policy something",
545
-		"always:2":           "maximum restart count not valid with restart policy of \"always\"",
546
-		"always:2:3":         "maximum restart count not valid with restart policy of \"always\"",
547
-		"on-failure:invalid": `strconv.ParseInt: parsing "invalid": invalid syntax`,
548
-		"on-failure:2:5":     "restart count format is not valid, usage: 'on-failure:N' or 'on-failure'",
549
-	}
550
-	valids := map[string]container.RestartPolicy{
551
-		"": {},
552
-		"always": {
553
-			Name:              "always",
554
-			MaximumRetryCount: 0,
555
-		},
556
-		"on-failure:1": {
557
-			Name:              "on-failure",
558
-			MaximumRetryCount: 1,
559
-		},
560
-	}
561
-	for restart, expectedError := range invalids {
562
-		if _, _, _, err := parseRun([]string{fmt.Sprintf("--restart=%s", restart), "img", "cmd"}); err == nil || err.Error() != expectedError {
563
-			t.Fatalf("Expected an error with message '%v' for %v, got %v", expectedError, restart, err)
564
-		}
565
-	}
566
-	for restart, expected := range valids {
567
-		_, hostconfig, _, err := parseRun([]string{fmt.Sprintf("--restart=%v", restart), "img", "cmd"})
568
-		if err != nil {
569
-			t.Fatal(err)
570
-		}
571
-		if hostconfig.RestartPolicy != expected {
572
-			t.Fatalf("Expected %v, got %v", expected, hostconfig.RestartPolicy)
573
-		}
574
-	}
575
-}
576
-
577
-func TestParseLoggingOpts(t *testing.T) {
578
-	// logging opts ko
579
-	if _, _, _, err := parseRun([]string{"--log-driver=none", "--log-opt=anything", "img", "cmd"}); err == nil || err.Error() != "Invalid logging opts for driver none" {
580
-		t.Fatalf("Expected an error with message 'Invalid logging opts for driver none', got %v", err)
581
-	}
582
-	// logging opts ok
583
-	_, hostconfig, _, err := parseRun([]string{"--log-driver=syslog", "--log-opt=something", "img", "cmd"})
584
-	if err != nil {
585
-		t.Fatal(err)
586
-	}
587
-	if hostconfig.LogConfig.Type != "syslog" || len(hostconfig.LogConfig.Config) != 1 {
588
-		t.Fatalf("Expected a 'syslog' LogConfig with one config, got %v", hostconfig.RestartPolicy)
589
-	}
590
-}
591
-
592
-func TestParseEnvfileVariables(t *testing.T) {
593
-	e := "open nonexistent: no such file or directory"
594
-	if runtime.GOOS == "windows" {
595
-		e = "open nonexistent: The system cannot find the file specified."
596
-	}
597
-	// env ko
598
-	if _, _, _, err := parseRun([]string{"--env-file=nonexistent", "img", "cmd"}); err == nil || err.Error() != e {
599
-		t.Fatalf("Expected an error with message '%s', got %v", e, err)
600
-	}
601
-	// env ok
602
-	config, _, _, err := parseRun([]string{"--env-file=fixtures/valid.env", "img", "cmd"})
603
-	if err != nil {
604
-		t.Fatal(err)
605
-	}
606
-	if len(config.Env) != 1 || config.Env[0] != "ENV1=value1" {
607
-		t.Fatalf("Expected a a config with [ENV1=value1], got %v", config.Env)
608
-	}
609
-	config, _, _, err = parseRun([]string{"--env-file=fixtures/valid.env", "--env=ENV2=value2", "img", "cmd"})
610
-	if err != nil {
611
-		t.Fatal(err)
612
-	}
613
-	if len(config.Env) != 2 || config.Env[0] != "ENV1=value1" || config.Env[1] != "ENV2=value2" {
614
-		t.Fatalf("Expected a a config with [ENV1=value1 ENV2=value2], got %v", config.Env)
615
-	}
616
-}
617
-
618
-func TestParseLabelfileVariables(t *testing.T) {
619
-	e := "open nonexistent: no such file or directory"
620
-	if runtime.GOOS == "windows" {
621
-		e = "open nonexistent: The system cannot find the file specified."
622
-	}
623
-	// label ko
624
-	if _, _, _, err := parseRun([]string{"--label-file=nonexistent", "img", "cmd"}); err == nil || err.Error() != e {
625
-		t.Fatalf("Expected an error with message '%s', got %v", e, err)
626
-	}
627
-	// label ok
628
-	config, _, _, err := parseRun([]string{"--label-file=fixtures/valid.label", "img", "cmd"})
629
-	if err != nil {
630
-		t.Fatal(err)
631
-	}
632
-	if len(config.Labels) != 1 || config.Labels["LABEL1"] != "value1" {
633
-		t.Fatalf("Expected a a config with [LABEL1:value1], got %v", config.Labels)
634
-	}
635
-	config, _, _, err = parseRun([]string{"--label-file=fixtures/valid.label", "--label=LABEL2=value2", "img", "cmd"})
636
-	if err != nil {
637
-		t.Fatal(err)
638
-	}
639
-	if len(config.Labels) != 2 || config.Labels["LABEL1"] != "value1" || config.Labels["LABEL2"] != "value2" {
640
-		t.Fatalf("Expected a a config with [LABEL1:value1 LABEL2:value2], got %v", config.Labels)
641
-	}
642
-}
643
-
644
-func TestParseEntryPoint(t *testing.T) {
645
-	config, _, _, err := parseRun([]string{"--entrypoint=anything", "cmd", "img"})
646
-	if err != nil {
647
-		t.Fatal(err)
648
-	}
649
-	if config.Entrypoint.Len() != 1 && config.Entrypoint.Slice()[0] != "anything" {
650
-		t.Fatalf("Expected entrypoint 'anything', got %v", config.Entrypoint)
651
-	}
652
-}
653
-
654
-func TestValidateLink(t *testing.T) {
655
-	valid := []string{
656
-		"name",
657
-		"dcdfbe62ecd0:alias",
658
-		"7a67485460b7642516a4ad82ecefe7f57d0c4916f530561b71a50a3f9c4e33da",
659
-		"angry_torvalds:linus",
660
-	}
661
-	invalid := map[string]string{
662
-		"":               "empty string specified for links",
663
-		"too:much:of:it": "bad format for links: too:much:of:it",
664
-	}
665
-
666
-	for _, link := range valid {
667
-		if _, err := ValidateLink(link); err != nil {
668
-			t.Fatalf("ValidateLink(`%q`) should succeed: error %q", link, err)
669
-		}
670
-	}
671
-
672
-	for link, expectedError := range invalid {
673
-		if _, err := ValidateLink(link); err == nil {
674
-			t.Fatalf("ValidateLink(`%q`) should have failed validation", link)
675
-		} else {
676
-			if !strings.Contains(err.Error(), expectedError) {
677
-				t.Fatalf("ValidateLink(`%q`) error should contain %q", link, expectedError)
678
-			}
679
-		}
680
-	}
681
-}
682
-
683
-func TestParseLink(t *testing.T) {
684
-	name, alias, err := ParseLink("name:alias")
685
-	if err != nil {
686
-		t.Fatalf("Expected not to error out on a valid name:alias format but got: %v", err)
687
-	}
688
-	if name != "name" {
689
-		t.Fatalf("Link name should have been name, got %s instead", name)
690
-	}
691
-	if alias != "alias" {
692
-		t.Fatalf("Link alias should have been alias, got %s instead", alias)
693
-	}
694
-	// short format definition
695
-	name, alias, err = ParseLink("name")
696
-	if err != nil {
697
-		t.Fatalf("Expected not to error out on a valid name only format but got: %v", err)
698
-	}
699
-	if name != "name" {
700
-		t.Fatalf("Link name should have been name, got %s instead", name)
701
-	}
702
-	if alias != "name" {
703
-		t.Fatalf("Link alias should have been name, got %s instead", alias)
704
-	}
705
-	// empty string link definition is not allowed
706
-	if _, _, err := ParseLink(""); err == nil || !strings.Contains(err.Error(), "empty string specified for links") {
707
-		t.Fatalf("Expected error 'empty string specified for links' but got: %v", err)
708
-	}
709
-	// more than two colons are not allowed
710
-	if _, _, err := ParseLink("link:alias:wrong"); err == nil || !strings.Contains(err.Error(), "bad format for links: link:alias:wrong") {
711
-		t.Fatalf("Expected error 'bad format for links: link:alias:wrong' but got: %v", err)
712
-	}
713
-}
714
-
715
-func TestValidateDevice(t *testing.T) {
716
-	valid := []string{
717
-		"/home",
718
-		"/home:/home",
719
-		"/home:/something/else",
720
-		"/with space",
721
-		"/home:/with space",
722
-		"relative:/absolute-path",
723
-		"hostPath:/containerPath:r",
724
-		"/hostPath:/containerPath:rw",
725
-		"/hostPath:/containerPath:mrw",
726
-	}
727
-	invalid := map[string]string{
728
-		"":        "bad format for path: ",
729
-		"./":      "./ is not an absolute path",
730
-		"../":     "../ is not an absolute path",
731
-		"/:../":   "../ is not an absolute path",
732
-		"/:path":  "path is not an absolute path",
733
-		":":       "bad format for path: :",
734
-		"/tmp:":   " is not an absolute path",
735
-		":test":   "bad format for path: :test",
736
-		":/test":  "bad format for path: :/test",
737
-		"tmp:":    " is not an absolute path",
738
-		":test:":  "bad format for path: :test:",
739
-		"::":      "bad format for path: ::",
740
-		":::":     "bad format for path: :::",
741
-		"/tmp:::": "bad format for path: /tmp:::",
742
-		":/tmp::": "bad format for path: :/tmp::",
743
-		"path:ro": "ro is not an absolute path",
744
-		"path:rr": "rr is not an absolute path",
745
-		"a:/b:ro": "bad mode specified: ro",
746
-		"a:/b:rr": "bad mode specified: rr",
747
-	}
748
-
749
-	for _, path := range valid {
750
-		if _, err := ValidateDevice(path); err != nil {
751
-			t.Fatalf("ValidateDevice(`%q`) should succeed: error %q", path, err)
752
-		}
753
-	}
754
-
755
-	for path, expectedError := range invalid {
756
-		if _, err := ValidateDevice(path); err == nil {
757
-			t.Fatalf("ValidateDevice(`%q`) should have failed validation", path)
758
-		} else {
759
-			if err.Error() != expectedError {
760
-				t.Fatalf("ValidateDevice(`%q`) error should contain %q, got %q", path, expectedError, err.Error())
761
-			}
762
-		}
763
-	}
764
-}
765 1
new file mode 100644
... ...
@@ -0,0 +1,32 @@
0
+package runconfig
1
+
2
+import (
3
+	"fmt"
4
+)
5
+
6
+var (
7
+	// ErrConflictContainerNetworkAndLinks conflict between --net=container and links
8
+	ErrConflictContainerNetworkAndLinks = fmt.Errorf("Conflicting options: container type network can't be used with links. This would result in undefined behavior")
9
+	// ErrConflictUserDefinedNetworkAndLinks conflict between --net=<NETWORK> and links
10
+	ErrConflictUserDefinedNetworkAndLinks = fmt.Errorf("Conflicting options: networking can't be used with links. This would result in undefined behavior")
11
+	// ErrConflictSharedNetwork conflict between private and other networks
12
+	ErrConflictSharedNetwork = fmt.Errorf("Container sharing network namespace with another container or host cannot be connected to any other network")
13
+	// ErrConflictHostNetwork conflict from being disconnected from host network or connected to host network.
14
+	ErrConflictHostNetwork = fmt.Errorf("Container cannot be disconnected from host network or connected to host network")
15
+	// ErrConflictNoNetwork conflict between private and other networks
16
+	ErrConflictNoNetwork = fmt.Errorf("Container cannot be connected to multiple networks with one of the networks in private (none) mode")
17
+	// ErrConflictNetworkAndDNS conflict between --dns and the network mode
18
+	ErrConflictNetworkAndDNS = fmt.Errorf("Conflicting options: dns and the network mode")
19
+	// ErrConflictNetworkHostname conflict between the hostname and the network mode
20
+	ErrConflictNetworkHostname = fmt.Errorf("Conflicting options: hostname and the network mode")
21
+	// ErrConflictHostNetworkAndLinks conflict between --net=host and links
22
+	ErrConflictHostNetworkAndLinks = fmt.Errorf("Conflicting options: host type networking can't be used with links. This would result in undefined behavior")
23
+	// ErrConflictContainerNetworkAndMac conflict between the mac address and the network mode
24
+	ErrConflictContainerNetworkAndMac = fmt.Errorf("Conflicting options: mac-address and the network mode")
25
+	// ErrConflictNetworkHosts conflict between add-host and the network mode
26
+	ErrConflictNetworkHosts = fmt.Errorf("Conflicting options: custom host-to-IP mapping and the network mode")
27
+	// ErrConflictNetworkPublishPorts conflict between the publish options and the network mode
28
+	ErrConflictNetworkPublishPorts = fmt.Errorf("Conflicting options: port publishing and the container type network mode")
29
+	// ErrConflictNetworkExposePorts conflict between the expose option and the network mode
30
+	ErrConflictNetworkExposePorts = fmt.Errorf("Conflicting options: port exposing and the container type network mode")
31
+)
0 32
deleted file mode 100644
... ...
@@ -1 +0,0 @@
1
-ENV1=value1
2 1
deleted file mode 100644
... ...
@@ -1 +0,0 @@
1
-LABEL1=value1
2 1
new file mode 100644
... ...
@@ -0,0 +1 @@
0
+ENV1=value1
0 1
new file mode 100644
... ...
@@ -0,0 +1 @@
0
+LABEL1=value1
0 1
new file mode 100644
... ...
@@ -0,0 +1,623 @@
0
+package opts
1
+
2
+import (
3
+	"fmt"
4
+	"path"
5
+	"strconv"
6
+	"strings"
7
+
8
+	"github.com/docker/docker/api/types/container"
9
+	"github.com/docker/docker/api/types/strslice"
10
+	"github.com/docker/docker/opts"
11
+	flag "github.com/docker/docker/pkg/mflag"
12
+	"github.com/docker/docker/pkg/mount"
13
+	"github.com/docker/docker/pkg/signal"
14
+	"github.com/docker/docker/volume"
15
+	"github.com/docker/go-connections/nat"
16
+	"github.com/docker/go-units"
17
+)
18
+
19
+// Parse parses the specified args for the specified command and generates a Config,
20
+// a HostConfig and returns them with the specified command.
21
+// If the specified args are not valid, it will return an error.
22
+func Parse(cmd *flag.FlagSet, args []string) (*container.Config, *container.HostConfig, *flag.FlagSet, error) {
23
+	var (
24
+		// FIXME: use utils.ListOpts for attach and volumes?
25
+		flAttach            = opts.NewListOpts(opts.ValidateAttach)
26
+		flVolumes           = opts.NewListOpts(nil)
27
+		flTmpfs             = opts.NewListOpts(nil)
28
+		flBlkioWeightDevice = NewWeightdeviceOpt(ValidateWeightDevice)
29
+		flDeviceReadBps     = NewThrottledeviceOpt(ValidateThrottleBpsDevice)
30
+		flDeviceWriteBps    = NewThrottledeviceOpt(ValidateThrottleBpsDevice)
31
+		flLinks             = opts.NewListOpts(ValidateLink)
32
+		flDeviceReadIOps    = NewThrottledeviceOpt(ValidateThrottleIOpsDevice)
33
+		flDeviceWriteIOps   = NewThrottledeviceOpt(ValidateThrottleIOpsDevice)
34
+		flEnv               = opts.NewListOpts(opts.ValidateEnv)
35
+		flLabels            = opts.NewListOpts(opts.ValidateEnv)
36
+		flDevices           = opts.NewListOpts(ValidateDevice)
37
+
38
+		flUlimits = NewUlimitOpt(nil)
39
+
40
+		flPublish           = opts.NewListOpts(nil)
41
+		flExpose            = opts.NewListOpts(nil)
42
+		flDNS               = opts.NewListOpts(opts.ValidateIPAddress)
43
+		flDNSSearch         = opts.NewListOpts(opts.ValidateDNSSearch)
44
+		flDNSOptions        = opts.NewListOpts(nil)
45
+		flExtraHosts        = opts.NewListOpts(opts.ValidateExtraHost)
46
+		flVolumesFrom       = opts.NewListOpts(nil)
47
+		flEnvFile           = opts.NewListOpts(nil)
48
+		flCapAdd            = opts.NewListOpts(nil)
49
+		flCapDrop           = opts.NewListOpts(nil)
50
+		flGroupAdd          = opts.NewListOpts(nil)
51
+		flSecurityOpt       = opts.NewListOpts(nil)
52
+		flLabelsFile        = opts.NewListOpts(nil)
53
+		flLoggingOpts       = opts.NewListOpts(nil)
54
+		flPrivileged        = cmd.Bool([]string{"-privileged"}, false, "Give extended privileges to this container")
55
+		flPidMode           = cmd.String([]string{"-pid"}, "", "PID namespace to use")
56
+		flUTSMode           = cmd.String([]string{"-uts"}, "", "UTS namespace to use")
57
+		flPublishAll        = cmd.Bool([]string{"P", "-publish-all"}, false, "Publish all exposed ports to random ports")
58
+		flStdin             = cmd.Bool([]string{"i", "-interactive"}, false, "Keep STDIN open even if not attached")
59
+		flTty               = cmd.Bool([]string{"t", "-tty"}, false, "Allocate a pseudo-TTY")
60
+		flOomKillDisable    = cmd.Bool([]string{"-oom-kill-disable"}, false, "Disable OOM Killer")
61
+		flOomScoreAdj       = cmd.Int([]string{"-oom-score-adj"}, 0, "Tune host's OOM preferences (-1000 to 1000)")
62
+		flContainerIDFile   = cmd.String([]string{"-cidfile"}, "", "Write the container ID to the file")
63
+		flEntrypoint        = cmd.String([]string{"-entrypoint"}, "", "Overwrite the default ENTRYPOINT of the image")
64
+		flHostname          = cmd.String([]string{"h", "-hostname"}, "", "Container host name")
65
+		flMemoryString      = cmd.String([]string{"m", "-memory"}, "", "Memory limit")
66
+		flMemoryReservation = cmd.String([]string{"-memory-reservation"}, "", "Memory soft limit")
67
+		flMemorySwap        = cmd.String([]string{"-memory-swap"}, "", "Swap limit equal to memory plus swap: '-1' to enable unlimited swap")
68
+		flKernelMemory      = cmd.String([]string{"-kernel-memory"}, "", "Kernel memory limit")
69
+		flUser              = cmd.String([]string{"u", "-user"}, "", "Username or UID (format: <name|uid>[:<group|gid>])")
70
+		flWorkingDir        = cmd.String([]string{"w", "-workdir"}, "", "Working directory inside the container")
71
+		flCPUShares         = cmd.Int64([]string{"#c", "-cpu-shares"}, 0, "CPU shares (relative weight)")
72
+		flCPUPeriod         = cmd.Int64([]string{"-cpu-period"}, 0, "Limit CPU CFS (Completely Fair Scheduler) period")
73
+		flCPUQuota          = cmd.Int64([]string{"-cpu-quota"}, 0, "Limit CPU CFS (Completely Fair Scheduler) quota")
74
+		flCpusetCpus        = cmd.String([]string{"-cpuset-cpus"}, "", "CPUs in which to allow execution (0-3, 0,1)")
75
+		flCpusetMems        = cmd.String([]string{"-cpuset-mems"}, "", "MEMs in which to allow execution (0-3, 0,1)")
76
+		flBlkioWeight       = cmd.Uint16([]string{"-blkio-weight"}, 0, "Block IO (relative weight), between 10 and 1000")
77
+		flSwappiness        = cmd.Int64([]string{"-memory-swappiness"}, -1, "Tune container memory swappiness (0 to 100)")
78
+		flNetMode           = cmd.String([]string{"-net"}, "default", "Connect a container to a network")
79
+		flMacAddress        = cmd.String([]string{"-mac-address"}, "", "Container MAC address (e.g. 92:d0:c6:0a:29:33)")
80
+		flIpcMode           = cmd.String([]string{"-ipc"}, "", "IPC namespace to use")
81
+		flRestartPolicy     = cmd.String([]string{"-restart"}, "no", "Restart policy to apply when a container exits")
82
+		flReadonlyRootfs    = cmd.Bool([]string{"-read-only"}, false, "Mount the container's root filesystem as read only")
83
+		flLoggingDriver     = cmd.String([]string{"-log-driver"}, "", "Logging driver for container")
84
+		flCgroupParent      = cmd.String([]string{"-cgroup-parent"}, "", "Optional parent cgroup for the container")
85
+		flVolumeDriver      = cmd.String([]string{"-volume-driver"}, "", "Optional volume driver for the container")
86
+		flStopSignal        = cmd.String([]string{"-stop-signal"}, signal.DefaultStopSignal, fmt.Sprintf("Signal to stop a container, %v by default", signal.DefaultStopSignal))
87
+		flIsolation         = cmd.String([]string{"-isolation"}, "", "Container isolation level")
88
+		flShmSize           = cmd.String([]string{"-shm-size"}, "", "Size of /dev/shm, default value is 64MB")
89
+	)
90
+
91
+	cmd.Var(&flAttach, []string{"a", "-attach"}, "Attach to STDIN, STDOUT or STDERR")
92
+	cmd.Var(&flBlkioWeightDevice, []string{"-blkio-weight-device"}, "Block IO weight (relative device weight)")
93
+	cmd.Var(&flDeviceReadBps, []string{"-device-read-bps"}, "Limit read rate (bytes per second) from a device")
94
+	cmd.Var(&flDeviceWriteBps, []string{"-device-write-bps"}, "Limit write rate (bytes per second) to a device")
95
+	cmd.Var(&flDeviceReadIOps, []string{"-device-read-iops"}, "Limit read rate (IO per second) from a device")
96
+	cmd.Var(&flDeviceWriteIOps, []string{"-device-write-iops"}, "Limit write rate (IO per second) to a device")
97
+	cmd.Var(&flVolumes, []string{"v", "-volume"}, "Bind mount a volume")
98
+	cmd.Var(&flTmpfs, []string{"-tmpfs"}, "Mount a tmpfs directory")
99
+	cmd.Var(&flLinks, []string{"-link"}, "Add link to another container")
100
+	cmd.Var(&flDevices, []string{"-device"}, "Add a host device to the container")
101
+	cmd.Var(&flLabels, []string{"l", "-label"}, "Set meta data on a container")
102
+	cmd.Var(&flLabelsFile, []string{"-label-file"}, "Read in a line delimited file of labels")
103
+	cmd.Var(&flEnv, []string{"e", "-env"}, "Set environment variables")
104
+	cmd.Var(&flEnvFile, []string{"-env-file"}, "Read in a file of environment variables")
105
+	cmd.Var(&flPublish, []string{"p", "-publish"}, "Publish a container's port(s) to the host")
106
+	cmd.Var(&flExpose, []string{"-expose"}, "Expose a port or a range of ports")
107
+	cmd.Var(&flDNS, []string{"-dns"}, "Set custom DNS servers")
108
+	cmd.Var(&flDNSSearch, []string{"-dns-search"}, "Set custom DNS search domains")
109
+	cmd.Var(&flDNSOptions, []string{"-dns-opt"}, "Set DNS options")
110
+	cmd.Var(&flExtraHosts, []string{"-add-host"}, "Add a custom host-to-IP mapping (host:ip)")
111
+	cmd.Var(&flVolumesFrom, []string{"-volumes-from"}, "Mount volumes from the specified container(s)")
112
+	cmd.Var(&flCapAdd, []string{"-cap-add"}, "Add Linux capabilities")
113
+	cmd.Var(&flCapDrop, []string{"-cap-drop"}, "Drop Linux capabilities")
114
+	cmd.Var(&flGroupAdd, []string{"-group-add"}, "Add additional groups to join")
115
+	cmd.Var(&flSecurityOpt, []string{"-security-opt"}, "Security Options")
116
+	cmd.Var(flUlimits, []string{"-ulimit"}, "Ulimit options")
117
+	cmd.Var(&flLoggingOpts, []string{"-log-opt"}, "Log driver options")
118
+
119
+	cmd.Require(flag.Min, 1)
120
+
121
+	if err := cmd.ParseFlags(args, true); err != nil {
122
+		return nil, nil, cmd, err
123
+	}
124
+
125
+	var (
126
+		attachStdin  = flAttach.Get("stdin")
127
+		attachStdout = flAttach.Get("stdout")
128
+		attachStderr = flAttach.Get("stderr")
129
+	)
130
+
131
+	// Validate the input mac address
132
+	if *flMacAddress != "" {
133
+		if _, err := opts.ValidateMACAddress(*flMacAddress); err != nil {
134
+			return nil, nil, cmd, fmt.Errorf("%s is not a valid mac address", *flMacAddress)
135
+		}
136
+	}
137
+	if *flStdin {
138
+		attachStdin = true
139
+	}
140
+	// If -a is not set attach to the output stdio
141
+	if flAttach.Len() == 0 {
142
+		attachStdout = true
143
+		attachStderr = true
144
+	}
145
+
146
+	var err error
147
+
148
+	var flMemory int64
149
+	if *flMemoryString != "" {
150
+		flMemory, err = units.RAMInBytes(*flMemoryString)
151
+		if err != nil {
152
+			return nil, nil, cmd, err
153
+		}
154
+	}
155
+
156
+	var MemoryReservation int64
157
+	if *flMemoryReservation != "" {
158
+		MemoryReservation, err = units.RAMInBytes(*flMemoryReservation)
159
+		if err != nil {
160
+			return nil, nil, cmd, err
161
+		}
162
+	}
163
+
164
+	var memorySwap int64
165
+	if *flMemorySwap != "" {
166
+		if *flMemorySwap == "-1" {
167
+			memorySwap = -1
168
+		} else {
169
+			memorySwap, err = units.RAMInBytes(*flMemorySwap)
170
+			if err != nil {
171
+				return nil, nil, cmd, err
172
+			}
173
+		}
174
+	}
175
+
176
+	var KernelMemory int64
177
+	if *flKernelMemory != "" {
178
+		KernelMemory, err = units.RAMInBytes(*flKernelMemory)
179
+		if err != nil {
180
+			return nil, nil, cmd, err
181
+		}
182
+	}
183
+
184
+	swappiness := *flSwappiness
185
+	if swappiness != -1 && (swappiness < 0 || swappiness > 100) {
186
+		return nil, nil, cmd, fmt.Errorf("Invalid value: %d. Valid memory swappiness range is 0-100", swappiness)
187
+	}
188
+
189
+	var parsedShm *int64
190
+	if *flShmSize != "" {
191
+		shmSize, err := units.RAMInBytes(*flShmSize)
192
+		if err != nil {
193
+			return nil, nil, cmd, err
194
+		}
195
+		parsedShm = &shmSize
196
+	}
197
+
198
+	var binds []string
199
+	// add any bind targets to the list of container volumes
200
+	for bind := range flVolumes.GetMap() {
201
+		if arr := volume.SplitN(bind, 2); len(arr) > 1 {
202
+			// after creating the bind mount we want to delete it from the flVolumes values because
203
+			// we do not want bind mounts being committed to image configs
204
+			binds = append(binds, bind)
205
+			flVolumes.Delete(bind)
206
+		}
207
+	}
208
+
209
+	// Can't evaluate options passed into --tmpfs until we actually mount
210
+	tmpfs := make(map[string]string)
211
+	for _, t := range flTmpfs.GetAll() {
212
+		if arr := strings.SplitN(t, ":", 2); len(arr) > 1 {
213
+			if _, _, err := mount.ParseTmpfsOptions(arr[1]); err != nil {
214
+				return nil, nil, cmd, err
215
+			}
216
+			tmpfs[arr[0]] = arr[1]
217
+		} else {
218
+			tmpfs[arr[0]] = ""
219
+		}
220
+	}
221
+
222
+	var (
223
+		parsedArgs = cmd.Args()
224
+		runCmd     *strslice.StrSlice
225
+		entrypoint *strslice.StrSlice
226
+		image      = cmd.Arg(0)
227
+	)
228
+	if len(parsedArgs) > 1 {
229
+		runCmd = strslice.New(parsedArgs[1:]...)
230
+	}
231
+	if *flEntrypoint != "" {
232
+		entrypoint = strslice.New(*flEntrypoint)
233
+	}
234
+
235
+	var (
236
+		domainname string
237
+		hostname   = *flHostname
238
+		parts      = strings.SplitN(hostname, ".", 2)
239
+	)
240
+	if len(parts) > 1 {
241
+		hostname = parts[0]
242
+		domainname = parts[1]
243
+	}
244
+
245
+	ports, portBindings, err := nat.ParsePortSpecs(flPublish.GetAll())
246
+	if err != nil {
247
+		return nil, nil, cmd, err
248
+	}
249
+
250
+	// Merge in exposed ports to the map of published ports
251
+	for _, e := range flExpose.GetAll() {
252
+		if strings.Contains(e, ":") {
253
+			return nil, nil, cmd, fmt.Errorf("Invalid port format for --expose: %s", e)
254
+		}
255
+		//support two formats for expose, original format <portnum>/[<proto>] or <startport-endport>/[<proto>]
256
+		proto, port := nat.SplitProtoPort(e)
257
+		//parse the start and end port and create a sequence of ports to expose
258
+		//if expose a port, the start and end port are the same
259
+		start, end, err := nat.ParsePortRange(port)
260
+		if err != nil {
261
+			return nil, nil, cmd, fmt.Errorf("Invalid range format for --expose: %s, error: %s", e, err)
262
+		}
263
+		for i := start; i <= end; i++ {
264
+			p, err := nat.NewPort(proto, strconv.FormatUint(i, 10))
265
+			if err != nil {
266
+				return nil, nil, cmd, err
267
+			}
268
+			if _, exists := ports[p]; !exists {
269
+				ports[p] = struct{}{}
270
+			}
271
+		}
272
+	}
273
+
274
+	// parse device mappings
275
+	deviceMappings := []container.DeviceMapping{}
276
+	for _, device := range flDevices.GetAll() {
277
+		deviceMapping, err := ParseDevice(device)
278
+		if err != nil {
279
+			return nil, nil, cmd, err
280
+		}
281
+		deviceMappings = append(deviceMappings, deviceMapping)
282
+	}
283
+
284
+	// collect all the environment variables for the container
285
+	envVariables, err := readKVStrings(flEnvFile.GetAll(), flEnv.GetAll())
286
+	if err != nil {
287
+		return nil, nil, cmd, err
288
+	}
289
+
290
+	// collect all the labels for the container
291
+	labels, err := readKVStrings(flLabelsFile.GetAll(), flLabels.GetAll())
292
+	if err != nil {
293
+		return nil, nil, cmd, err
294
+	}
295
+
296
+	ipcMode := container.IpcMode(*flIpcMode)
297
+	if !ipcMode.Valid() {
298
+		return nil, nil, cmd, fmt.Errorf("--ipc: invalid IPC mode")
299
+	}
300
+
301
+	pidMode := container.PidMode(*flPidMode)
302
+	if !pidMode.Valid() {
303
+		return nil, nil, cmd, fmt.Errorf("--pid: invalid PID mode")
304
+	}
305
+
306
+	utsMode := container.UTSMode(*flUTSMode)
307
+	if !utsMode.Valid() {
308
+		return nil, nil, cmd, fmt.Errorf("--uts: invalid UTS mode")
309
+	}
310
+
311
+	restartPolicy, err := ParseRestartPolicy(*flRestartPolicy)
312
+	if err != nil {
313
+		return nil, nil, cmd, err
314
+	}
315
+
316
+	loggingOpts, err := parseLoggingOpts(*flLoggingDriver, flLoggingOpts.GetAll())
317
+	if err != nil {
318
+		return nil, nil, cmd, err
319
+	}
320
+
321
+	resources := container.Resources{
322
+		CgroupParent:         *flCgroupParent,
323
+		Memory:               flMemory,
324
+		MemoryReservation:    MemoryReservation,
325
+		MemorySwap:           memorySwap,
326
+		MemorySwappiness:     flSwappiness,
327
+		KernelMemory:         KernelMemory,
328
+		OomKillDisable:       *flOomKillDisable,
329
+		CPUShares:            *flCPUShares,
330
+		CPUPeriod:            *flCPUPeriod,
331
+		CpusetCpus:           *flCpusetCpus,
332
+		CpusetMems:           *flCpusetMems,
333
+		CPUQuota:             *flCPUQuota,
334
+		BlkioWeight:          *flBlkioWeight,
335
+		BlkioWeightDevice:    flBlkioWeightDevice.GetList(),
336
+		BlkioDeviceReadBps:   flDeviceReadBps.GetList(),
337
+		BlkioDeviceWriteBps:  flDeviceWriteBps.GetList(),
338
+		BlkioDeviceReadIOps:  flDeviceReadIOps.GetList(),
339
+		BlkioDeviceWriteIOps: flDeviceWriteIOps.GetList(),
340
+		Ulimits:              flUlimits.GetList(),
341
+		Devices:              deviceMappings,
342
+	}
343
+
344
+	config := &container.Config{
345
+		Hostname:     hostname,
346
+		Domainname:   domainname,
347
+		ExposedPorts: ports,
348
+		User:         *flUser,
349
+		Tty:          *flTty,
350
+		// TODO: deprecated, it comes from -n, --networking
351
+		// it's still needed internally to set the network to disabled
352
+		// if e.g. bridge is none in daemon opts, and in inspect
353
+		NetworkDisabled: false,
354
+		OpenStdin:       *flStdin,
355
+		AttachStdin:     attachStdin,
356
+		AttachStdout:    attachStdout,
357
+		AttachStderr:    attachStderr,
358
+		Env:             envVariables,
359
+		Cmd:             runCmd,
360
+		Image:           image,
361
+		Volumes:         flVolumes.GetMap(),
362
+		MacAddress:      *flMacAddress,
363
+		Entrypoint:      entrypoint,
364
+		WorkingDir:      *flWorkingDir,
365
+		Labels:          ConvertKVStringsToMap(labels),
366
+		StopSignal:      *flStopSignal,
367
+	}
368
+
369
+	hostConfig := &container.HostConfig{
370
+		Binds:           binds,
371
+		ContainerIDFile: *flContainerIDFile,
372
+		OomScoreAdj:     *flOomScoreAdj,
373
+		Privileged:      *flPrivileged,
374
+		PortBindings:    portBindings,
375
+		Links:           flLinks.GetAll(),
376
+		PublishAllPorts: *flPublishAll,
377
+		// Make sure the dns fields are never nil.
378
+		// New containers don't ever have those fields nil,
379
+		// but pre created containers can still have those nil values.
380
+		// See https://github.com/docker/docker/pull/17779
381
+		// for a more detailed explanation on why we don't want that.
382
+		DNS:            flDNS.GetAllOrEmpty(),
383
+		DNSSearch:      flDNSSearch.GetAllOrEmpty(),
384
+		DNSOptions:     flDNSOptions.GetAllOrEmpty(),
385
+		ExtraHosts:     flExtraHosts.GetAll(),
386
+		VolumesFrom:    flVolumesFrom.GetAll(),
387
+		NetworkMode:    container.NetworkMode(*flNetMode),
388
+		IpcMode:        ipcMode,
389
+		PidMode:        pidMode,
390
+		UTSMode:        utsMode,
391
+		CapAdd:         strslice.New(flCapAdd.GetAll()...),
392
+		CapDrop:        strslice.New(flCapDrop.GetAll()...),
393
+		GroupAdd:       flGroupAdd.GetAll(),
394
+		RestartPolicy:  restartPolicy,
395
+		SecurityOpt:    flSecurityOpt.GetAll(),
396
+		ReadonlyRootfs: *flReadonlyRootfs,
397
+		LogConfig:      container.LogConfig{Type: *flLoggingDriver, Config: loggingOpts},
398
+		VolumeDriver:   *flVolumeDriver,
399
+		Isolation:      container.IsolationLevel(*flIsolation),
400
+		ShmSize:        parsedShm,
401
+		Resources:      resources,
402
+		Tmpfs:          tmpfs,
403
+	}
404
+
405
+	// When allocating stdin in attached mode, close stdin at client disconnect
406
+	if config.OpenStdin && config.AttachStdin {
407
+		config.StdinOnce = true
408
+	}
409
+	return config, hostConfig, cmd, nil
410
+}
411
+
412
+// reads a file of line terminated key=value pairs and override that with override parameter
413
+func readKVStrings(files []string, override []string) ([]string, error) {
414
+	envVariables := []string{}
415
+	for _, ef := range files {
416
+		parsedVars, err := opts.ParseEnvFile(ef)
417
+		if err != nil {
418
+			return nil, err
419
+		}
420
+		envVariables = append(envVariables, parsedVars...)
421
+	}
422
+	// parse the '-e' and '--env' after, to allow override
423
+	envVariables = append(envVariables, override...)
424
+
425
+	return envVariables, nil
426
+}
427
+
428
+// ConvertKVStringsToMap converts ["key=value"] to {"key":"value"}
429
+func ConvertKVStringsToMap(values []string) map[string]string {
430
+	result := make(map[string]string, len(values))
431
+	for _, value := range values {
432
+		kv := strings.SplitN(value, "=", 2)
433
+		if len(kv) == 1 {
434
+			result[kv[0]] = ""
435
+		} else {
436
+			result[kv[0]] = kv[1]
437
+		}
438
+	}
439
+
440
+	return result
441
+}
442
+
443
+func parseLoggingOpts(loggingDriver string, loggingOpts []string) (map[string]string, error) {
444
+	loggingOptsMap := ConvertKVStringsToMap(loggingOpts)
445
+	if loggingDriver == "none" && len(loggingOpts) > 0 {
446
+		return map[string]string{}, fmt.Errorf("Invalid logging opts for driver %s", loggingDriver)
447
+	}
448
+	return loggingOptsMap, nil
449
+}
450
+
451
+// ParseRestartPolicy returns the parsed policy or an error indicating what is incorrect
452
+func ParseRestartPolicy(policy string) (container.RestartPolicy, error) {
453
+	p := container.RestartPolicy{}
454
+
455
+	if policy == "" {
456
+		return p, nil
457
+	}
458
+
459
+	var (
460
+		parts = strings.Split(policy, ":")
461
+		name  = parts[0]
462
+	)
463
+
464
+	p.Name = name
465
+	switch name {
466
+	case "always", "unless-stopped":
467
+		if len(parts) > 1 {
468
+			return p, fmt.Errorf("maximum restart count not valid with restart policy of \"%s\"", name)
469
+		}
470
+	case "no":
471
+		// do nothing
472
+	case "on-failure":
473
+		if len(parts) > 2 {
474
+			return p, fmt.Errorf("restart count format is not valid, usage: 'on-failure:N' or 'on-failure'")
475
+		}
476
+		if len(parts) == 2 {
477
+			count, err := strconv.Atoi(parts[1])
478
+			if err != nil {
479
+				return p, err
480
+			}
481
+
482
+			p.MaximumRetryCount = count
483
+		}
484
+	default:
485
+		return p, fmt.Errorf("invalid restart policy %s", name)
486
+	}
487
+
488
+	return p, nil
489
+}
490
+
491
+// ParseDevice parses a device mapping string to a container.DeviceMapping struct
492
+func ParseDevice(device string) (container.DeviceMapping, error) {
493
+	src := ""
494
+	dst := ""
495
+	permissions := "rwm"
496
+	arr := strings.Split(device, ":")
497
+	switch len(arr) {
498
+	case 3:
499
+		permissions = arr[2]
500
+		fallthrough
501
+	case 2:
502
+		if ValidDeviceMode(arr[1]) {
503
+			permissions = arr[1]
504
+		} else {
505
+			dst = arr[1]
506
+		}
507
+		fallthrough
508
+	case 1:
509
+		src = arr[0]
510
+	default:
511
+		return container.DeviceMapping{}, fmt.Errorf("Invalid device specification: %s", device)
512
+	}
513
+
514
+	if dst == "" {
515
+		dst = src
516
+	}
517
+
518
+	deviceMapping := container.DeviceMapping{
519
+		PathOnHost:        src,
520
+		PathInContainer:   dst,
521
+		CgroupPermissions: permissions,
522
+	}
523
+	return deviceMapping, nil
524
+}
525
+
526
+// ParseLink parses and validates the specified string as a link format (name:alias)
527
+func ParseLink(val string) (string, string, error) {
528
+	if val == "" {
529
+		return "", "", fmt.Errorf("empty string specified for links")
530
+	}
531
+	arr := strings.Split(val, ":")
532
+	if len(arr) > 2 {
533
+		return "", "", fmt.Errorf("bad format for links: %s", val)
534
+	}
535
+	if len(arr) == 1 {
536
+		return val, val, nil
537
+	}
538
+	// This is kept because we can actually get an HostConfig with links
539
+	// from an already created container and the format is not `foo:bar`
540
+	// but `/foo:/c1/bar`
541
+	if strings.HasPrefix(arr[0], "/") {
542
+		_, alias := path.Split(arr[1])
543
+		return arr[0][1:], alias, nil
544
+	}
545
+	return arr[0], arr[1], nil
546
+}
547
+
548
+// ValidateLink validates that the specified string has a valid link format (containerName:alias).
549
+func ValidateLink(val string) (string, error) {
550
+	if _, _, err := ParseLink(val); err != nil {
551
+		return val, err
552
+	}
553
+	return val, nil
554
+}
555
+
556
+// ValidDeviceMode checks if the mode for device is valid or not.
557
+// Valid mode is a composition of r (read), w (write), and m (mknod).
558
+func ValidDeviceMode(mode string) bool {
559
+	var legalDeviceMode = map[rune]bool{
560
+		'r': true,
561
+		'w': true,
562
+		'm': true,
563
+	}
564
+	if mode == "" {
565
+		return false
566
+	}
567
+	for _, c := range mode {
568
+		if !legalDeviceMode[c] {
569
+			return false
570
+		}
571
+		legalDeviceMode[c] = false
572
+	}
573
+	return true
574
+}
575
+
576
+// ValidateDevice validates a path for devices
577
+// It will make sure 'val' is in the form:
578
+//    [host-dir:]container-path[:mode]
579
+// It also validates the device mode.
580
+func ValidateDevice(val string) (string, error) {
581
+	return validatePath(val, ValidDeviceMode)
582
+}
583
+
584
+func validatePath(val string, validator func(string) bool) (string, error) {
585
+	var containerPath string
586
+	var mode string
587
+
588
+	if strings.Count(val, ":") > 2 {
589
+		return val, fmt.Errorf("bad format for path: %s", val)
590
+	}
591
+
592
+	split := strings.SplitN(val, ":", 3)
593
+	if split[0] == "" {
594
+		return val, fmt.Errorf("bad format for path: %s", val)
595
+	}
596
+	switch len(split) {
597
+	case 1:
598
+		containerPath = split[0]
599
+		val = path.Clean(containerPath)
600
+	case 2:
601
+		if isValid := validator(split[1]); isValid {
602
+			containerPath = split[0]
603
+			mode = split[1]
604
+			val = fmt.Sprintf("%s:%s", path.Clean(containerPath), mode)
605
+		} else {
606
+			containerPath = split[1]
607
+			val = fmt.Sprintf("%s:%s", split[0], path.Clean(containerPath))
608
+		}
609
+	case 3:
610
+		containerPath = split[1]
611
+		mode = split[2]
612
+		if isValid := validator(split[2]); !isValid {
613
+			return val, fmt.Errorf("bad mode specified: %s", mode)
614
+		}
615
+		val = fmt.Sprintf("%s:%s:%s", split[0], containerPath, mode)
616
+	}
617
+
618
+	if !path.IsAbs(containerPath) {
619
+		return val, fmt.Errorf("%s is not an absolute path", containerPath)
620
+	}
621
+	return val, nil
622
+}
0 623
new file mode 100644
... ...
@@ -0,0 +1,765 @@
0
+package opts
1
+
2
+import (
3
+	"bytes"
4
+	"encoding/json"
5
+	"fmt"
6
+	"io/ioutil"
7
+	"os"
8
+	"runtime"
9
+	"strings"
10
+	"testing"
11
+
12
+	"github.com/docker/docker/api/types/container"
13
+	flag "github.com/docker/docker/pkg/mflag"
14
+	"github.com/docker/docker/runconfig"
15
+	"github.com/docker/go-connections/nat"
16
+)
17
+
18
+func parseRun(args []string) (*container.Config, *container.HostConfig, *flag.FlagSet, error) {
19
+	cmd := flag.NewFlagSet("run", flag.ContinueOnError)
20
+	cmd.SetOutput(ioutil.Discard)
21
+	cmd.Usage = nil
22
+	return Parse(cmd, args)
23
+}
24
+
25
+func parse(t *testing.T, args string) (*container.Config, *container.HostConfig, error) {
26
+	config, hostConfig, _, err := parseRun(strings.Split(args+" ubuntu bash", " "))
27
+	return config, hostConfig, err
28
+}
29
+
30
+func mustParse(t *testing.T, args string) (*container.Config, *container.HostConfig) {
31
+	config, hostConfig, err := parse(t, args)
32
+	if err != nil {
33
+		t.Fatal(err)
34
+	}
35
+	return config, hostConfig
36
+}
37
+
38
+func TestParseRunLinks(t *testing.T) {
39
+	if _, hostConfig := mustParse(t, "--link a:b"); len(hostConfig.Links) == 0 || hostConfig.Links[0] != "a:b" {
40
+		t.Fatalf("Error parsing links. Expected []string{\"a:b\"}, received: %v", hostConfig.Links)
41
+	}
42
+	if _, hostConfig := mustParse(t, "--link a:b --link c:d"); len(hostConfig.Links) < 2 || hostConfig.Links[0] != "a:b" || hostConfig.Links[1] != "c:d" {
43
+		t.Fatalf("Error parsing links. Expected []string{\"a:b\", \"c:d\"}, received: %v", hostConfig.Links)
44
+	}
45
+	if _, hostConfig := mustParse(t, ""); len(hostConfig.Links) != 0 {
46
+		t.Fatalf("Error parsing links. No link expected, received: %v", hostConfig.Links)
47
+	}
48
+}
49
+
50
+func TestParseRunAttach(t *testing.T) {
51
+	if config, _ := mustParse(t, "-a stdin"); !config.AttachStdin || config.AttachStdout || config.AttachStderr {
52
+		t.Fatalf("Error parsing attach flags. Expect only Stdin enabled. Received: in: %v, out: %v, err: %v", config.AttachStdin, config.AttachStdout, config.AttachStderr)
53
+	}
54
+	if config, _ := mustParse(t, "-a stdin -a stdout"); !config.AttachStdin || !config.AttachStdout || config.AttachStderr {
55
+		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)
56
+	}
57
+	if config, _ := mustParse(t, "-a stdin -a stdout -a stderr"); !config.AttachStdin || !config.AttachStdout || !config.AttachStderr {
58
+		t.Fatalf("Error parsing attach flags. Expect all attach enabled. Received: in: %v, out: %v, err: %v", config.AttachStdin, config.AttachStdout, config.AttachStderr)
59
+	}
60
+	if config, _ := mustParse(t, ""); config.AttachStdin || !config.AttachStdout || !config.AttachStderr {
61
+		t.Fatalf("Error parsing attach flags. Expect Stdin disabled. Received: in: %v, out: %v, err: %v", config.AttachStdin, config.AttachStdout, config.AttachStderr)
62
+	}
63
+	if config, _ := mustParse(t, "-i"); !config.AttachStdin || !config.AttachStdout || !config.AttachStderr {
64
+		t.Fatalf("Error parsing attach flags. Expect Stdin enabled. Received: in: %v, out: %v, err: %v", config.AttachStdin, config.AttachStdout, config.AttachStderr)
65
+	}
66
+
67
+	if _, _, err := parse(t, "-a"); err == nil {
68
+		t.Fatalf("Error parsing attach flags, `-a` should be an error but is not")
69
+	}
70
+	if _, _, err := parse(t, "-a invalid"); err == nil {
71
+		t.Fatalf("Error parsing attach flags, `-a invalid` should be an error but is not")
72
+	}
73
+	if _, _, err := parse(t, "-a invalid -a stdout"); err == nil {
74
+		t.Fatalf("Error parsing attach flags, `-a stdout -a invalid` should be an error but is not")
75
+	}
76
+	if _, _, err := parse(t, "-a stdout -a stderr -d"); err == nil {
77
+		t.Fatalf("Error parsing attach flags, `-a stdout -a stderr -d` should be an error but is not")
78
+	}
79
+	if _, _, err := parse(t, "-a stdin -d"); err == nil {
80
+		t.Fatalf("Error parsing attach flags, `-a stdin -d` should be an error but is not")
81
+	}
82
+	if _, _, err := parse(t, "-a stdout -d"); err == nil {
83
+		t.Fatalf("Error parsing attach flags, `-a stdout -d` should be an error but is not")
84
+	}
85
+	if _, _, err := parse(t, "-a stderr -d"); err == nil {
86
+		t.Fatalf("Error parsing attach flags, `-a stderr -d` should be an error but is not")
87
+	}
88
+	if _, _, err := parse(t, "-d --rm"); err == nil {
89
+		t.Fatalf("Error parsing attach flags, `-d --rm` should be an error but is not")
90
+	}
91
+}
92
+
93
+func TestParseRunVolumes(t *testing.T) {
94
+
95
+	// A single volume
96
+	arr, tryit := setupPlatformVolume([]string{`/tmp`}, []string{`c:\tmp`})
97
+	if config, hostConfig := mustParse(t, tryit); hostConfig.Binds != nil {
98
+		t.Fatalf("Error parsing volume flags, %q should not mount-bind anything. Received %v", tryit, hostConfig.Binds)
99
+	} else if _, exists := config.Volumes[arr[0]]; !exists {
100
+		t.Fatalf("Error parsing volume flags, %q is missing from volumes. Received %v", tryit, config.Volumes)
101
+	}
102
+
103
+	// Two volumes
104
+	arr, tryit = setupPlatformVolume([]string{`/tmp`, `/var`}, []string{`c:\tmp`, `c:\var`})
105
+	if config, hostConfig := mustParse(t, tryit); hostConfig.Binds != nil {
106
+		t.Fatalf("Error parsing volume flags, %q should not mount-bind anything. Received %v", tryit, hostConfig.Binds)
107
+	} else if _, exists := config.Volumes[arr[0]]; !exists {
108
+		t.Fatalf("Error parsing volume flags, %s is missing from volumes. Received %v", arr[0], config.Volumes)
109
+	} else if _, exists := config.Volumes[arr[1]]; !exists {
110
+		t.Fatalf("Error parsing volume flags, %s is missing from volumes. Received %v", arr[1], config.Volumes)
111
+	}
112
+
113
+	// A single bind-mount
114
+	arr, tryit = setupPlatformVolume([]string{`/hostTmp:/containerTmp`}, []string{os.Getenv("TEMP") + `:c:\containerTmp`})
115
+	if config, hostConfig := mustParse(t, tryit); hostConfig.Binds == nil || hostConfig.Binds[0] != arr[0] {
116
+		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)
117
+	}
118
+
119
+	// Two bind-mounts.
120
+	arr, tryit = setupPlatformVolume([]string{`/hostTmp:/containerTmp`, `/hostVar:/containerVar`}, []string{os.Getenv("ProgramData") + `:c:\ContainerPD`, os.Getenv("TEMP") + `:c:\containerTmp`})
121
+	if _, hostConfig := mustParse(t, tryit); hostConfig.Binds == nil || compareRandomizedStrings(hostConfig.Binds[0], hostConfig.Binds[1], arr[0], arr[1]) != nil {
122
+		t.Fatalf("Error parsing volume flags, `%s and %s` did not mount-bind correctly. Received %v", arr[0], arr[1], hostConfig.Binds)
123
+	}
124
+
125
+	// Two bind-mounts, first read-only, second read-write.
126
+	// TODO Windows: The Windows version uses read-write as that's the only mode it supports. Can change this post TP4
127
+	arr, tryit = setupPlatformVolume([]string{`/hostTmp:/containerTmp:ro`, `/hostVar:/containerVar:rw`}, []string{os.Getenv("TEMP") + `:c:\containerTmp:rw`, os.Getenv("ProgramData") + `:c:\ContainerPD:rw`})
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
+	// Similar to previous test but with alternate modes which are only supported by Linux
133
+	if runtime.GOOS != "windows" {
134
+		arr, tryit = setupPlatformVolume([]string{`/hostTmp:/containerTmp:ro,Z`, `/hostVar:/containerVar:rw,Z`}, []string{})
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
+		arr, tryit = setupPlatformVolume([]string{`/hostTmp:/containerTmp:Z`, `/hostVar:/containerVar:z`}, []string{})
140
+		if _, hostConfig := mustParse(t, tryit); hostConfig.Binds == nil || compareRandomizedStrings(hostConfig.Binds[0], hostConfig.Binds[1], arr[0], arr[1]) != nil {
141
+			t.Fatalf("Error parsing volume flags, `%s and %s` did not mount-bind correctly. Received %v", arr[0], arr[1], hostConfig.Binds)
142
+		}
143
+	}
144
+
145
+	// One bind mount and one volume
146
+	arr, tryit = setupPlatformVolume([]string{`/hostTmp:/containerTmp`, `/containerVar`}, []string{os.Getenv("TEMP") + `:c:\containerTmp`, `c:\containerTmp`})
147
+	if config, hostConfig := mustParse(t, tryit); hostConfig.Binds == nil || len(hostConfig.Binds) > 1 || hostConfig.Binds[0] != arr[0] {
148
+		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)
149
+	} else if _, exists := config.Volumes[arr[1]]; !exists {
150
+		t.Fatalf("Error parsing volume flags %s and %s. %s is missing from volumes. Received %v", arr[0], arr[1], arr[1], config.Volumes)
151
+	}
152
+
153
+	// Root to non-c: drive letter (Windows specific)
154
+	if runtime.GOOS == "windows" {
155
+		arr, tryit = setupPlatformVolume([]string{}, []string{os.Getenv("SystemDrive") + `\:d:`})
156
+		if config, hostConfig := mustParse(t, tryit); hostConfig.Binds == nil || len(hostConfig.Binds) > 1 || hostConfig.Binds[0] != arr[0] || len(config.Volumes) != 0 {
157
+			t.Fatalf("Error parsing %s. Should have a single bind mount and no volumes", arr[0])
158
+		}
159
+	}
160
+
161
+}
162
+
163
+// This tests the cases for binds which are generated through
164
+// DecodeContainerConfig rather than Parse()
165
+func TestDecodeContainerConfigVolumes(t *testing.T) {
166
+
167
+	// Root to root
168
+	bindsOrVols, _ := setupPlatformVolume([]string{`/:/`}, []string{os.Getenv("SystemDrive") + `\:c:\`})
169
+	if _, _, err := callDecodeContainerConfig(nil, bindsOrVols); err == nil {
170
+		t.Fatalf("binds %v should have failed", bindsOrVols)
171
+	}
172
+	if _, _, err := callDecodeContainerConfig(bindsOrVols, nil); err == nil {
173
+		t.Fatalf("volume %v should have failed", bindsOrVols)
174
+	}
175
+
176
+	// No destination path
177
+	bindsOrVols, _ = setupPlatformVolume([]string{`/tmp:`}, []string{os.Getenv("TEMP") + `\:`})
178
+	if _, _, err := callDecodeContainerConfig(nil, bindsOrVols); err == nil {
179
+		t.Fatalf("binds %v should have failed", bindsOrVols)
180
+	}
181
+	if _, _, err := callDecodeContainerConfig(bindsOrVols, nil); err == nil {
182
+		t.Fatalf("binds %v should have failed", bindsOrVols)
183
+	}
184
+
185
+	//	// No destination path or mode
186
+	bindsOrVols, _ = setupPlatformVolume([]string{`/tmp::`}, []string{os.Getenv("TEMP") + `\::`})
187
+	if _, _, err := callDecodeContainerConfig(nil, bindsOrVols); err == nil {
188
+		t.Fatalf("binds %v should have failed", bindsOrVols)
189
+	}
190
+	if _, _, err := callDecodeContainerConfig(bindsOrVols, nil); err == nil {
191
+		t.Fatalf("binds %v should have failed", bindsOrVols)
192
+	}
193
+
194
+	// A whole lot of nothing
195
+	bindsOrVols = []string{`:`}
196
+	if _, _, err := callDecodeContainerConfig(nil, bindsOrVols); err == nil {
197
+		t.Fatalf("binds %v should have failed", bindsOrVols)
198
+	}
199
+	if _, _, err := callDecodeContainerConfig(bindsOrVols, nil); err == nil {
200
+		t.Fatalf("binds %v should have failed", bindsOrVols)
201
+	}
202
+
203
+	// A whole lot of nothing with no mode
204
+	bindsOrVols = []string{`::`}
205
+	if _, _, err := callDecodeContainerConfig(nil, bindsOrVols); err == nil {
206
+		t.Fatalf("binds %v should have failed", bindsOrVols)
207
+	}
208
+	if _, _, err := callDecodeContainerConfig(bindsOrVols, nil); err == nil {
209
+		t.Fatalf("binds %v should have failed", bindsOrVols)
210
+	}
211
+
212
+	// Too much including an invalid mode
213
+	wTmp := os.Getenv("TEMP")
214
+	bindsOrVols, _ = setupPlatformVolume([]string{`/tmp:/tmp:/tmp:/tmp`}, []string{wTmp + ":" + wTmp + ":" + wTmp + ":" + wTmp})
215
+	if _, _, err := callDecodeContainerConfig(nil, bindsOrVols); err == nil {
216
+		t.Fatalf("binds %v should have failed", bindsOrVols)
217
+	}
218
+	if _, _, err := callDecodeContainerConfig(bindsOrVols, nil); err == nil {
219
+		t.Fatalf("binds %v should have failed", bindsOrVols)
220
+	}
221
+
222
+	// Windows specific error tests
223
+	if runtime.GOOS == "windows" {
224
+		// Volume which does not include a drive letter
225
+		bindsOrVols = []string{`\tmp`}
226
+		if _, _, err := callDecodeContainerConfig(nil, bindsOrVols); err == nil {
227
+			t.Fatalf("binds %v should have failed", bindsOrVols)
228
+		}
229
+		if _, _, err := callDecodeContainerConfig(bindsOrVols, nil); err == nil {
230
+			t.Fatalf("binds %v should have failed", bindsOrVols)
231
+		}
232
+
233
+		// Root to C-Drive
234
+		bindsOrVols = []string{os.Getenv("SystemDrive") + `\:c:`}
235
+		if _, _, err := callDecodeContainerConfig(nil, bindsOrVols); err == nil {
236
+			t.Fatalf("binds %v should have failed", bindsOrVols)
237
+		}
238
+		if _, _, err := callDecodeContainerConfig(bindsOrVols, nil); err == nil {
239
+			t.Fatalf("binds %v should have failed", bindsOrVols)
240
+		}
241
+
242
+		// Container path that does not include a drive letter
243
+		bindsOrVols = []string{`c:\windows:\somewhere`}
244
+		if _, _, err := callDecodeContainerConfig(nil, bindsOrVols); err == nil {
245
+			t.Fatalf("binds %v should have failed", bindsOrVols)
246
+		}
247
+		if _, _, err := callDecodeContainerConfig(bindsOrVols, nil); err == nil {
248
+			t.Fatalf("binds %v should have failed", bindsOrVols)
249
+		}
250
+	}
251
+
252
+	// Linux-specific error tests
253
+	if runtime.GOOS != "windows" {
254
+		// Just root
255
+		bindsOrVols = []string{`/`}
256
+		if _, _, err := callDecodeContainerConfig(nil, bindsOrVols); err == nil {
257
+			t.Fatalf("binds %v should have failed", bindsOrVols)
258
+		}
259
+		if _, _, err := callDecodeContainerConfig(bindsOrVols, nil); err == nil {
260
+			t.Fatalf("binds %v should have failed", bindsOrVols)
261
+		}
262
+
263
+		// A single volume that looks like a bind mount passed in Volumes.
264
+		// This should be handled as a bind mount, not a volume.
265
+		vols := []string{`/foo:/bar`}
266
+		if config, hostConfig, err := callDecodeContainerConfig(vols, nil); err != nil {
267
+			t.Fatal("Volume /foo:/bar should have succeeded as a volume name")
268
+		} else if hostConfig.Binds != nil {
269
+			t.Fatalf("Error parsing volume flags, /foo:/bar should not mount-bind anything. Received %v", hostConfig.Binds)
270
+		} else if _, exists := config.Volumes[vols[0]]; !exists {
271
+			t.Fatalf("Error parsing volume flags, /foo:/bar is missing from volumes. Received %v", config.Volumes)
272
+		}
273
+
274
+	}
275
+}
276
+
277
+// callDecodeContainerConfig is a utility function used by TestDecodeContainerConfigVolumes
278
+// to call DecodeContainerConfig. It effectively does what a client would
279
+// do when calling the daemon by constructing a JSON stream of a
280
+// ContainerConfigWrapper which is populated by the set of volume specs
281
+// passed into it. It returns a config and a hostconfig which can be
282
+// validated to ensure DecodeContainerConfig has manipulated the structures
283
+// correctly.
284
+func callDecodeContainerConfig(volumes []string, binds []string) (*container.Config, *container.HostConfig, error) {
285
+	var (
286
+		b   []byte
287
+		err error
288
+		c   *container.Config
289
+		h   *container.HostConfig
290
+	)
291
+	w := runconfig.ContainerConfigWrapper{
292
+		Config: &container.Config{
293
+			Volumes: map[string]struct{}{},
294
+		},
295
+		HostConfig: &container.HostConfig{
296
+			NetworkMode: "none",
297
+			Binds:       binds,
298
+		},
299
+	}
300
+	for _, v := range volumes {
301
+		w.Config.Volumes[v] = struct{}{}
302
+	}
303
+	if b, err = json.Marshal(w); err != nil {
304
+		return nil, nil, fmt.Errorf("Error on marshal %s", err.Error())
305
+	}
306
+	c, h, err = runconfig.DecodeContainerConfig(bytes.NewReader(b))
307
+	if err != nil {
308
+		return nil, nil, fmt.Errorf("Error parsing %s: %v", string(b), err)
309
+	}
310
+	if c == nil || h == nil {
311
+		return nil, nil, fmt.Errorf("Empty config or hostconfig")
312
+	}
313
+
314
+	return c, h, err
315
+}
316
+
317
+// check if (a == c && b == d) || (a == d && b == c)
318
+// because maps are randomized
319
+func compareRandomizedStrings(a, b, c, d string) error {
320
+	if a == c && b == d {
321
+		return nil
322
+	}
323
+	if a == d && b == c {
324
+		return nil
325
+	}
326
+	return fmt.Errorf("strings don't match")
327
+}
328
+
329
+// setupPlatformVolume takes two arrays of volume specs - a Unix style
330
+// spec and a Windows style spec. Depending on the platform being unit tested,
331
+// it returns one of them, along with a volume string that would be passed
332
+// on the docker CLI (eg -v /bar -v /foo).
333
+func setupPlatformVolume(u []string, w []string) ([]string, string) {
334
+	var a []string
335
+	if runtime.GOOS == "windows" {
336
+		a = w
337
+	} else {
338
+		a = u
339
+	}
340
+	s := ""
341
+	for _, v := range a {
342
+		s = s + "-v " + v + " "
343
+	}
344
+	return a, s
345
+}
346
+
347
+// Simple parse with MacAddress validation
348
+func TestParseWithMacAddress(t *testing.T) {
349
+	invalidMacAddress := "--mac-address=invalidMacAddress"
350
+	validMacAddress := "--mac-address=92:d0:c6:0a:29:33"
351
+	if _, _, _, err := parseRun([]string{invalidMacAddress, "img", "cmd"}); err != nil && err.Error() != "invalidMacAddress is not a valid mac address" {
352
+		t.Fatalf("Expected an error with %v mac-address, got %v", invalidMacAddress, err)
353
+	}
354
+	if config, _ := mustParse(t, validMacAddress); config.MacAddress != "92:d0:c6:0a:29:33" {
355
+		t.Fatalf("Expected the config to have '92:d0:c6:0a:29:33' as MacAddress, got '%v'", config.MacAddress)
356
+	}
357
+}
358
+
359
+func TestParseWithMemory(t *testing.T) {
360
+	invalidMemory := "--memory=invalid"
361
+	validMemory := "--memory=1G"
362
+	if _, _, _, err := parseRun([]string{invalidMemory, "img", "cmd"}); err != nil && err.Error() != "invalid size: 'invalid'" {
363
+		t.Fatalf("Expected an error with '%v' Memory, got '%v'", invalidMemory, err)
364
+	}
365
+	if _, hostconfig := mustParse(t, validMemory); hostconfig.Memory != 1073741824 {
366
+		t.Fatalf("Expected the config to have '1G' as Memory, got '%v'", hostconfig.Memory)
367
+	}
368
+}
369
+
370
+func TestParseWithMemorySwap(t *testing.T) {
371
+	invalidMemory := "--memory-swap=invalid"
372
+	validMemory := "--memory-swap=1G"
373
+	anotherValidMemory := "--memory-swap=-1"
374
+	if _, _, _, err := parseRun([]string{invalidMemory, "img", "cmd"}); err == nil || err.Error() != "invalid size: 'invalid'" {
375
+		t.Fatalf("Expected an error with '%v' MemorySwap, got '%v'", invalidMemory, err)
376
+	}
377
+	if _, hostconfig := mustParse(t, validMemory); hostconfig.MemorySwap != 1073741824 {
378
+		t.Fatalf("Expected the config to have '1073741824' as MemorySwap, got '%v'", hostconfig.MemorySwap)
379
+	}
380
+	if _, hostconfig := mustParse(t, anotherValidMemory); hostconfig.MemorySwap != -1 {
381
+		t.Fatalf("Expected the config to have '-1' as MemorySwap, got '%v'", hostconfig.MemorySwap)
382
+	}
383
+}
384
+
385
+func TestParseHostname(t *testing.T) {
386
+	hostname := "--hostname=hostname"
387
+	hostnameWithDomain := "--hostname=hostname.domainname"
388
+	hostnameWithDomainTld := "--hostname=hostname.domainname.tld"
389
+	if config, _ := mustParse(t, hostname); config.Hostname != "hostname" && config.Domainname != "" {
390
+		t.Fatalf("Expected the config to have 'hostname' as hostname, got '%v'", config.Hostname)
391
+	}
392
+	if config, _ := mustParse(t, hostnameWithDomain); config.Hostname != "hostname" && config.Domainname != "domainname" {
393
+		t.Fatalf("Expected the config to have 'hostname' as hostname, got '%v'", config.Hostname)
394
+	}
395
+	if config, _ := mustParse(t, hostnameWithDomainTld); config.Hostname != "hostname" && config.Domainname != "domainname.tld" {
396
+		t.Fatalf("Expected the config to have 'hostname' as hostname, got '%v'", config.Hostname)
397
+	}
398
+}
399
+
400
+func TestParseWithExpose(t *testing.T) {
401
+	invalids := map[string]string{
402
+		":":                   "Invalid port format for --expose: :",
403
+		"8080:9090":           "Invalid port format for --expose: 8080:9090",
404
+		"/tcp":                "Invalid range format for --expose: /tcp, error: Empty string specified for ports.",
405
+		"/udp":                "Invalid range format for --expose: /udp, error: Empty string specified for ports.",
406
+		"NaN/tcp":             `Invalid range format for --expose: NaN/tcp, error: strconv.ParseUint: parsing "NaN": invalid syntax`,
407
+		"NaN-NaN/tcp":         `Invalid range format for --expose: NaN-NaN/tcp, error: strconv.ParseUint: parsing "NaN": invalid syntax`,
408
+		"8080-NaN/tcp":        `Invalid range format for --expose: 8080-NaN/tcp, error: strconv.ParseUint: parsing "NaN": invalid syntax`,
409
+		"1234567890-8080/tcp": `Invalid range format for --expose: 1234567890-8080/tcp, error: strconv.ParseUint: parsing "1234567890": value out of range`,
410
+	}
411
+	valids := map[string][]nat.Port{
412
+		"8080/tcp":      {"8080/tcp"},
413
+		"8080/udp":      {"8080/udp"},
414
+		"8080/ncp":      {"8080/ncp"},
415
+		"8080-8080/udp": {"8080/udp"},
416
+		"8080-8082/tcp": {"8080/tcp", "8081/tcp", "8082/tcp"},
417
+	}
418
+	for expose, expectedError := range invalids {
419
+		if _, _, _, err := parseRun([]string{fmt.Sprintf("--expose=%v", expose), "img", "cmd"}); err == nil || err.Error() != expectedError {
420
+			t.Fatalf("Expected error '%v' with '--expose=%v', got '%v'", expectedError, expose, err)
421
+		}
422
+	}
423
+	for expose, exposedPorts := range valids {
424
+		config, _, _, err := parseRun([]string{fmt.Sprintf("--expose=%v", expose), "img", "cmd"})
425
+		if err != nil {
426
+			t.Fatal(err)
427
+		}
428
+		if len(config.ExposedPorts) != len(exposedPorts) {
429
+			t.Fatalf("Expected %v exposed port, got %v", len(exposedPorts), len(config.ExposedPorts))
430
+		}
431
+		for _, port := range exposedPorts {
432
+			if _, ok := config.ExposedPorts[port]; !ok {
433
+				t.Fatalf("Expected %v, got %v", exposedPorts, config.ExposedPorts)
434
+			}
435
+		}
436
+	}
437
+	// Merge with actual published port
438
+	config, _, _, err := parseRun([]string{"--publish=80", "--expose=80-81/tcp", "img", "cmd"})
439
+	if err != nil {
440
+		t.Fatal(err)
441
+	}
442
+	if len(config.ExposedPorts) != 2 {
443
+		t.Fatalf("Expected 2 exposed ports, got %v", config.ExposedPorts)
444
+	}
445
+	ports := []nat.Port{"80/tcp", "81/tcp"}
446
+	for _, port := range ports {
447
+		if _, ok := config.ExposedPorts[port]; !ok {
448
+			t.Fatalf("Expected %v, got %v", ports, config.ExposedPorts)
449
+		}
450
+	}
451
+}
452
+
453
+func TestParseDevice(t *testing.T) {
454
+	valids := map[string]container.DeviceMapping{
455
+		"/dev/snd": {
456
+			PathOnHost:        "/dev/snd",
457
+			PathInContainer:   "/dev/snd",
458
+			CgroupPermissions: "rwm",
459
+		},
460
+		"/dev/snd:rw": {
461
+			PathOnHost:        "/dev/snd",
462
+			PathInContainer:   "/dev/snd",
463
+			CgroupPermissions: "rw",
464
+		},
465
+		"/dev/snd:/something": {
466
+			PathOnHost:        "/dev/snd",
467
+			PathInContainer:   "/something",
468
+			CgroupPermissions: "rwm",
469
+		},
470
+		"/dev/snd:/something:rw": {
471
+			PathOnHost:        "/dev/snd",
472
+			PathInContainer:   "/something",
473
+			CgroupPermissions: "rw",
474
+		},
475
+	}
476
+	for device, deviceMapping := range valids {
477
+		_, hostconfig, _, err := parseRun([]string{fmt.Sprintf("--device=%v", device), "img", "cmd"})
478
+		if err != nil {
479
+			t.Fatal(err)
480
+		}
481
+		if len(hostconfig.Devices) != 1 {
482
+			t.Fatalf("Expected 1 devices, got %v", hostconfig.Devices)
483
+		}
484
+		if hostconfig.Devices[0] != deviceMapping {
485
+			t.Fatalf("Expected %v, got %v", deviceMapping, hostconfig.Devices)
486
+		}
487
+	}
488
+
489
+}
490
+
491
+func TestParseModes(t *testing.T) {
492
+	// ipc ko
493
+	if _, _, _, err := parseRun([]string{"--ipc=container:", "img", "cmd"}); err == nil || err.Error() != "--ipc: invalid IPC mode" {
494
+		t.Fatalf("Expected an error with message '--ipc: invalid IPC mode', got %v", err)
495
+	}
496
+	// ipc ok
497
+	_, hostconfig, _, err := parseRun([]string{"--ipc=host", "img", "cmd"})
498
+	if err != nil {
499
+		t.Fatal(err)
500
+	}
501
+	if !hostconfig.IpcMode.Valid() {
502
+		t.Fatalf("Expected a valid IpcMode, got %v", hostconfig.IpcMode)
503
+	}
504
+	// pid ko
505
+	if _, _, _, err := parseRun([]string{"--pid=container:", "img", "cmd"}); err == nil || err.Error() != "--pid: invalid PID mode" {
506
+		t.Fatalf("Expected an error with message '--pid: invalid PID mode', got %v", err)
507
+	}
508
+	// pid ok
509
+	_, hostconfig, _, err = parseRun([]string{"--pid=host", "img", "cmd"})
510
+	if err != nil {
511
+		t.Fatal(err)
512
+	}
513
+	if !hostconfig.PidMode.Valid() {
514
+		t.Fatalf("Expected a valid PidMode, got %v", hostconfig.PidMode)
515
+	}
516
+	// uts ko
517
+	if _, _, _, err := parseRun([]string{"--uts=container:", "img", "cmd"}); err == nil || err.Error() != "--uts: invalid UTS mode" {
518
+		t.Fatalf("Expected an error with message '--uts: invalid UTS mode', got %v", err)
519
+	}
520
+	// uts ok
521
+	_, hostconfig, _, err = parseRun([]string{"--uts=host", "img", "cmd"})
522
+	if err != nil {
523
+		t.Fatal(err)
524
+	}
525
+	if !hostconfig.UTSMode.Valid() {
526
+		t.Fatalf("Expected a valid UTSMode, got %v", hostconfig.UTSMode)
527
+	}
528
+	// shm-size ko
529
+	if _, _, _, err = parseRun([]string{"--shm-size=a128m", "img", "cmd"}); err == nil || err.Error() != "invalid size: 'a128m'" {
530
+		t.Fatalf("Expected an error with message 'invalid size: a128m', got %v", err)
531
+	}
532
+	// shm-size ok
533
+	_, hostconfig, _, err = parseRun([]string{"--shm-size=128m", "img", "cmd"})
534
+	if err != nil {
535
+		t.Fatal(err)
536
+	}
537
+	if *hostconfig.ShmSize != 134217728 {
538
+		t.Fatalf("Expected a valid ShmSize, got %d", *hostconfig.ShmSize)
539
+	}
540
+}
541
+
542
+func TestParseRestartPolicy(t *testing.T) {
543
+	invalids := map[string]string{
544
+		"something":          "invalid restart policy something",
545
+		"always:2":           "maximum restart count not valid with restart policy of \"always\"",
546
+		"always:2:3":         "maximum restart count not valid with restart policy of \"always\"",
547
+		"on-failure:invalid": `strconv.ParseInt: parsing "invalid": invalid syntax`,
548
+		"on-failure:2:5":     "restart count format is not valid, usage: 'on-failure:N' or 'on-failure'",
549
+	}
550
+	valids := map[string]container.RestartPolicy{
551
+		"": {},
552
+		"always": {
553
+			Name:              "always",
554
+			MaximumRetryCount: 0,
555
+		},
556
+		"on-failure:1": {
557
+			Name:              "on-failure",
558
+			MaximumRetryCount: 1,
559
+		},
560
+	}
561
+	for restart, expectedError := range invalids {
562
+		if _, _, _, err := parseRun([]string{fmt.Sprintf("--restart=%s", restart), "img", "cmd"}); err == nil || err.Error() != expectedError {
563
+			t.Fatalf("Expected an error with message '%v' for %v, got %v", expectedError, restart, err)
564
+		}
565
+	}
566
+	for restart, expected := range valids {
567
+		_, hostconfig, _, err := parseRun([]string{fmt.Sprintf("--restart=%v", restart), "img", "cmd"})
568
+		if err != nil {
569
+			t.Fatal(err)
570
+		}
571
+		if hostconfig.RestartPolicy != expected {
572
+			t.Fatalf("Expected %v, got %v", expected, hostconfig.RestartPolicy)
573
+		}
574
+	}
575
+}
576
+
577
+func TestParseLoggingOpts(t *testing.T) {
578
+	// logging opts ko
579
+	if _, _, _, err := parseRun([]string{"--log-driver=none", "--log-opt=anything", "img", "cmd"}); err == nil || err.Error() != "Invalid logging opts for driver none" {
580
+		t.Fatalf("Expected an error with message 'Invalid logging opts for driver none', got %v", err)
581
+	}
582
+	// logging opts ok
583
+	_, hostconfig, _, err := parseRun([]string{"--log-driver=syslog", "--log-opt=something", "img", "cmd"})
584
+	if err != nil {
585
+		t.Fatal(err)
586
+	}
587
+	if hostconfig.LogConfig.Type != "syslog" || len(hostconfig.LogConfig.Config) != 1 {
588
+		t.Fatalf("Expected a 'syslog' LogConfig with one config, got %v", hostconfig.RestartPolicy)
589
+	}
590
+}
591
+
592
+func TestParseEnvfileVariables(t *testing.T) {
593
+	e := "open nonexistent: no such file or directory"
594
+	if runtime.GOOS == "windows" {
595
+		e = "open nonexistent: The system cannot find the file specified."
596
+	}
597
+	// env ko
598
+	if _, _, _, err := parseRun([]string{"--env-file=nonexistent", "img", "cmd"}); err == nil || err.Error() != e {
599
+		t.Fatalf("Expected an error with message '%s', got %v", e, err)
600
+	}
601
+	// env ok
602
+	config, _, _, err := parseRun([]string{"--env-file=fixtures/valid.env", "img", "cmd"})
603
+	if err != nil {
604
+		t.Fatal(err)
605
+	}
606
+	if len(config.Env) != 1 || config.Env[0] != "ENV1=value1" {
607
+		t.Fatalf("Expected a a config with [ENV1=value1], got %v", config.Env)
608
+	}
609
+	config, _, _, err = parseRun([]string{"--env-file=fixtures/valid.env", "--env=ENV2=value2", "img", "cmd"})
610
+	if err != nil {
611
+		t.Fatal(err)
612
+	}
613
+	if len(config.Env) != 2 || config.Env[0] != "ENV1=value1" || config.Env[1] != "ENV2=value2" {
614
+		t.Fatalf("Expected a a config with [ENV1=value1 ENV2=value2], got %v", config.Env)
615
+	}
616
+}
617
+
618
+func TestParseLabelfileVariables(t *testing.T) {
619
+	e := "open nonexistent: no such file or directory"
620
+	if runtime.GOOS == "windows" {
621
+		e = "open nonexistent: The system cannot find the file specified."
622
+	}
623
+	// label ko
624
+	if _, _, _, err := parseRun([]string{"--label-file=nonexistent", "img", "cmd"}); err == nil || err.Error() != e {
625
+		t.Fatalf("Expected an error with message '%s', got %v", e, err)
626
+	}
627
+	// label ok
628
+	config, _, _, err := parseRun([]string{"--label-file=fixtures/valid.label", "img", "cmd"})
629
+	if err != nil {
630
+		t.Fatal(err)
631
+	}
632
+	if len(config.Labels) != 1 || config.Labels["LABEL1"] != "value1" {
633
+		t.Fatalf("Expected a a config with [LABEL1:value1], got %v", config.Labels)
634
+	}
635
+	config, _, _, err = parseRun([]string{"--label-file=fixtures/valid.label", "--label=LABEL2=value2", "img", "cmd"})
636
+	if err != nil {
637
+		t.Fatal(err)
638
+	}
639
+	if len(config.Labels) != 2 || config.Labels["LABEL1"] != "value1" || config.Labels["LABEL2"] != "value2" {
640
+		t.Fatalf("Expected a a config with [LABEL1:value1 LABEL2:value2], got %v", config.Labels)
641
+	}
642
+}
643
+
644
+func TestParseEntryPoint(t *testing.T) {
645
+	config, _, _, err := parseRun([]string{"--entrypoint=anything", "cmd", "img"})
646
+	if err != nil {
647
+		t.Fatal(err)
648
+	}
649
+	if config.Entrypoint.Len() != 1 && config.Entrypoint.Slice()[0] != "anything" {
650
+		t.Fatalf("Expected entrypoint 'anything', got %v", config.Entrypoint)
651
+	}
652
+}
653
+
654
+func TestValidateLink(t *testing.T) {
655
+	valid := []string{
656
+		"name",
657
+		"dcdfbe62ecd0:alias",
658
+		"7a67485460b7642516a4ad82ecefe7f57d0c4916f530561b71a50a3f9c4e33da",
659
+		"angry_torvalds:linus",
660
+	}
661
+	invalid := map[string]string{
662
+		"":               "empty string specified for links",
663
+		"too:much:of:it": "bad format for links: too:much:of:it",
664
+	}
665
+
666
+	for _, link := range valid {
667
+		if _, err := ValidateLink(link); err != nil {
668
+			t.Fatalf("ValidateLink(`%q`) should succeed: error %q", link, err)
669
+		}
670
+	}
671
+
672
+	for link, expectedError := range invalid {
673
+		if _, err := ValidateLink(link); err == nil {
674
+			t.Fatalf("ValidateLink(`%q`) should have failed validation", link)
675
+		} else {
676
+			if !strings.Contains(err.Error(), expectedError) {
677
+				t.Fatalf("ValidateLink(`%q`) error should contain %q", link, expectedError)
678
+			}
679
+		}
680
+	}
681
+}
682
+
683
+func TestParseLink(t *testing.T) {
684
+	name, alias, err := ParseLink("name:alias")
685
+	if err != nil {
686
+		t.Fatalf("Expected not to error out on a valid name:alias format but got: %v", err)
687
+	}
688
+	if name != "name" {
689
+		t.Fatalf("Link name should have been name, got %s instead", name)
690
+	}
691
+	if alias != "alias" {
692
+		t.Fatalf("Link alias should have been alias, got %s instead", alias)
693
+	}
694
+	// short format definition
695
+	name, alias, err = ParseLink("name")
696
+	if err != nil {
697
+		t.Fatalf("Expected not to error out on a valid name only format but got: %v", err)
698
+	}
699
+	if name != "name" {
700
+		t.Fatalf("Link name should have been name, got %s instead", name)
701
+	}
702
+	if alias != "name" {
703
+		t.Fatalf("Link alias should have been name, got %s instead", alias)
704
+	}
705
+	// empty string link definition is not allowed
706
+	if _, _, err := ParseLink(""); err == nil || !strings.Contains(err.Error(), "empty string specified for links") {
707
+		t.Fatalf("Expected error 'empty string specified for links' but got: %v", err)
708
+	}
709
+	// more than two colons are not allowed
710
+	if _, _, err := ParseLink("link:alias:wrong"); err == nil || !strings.Contains(err.Error(), "bad format for links: link:alias:wrong") {
711
+		t.Fatalf("Expected error 'bad format for links: link:alias:wrong' but got: %v", err)
712
+	}
713
+}
714
+
715
+func TestValidateDevice(t *testing.T) {
716
+	valid := []string{
717
+		"/home",
718
+		"/home:/home",
719
+		"/home:/something/else",
720
+		"/with space",
721
+		"/home:/with space",
722
+		"relative:/absolute-path",
723
+		"hostPath:/containerPath:r",
724
+		"/hostPath:/containerPath:rw",
725
+		"/hostPath:/containerPath:mrw",
726
+	}
727
+	invalid := map[string]string{
728
+		"":        "bad format for path: ",
729
+		"./":      "./ is not an absolute path",
730
+		"../":     "../ is not an absolute path",
731
+		"/:../":   "../ is not an absolute path",
732
+		"/:path":  "path is not an absolute path",
733
+		":":       "bad format for path: :",
734
+		"/tmp:":   " is not an absolute path",
735
+		":test":   "bad format for path: :test",
736
+		":/test":  "bad format for path: :/test",
737
+		"tmp:":    " is not an absolute path",
738
+		":test:":  "bad format for path: :test:",
739
+		"::":      "bad format for path: ::",
740
+		":::":     "bad format for path: :::",
741
+		"/tmp:::": "bad format for path: /tmp:::",
742
+		":/tmp::": "bad format for path: :/tmp::",
743
+		"path:ro": "ro is not an absolute path",
744
+		"path:rr": "rr is not an absolute path",
745
+		"a:/b:ro": "bad mode specified: ro",
746
+		"a:/b:rr": "bad mode specified: rr",
747
+	}
748
+
749
+	for _, path := range valid {
750
+		if _, err := ValidateDevice(path); err != nil {
751
+			t.Fatalf("ValidateDevice(`%q`) should succeed: error %q", path, err)
752
+		}
753
+	}
754
+
755
+	for path, expectedError := range invalid {
756
+		if _, err := ValidateDevice(path); err == nil {
757
+			t.Fatalf("ValidateDevice(`%q`) should have failed validation", path)
758
+		} else {
759
+			if err.Error() != expectedError {
760
+				t.Fatalf("ValidateDevice(`%q`) error should contain %q, got %q", path, expectedError, err.Error())
761
+			}
762
+		}
763
+	}
764
+}