Browse code

Move ParseExec to the client where it is used.

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

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