Browse code

Add test coverage to opts and refactor

- Refactor opts.ValidatePath and add an opts.ValidateDevice
ValidePath will now accept : containerPath:mode, hostPath:containerPath:mode
and hostPath:containerPath.
ValidateDevice will have the same behavior as current.

- Refactor opts.ValidateEnv, opts.ParseEnvFile
Environment variables will now be validated with the following
definition :
> Environment variables set by the user must have a name consisting
> solely of alphabetics, numerics, and underscores - the first of
> which must not be numeric.

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

Vincent Demeester authored on 2015/07/12 17:33:30
Showing 12 changed files
... ...
@@ -54,7 +54,7 @@ func (config *Config) InstallCommonFlags() {
54 54
 	flag.StringVar(&config.CorsHeaders, []string{"-api-cors-header"}, "", "Set CORS headers in the remote API")
55 55
 	// FIXME: why the inconsistency between "hosts" and "sockets"?
56 56
 	opts.IPListVar(&config.Dns, []string{"#dns", "-dns"}, "DNS server to use")
57
-	opts.DnsSearchListVar(&config.DnsSearch, []string{"-dns-search"}, "DNS search domains to use")
57
+	opts.DNSSearchListVar(&config.DnsSearch, []string{"-dns-search"}, "DNS search domains to use")
58 58
 	opts.LabelListVar(&config.Labels, []string{"-label"}, "Set key=value labels to the daemon")
59 59
 	flag.StringVar(&config.LogConfig.Type, []string{"-log-driver"}, "json-file", "Default driver for container logs")
60 60
 	opts.LogOptsVar(config.LogConfig.Config, []string{"-log-opt"}, "Set log driver options")
... ...
@@ -73,10 +73,11 @@ func parseBindMount(spec string, mountLabel string, config *runconfig.Config) (*
73 73
 	case 3:
74 74
 		bind.Destination = arr[1]
75 75
 		mode := arr[2]
76
-		if !validMountMode(mode) {
76
+		isValid, isRw := volume.ValidateMountMode(mode)
77
+		if !isValid {
77 78
 			return nil, fmt.Errorf("invalid mode for volumes-from: %s", mode)
78 79
 		}
79
-		bind.RW = rwModes[mode]
80
+		bind.RW = isRw
80 81
 		// Relabel will apply a SELinux label, if necessary
81 82
 		bind.Relabel = mode
82 83
 	default:
... ...
@@ -113,37 +114,13 @@ func parseVolumesFrom(spec string) (string, string, error) {
113 113
 
114 114
 	if len(specParts) == 2 {
115 115
 		mode = specParts[1]
116
-		if !validMountMode(mode) {
116
+		if isValid, _ := volume.ValidateMountMode(mode); !isValid {
117 117
 			return "", "", fmt.Errorf("invalid mode for volumes-from: %s", mode)
118 118
 		}
119 119
 	}
120 120
 	return id, mode, nil
121 121
 }
122 122
 
123
-// read-write modes
124
-var rwModes = map[string]bool{
125
-	"rw":   true,
126
-	"rw,Z": true,
127
-	"rw,z": true,
128
-	"z,rw": true,
129
-	"Z,rw": true,
130
-	"Z":    true,
131
-	"z":    true,
132
-}
133
-
134
-// read-only modes
135
-var roModes = map[string]bool{
136
-	"ro":   true,
137
-	"ro,Z": true,
138
-	"ro,z": true,
139
-	"z,ro": true,
140
-	"Z,ro": true,
141
-}
142
-
143
-func validMountMode(mode string) bool {
144
-	return roModes[mode] || rwModes[mode]
145
-}
146
-
147 123
 func copyExistingContents(source, destination string) error {
148 124
 	volList, err := ioutil.ReadDir(source)
149 125
 	if err != nil {
... ...
@@ -4,12 +4,18 @@ import (
4 4
 	"bufio"
5 5
 	"fmt"
6 6
 	"os"
7
+	"regexp"
7 8
 	"strings"
8 9
 )
9 10
 
10
-/*
11
-Read in a line delimited file with environment variables enumerated
12
-*/
11
+var (
12
+	// EnvironmentVariableRegexp A regexp to validate correct environment variables
13
+	// Environment variables set by the user must have a name consisting solely of
14
+	// alphabetics, numerics, and underscores - the first of which must not be numeric.
15
+	EnvironmentVariableRegexp = regexp.MustCompile("^[[:alpha:]_][[:alpha:][:digit:]_]*$")
16
+)
17
+
18
+// ParseEnvFile Read in a line delimited file with environment variables enumerated
13 19
 func ParseEnvFile(filename string) ([]string, error) {
14 20
 	fh, err := os.Open(filename)
15 21
 	if err != nil {
... ...
@@ -23,14 +29,15 @@ func ParseEnvFile(filename string) ([]string, error) {
23 23
 		line := scanner.Text()
24 24
 		// line is not empty, and not starting with '#'
25 25
 		if len(line) > 0 && !strings.HasPrefix(line, "#") {
26
-			if strings.Contains(line, "=") {
27
-				data := strings.SplitN(line, "=", 2)
26
+			data := strings.SplitN(line, "=", 2)
28 27
 
29
-				// trim the front of a variable, but nothing else
30
-				variable := strings.TrimLeft(data[0], whiteSpaces)
31
-				if strings.ContainsAny(variable, whiteSpaces) {
32
-					return []string{}, ErrBadEnvVariable{fmt.Sprintf("variable '%s' has white spaces", variable)}
33
-				}
28
+			// trim the front of a variable, but nothing else
29
+			variable := strings.TrimLeft(data[0], whiteSpaces)
30
+
31
+			if !EnvironmentVariableRegexp.MatchString(variable) {
32
+				return []string{}, ErrBadEnvVariable{fmt.Sprintf("variable '%s' is not a valid environment variable", variable)}
33
+			}
34
+			if len(data) > 1 {
34 35
 
35 36
 				// pass the value through, no trimming
36 37
 				lines = append(lines, fmt.Sprintf("%s=%s", variable, data[1]))
... ...
@@ -45,6 +52,7 @@ func ParseEnvFile(filename string) ([]string, error) {
45 45
 
46 46
 var whiteSpaces = " \t"
47 47
 
48
+// ErrBadEnvVariable typed error for bad environment variable
48 49
 type ErrBadEnvVariable struct {
49 50
 	msg string
50 51
 }
... ...
@@ -10,15 +10,15 @@ import (
10 10
 	"testing"
11 11
 )
12 12
 
13
-func tmpFileWithContent(content string) (string, error) {
13
+func tmpFileWithContent(content string, t *testing.T) string {
14 14
 	tmpFile, err := ioutil.TempFile("", "envfile-test")
15 15
 	if err != nil {
16
-		return "", err
16
+		t.Fatal(err)
17 17
 	}
18 18
 	defer tmpFile.Close()
19 19
 
20 20
 	tmpFile.WriteString(content)
21
-	return tmpFile.Name(), nil
21
+	return tmpFile.Name()
22 22
 }
23 23
 
24 24
 // Test ParseEnvFile for a file with a few well formatted lines
... ...
@@ -27,42 +27,36 @@ func TestParseEnvFileGoodFile(t *testing.T) {
27 27
     baz=quux
28 28
 # comment
29 29
 
30
-foobar=foobaz
30
+_foobar=foobaz
31 31
 `
32 32
 
33
-	tmpFile, err := tmpFileWithContent(content)
34
-	if err != nil {
35
-		t.Fatal("failed to create test data file")
36
-	}
33
+	tmpFile := tmpFileWithContent(content, t)
37 34
 	defer os.Remove(tmpFile)
38 35
 
39 36
 	lines, err := ParseEnvFile(tmpFile)
40 37
 	if err != nil {
41
-		t.Fatal("ParseEnvFile failed; expected success")
38
+		t.Fatal(err)
42 39
 	}
43 40
 
44
-	expected_lines := []string{
41
+	expectedLines := []string{
45 42
 		"foo=bar",
46 43
 		"baz=quux",
47
-		"foobar=foobaz",
44
+		"_foobar=foobaz",
48 45
 	}
49 46
 
50
-	if !reflect.DeepEqual(lines, expected_lines) {
47
+	if !reflect.DeepEqual(lines, expectedLines) {
51 48
 		t.Fatal("lines not equal to expected_lines")
52 49
 	}
53 50
 }
54 51
 
55 52
 // Test ParseEnvFile for an empty file
56 53
 func TestParseEnvFileEmptyFile(t *testing.T) {
57
-	tmpFile, err := tmpFileWithContent("")
58
-	if err != nil {
59
-		t.Fatal("failed to create test data file")
60
-	}
54
+	tmpFile := tmpFileWithContent("", t)
61 55
 	defer os.Remove(tmpFile)
62 56
 
63 57
 	lines, err := ParseEnvFile(tmpFile)
64 58
 	if err != nil {
65
-		t.Fatal("ParseEnvFile failed; expected success")
59
+		t.Fatal(err)
66 60
 	}
67 61
 
68 62
 	if len(lines) != 0 {
... ...
@@ -76,6 +70,9 @@ func TestParseEnvFileNonExistentFile(t *testing.T) {
76 76
 	if err == nil {
77 77
 		t.Fatal("ParseEnvFile succeeded; expected failure")
78 78
 	}
79
+	if _, ok := err.(*os.PathError); !ok {
80
+		t.Fatalf("Expected a PathError, got [%v]", err)
81
+	}
79 82
 }
80 83
 
81 84
 // Test ParseEnvFile for a badly formatted file
... ...
@@ -84,15 +81,19 @@ func TestParseEnvFileBadlyFormattedFile(t *testing.T) {
84 84
     f   =quux
85 85
 `
86 86
 
87
-	tmpFile, err := tmpFileWithContent(content)
88
-	if err != nil {
89
-		t.Fatal("failed to create test data file")
90
-	}
87
+	tmpFile := tmpFileWithContent(content, t)
91 88
 	defer os.Remove(tmpFile)
92 89
 
93
-	_, err = ParseEnvFile(tmpFile)
90
+	_, err := ParseEnvFile(tmpFile)
94 91
 	if err == nil {
95
-		t.Fatal("ParseEnvFile succeeded; expected failure")
92
+		t.Fatalf("Expected a ErrBadEnvVariable, got nothing")
93
+	}
94
+	if _, ok := err.(ErrBadEnvVariable); !ok {
95
+		t.Fatalf("Expected a ErrBadEnvVariable, got [%v]", err)
96
+	}
97
+	expectedMessage := "poorly formatted environment: variable 'f   ' is not a valid environment variable"
98
+	if err.Error() != expectedMessage {
99
+		t.Fatalf("Expected [%v], got [%v]", expectedMessage, err.Error())
96 100
 	}
97 101
 }
98 102
 
... ...
@@ -101,14 +102,32 @@ func TestParseEnvFileLineTooLongFile(t *testing.T) {
101 101
 	content := strings.Repeat("a", bufio.MaxScanTokenSize+42)
102 102
 	content = fmt.Sprint("foo=", content)
103 103
 
104
-	tmpFile, err := tmpFileWithContent(content)
105
-	if err != nil {
106
-		t.Fatal("failed to create test data file")
107
-	}
104
+	tmpFile := tmpFileWithContent(content, t)
108 105
 	defer os.Remove(tmpFile)
109 106
 
110
-	_, err = ParseEnvFile(tmpFile)
107
+	_, err := ParseEnvFile(tmpFile)
111 108
 	if err == nil {
112 109
 		t.Fatal("ParseEnvFile succeeded; expected failure")
113 110
 	}
114 111
 }
112
+
113
+// ParseEnvFile with a random file, pass through
114
+func TestParseEnvFileRandomFile(t *testing.T) {
115
+	content := `first line
116
+another invalid line`
117
+	tmpFile := tmpFileWithContent(content, t)
118
+	defer os.Remove(tmpFile)
119
+
120
+	_, err := ParseEnvFile(tmpFile)
121
+
122
+	if err == nil {
123
+		t.Fatalf("Expected a ErrBadEnvVariable, got nothing")
124
+	}
125
+	if _, ok := err.(ErrBadEnvVariable); !ok {
126
+		t.Fatalf("Expected a ErrBadEnvvariable, got [%v]", err)
127
+	}
128
+	expectedMessage := "poorly formatted environment: variable 'first line' is not a valid environment variable"
129
+	if err.Error() != expectedMessage {
130
+		t.Fatalf("Expected [%v], got [%v]", expectedMessage, err.Error())
131
+	}
132
+}
... ...
@@ -5,6 +5,7 @@ import (
5 5
 	"net"
6 6
 )
7 7
 
8
+// IpOpt type that hold an IP
8 9
 type IpOpt struct {
9 10
 	*net.IP
10 11
 }
11 12
new file mode 100644
... ...
@@ -0,0 +1,54 @@
0
+package opts
1
+
2
+import (
3
+	"net"
4
+	"testing"
5
+)
6
+
7
+func TestIpOptString(t *testing.T) {
8
+	addresses := []string{"", "0.0.0.0"}
9
+	var ip net.IP
10
+
11
+	for _, address := range addresses {
12
+		stringAddress := NewIpOpt(&ip, address).String()
13
+		if stringAddress != address {
14
+			t.Fatalf("IpOpt string should be `%s`, not `%s`", address, stringAddress)
15
+		}
16
+	}
17
+}
18
+
19
+func TestNewIpOptInvalidDefaultVal(t *testing.T) {
20
+	ip := net.IPv4(127, 0, 0, 1)
21
+	defaultVal := "Not an ip"
22
+
23
+	ipOpt := NewIpOpt(&ip, defaultVal)
24
+
25
+	expected := "127.0.0.1"
26
+	if ipOpt.String() != expected {
27
+		t.Fatalf("Expected [%v], got [%v]", expected, ipOpt.String())
28
+	}
29
+}
30
+
31
+func TestNewIpOptValidDefaultVal(t *testing.T) {
32
+	ip := net.IPv4(127, 0, 0, 1)
33
+	defaultVal := "192.168.1.1"
34
+
35
+	ipOpt := NewIpOpt(&ip, defaultVal)
36
+
37
+	expected := "192.168.1.1"
38
+	if ipOpt.String() != expected {
39
+		t.Fatalf("Expected [%v], got [%v]", expected, ipOpt.String())
40
+	}
41
+}
42
+
43
+func TestIpOptSetInvalidVal(t *testing.T) {
44
+	ip := net.IPv4(127, 0, 0, 1)
45
+	ipOpt := &IpOpt{IP: &ip}
46
+
47
+	invalidIp := "invalid ip"
48
+	expectedError := "invalid ip is not an ip address"
49
+	err := ipOpt.Set(invalidIp)
50
+	if err == nil || err.Error() != expectedError {
51
+		t.Fatalf("Expected an Error with [%v], got [%v]", expectedError, err.Error())
52
+	}
53
+}
... ...
@@ -11,61 +11,85 @@ import (
11 11
 	flag "github.com/docker/docker/pkg/mflag"
12 12
 	"github.com/docker/docker/pkg/parsers"
13 13
 	"github.com/docker/docker/pkg/ulimit"
14
+	"github.com/docker/docker/volume"
14 15
 )
15 16
 
16 17
 var (
17
-	alphaRegexp     = regexp.MustCompile(`[a-zA-Z]`)
18
-	domainRegexp    = regexp.MustCompile(`^(:?(:?[a-zA-Z0-9]|(:?[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9]))(:?\.(:?[a-zA-Z0-9]|(:?[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])))*)\.?\s*$`)
19
-	DefaultHTTPHost = "127.0.0.1" // Default HTTP Host used if only port is provided to -H flag e.g. docker -d -H tcp://:8080
18
+	alphaRegexp  = regexp.MustCompile(`[a-zA-Z]`)
19
+	domainRegexp = regexp.MustCompile(`^(:?(:?[a-zA-Z0-9]|(:?[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9]))(:?\.(:?[a-zA-Z0-9]|(:?[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])))*)\.?\s*$`)
20
+	// DefaultHTTPHost Default HTTP Host used if only port is provided to -H flag e.g. docker -d -H tcp://:8080
21
+	DefaultHTTPHost = "127.0.0.1"
22
+	// DefaultHTTPPort Default HTTP Port used if only the protocol is provided to -H flag e.g. docker -d -H tcp://
20 23
 	// TODO Windows. DefaultHTTPPort is only used on Windows if a -H parameter
21 24
 	// is not supplied. A better longer term solution would be to use a named
22 25
 	// pipe as the default on the Windows daemon.
23
-	DefaultHTTPPort   = 2375                   // Default HTTP Port
24
-	DefaultUnixSocket = "/var/run/docker.sock" // Docker daemon by default always listens on the default unix socket
26
+	DefaultHTTPPort = 2375 // Default HTTP Port
27
+	// DefaultUnixSocket Path for the unix socket.
28
+	// Docker daemon by default always listens on the default unix socket
29
+	DefaultUnixSocket = "/var/run/docker.sock"
25 30
 )
26 31
 
32
+// ListVar Defines a flag with the specified names and usage, and put the value
33
+// list into ListOpts that will hold the values.
27 34
 func ListVar(values *[]string, names []string, usage string) {
28 35
 	flag.Var(newListOptsRef(values, nil), names, usage)
29 36
 }
30 37
 
38
+// MapVar Defines a flag with the specified names and usage, and put the value
39
+// map into MapOpt that will hold the values (key,value).
31 40
 func MapVar(values map[string]string, names []string, usage string) {
32 41
 	flag.Var(newMapOpt(values, nil), names, usage)
33 42
 }
34 43
 
44
+// LogOptsVar Defines a flag with the specified names and usage for --log-opts,
45
+// and put the value map into MapOpt that will hold the values (key,value).
35 46
 func LogOptsVar(values map[string]string, names []string, usage string) {
36 47
 	flag.Var(newMapOpt(values, nil), names, usage)
37 48
 }
38 49
 
50
+// HostListVar Defines a flag with the specified names and usage and put the
51
+// value into a ListOpts that will hold the values, validating the Host format.
39 52
 func HostListVar(values *[]string, names []string, usage string) {
40 53
 	flag.Var(newListOptsRef(values, ValidateHost), names, usage)
41 54
 }
42 55
 
56
+// IPListVar Defines a flag with the specified names and usage and put the
57
+// value into a ListOpts that will hold the values, validating the IP format.
43 58
 func IPListVar(values *[]string, names []string, usage string) {
44 59
 	flag.Var(newListOptsRef(values, ValidateIPAddress), names, usage)
45 60
 }
46 61
 
47
-func DnsSearchListVar(values *[]string, names []string, usage string) {
48
-	flag.Var(newListOptsRef(values, ValidateDnsSearch), names, usage)
62
+// DNSSearchListVar Defines a flag with the specified names and usage and put the
63
+// value into a ListOpts that will hold the values, validating the DNS search format.
64
+func DNSSearchListVar(values *[]string, names []string, usage string) {
65
+	flag.Var(newListOptsRef(values, ValidateDNSSearch), names, usage)
49 66
 }
50 67
 
68
+// IPVar Defines a flag with the specified names and usage for IP and will use
69
+// the specified defaultValue if the specified value is not valid.
51 70
 func IPVar(value *net.IP, names []string, defaultValue, usage string) {
52 71
 	flag.Var(NewIpOpt(value, defaultValue), names, usage)
53 72
 }
54 73
 
74
+// LabelListVar Defines a flag with the specified names and usage and put the
75
+// value into a ListOpts that will hold the values, validating the label format.
55 76
 func LabelListVar(values *[]string, names []string, usage string) {
56 77
 	flag.Var(newListOptsRef(values, ValidateLabel), names, usage)
57 78
 }
58 79
 
80
+// UlimitMapVar Defines a flag with the specified names and usage for --ulimit,
81
+// and put the value map into a UlimitOpt that will hold the values.
59 82
 func UlimitMapVar(values map[string]*ulimit.Ulimit, names []string, usage string) {
60 83
 	flag.Var(NewUlimitOpt(values), names, usage)
61 84
 }
62 85
 
63
-// ListOpts type
86
+// ListOpts type that hold a list of values and a validation function.
64 87
 type ListOpts struct {
65 88
 	values    *[]string
66 89
 	validator ValidatorFctType
67 90
 }
68 91
 
92
+// NewListOpts Create a new ListOpts with the specified validator.
69 93
 func NewListOpts(validator ValidatorFctType) ListOpts {
70 94
 	var values []string
71 95
 	return *newListOptsRef(&values, validator)
... ...
@@ -138,12 +162,14 @@ func (opts *ListOpts) Len() int {
138 138
 	return len((*opts.values))
139 139
 }
140 140
 
141
-//MapOpts type
141
+//MapOpts type that holds a map of values and a validation function.
142 142
 type MapOpts struct {
143 143
 	values    map[string]string
144 144
 	validator ValidatorFctType
145 145
 }
146 146
 
147
+// Set validates if needed the input value and add it to the
148
+// internal map, by splitting on '='.
147 149
 func (opts *MapOpts) Set(value string) error {
148 150
 	if opts.validator != nil {
149 151
 		v, err := opts.validator(value)
... ...
@@ -172,10 +198,13 @@ func newMapOpt(values map[string]string, validator ValidatorFctType) *MapOpts {
172 172
 	}
173 173
 }
174 174
 
175
-// Validators
175
+// ValidatorFctType validator that return a validate string and/or an error
176 176
 type ValidatorFctType func(val string) (string, error)
177
+
178
+// ValidatorFctListType validator that return a validate list of string and/or an error
177 179
 type ValidatorFctListType func(val string) ([]string, error)
178 180
 
181
+// ValidateAttach Validates that the specified string is a valid attach option.
179 182
 func ValidateAttach(val string) (string, error) {
180 183
 	s := strings.ToLower(val)
181 184
 	for _, str := range []string{"stdin", "stdout", "stderr"} {
... ...
@@ -183,9 +212,10 @@ func ValidateAttach(val string) (string, error) {
183 183
 			return s, nil
184 184
 		}
185 185
 	}
186
-	return val, fmt.Errorf("valid streams are STDIN, STDOUT and STDERR.")
186
+	return val, fmt.Errorf("valid streams are STDIN, STDOUT and STDERR")
187 187
 }
188 188
 
189
+// ValidateLink Validates that the specified string has a valid link format (containerName:alias).
189 190
 func ValidateLink(val string) (string, error) {
190 191
 	if _, _, err := parsers.ParseLink(val); err != nil {
191 192
 		return val, err
... ...
@@ -193,22 +223,53 @@ func ValidateLink(val string) (string, error) {
193 193
 	return val, nil
194 194
 }
195 195
 
196
-// ValidatePath will make sure 'val' is in the form:
197
-//    [host-dir:]container-path[:rw|ro]  - but doesn't validate the mode part
196
+// ValidateDevice Validate a path for devices
197
+// It will make sure 'val' is in the form:
198
+//    [host-dir:]container-path[:mode]
199
+func ValidateDevice(val string) (string, error) {
200
+	return validatePath(val, false)
201
+}
202
+
203
+// ValidatePath Validate a path for volumes
204
+// It will make sure 'val' is in the form:
205
+//    [host-dir:]container-path[:rw|ro]
206
+// It will also validate the mount mode.
198 207
 func ValidatePath(val string) (string, error) {
208
+	return validatePath(val, true)
209
+}
210
+
211
+func validatePath(val string, validateMountMode bool) (string, error) {
199 212
 	var containerPath string
213
+	var mode string
200 214
 
201 215
 	if strings.Count(val, ":") > 2 {
202 216
 		return val, fmt.Errorf("bad format for volumes: %s", val)
203 217
 	}
204 218
 
205
-	splited := strings.SplitN(val, ":", 2)
206
-	if len(splited) == 1 {
219
+	splited := strings.SplitN(val, ":", 3)
220
+	if splited[0] == "" {
221
+		return val, fmt.Errorf("bad format for volumes: %s", val)
222
+	}
223
+	switch len(splited) {
224
+	case 1:
207 225
 		containerPath = splited[0]
208
-		val = path.Clean(splited[0])
209
-	} else {
226
+		val = path.Clean(containerPath)
227
+	case 2:
228
+		if isValid, _ := volume.ValidateMountMode(splited[1]); validateMountMode && isValid {
229
+			containerPath = splited[0]
230
+			mode = splited[1]
231
+			val = fmt.Sprintf("%s:%s", path.Clean(containerPath), mode)
232
+		} else {
233
+			containerPath = splited[1]
234
+			val = fmt.Sprintf("%s:%s", splited[0], path.Clean(containerPath))
235
+		}
236
+	case 3:
210 237
 		containerPath = splited[1]
211
-		val = fmt.Sprintf("%s:%s", splited[0], path.Clean(splited[1]))
238
+		mode = splited[2]
239
+		if isValid, _ := volume.ValidateMountMode(splited[2]); validateMountMode && !isValid {
240
+			return val, fmt.Errorf("bad mount mode specified : %s", mode)
241
+		}
242
+		val = fmt.Sprintf("%s:%s:%s", splited[0], containerPath, mode)
212 243
 	}
213 244
 
214 245
 	if !path.IsAbs(containerPath) {
... ...
@@ -217,17 +278,24 @@ func ValidatePath(val string) (string, error) {
217 217
 	return val, nil
218 218
 }
219 219
 
220
+// ValidateEnv Validate an environment variable and returns it
221
+// It will use EnvironmentVariableRegexp to ensure the name of the environment variable is valid.
222
+// If no value is specified, it returns the current value using os.Getenv.
220 223
 func ValidateEnv(val string) (string, error) {
221 224
 	arr := strings.Split(val, "=")
222 225
 	if len(arr) > 1 {
223 226
 		return val, nil
224 227
 	}
228
+	if !EnvironmentVariableRegexp.MatchString(arr[0]) {
229
+		return val, ErrBadEnvVariable{fmt.Sprintf("variable '%s' is not a valid environment variable", val)}
230
+	}
225 231
 	if !doesEnvExist(val) {
226 232
 		return val, nil
227 233
 	}
228 234
 	return fmt.Sprintf("%s=%s", val, os.Getenv(val)), nil
229 235
 }
230 236
 
237
+// ValidateIPAddress Validates an Ip address
231 238
 func ValidateIPAddress(val string) (string, error) {
232 239
 	var ip = net.ParseIP(strings.TrimSpace(val))
233 240
 	if ip != nil {
... ...
@@ -236,6 +304,7 @@ func ValidateIPAddress(val string) (string, error) {
236 236
 	return "", fmt.Errorf("%s is not an ip address", val)
237 237
 }
238 238
 
239
+// ValidateMACAddress Validates a MAC address
239 240
 func ValidateMACAddress(val string) (string, error) {
240 241
 	_, err := net.ParseMAC(strings.TrimSpace(val))
241 242
 	if err != nil {
... ...
@@ -244,9 +313,9 @@ func ValidateMACAddress(val string) (string, error) {
244 244
 	return val, nil
245 245
 }
246 246
 
247
-// Validates domain for resolvconf search configuration.
247
+// ValidateDNSSearch Validates domain for resolvconf search configuration.
248 248
 // A zero length domain is represented by .
249
-func ValidateDnsSearch(val string) (string, error) {
249
+func ValidateDNSSearch(val string) (string, error) {
250 250
 	if val = strings.Trim(val, " "); val == "." {
251 251
 		return val, nil
252 252
 	}
... ...
@@ -264,6 +333,8 @@ func validateDomain(val string) (string, error) {
264 264
 	return "", fmt.Errorf("%s is not a valid domain", val)
265 265
 }
266 266
 
267
+// ValidateExtraHost Validate that the given string is a valid extrahost and returns it
268
+// ExtraHost are in the form of name:ip where the ip has to be a valid ip (ipv4 or ipv6)
267 269
 func ValidateExtraHost(val string) (string, error) {
268 270
 	// allow for IPv6 addresses in extra hosts by only splitting on first ":"
269 271
 	arr := strings.SplitN(val, ":", 2)
... ...
@@ -276,13 +347,16 @@ func ValidateExtraHost(val string) (string, error) {
276 276
 	return val, nil
277 277
 }
278 278
 
279
+// ValidateLabel Validate that the given string is a valid label, and returns it
280
+// Labels are in the form on key=value
279 281
 func ValidateLabel(val string) (string, error) {
280
-	if strings.Count(val, "=") != 1 {
282
+	if strings.Count(val, "=") < 1 {
281 283
 		return "", fmt.Errorf("bad attribute format: %s", val)
282 284
 	}
283 285
 	return val, nil
284 286
 }
285 287
 
288
+// ValidateHost Validate that the given string is a valid host and returns it
286 289
 func ValidateHost(val string) (string, error) {
287 290
 	host, err := parsers.ParseHost(DefaultHTTPHost, DefaultUnixSocket, val)
288 291
 	if err != nil {
... ...
@@ -2,7 +2,7 @@ package opts
2 2
 
3 3
 import (
4 4
 	"fmt"
5
-	"net"
5
+	"os"
6 6
 	"strings"
7 7
 	"testing"
8 8
 )
... ...
@@ -69,7 +69,7 @@ func TestValidateMACAddress(t *testing.T) {
69 69
 	}
70 70
 }
71 71
 
72
-func TestListOpts(t *testing.T) {
72
+func TestListOptsWithoutValidator(t *testing.T) {
73 73
 	o := NewListOpts(nil)
74 74
 	o.Set("foo")
75 75
 	if o.String() != "[foo]" {
... ...
@@ -79,6 +79,10 @@ func TestListOpts(t *testing.T) {
79 79
 	if o.Len() != 2 {
80 80
 		t.Errorf("%d != 2", o.Len())
81 81
 	}
82
+	o.Set("bar")
83
+	if o.Len() != 3 {
84
+		t.Errorf("%d != 3", o.Len())
85
+	}
82 86
 	if !o.Get("bar") {
83 87
 		t.Error("o.Get(\"bar\") == false")
84 88
 	}
... ...
@@ -86,12 +90,48 @@ func TestListOpts(t *testing.T) {
86 86
 		t.Error("o.Get(\"baz\") == true")
87 87
 	}
88 88
 	o.Delete("foo")
89
-	if o.String() != "[bar]" {
90
-		t.Errorf("%s != [bar]", o.String())
89
+	if o.String() != "[bar bar]" {
90
+		t.Errorf("%s != [bar bar]", o.String())
91
+	}
92
+	listOpts := o.GetAll()
93
+	if len(listOpts) != 2 || listOpts[0] != "bar" || listOpts[1] != "bar" {
94
+		t.Errorf("Expected [[bar bar]], got [%v]", listOpts)
91 95
 	}
96
+	mapListOpts := o.GetMap()
97
+	if len(mapListOpts) != 1 {
98
+		t.Errorf("Expected [map[bar:{}]], got [%v]", mapListOpts)
99
+	}
100
+
92 101
 }
93 102
 
94
-func TestValidateDnsSearch(t *testing.T) {
103
+func TestListOptsWithValidator(t *testing.T) {
104
+	// Re-using logOptsvalidator (used by MapOpts)
105
+	o := NewListOpts(logOptsValidator)
106
+	o.Set("foo")
107
+	if o.String() != "[]" {
108
+		t.Errorf("%s != []", o.String())
109
+	}
110
+	o.Set("foo=bar")
111
+	if o.String() != "[]" {
112
+		t.Errorf("%s != []", o.String())
113
+	}
114
+	o.Set("max-file=2")
115
+	if o.Len() != 1 {
116
+		t.Errorf("%d != 1", o.Len())
117
+	}
118
+	if !o.Get("max-file=2") {
119
+		t.Error("o.Get(\"max-file=2\") == false")
120
+	}
121
+	if o.Get("baz") {
122
+		t.Error("o.Get(\"baz\") == true")
123
+	}
124
+	o.Delete("max-file=2")
125
+	if o.String() != "[]" {
126
+		t.Errorf("%s != []", o.String())
127
+	}
128
+}
129
+
130
+func TestValidateDNSSearch(t *testing.T) {
95 131
 	valid := []string{
96 132
 		`.`,
97 133
 		`a`,
... ...
@@ -136,14 +176,14 @@ func TestValidateDnsSearch(t *testing.T) {
136 136
 	}
137 137
 
138 138
 	for _, domain := range valid {
139
-		if ret, err := ValidateDnsSearch(domain); err != nil || ret == "" {
140
-			t.Fatalf("ValidateDnsSearch(`"+domain+"`) got %s %s", ret, err)
139
+		if ret, err := ValidateDNSSearch(domain); err != nil || ret == "" {
140
+			t.Fatalf("ValidateDNSSearch(`"+domain+"`) got %s %s", ret, err)
141 141
 		}
142 142
 	}
143 143
 
144 144
 	for _, domain := range invalid {
145
-		if ret, err := ValidateDnsSearch(domain); err == nil || ret != "" {
146
-			t.Fatalf("ValidateDnsSearch(`"+domain+"`) got %s %s", ret, err)
145
+		if ret, err := ValidateDNSSearch(domain); err == nil || ret != "" {
146
+			t.Fatalf("ValidateDNSSearch(`"+domain+"`) got %s %s", ret, err)
147 147
 		}
148 148
 	}
149 149
 }
... ...
@@ -180,14 +220,251 @@ func TestValidateExtraHosts(t *testing.T) {
180 180
 	}
181 181
 }
182 182
 
183
-func TestIpOptString(t *testing.T) {
184
-	addresses := []string{"", "0.0.0.0"}
185
-	var ip net.IP
183
+func TestValidateAttach(t *testing.T) {
184
+	valid := []string{
185
+		"stdin",
186
+		"stdout",
187
+		"stderr",
188
+		"STDIN",
189
+		"STDOUT",
190
+		"STDERR",
191
+	}
192
+	if _, err := ValidateAttach("invalid"); err == nil {
193
+		t.Fatalf("Expected error with [valid streams are STDIN, STDOUT and STDERR], got nothing")
194
+	}
195
+
196
+	for _, attach := range valid {
197
+		value, err := ValidateAttach(attach)
198
+		if err != nil {
199
+			t.Fatal(err)
200
+		}
201
+		if value != strings.ToLower(attach) {
202
+			t.Fatalf("Expected [%v], got [%v]", attach, value)
203
+		}
204
+	}
205
+}
206
+
207
+func TestValidateLink(t *testing.T) {
208
+	valid := []string{
209
+		"name",
210
+		"dcdfbe62ecd0:alias",
211
+		"7a67485460b7642516a4ad82ecefe7f57d0c4916f530561b71a50a3f9c4e33da",
212
+		"angry_torvalds:linus",
213
+	}
214
+	invalid := map[string]string{
215
+		"":               "empty string specified for links",
216
+		"too:much:of:it": "bad format for links: too:much:of:it",
217
+	}
218
+
219
+	for _, link := range valid {
220
+		if _, err := ValidateLink(link); err != nil {
221
+			t.Fatalf("ValidateLink(`%q`) should succeed: error %q", link, err)
222
+		}
223
+	}
224
+
225
+	for link, expectedError := range invalid {
226
+		if _, err := ValidateLink(link); err == nil {
227
+			t.Fatalf("ValidateLink(`%q`) should have failed validation", link)
228
+		} else {
229
+			if !strings.Contains(err.Error(), expectedError) {
230
+				t.Fatalf("ValidateLink(`%q`) error should contain %q", link, expectedError)
231
+			}
232
+		}
233
+	}
234
+}
235
+
236
+func TestValidatePath(t *testing.T) {
237
+	valid := []string{
238
+		"/home",
239
+		"/home:/home",
240
+		"/home:/something/else",
241
+		"/with space",
242
+		"/home:/with space",
243
+		"relative:/absolute-path",
244
+		"hostPath:/containerPath:ro",
245
+		"/hostPath:/containerPath:rw",
246
+		"/rw:/ro",
247
+		"/path:rw",
248
+		"/path:ro",
249
+		"/rw:rw",
250
+	}
251
+	invalid := map[string]string{
252
+		"":                "bad format for volumes: ",
253
+		"./":              "./ is not an absolute path",
254
+		"../":             "../ is not an absolute path",
255
+		"/:../":           "../ is not an absolute path",
256
+		"/:path":          "path is not an absolute path",
257
+		":":               "bad format for volumes: :",
258
+		"/tmp:":           " is not an absolute path",
259
+		":test":           "bad format for volumes: :test",
260
+		":/test":          "bad format for volumes: :/test",
261
+		"tmp:":            " is not an absolute path",
262
+		":test:":          "bad format for volumes: :test:",
263
+		"::":              "bad format for volumes: ::",
264
+		":::":             "bad format for volumes: :::",
265
+		"/tmp:::":         "bad format for volumes: /tmp:::",
266
+		":/tmp::":         "bad format for volumes: :/tmp::",
267
+		"path:ro":         "path is not an absolute path",
268
+		"/path:/path:sw":  "bad mount mode specified : sw",
269
+		"/path:/path:rwz": "bad mount mode specified : rwz",
270
+	}
271
+
272
+	for _, path := range valid {
273
+		if _, err := ValidatePath(path); err != nil {
274
+			t.Fatalf("ValidatePath(`%q`) should succeed: error %q", path, err)
275
+		}
276
+	}
277
+
278
+	for path, expectedError := range invalid {
279
+		if _, err := ValidatePath(path); err == nil {
280
+			t.Fatalf("ValidatePath(`%q`) should have failed validation", path)
281
+		} else {
282
+			if err.Error() != expectedError {
283
+				t.Fatalf("ValidatePath(`%q`) error should contain %q, got %q", path, expectedError, err.Error())
284
+			}
285
+		}
286
+	}
287
+}
288
+func TestValidateDevice(t *testing.T) {
289
+	valid := []string{
290
+		"/home",
291
+		"/home:/home",
292
+		"/home:/something/else",
293
+		"/with space",
294
+		"/home:/with space",
295
+		"relative:/absolute-path",
296
+		"hostPath:/containerPath:ro",
297
+		"/hostPath:/containerPath:rw",
298
+		"/hostPath:/containerPath:mrw",
299
+	}
300
+	invalid := map[string]string{
301
+		"":        "bad format for volumes: ",
302
+		"./":      "./ is not an absolute path",
303
+		"../":     "../ is not an absolute path",
304
+		"/:../":   "../ is not an absolute path",
305
+		"/:path":  "path is not an absolute path",
306
+		":":       "bad format for volumes: :",
307
+		"/tmp:":   " is not an absolute path",
308
+		":test":   "bad format for volumes: :test",
309
+		":/test":  "bad format for volumes: :/test",
310
+		"tmp:":    " is not an absolute path",
311
+		":test:":  "bad format for volumes: :test:",
312
+		"::":      "bad format for volumes: ::",
313
+		":::":     "bad format for volumes: :::",
314
+		"/tmp:::": "bad format for volumes: /tmp:::",
315
+		":/tmp::": "bad format for volumes: :/tmp::",
316
+		"path:ro": "ro is not an absolute path",
317
+	}
318
+
319
+	for _, path := range valid {
320
+		if _, err := ValidateDevice(path); err != nil {
321
+			t.Fatalf("ValidateDevice(`%q`) should succeed: error %q", path, err)
322
+		}
323
+	}
186 324
 
187
-	for _, address := range addresses {
188
-		stringAddress := NewIpOpt(&ip, address).String()
189
-		if stringAddress != address {
190
-			t.Fatalf("IpOpt string should be `%s`, not `%s`", address, stringAddress)
325
+	for path, expectedError := range invalid {
326
+		if _, err := ValidateDevice(path); err == nil {
327
+			t.Fatalf("ValidateDevice(`%q`) should have failed validation", path)
328
+		} else {
329
+			if err.Error() != expectedError {
330
+				t.Fatalf("ValidateDevice(`%q`) error should contain %q, got %q", path, expectedError, err.Error())
331
+			}
332
+		}
333
+	}
334
+}
335
+
336
+func TestValidateEnv(t *testing.T) {
337
+	invalids := map[string]string{
338
+		"some spaces": "poorly formatted environment: variable 'some spaces' is not a valid environment variable",
339
+		"asd!qwe":     "poorly formatted environment: variable 'asd!qwe' is not a valid environment variable",
340
+		"1asd":        "poorly formatted environment: variable '1asd' is not a valid environment variable",
341
+		"123":         "poorly formatted environment: variable '123' is not a valid environment variable",
342
+	}
343
+	valids := map[string]string{
344
+		"a":                  "a",
345
+		"something":          "something",
346
+		"_=a":                "_=a",
347
+		"env1=value1":        "env1=value1",
348
+		"_env1=value1":       "_env1=value1",
349
+		"env2=value2=value3": "env2=value2=value3",
350
+		"env3=abc!qwe":       "env3=abc!qwe",
351
+		"env_4=value 4":      "env_4=value 4",
352
+		"PATH":               fmt.Sprintf("PATH=%v", os.Getenv("PATH")),
353
+		"PATH=something":     "PATH=something",
354
+	}
355
+	for value, expectedError := range invalids {
356
+		_, err := ValidateEnv(value)
357
+		if err == nil {
358
+			t.Fatalf("Expected ErrBadEnvVariable, got nothing")
359
+		}
360
+		if _, ok := err.(ErrBadEnvVariable); !ok {
361
+			t.Fatalf("Expected ErrBadEnvVariable, got [%s]", err)
362
+		}
363
+		if err.Error() != expectedError {
364
+			t.Fatalf("Expected ErrBadEnvVariable with message [%s], got [%s]", expectedError, err.Error())
365
+		}
366
+	}
367
+	for value, expected := range valids {
368
+		actual, err := ValidateEnv(value)
369
+		if err != nil {
370
+			t.Fatal(err)
371
+		}
372
+		if actual != expected {
373
+			t.Fatalf("Expected [%v], got [%v]", expected, actual)
374
+		}
375
+	}
376
+}
377
+
378
+func TestValidateLabel(t *testing.T) {
379
+	if _, err := ValidateLabel("label"); err == nil || err.Error() != "bad attribute format: label" {
380
+		t.Fatalf("Expected an error [bad attribute format: label], go %v", err)
381
+	}
382
+	if actual, err := ValidateLabel("key1=value1"); err != nil || actual != "key1=value1" {
383
+		t.Fatalf("Expected [key1=value1], got [%v,%v]", actual, err)
384
+	}
385
+	// Validate it's working with more than one =
386
+	if actual, err := ValidateLabel("key1=value1=value2"); err != nil {
387
+		t.Fatalf("Expected [key1=value1=value2], got [%v,%v]", actual, err)
388
+	}
389
+	// Validate it's working with one more
390
+	if actual, err := ValidateLabel("key1=value1=value2=value3"); err != nil {
391
+		t.Fatalf("Expected [key1=value1=value2=value2], got [%v,%v]", actual, err)
392
+	}
393
+}
394
+
395
+func TestValidateHost(t *testing.T) {
396
+	invalid := map[string]string{
397
+		"anything":              "Invalid bind address format: anything",
398
+		"something with spaces": "Invalid bind address format: something with spaces",
399
+		"://":                "Invalid bind address format: ://",
400
+		"unknown://":         "Invalid bind address format: unknown://",
401
+		"tcp://":             "Invalid proto, expected tcp: ",
402
+		"tcp://:port":        "Invalid bind address format: :port",
403
+		"tcp://invalid":      "Invalid bind address format: invalid",
404
+		"tcp://invalid:port": "Invalid bind address format: invalid:port",
405
+	}
406
+	valid := map[string]string{
407
+		"fd://":                    "fd://",
408
+		"fd://something":           "fd://something",
409
+		"tcp://:2375":              "tcp://127.0.0.1:2375", // default ip address
410
+		"tcp://:2376":              "tcp://127.0.0.1:2376", // default ip address
411
+		"tcp://0.0.0.0:8080":       "tcp://0.0.0.0:8080",
412
+		"tcp://192.168.0.0:12000":  "tcp://192.168.0.0:12000",
413
+		"tcp://192.168:8080":       "tcp://192.168:8080",
414
+		"tcp://0.0.0.0:1234567890": "tcp://0.0.0.0:1234567890", // yeah it's valid :P
415
+		"tcp://docker.com:2375":    "tcp://docker.com:2375",
416
+		"unix://":                  "unix:///var/run/docker.sock", // default unix:// value
417
+		"unix://path/to/socket":    "unix://path/to/socket",
418
+	}
419
+
420
+	for value, errorMessage := range invalid {
421
+		if _, err := ValidateHost(value); err == nil || err.Error() != errorMessage {
422
+			t.Fatalf("Expected an error for %v with [%v], got [%v]", value, errorMessage, err)
423
+		}
424
+	}
425
+	for value, expected := range valid {
426
+		if actual, err := ValidateHost(value); err != nil || actual != expected {
427
+			t.Fatalf("Expected for %v [%v], got [%v, %v]", value, expected, actual, err)
191 428
 		}
192 429
 	}
193 430
 }
194 431
new file mode 100644
... ...
@@ -0,0 +1,42 @@
0
+package opts
1
+
2
+import (
3
+	"testing"
4
+
5
+	"github.com/docker/docker/pkg/ulimit"
6
+)
7
+
8
+func TestUlimitOpt(t *testing.T) {
9
+	ulimitMap := map[string]*ulimit.Ulimit{
10
+		"nofile": {"nofile", 1024, 512},
11
+	}
12
+
13
+	ulimitOpt := NewUlimitOpt(ulimitMap)
14
+
15
+	expected := "[nofile=512:1024]"
16
+	if ulimitOpt.String() != expected {
17
+		t.Fatalf("Expected %v, got %v", expected, ulimitOpt)
18
+	}
19
+
20
+	// Valid ulimit append to opts
21
+	if err := ulimitOpt.Set("core=1024:1024"); err != nil {
22
+		t.Fatal(err)
23
+	}
24
+
25
+	// Invalid ulimit type returns an error and do not append to opts
26
+	if err := ulimitOpt.Set("notavalidtype=1024:1024"); err == nil {
27
+		t.Fatalf("Expected error on invalid ulimit type")
28
+	}
29
+	expected = "[nofile=512:1024 core=1024:1024]"
30
+	expected2 := "[core=1024:1024 nofile=512:1024]"
31
+	result := ulimitOpt.String()
32
+	if result != expected && result != expected2 {
33
+		t.Fatalf("Expected %v or %v, got %v", expected, expected2, ulimitOpt)
34
+	}
35
+
36
+	// And test GetList
37
+	ulimits := ulimitOpt.GetList()
38
+	if len(ulimits) != 2 {
39
+		t.Fatalf("Expected a ulimit list of 2, got %v", ulimits)
40
+	}
41
+}
... ...
@@ -45,7 +45,7 @@ func Parse(cmd *flag.FlagSet, args []string) (*Config, *HostConfig, *flag.FlagSe
45 45
 		flLinks   = opts.NewListOpts(opts.ValidateLink)
46 46
 		flEnv     = opts.NewListOpts(opts.ValidateEnv)
47 47
 		flLabels  = opts.NewListOpts(opts.ValidateEnv)
48
-		flDevices = opts.NewListOpts(opts.ValidatePath)
48
+		flDevices = opts.NewListOpts(opts.ValidateDevice)
49 49
 
50 50
 		ulimits   = make(map[string]*ulimit.Ulimit)
51 51
 		flUlimits = opts.NewUlimitOpt(ulimits)
... ...
@@ -53,7 +53,7 @@ func Parse(cmd *flag.FlagSet, args []string) (*Config, *HostConfig, *flag.FlagSe
53 53
 		flPublish     = opts.NewListOpts(nil)
54 54
 		flExpose      = opts.NewListOpts(nil)
55 55
 		flDns         = opts.NewListOpts(opts.ValidateIPAddress)
56
-		flDnsSearch   = opts.NewListOpts(opts.ValidateDnsSearch)
56
+		flDnsSearch   = opts.NewListOpts(opts.ValidateDNSSearch)
57 57
 		flExtraHosts  = opts.NewListOpts(opts.ValidateExtraHost)
58 58
 		flVolumesFrom = opts.NewListOpts(nil)
59 59
 		flLxcOpts     = opts.NewListOpts(nil)
... ...
@@ -124,8 +124,12 @@ func TestParseRunVolumes(t *testing.T) {
124 124
 		t.Fatalf("Error parsing volume flags, `-v /hostTmp:/containerTmp:ro -v /hostVar:/containerVar:rw` should mount-bind /hostTmp into /containeTmp and /hostVar into /hostContainer. Received %v", hostConfig.Binds)
125 125
 	}
126 126
 
127
-	if _, hostConfig := mustParse(t, "-v /hostTmp:/containerTmp:roZ -v /hostVar:/containerVar:rwZ"); hostConfig.Binds == nil || compareRandomizedStrings(hostConfig.Binds[0], hostConfig.Binds[1], "/hostTmp:/containerTmp:roZ", "/hostVar:/containerVar:rwZ") != nil {
128
-		t.Fatalf("Error parsing volume flags, `-v /hostTmp:/containerTmp:roZ -v /hostVar:/containerVar:rwZ` should mount-bind /hostTmp into /containeTmp and /hostVar into /hostContainer. Received %v", hostConfig.Binds)
127
+	if _, hostConfig := mustParse(t, "-v /containerTmp:ro -v /containerVar:rw"); hostConfig.Binds == nil || compareRandomizedStrings(hostConfig.Binds[0], hostConfig.Binds[1], "/containerTmp:ro", "/containerVar:rw") != nil {
128
+		t.Fatalf("Error parsing volume flags, `-v /hostTmp:/containerTmp:ro -v /hostVar:/containerVar:rw` should mount-bind /hostTmp into /containeTmp and /hostVar into /hostContainer. Received %v", hostConfig.Binds)
129
+	}
130
+
131
+	if _, hostConfig := mustParse(t, "-v /hostTmp:/containerTmp:ro,Z -v /hostVar:/containerVar:rw,Z"); hostConfig.Binds == nil || compareRandomizedStrings(hostConfig.Binds[0], hostConfig.Binds[1], "/hostTmp:/containerTmp:ro,Z", "/hostVar:/containerVar:rw,Z") != nil {
132
+		t.Fatalf("Error parsing volume flags, `-v /hostTmp:/containerTmp:ro,Z -v /hostVar:/containerVar:rw,Z` should mount-bind /hostTmp into /containeTmp and /hostVar into /hostContainer. Received %v", hostConfig.Binds)
129 133
 	}
130 134
 
131 135
 	if _, hostConfig := mustParse(t, "-v /hostTmp:/containerTmp:Z -v /hostVar:/containerVar:z"); hostConfig.Binds == nil || compareRandomizedStrings(hostConfig.Binds[0], hostConfig.Binds[1], "/hostTmp:/containerTmp:Z", "/hostVar:/containerVar:z") != nil {
... ...
@@ -157,9 +161,6 @@ func TestParseRunVolumes(t *testing.T) {
157 157
 	if _, _, err := parse(t, "-v /tmp:"); err == nil {
158 158
 		t.Fatalf("Error parsing volume flags, `-v /tmp:` should fail but didn't")
159 159
 	}
160
-	if _, _, err := parse(t, "-v /tmp:ro"); err == nil {
161
-		t.Fatalf("Error parsing volume flags, `-v /tmp:ro` should fail but didn't")
162
-	}
163 160
 	if _, _, err := parse(t, "-v /tmp::"); err == nil {
164 161
 		t.Fatalf("Error parsing volume flags, `-v /tmp::` should fail but didn't")
165 162
 	}
... ...
@@ -24,3 +24,29 @@ type Volume interface {
24 24
 	// Unmount unmounts the volume when it is no longer in use.
25 25
 	Unmount() error
26 26
 }
27
+
28
+// read-write modes
29
+var rwModes = map[string]bool{
30
+	"rw":   true,
31
+	"rw,Z": true,
32
+	"rw,z": true,
33
+	"z,rw": true,
34
+	"Z,rw": true,
35
+	"Z":    true,
36
+	"z":    true,
37
+}
38
+
39
+// read-only modes
40
+var roModes = map[string]bool{
41
+	"ro":   true,
42
+	"ro,Z": true,
43
+	"ro,z": true,
44
+	"z,ro": true,
45
+	"Z,ro": true,
46
+}
47
+
48
+// ValidateMountMode will make sure the mount mode is valid.
49
+// returns if it's a valid mount mode and if it's read-write or not.
50
+func ValidateMountMode(mode string) (bool, bool) {
51
+	return roModes[mode] || rwModes[mode], rwModes[mode]
52
+}