فهرست منبع

Merge pull request #13694 from vdemeester/opts-test-coverage

Tests, refactor and coverage on package opts
David Calavera 10 سال پیش
والد
کامیت
ecdbf86884
12فایلهای تغییر یافته به همراه589 افزوده شده و 110 حذف شده
  1. 1 1
      daemon/config.go
  2. 4 27
      daemon/volumes.go
  3. 18 10
      opts/envfile.go
  4. 47 28
      opts/envfile_test.go
  5. 1 0
      opts/ip.go
  6. 54 0
      opts/ip_test.go
  7. 95 21
      opts/opts.go
  8. 293 16
      opts/opts_test.go
  9. 42 0
      opts/ulimit_test.go
  10. 2 2
      runconfig/parse.go
  11. 6 5
      runconfig/parse_test.go
  12. 26 0
      volume/volume.go

+ 1 - 1
daemon/config.go

@@ -54,7 +54,7 @@ func (config *Config) InstallCommonFlags() {
 	flag.StringVar(&config.CorsHeaders, []string{"-api-cors-header"}, "", "Set CORS headers in the remote API")
 	flag.StringVar(&config.CorsHeaders, []string{"-api-cors-header"}, "", "Set CORS headers in the remote API")
 	// FIXME: why the inconsistency between "hosts" and "sockets"?
 	// FIXME: why the inconsistency between "hosts" and "sockets"?
 	opts.IPListVar(&config.Dns, []string{"#dns", "-dns"}, "DNS server to use")
 	opts.IPListVar(&config.Dns, []string{"#dns", "-dns"}, "DNS server to use")
-	opts.DnsSearchListVar(&config.DnsSearch, []string{"-dns-search"}, "DNS search domains to use")
+	opts.DNSSearchListVar(&config.DnsSearch, []string{"-dns-search"}, "DNS search domains to use")
 	opts.LabelListVar(&config.Labels, []string{"-label"}, "Set key=value labels to the daemon")
 	opts.LabelListVar(&config.Labels, []string{"-label"}, "Set key=value labels to the daemon")
 	flag.StringVar(&config.LogConfig.Type, []string{"-log-driver"}, "json-file", "Default driver for container logs")
 	flag.StringVar(&config.LogConfig.Type, []string{"-log-driver"}, "json-file", "Default driver for container logs")
 	opts.LogOptsVar(config.LogConfig.Config, []string{"-log-opt"}, "Set log driver options")
 	opts.LogOptsVar(config.LogConfig.Config, []string{"-log-opt"}, "Set log driver options")

+ 4 - 27
daemon/volumes.go

@@ -73,10 +73,11 @@ func parseBindMount(spec string, mountLabel string, config *runconfig.Config) (*
 	case 3:
 	case 3:
 		bind.Destination = arr[1]
 		bind.Destination = arr[1]
 		mode := arr[2]
 		mode := arr[2]
-		if !validMountMode(mode) {
+		isValid, isRw := volume.ValidateMountMode(mode)
+		if !isValid {
 			return nil, fmt.Errorf("invalid mode for volumes-from: %s", mode)
 			return nil, fmt.Errorf("invalid mode for volumes-from: %s", mode)
 		}
 		}
-		bind.RW = rwModes[mode]
+		bind.RW = isRw
 		// Relabel will apply a SELinux label, if necessary
 		// Relabel will apply a SELinux label, if necessary
 		bind.Relabel = mode
 		bind.Relabel = mode
 	default:
 	default:
@@ -113,37 +114,13 @@ func parseVolumesFrom(spec string) (string, string, error) {
 
 
 	if len(specParts) == 2 {
 	if len(specParts) == 2 {
 		mode = specParts[1]
 		mode = specParts[1]
-		if !validMountMode(mode) {
+		if isValid, _ := volume.ValidateMountMode(mode); !isValid {
 			return "", "", fmt.Errorf("invalid mode for volumes-from: %s", mode)
 			return "", "", fmt.Errorf("invalid mode for volumes-from: %s", mode)
 		}
 		}
 	}
 	}
 	return id, mode, nil
 	return id, mode, nil
 }
 }
 
 
-// read-write modes
-var rwModes = map[string]bool{
-	"rw":   true,
-	"rw,Z": true,
-	"rw,z": true,
-	"z,rw": true,
-	"Z,rw": true,
-	"Z":    true,
-	"z":    true,
-}
-
-// read-only modes
-var roModes = map[string]bool{
-	"ro":   true,
-	"ro,Z": true,
-	"ro,z": true,
-	"z,ro": true,
-	"Z,ro": true,
-}
-
-func validMountMode(mode string) bool {
-	return roModes[mode] || rwModes[mode]
-}
-
 func copyExistingContents(source, destination string) error {
 func copyExistingContents(source, destination string) error {
 	volList, err := ioutil.ReadDir(source)
 	volList, err := ioutil.ReadDir(source)
 	if err != nil {
 	if err != nil {

+ 18 - 10
opts/envfile.go

@@ -4,12 +4,18 @@ import (
 	"bufio"
 	"bufio"
 	"fmt"
 	"fmt"
 	"os"
 	"os"
+	"regexp"
 	"strings"
 	"strings"
 )
 )
 
 
-/*
-Read in a line delimited file with environment variables enumerated
-*/
+var (
+	// EnvironmentVariableRegexp A regexp to validate correct environment variables
+	// 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.
+	EnvironmentVariableRegexp = regexp.MustCompile("^[[:alpha:]_][[:alpha:][:digit:]_]*$")
+)
+
+// ParseEnvFile Read in a line delimited file with environment variables enumerated
 func ParseEnvFile(filename string) ([]string, error) {
 func ParseEnvFile(filename string) ([]string, error) {
 	fh, err := os.Open(filename)
 	fh, err := os.Open(filename)
 	if err != nil {
 	if err != nil {
@@ -23,14 +29,15 @@ func ParseEnvFile(filename string) ([]string, error) {
 		line := scanner.Text()
 		line := scanner.Text()
 		// line is not empty, and not starting with '#'
 		// line is not empty, and not starting with '#'
 		if len(line) > 0 && !strings.HasPrefix(line, "#") {
 		if len(line) > 0 && !strings.HasPrefix(line, "#") {
-			if strings.Contains(line, "=") {
-				data := strings.SplitN(line, "=", 2)
+			data := strings.SplitN(line, "=", 2)
 
 
-				// trim the front of a variable, but nothing else
-				variable := strings.TrimLeft(data[0], whiteSpaces)
-				if strings.ContainsAny(variable, whiteSpaces) {
-					return []string{}, ErrBadEnvVariable{fmt.Sprintf("variable '%s' has white spaces", variable)}
-				}
+			// trim the front of a variable, but nothing else
+			variable := strings.TrimLeft(data[0], whiteSpaces)
+
+			if !EnvironmentVariableRegexp.MatchString(variable) {
+				return []string{}, ErrBadEnvVariable{fmt.Sprintf("variable '%s' is not a valid environment variable", variable)}
+			}
+			if len(data) > 1 {
 
 
 				// pass the value through, no trimming
 				// pass the value through, no trimming
 				lines = append(lines, fmt.Sprintf("%s=%s", variable, data[1]))
 				lines = append(lines, fmt.Sprintf("%s=%s", variable, data[1]))
@@ -45,6 +52,7 @@ func ParseEnvFile(filename string) ([]string, error) {
 
 
 var whiteSpaces = " \t"
 var whiteSpaces = " \t"
 
 
+// ErrBadEnvVariable typed error for bad environment variable
 type ErrBadEnvVariable struct {
 type ErrBadEnvVariable struct {
 	msg string
 	msg string
 }
 }

+ 47 - 28
opts/envfile_test.go

@@ -10,15 +10,15 @@ import (
 	"testing"
 	"testing"
 )
 )
 
 
-func tmpFileWithContent(content string) (string, error) {
+func tmpFileWithContent(content string, t *testing.T) string {
 	tmpFile, err := ioutil.TempFile("", "envfile-test")
 	tmpFile, err := ioutil.TempFile("", "envfile-test")
 	if err != nil {
 	if err != nil {
-		return "", err
+		t.Fatal(err)
 	}
 	}
 	defer tmpFile.Close()
 	defer tmpFile.Close()
 
 
 	tmpFile.WriteString(content)
 	tmpFile.WriteString(content)
-	return tmpFile.Name(), nil
+	return tmpFile.Name()
 }
 }
 
 
 // Test ParseEnvFile for a file with a few well formatted lines
 // Test ParseEnvFile for a file with a few well formatted lines
@@ -27,42 +27,36 @@ func TestParseEnvFileGoodFile(t *testing.T) {
     baz=quux
     baz=quux
 # comment
 # comment
 
 
-foobar=foobaz
+_foobar=foobaz
 `
 `
 
 
-	tmpFile, err := tmpFileWithContent(content)
-	if err != nil {
-		t.Fatal("failed to create test data file")
-	}
+	tmpFile := tmpFileWithContent(content, t)
 	defer os.Remove(tmpFile)
 	defer os.Remove(tmpFile)
 
 
 	lines, err := ParseEnvFile(tmpFile)
 	lines, err := ParseEnvFile(tmpFile)
 	if err != nil {
 	if err != nil {
-		t.Fatal("ParseEnvFile failed; expected success")
+		t.Fatal(err)
 	}
 	}
 
 
-	expected_lines := []string{
+	expectedLines := []string{
 		"foo=bar",
 		"foo=bar",
 		"baz=quux",
 		"baz=quux",
-		"foobar=foobaz",
+		"_foobar=foobaz",
 	}
 	}
 
 
-	if !reflect.DeepEqual(lines, expected_lines) {
+	if !reflect.DeepEqual(lines, expectedLines) {
 		t.Fatal("lines not equal to expected_lines")
 		t.Fatal("lines not equal to expected_lines")
 	}
 	}
 }
 }
 
 
 // Test ParseEnvFile for an empty file
 // Test ParseEnvFile for an empty file
 func TestParseEnvFileEmptyFile(t *testing.T) {
 func TestParseEnvFileEmptyFile(t *testing.T) {
-	tmpFile, err := tmpFileWithContent("")
-	if err != nil {
-		t.Fatal("failed to create test data file")
-	}
+	tmpFile := tmpFileWithContent("", t)
 	defer os.Remove(tmpFile)
 	defer os.Remove(tmpFile)
 
 
 	lines, err := ParseEnvFile(tmpFile)
 	lines, err := ParseEnvFile(tmpFile)
 	if err != nil {
 	if err != nil {
-		t.Fatal("ParseEnvFile failed; expected success")
+		t.Fatal(err)
 	}
 	}
 
 
 	if len(lines) != 0 {
 	if len(lines) != 0 {
@@ -76,6 +70,9 @@ func TestParseEnvFileNonExistentFile(t *testing.T) {
 	if err == nil {
 	if err == nil {
 		t.Fatal("ParseEnvFile succeeded; expected failure")
 		t.Fatal("ParseEnvFile succeeded; expected failure")
 	}
 	}
+	if _, ok := err.(*os.PathError); !ok {
+		t.Fatalf("Expected a PathError, got [%v]", err)
+	}
 }
 }
 
 
 // Test ParseEnvFile for a badly formatted file
 // Test ParseEnvFile for a badly formatted file
@@ -84,15 +81,19 @@ func TestParseEnvFileBadlyFormattedFile(t *testing.T) {
     f   =quux
     f   =quux
 `
 `
 
 
-	tmpFile, err := tmpFileWithContent(content)
-	if err != nil {
-		t.Fatal("failed to create test data file")
-	}
+	tmpFile := tmpFileWithContent(content, t)
 	defer os.Remove(tmpFile)
 	defer os.Remove(tmpFile)
 
 
-	_, err = ParseEnvFile(tmpFile)
+	_, err := ParseEnvFile(tmpFile)
 	if err == nil {
 	if err == nil {
-		t.Fatal("ParseEnvFile succeeded; expected failure")
+		t.Fatalf("Expected a ErrBadEnvVariable, got nothing")
+	}
+	if _, ok := err.(ErrBadEnvVariable); !ok {
+		t.Fatalf("Expected a ErrBadEnvVariable, got [%v]", err)
+	}
+	expectedMessage := "poorly formatted environment: variable 'f   ' is not a valid environment variable"
+	if err.Error() != expectedMessage {
+		t.Fatalf("Expected [%v], got [%v]", expectedMessage, err.Error())
 	}
 	}
 }
 }
 
 
@@ -101,14 +102,32 @@ func TestParseEnvFileLineTooLongFile(t *testing.T) {
 	content := strings.Repeat("a", bufio.MaxScanTokenSize+42)
 	content := strings.Repeat("a", bufio.MaxScanTokenSize+42)
 	content = fmt.Sprint("foo=", content)
 	content = fmt.Sprint("foo=", content)
 
 
-	tmpFile, err := tmpFileWithContent(content)
-	if err != nil {
-		t.Fatal("failed to create test data file")
-	}
+	tmpFile := tmpFileWithContent(content, t)
 	defer os.Remove(tmpFile)
 	defer os.Remove(tmpFile)
 
 
-	_, err = ParseEnvFile(tmpFile)
+	_, err := ParseEnvFile(tmpFile)
 	if err == nil {
 	if err == nil {
 		t.Fatal("ParseEnvFile succeeded; expected failure")
 		t.Fatal("ParseEnvFile succeeded; expected failure")
 	}
 	}
 }
 }
+
+// ParseEnvFile with a random file, pass through
+func TestParseEnvFileRandomFile(t *testing.T) {
+	content := `first line
+another invalid line`
+	tmpFile := tmpFileWithContent(content, t)
+	defer os.Remove(tmpFile)
+
+	_, err := ParseEnvFile(tmpFile)
+
+	if err == nil {
+		t.Fatalf("Expected a ErrBadEnvVariable, got nothing")
+	}
+	if _, ok := err.(ErrBadEnvVariable); !ok {
+		t.Fatalf("Expected a ErrBadEnvvariable, got [%v]", err)
+	}
+	expectedMessage := "poorly formatted environment: variable 'first line' is not a valid environment variable"
+	if err.Error() != expectedMessage {
+		t.Fatalf("Expected [%v], got [%v]", expectedMessage, err.Error())
+	}
+}

+ 1 - 0
opts/ip.go

@@ -5,6 +5,7 @@ import (
 	"net"
 	"net"
 )
 )
 
 
+// IpOpt type that hold an IP
 type IpOpt struct {
 type IpOpt struct {
 	*net.IP
 	*net.IP
 }
 }

+ 54 - 0
opts/ip_test.go

@@ -0,0 +1,54 @@
+package opts
+
+import (
+	"net"
+	"testing"
+)
+
+func TestIpOptString(t *testing.T) {
+	addresses := []string{"", "0.0.0.0"}
+	var ip net.IP
+
+	for _, address := range addresses {
+		stringAddress := NewIpOpt(&ip, address).String()
+		if stringAddress != address {
+			t.Fatalf("IpOpt string should be `%s`, not `%s`", address, stringAddress)
+		}
+	}
+}
+
+func TestNewIpOptInvalidDefaultVal(t *testing.T) {
+	ip := net.IPv4(127, 0, 0, 1)
+	defaultVal := "Not an ip"
+
+	ipOpt := NewIpOpt(&ip, defaultVal)
+
+	expected := "127.0.0.1"
+	if ipOpt.String() != expected {
+		t.Fatalf("Expected [%v], got [%v]", expected, ipOpt.String())
+	}
+}
+
+func TestNewIpOptValidDefaultVal(t *testing.T) {
+	ip := net.IPv4(127, 0, 0, 1)
+	defaultVal := "192.168.1.1"
+
+	ipOpt := NewIpOpt(&ip, defaultVal)
+
+	expected := "192.168.1.1"
+	if ipOpt.String() != expected {
+		t.Fatalf("Expected [%v], got [%v]", expected, ipOpt.String())
+	}
+}
+
+func TestIpOptSetInvalidVal(t *testing.T) {
+	ip := net.IPv4(127, 0, 0, 1)
+	ipOpt := &IpOpt{IP: &ip}
+
+	invalidIp := "invalid ip"
+	expectedError := "invalid ip is not an ip address"
+	err := ipOpt.Set(invalidIp)
+	if err == nil || err.Error() != expectedError {
+		t.Fatalf("Expected an Error with [%v], got [%v]", expectedError, err.Error())
+	}
+}

+ 95 - 21
opts/opts.go

@@ -11,61 +11,85 @@ import (
 	flag "github.com/docker/docker/pkg/mflag"
 	flag "github.com/docker/docker/pkg/mflag"
 	"github.com/docker/docker/pkg/parsers"
 	"github.com/docker/docker/pkg/parsers"
 	"github.com/docker/docker/pkg/ulimit"
 	"github.com/docker/docker/pkg/ulimit"
+	"github.com/docker/docker/volume"
 )
 )
 
 
 var (
 var (
-	alphaRegexp     = regexp.MustCompile(`[a-zA-Z]`)
-	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*$`)
-	DefaultHTTPHost = "127.0.0.1" // Default HTTP Host used if only port is provided to -H flag e.g. docker -d -H tcp://:8080
+	alphaRegexp  = regexp.MustCompile(`[a-zA-Z]`)
+	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*$`)
+	// DefaultHTTPHost Default HTTP Host used if only port is provided to -H flag e.g. docker -d -H tcp://:8080
+	DefaultHTTPHost = "127.0.0.1"
+	// DefaultHTTPPort Default HTTP Port used if only the protocol is provided to -H flag e.g. docker -d -H tcp://
 	// TODO Windows. DefaultHTTPPort is only used on Windows if a -H parameter
 	// TODO Windows. DefaultHTTPPort is only used on Windows if a -H parameter
 	// is not supplied. A better longer term solution would be to use a named
 	// is not supplied. A better longer term solution would be to use a named
 	// pipe as the default on the Windows daemon.
 	// pipe as the default on the Windows daemon.
-	DefaultHTTPPort   = 2375                   // Default HTTP Port
-	DefaultUnixSocket = "/var/run/docker.sock" // Docker daemon by default always listens on the default unix socket
+	DefaultHTTPPort = 2375 // Default HTTP Port
+	// DefaultUnixSocket Path for the unix socket.
+	// Docker daemon by default always listens on the default unix socket
+	DefaultUnixSocket = "/var/run/docker.sock"
 )
 )
 
 
+// ListVar Defines a flag with the specified names and usage, and put the value
+// list into ListOpts that will hold the values.
 func ListVar(values *[]string, names []string, usage string) {
 func ListVar(values *[]string, names []string, usage string) {
 	flag.Var(newListOptsRef(values, nil), names, usage)
 	flag.Var(newListOptsRef(values, nil), names, usage)
 }
 }
 
 
+// MapVar Defines a flag with the specified names and usage, and put the value
+// map into MapOpt that will hold the values (key,value).
 func MapVar(values map[string]string, names []string, usage string) {
 func MapVar(values map[string]string, names []string, usage string) {
 	flag.Var(newMapOpt(values, nil), names, usage)
 	flag.Var(newMapOpt(values, nil), names, usage)
 }
 }
 
 
+// LogOptsVar Defines a flag with the specified names and usage for --log-opts,
+// and put the value map into MapOpt that will hold the values (key,value).
 func LogOptsVar(values map[string]string, names []string, usage string) {
 func LogOptsVar(values map[string]string, names []string, usage string) {
 	flag.Var(newMapOpt(values, nil), names, usage)
 	flag.Var(newMapOpt(values, nil), names, usage)
 }
 }
 
 
+// HostListVar Defines a flag with the specified names and usage and put the
+// value into a ListOpts that will hold the values, validating the Host format.
 func HostListVar(values *[]string, names []string, usage string) {
 func HostListVar(values *[]string, names []string, usage string) {
 	flag.Var(newListOptsRef(values, ValidateHost), names, usage)
 	flag.Var(newListOptsRef(values, ValidateHost), names, usage)
 }
 }
 
 
+// IPListVar Defines a flag with the specified names and usage and put the
+// value into a ListOpts that will hold the values, validating the IP format.
 func IPListVar(values *[]string, names []string, usage string) {
 func IPListVar(values *[]string, names []string, usage string) {
 	flag.Var(newListOptsRef(values, ValidateIPAddress), names, usage)
 	flag.Var(newListOptsRef(values, ValidateIPAddress), names, usage)
 }
 }
 
 
-func DnsSearchListVar(values *[]string, names []string, usage string) {
-	flag.Var(newListOptsRef(values, ValidateDnsSearch), names, usage)
+// DNSSearchListVar Defines a flag with the specified names and usage and put the
+// value into a ListOpts that will hold the values, validating the DNS search format.
+func DNSSearchListVar(values *[]string, names []string, usage string) {
+	flag.Var(newListOptsRef(values, ValidateDNSSearch), names, usage)
 }
 }
 
 
+// IPVar Defines a flag with the specified names and usage for IP and will use
+// the specified defaultValue if the specified value is not valid.
 func IPVar(value *net.IP, names []string, defaultValue, usage string) {
 func IPVar(value *net.IP, names []string, defaultValue, usage string) {
 	flag.Var(NewIpOpt(value, defaultValue), names, usage)
 	flag.Var(NewIpOpt(value, defaultValue), names, usage)
 }
 }
 
 
+// LabelListVar Defines a flag with the specified names and usage and put the
+// value into a ListOpts that will hold the values, validating the label format.
 func LabelListVar(values *[]string, names []string, usage string) {
 func LabelListVar(values *[]string, names []string, usage string) {
 	flag.Var(newListOptsRef(values, ValidateLabel), names, usage)
 	flag.Var(newListOptsRef(values, ValidateLabel), names, usage)
 }
 }
 
 
+// UlimitMapVar Defines a flag with the specified names and usage for --ulimit,
+// and put the value map into a UlimitOpt that will hold the values.
 func UlimitMapVar(values map[string]*ulimit.Ulimit, names []string, usage string) {
 func UlimitMapVar(values map[string]*ulimit.Ulimit, names []string, usage string) {
 	flag.Var(NewUlimitOpt(values), names, usage)
 	flag.Var(NewUlimitOpt(values), names, usage)
 }
 }
 
 
-// ListOpts type
+// ListOpts type that hold a list of values and a validation function.
 type ListOpts struct {
 type ListOpts struct {
 	values    *[]string
 	values    *[]string
 	validator ValidatorFctType
 	validator ValidatorFctType
 }
 }
 
 
+// NewListOpts Create a new ListOpts with the specified validator.
 func NewListOpts(validator ValidatorFctType) ListOpts {
 func NewListOpts(validator ValidatorFctType) ListOpts {
 	var values []string
 	var values []string
 	return *newListOptsRef(&values, validator)
 	return *newListOptsRef(&values, validator)
@@ -138,12 +162,14 @@ func (opts *ListOpts) Len() int {
 	return len((*opts.values))
 	return len((*opts.values))
 }
 }
 
 
-//MapOpts type
+//MapOpts type that holds a map of values and a validation function.
 type MapOpts struct {
 type MapOpts struct {
 	values    map[string]string
 	values    map[string]string
 	validator ValidatorFctType
 	validator ValidatorFctType
 }
 }
 
 
+// Set validates if needed the input value and add it to the
+// internal map, by splitting on '='.
 func (opts *MapOpts) Set(value string) error {
 func (opts *MapOpts) Set(value string) error {
 	if opts.validator != nil {
 	if opts.validator != nil {
 		v, err := opts.validator(value)
 		v, err := opts.validator(value)
@@ -172,10 +198,13 @@ func newMapOpt(values map[string]string, validator ValidatorFctType) *MapOpts {
 	}
 	}
 }
 }
 
 
-// Validators
+// ValidatorFctType validator that return a validate string and/or an error
 type ValidatorFctType func(val string) (string, error)
 type ValidatorFctType func(val string) (string, error)
+
+// ValidatorFctListType validator that return a validate list of string and/or an error
 type ValidatorFctListType func(val string) ([]string, error)
 type ValidatorFctListType func(val string) ([]string, error)
 
 
+// ValidateAttach Validates that the specified string is a valid attach option.
 func ValidateAttach(val string) (string, error) {
 func ValidateAttach(val string) (string, error) {
 	s := strings.ToLower(val)
 	s := strings.ToLower(val)
 	for _, str := range []string{"stdin", "stdout", "stderr"} {
 	for _, str := range []string{"stdin", "stdout", "stderr"} {
@@ -183,9 +212,10 @@ func ValidateAttach(val string) (string, error) {
 			return s, nil
 			return s, nil
 		}
 		}
 	}
 	}
-	return val, fmt.Errorf("valid streams are STDIN, STDOUT and STDERR.")
+	return val, fmt.Errorf("valid streams are STDIN, STDOUT and STDERR")
 }
 }
 
 
+// ValidateLink Validates that the specified string has a valid link format (containerName:alias).
 func ValidateLink(val string) (string, error) {
 func ValidateLink(val string) (string, error) {
 	if _, _, err := parsers.ParseLink(val); err != nil {
 	if _, _, err := parsers.ParseLink(val); err != nil {
 		return val, err
 		return val, err
@@ -193,22 +223,53 @@ func ValidateLink(val string) (string, error) {
 	return val, nil
 	return val, nil
 }
 }
 
 
-// ValidatePath will make sure 'val' is in the form:
-//    [host-dir:]container-path[:rw|ro]  - but doesn't validate the mode part
+// ValidateDevice Validate a path for devices
+// It will make sure 'val' is in the form:
+//    [host-dir:]container-path[:mode]
+func ValidateDevice(val string) (string, error) {
+	return validatePath(val, false)
+}
+
+// ValidatePath Validate a path for volumes
+// It will make sure 'val' is in the form:
+//    [host-dir:]container-path[:rw|ro]
+// It will also validate the mount mode.
 func ValidatePath(val string) (string, error) {
 func ValidatePath(val string) (string, error) {
+	return validatePath(val, true)
+}
+
+func validatePath(val string, validateMountMode bool) (string, error) {
 	var containerPath string
 	var containerPath string
+	var mode string
 
 
 	if strings.Count(val, ":") > 2 {
 	if strings.Count(val, ":") > 2 {
 		return val, fmt.Errorf("bad format for volumes: %s", val)
 		return val, fmt.Errorf("bad format for volumes: %s", val)
 	}
 	}
 
 
-	splited := strings.SplitN(val, ":", 2)
-	if len(splited) == 1 {
+	splited := strings.SplitN(val, ":", 3)
+	if splited[0] == "" {
+		return val, fmt.Errorf("bad format for volumes: %s", val)
+	}
+	switch len(splited) {
+	case 1:
 		containerPath = splited[0]
 		containerPath = splited[0]
-		val = path.Clean(splited[0])
-	} else {
+		val = path.Clean(containerPath)
+	case 2:
+		if isValid, _ := volume.ValidateMountMode(splited[1]); validateMountMode && isValid {
+			containerPath = splited[0]
+			mode = splited[1]
+			val = fmt.Sprintf("%s:%s", path.Clean(containerPath), mode)
+		} else {
+			containerPath = splited[1]
+			val = fmt.Sprintf("%s:%s", splited[0], path.Clean(containerPath))
+		}
+	case 3:
 		containerPath = splited[1]
 		containerPath = splited[1]
-		val = fmt.Sprintf("%s:%s", splited[0], path.Clean(splited[1]))
+		mode = splited[2]
+		if isValid, _ := volume.ValidateMountMode(splited[2]); validateMountMode && !isValid {
+			return val, fmt.Errorf("bad mount mode specified : %s", mode)
+		}
+		val = fmt.Sprintf("%s:%s:%s", splited[0], containerPath, mode)
 	}
 	}
 
 
 	if !path.IsAbs(containerPath) {
 	if !path.IsAbs(containerPath) {
@@ -217,17 +278,24 @@ func ValidatePath(val string) (string, error) {
 	return val, nil
 	return val, nil
 }
 }
 
 
+// ValidateEnv Validate an environment variable and returns it
+// It will use EnvironmentVariableRegexp to ensure the name of the environment variable is valid.
+// If no value is specified, it returns the current value using os.Getenv.
 func ValidateEnv(val string) (string, error) {
 func ValidateEnv(val string) (string, error) {
 	arr := strings.Split(val, "=")
 	arr := strings.Split(val, "=")
 	if len(arr) > 1 {
 	if len(arr) > 1 {
 		return val, nil
 		return val, nil
 	}
 	}
+	if !EnvironmentVariableRegexp.MatchString(arr[0]) {
+		return val, ErrBadEnvVariable{fmt.Sprintf("variable '%s' is not a valid environment variable", val)}
+	}
 	if !doesEnvExist(val) {
 	if !doesEnvExist(val) {
 		return val, nil
 		return val, nil
 	}
 	}
 	return fmt.Sprintf("%s=%s", val, os.Getenv(val)), nil
 	return fmt.Sprintf("%s=%s", val, os.Getenv(val)), nil
 }
 }
 
 
+// ValidateIPAddress Validates an Ip address
 func ValidateIPAddress(val string) (string, error) {
 func ValidateIPAddress(val string) (string, error) {
 	var ip = net.ParseIP(strings.TrimSpace(val))
 	var ip = net.ParseIP(strings.TrimSpace(val))
 	if ip != nil {
 	if ip != nil {
@@ -236,6 +304,7 @@ func ValidateIPAddress(val string) (string, error) {
 	return "", fmt.Errorf("%s is not an ip address", val)
 	return "", fmt.Errorf("%s is not an ip address", val)
 }
 }
 
 
+// ValidateMACAddress Validates a MAC address
 func ValidateMACAddress(val string) (string, error) {
 func ValidateMACAddress(val string) (string, error) {
 	_, err := net.ParseMAC(strings.TrimSpace(val))
 	_, err := net.ParseMAC(strings.TrimSpace(val))
 	if err != nil {
 	if err != nil {
@@ -244,9 +313,9 @@ func ValidateMACAddress(val string) (string, error) {
 	return val, nil
 	return val, nil
 }
 }
 
 
-// Validates domain for resolvconf search configuration.
+// ValidateDNSSearch Validates domain for resolvconf search configuration.
 // A zero length domain is represented by .
 // A zero length domain is represented by .
-func ValidateDnsSearch(val string) (string, error) {
+func ValidateDNSSearch(val string) (string, error) {
 	if val = strings.Trim(val, " "); val == "." {
 	if val = strings.Trim(val, " "); val == "." {
 		return val, nil
 		return val, nil
 	}
 	}
@@ -264,6 +333,8 @@ func validateDomain(val string) (string, error) {
 	return "", fmt.Errorf("%s is not a valid domain", val)
 	return "", fmt.Errorf("%s is not a valid domain", val)
 }
 }
 
 
+// ValidateExtraHost Validate that the given string is a valid extrahost and returns it
+// ExtraHost are in the form of name:ip where the ip has to be a valid ip (ipv4 or ipv6)
 func ValidateExtraHost(val string) (string, error) {
 func ValidateExtraHost(val string) (string, error) {
 	// allow for IPv6 addresses in extra hosts by only splitting on first ":"
 	// allow for IPv6 addresses in extra hosts by only splitting on first ":"
 	arr := strings.SplitN(val, ":", 2)
 	arr := strings.SplitN(val, ":", 2)
@@ -276,13 +347,16 @@ func ValidateExtraHost(val string) (string, error) {
 	return val, nil
 	return val, nil
 }
 }
 
 
+// ValidateLabel Validate that the given string is a valid label, and returns it
+// Labels are in the form on key=value
 func ValidateLabel(val string) (string, error) {
 func ValidateLabel(val string) (string, error) {
-	if strings.Count(val, "=") != 1 {
+	if strings.Count(val, "=") < 1 {
 		return "", fmt.Errorf("bad attribute format: %s", val)
 		return "", fmt.Errorf("bad attribute format: %s", val)
 	}
 	}
 	return val, nil
 	return val, nil
 }
 }
 
 
+// ValidateHost Validate that the given string is a valid host and returns it
 func ValidateHost(val string) (string, error) {
 func ValidateHost(val string) (string, error) {
 	host, err := parsers.ParseHost(DefaultHTTPHost, DefaultUnixSocket, val)
 	host, err := parsers.ParseHost(DefaultHTTPHost, DefaultUnixSocket, val)
 	if err != nil {
 	if err != nil {

+ 293 - 16
opts/opts_test.go

@@ -2,7 +2,7 @@ package opts
 
 
 import (
 import (
 	"fmt"
 	"fmt"
-	"net"
+	"os"
 	"strings"
 	"strings"
 	"testing"
 	"testing"
 )
 )
@@ -69,7 +69,7 @@ func TestValidateMACAddress(t *testing.T) {
 	}
 	}
 }
 }
 
 
-func TestListOpts(t *testing.T) {
+func TestListOptsWithoutValidator(t *testing.T) {
 	o := NewListOpts(nil)
 	o := NewListOpts(nil)
 	o.Set("foo")
 	o.Set("foo")
 	if o.String() != "[foo]" {
 	if o.String() != "[foo]" {
@@ -79,6 +79,10 @@ func TestListOpts(t *testing.T) {
 	if o.Len() != 2 {
 	if o.Len() != 2 {
 		t.Errorf("%d != 2", o.Len())
 		t.Errorf("%d != 2", o.Len())
 	}
 	}
+	o.Set("bar")
+	if o.Len() != 3 {
+		t.Errorf("%d != 3", o.Len())
+	}
 	if !o.Get("bar") {
 	if !o.Get("bar") {
 		t.Error("o.Get(\"bar\") == false")
 		t.Error("o.Get(\"bar\") == false")
 	}
 	}
@@ -86,12 +90,48 @@ func TestListOpts(t *testing.T) {
 		t.Error("o.Get(\"baz\") == true")
 		t.Error("o.Get(\"baz\") == true")
 	}
 	}
 	o.Delete("foo")
 	o.Delete("foo")
-	if o.String() != "[bar]" {
-		t.Errorf("%s != [bar]", o.String())
+	if o.String() != "[bar bar]" {
+		t.Errorf("%s != [bar bar]", o.String())
+	}
+	listOpts := o.GetAll()
+	if len(listOpts) != 2 || listOpts[0] != "bar" || listOpts[1] != "bar" {
+		t.Errorf("Expected [[bar bar]], got [%v]", listOpts)
 	}
 	}
+	mapListOpts := o.GetMap()
+	if len(mapListOpts) != 1 {
+		t.Errorf("Expected [map[bar:{}]], got [%v]", mapListOpts)
+	}
+
 }
 }
 
 
-func TestValidateDnsSearch(t *testing.T) {
+func TestListOptsWithValidator(t *testing.T) {
+	// Re-using logOptsvalidator (used by MapOpts)
+	o := NewListOpts(logOptsValidator)
+	o.Set("foo")
+	if o.String() != "[]" {
+		t.Errorf("%s != []", o.String())
+	}
+	o.Set("foo=bar")
+	if o.String() != "[]" {
+		t.Errorf("%s != []", o.String())
+	}
+	o.Set("max-file=2")
+	if o.Len() != 1 {
+		t.Errorf("%d != 1", o.Len())
+	}
+	if !o.Get("max-file=2") {
+		t.Error("o.Get(\"max-file=2\") == false")
+	}
+	if o.Get("baz") {
+		t.Error("o.Get(\"baz\") == true")
+	}
+	o.Delete("max-file=2")
+	if o.String() != "[]" {
+		t.Errorf("%s != []", o.String())
+	}
+}
+
+func TestValidateDNSSearch(t *testing.T) {
 	valid := []string{
 	valid := []string{
 		`.`,
 		`.`,
 		`a`,
 		`a`,
@@ -136,14 +176,14 @@ func TestValidateDnsSearch(t *testing.T) {
 	}
 	}
 
 
 	for _, domain := range valid {
 	for _, domain := range valid {
-		if ret, err := ValidateDnsSearch(domain); err != nil || ret == "" {
-			t.Fatalf("ValidateDnsSearch(`"+domain+"`) got %s %s", ret, err)
+		if ret, err := ValidateDNSSearch(domain); err != nil || ret == "" {
+			t.Fatalf("ValidateDNSSearch(`"+domain+"`) got %s %s", ret, err)
 		}
 		}
 	}
 	}
 
 
 	for _, domain := range invalid {
 	for _, domain := range invalid {
-		if ret, err := ValidateDnsSearch(domain); err == nil || ret != "" {
-			t.Fatalf("ValidateDnsSearch(`"+domain+"`) got %s %s", ret, err)
+		if ret, err := ValidateDNSSearch(domain); err == nil || ret != "" {
+			t.Fatalf("ValidateDNSSearch(`"+domain+"`) got %s %s", ret, err)
 		}
 		}
 	}
 	}
 }
 }
@@ -180,14 +220,251 @@ func TestValidateExtraHosts(t *testing.T) {
 	}
 	}
 }
 }
 
 
-func TestIpOptString(t *testing.T) {
-	addresses := []string{"", "0.0.0.0"}
-	var ip net.IP
+func TestValidateAttach(t *testing.T) {
+	valid := []string{
+		"stdin",
+		"stdout",
+		"stderr",
+		"STDIN",
+		"STDOUT",
+		"STDERR",
+	}
+	if _, err := ValidateAttach("invalid"); err == nil {
+		t.Fatalf("Expected error with [valid streams are STDIN, STDOUT and STDERR], got nothing")
+	}
+
+	for _, attach := range valid {
+		value, err := ValidateAttach(attach)
+		if err != nil {
+			t.Fatal(err)
+		}
+		if value != strings.ToLower(attach) {
+			t.Fatalf("Expected [%v], got [%v]", attach, value)
+		}
+	}
+}
+
+func TestValidateLink(t *testing.T) {
+	valid := []string{
+		"name",
+		"dcdfbe62ecd0:alias",
+		"7a67485460b7642516a4ad82ecefe7f57d0c4916f530561b71a50a3f9c4e33da",
+		"angry_torvalds:linus",
+	}
+	invalid := map[string]string{
+		"":               "empty string specified for links",
+		"too:much:of:it": "bad format for links: too:much:of:it",
+	}
+
+	for _, link := range valid {
+		if _, err := ValidateLink(link); err != nil {
+			t.Fatalf("ValidateLink(`%q`) should succeed: error %q", link, err)
+		}
+	}
+
+	for link, expectedError := range invalid {
+		if _, err := ValidateLink(link); err == nil {
+			t.Fatalf("ValidateLink(`%q`) should have failed validation", link)
+		} else {
+			if !strings.Contains(err.Error(), expectedError) {
+				t.Fatalf("ValidateLink(`%q`) error should contain %q", link, expectedError)
+			}
+		}
+	}
+}
+
+func TestValidatePath(t *testing.T) {
+	valid := []string{
+		"/home",
+		"/home:/home",
+		"/home:/something/else",
+		"/with space",
+		"/home:/with space",
+		"relative:/absolute-path",
+		"hostPath:/containerPath:ro",
+		"/hostPath:/containerPath:rw",
+		"/rw:/ro",
+		"/path:rw",
+		"/path:ro",
+		"/rw:rw",
+	}
+	invalid := map[string]string{
+		"":                "bad format for volumes: ",
+		"./":              "./ is not an absolute path",
+		"../":             "../ is not an absolute path",
+		"/:../":           "../ is not an absolute path",
+		"/:path":          "path is not an absolute path",
+		":":               "bad format for volumes: :",
+		"/tmp:":           " is not an absolute path",
+		":test":           "bad format for volumes: :test",
+		":/test":          "bad format for volumes: :/test",
+		"tmp:":            " is not an absolute path",
+		":test:":          "bad format for volumes: :test:",
+		"::":              "bad format for volumes: ::",
+		":::":             "bad format for volumes: :::",
+		"/tmp:::":         "bad format for volumes: /tmp:::",
+		":/tmp::":         "bad format for volumes: :/tmp::",
+		"path:ro":         "path is not an absolute path",
+		"/path:/path:sw":  "bad mount mode specified : sw",
+		"/path:/path:rwz": "bad mount mode specified : rwz",
+	}
+
+	for _, path := range valid {
+		if _, err := ValidatePath(path); err != nil {
+			t.Fatalf("ValidatePath(`%q`) should succeed: error %q", path, err)
+		}
+	}
+
+	for path, expectedError := range invalid {
+		if _, err := ValidatePath(path); err == nil {
+			t.Fatalf("ValidatePath(`%q`) should have failed validation", path)
+		} else {
+			if err.Error() != expectedError {
+				t.Fatalf("ValidatePath(`%q`) error should contain %q, got %q", path, expectedError, err.Error())
+			}
+		}
+	}
+}
+func TestValidateDevice(t *testing.T) {
+	valid := []string{
+		"/home",
+		"/home:/home",
+		"/home:/something/else",
+		"/with space",
+		"/home:/with space",
+		"relative:/absolute-path",
+		"hostPath:/containerPath:ro",
+		"/hostPath:/containerPath:rw",
+		"/hostPath:/containerPath:mrw",
+	}
+	invalid := map[string]string{
+		"":        "bad format for volumes: ",
+		"./":      "./ is not an absolute path",
+		"../":     "../ is not an absolute path",
+		"/:../":   "../ is not an absolute path",
+		"/:path":  "path is not an absolute path",
+		":":       "bad format for volumes: :",
+		"/tmp:":   " is not an absolute path",
+		":test":   "bad format for volumes: :test",
+		":/test":  "bad format for volumes: :/test",
+		"tmp:":    " is not an absolute path",
+		":test:":  "bad format for volumes: :test:",
+		"::":      "bad format for volumes: ::",
+		":::":     "bad format for volumes: :::",
+		"/tmp:::": "bad format for volumes: /tmp:::",
+		":/tmp::": "bad format for volumes: :/tmp::",
+		"path:ro": "ro is not an absolute path",
+	}
+
+	for _, path := range valid {
+		if _, err := ValidateDevice(path); err != nil {
+			t.Fatalf("ValidateDevice(`%q`) should succeed: error %q", path, err)
+		}
+	}
 
 
-	for _, address := range addresses {
-		stringAddress := NewIpOpt(&ip, address).String()
-		if stringAddress != address {
-			t.Fatalf("IpOpt string should be `%s`, not `%s`", address, stringAddress)
+	for path, expectedError := range invalid {
+		if _, err := ValidateDevice(path); err == nil {
+			t.Fatalf("ValidateDevice(`%q`) should have failed validation", path)
+		} else {
+			if err.Error() != expectedError {
+				t.Fatalf("ValidateDevice(`%q`) error should contain %q, got %q", path, expectedError, err.Error())
+			}
+		}
+	}
+}
+
+func TestValidateEnv(t *testing.T) {
+	invalids := map[string]string{
+		"some spaces": "poorly formatted environment: variable 'some spaces' is not a valid environment variable",
+		"asd!qwe":     "poorly formatted environment: variable 'asd!qwe' is not a valid environment variable",
+		"1asd":        "poorly formatted environment: variable '1asd' is not a valid environment variable",
+		"123":         "poorly formatted environment: variable '123' is not a valid environment variable",
+	}
+	valids := map[string]string{
+		"a":                  "a",
+		"something":          "something",
+		"_=a":                "_=a",
+		"env1=value1":        "env1=value1",
+		"_env1=value1":       "_env1=value1",
+		"env2=value2=value3": "env2=value2=value3",
+		"env3=abc!qwe":       "env3=abc!qwe",
+		"env_4=value 4":      "env_4=value 4",
+		"PATH":               fmt.Sprintf("PATH=%v", os.Getenv("PATH")),
+		"PATH=something":     "PATH=something",
+	}
+	for value, expectedError := range invalids {
+		_, err := ValidateEnv(value)
+		if err == nil {
+			t.Fatalf("Expected ErrBadEnvVariable, got nothing")
+		}
+		if _, ok := err.(ErrBadEnvVariable); !ok {
+			t.Fatalf("Expected ErrBadEnvVariable, got [%s]", err)
+		}
+		if err.Error() != expectedError {
+			t.Fatalf("Expected ErrBadEnvVariable with message [%s], got [%s]", expectedError, err.Error())
+		}
+	}
+	for value, expected := range valids {
+		actual, err := ValidateEnv(value)
+		if err != nil {
+			t.Fatal(err)
+		}
+		if actual != expected {
+			t.Fatalf("Expected [%v], got [%v]", expected, actual)
+		}
+	}
+}
+
+func TestValidateLabel(t *testing.T) {
+	if _, err := ValidateLabel("label"); err == nil || err.Error() != "bad attribute format: label" {
+		t.Fatalf("Expected an error [bad attribute format: label], go %v", err)
+	}
+	if actual, err := ValidateLabel("key1=value1"); err != nil || actual != "key1=value1" {
+		t.Fatalf("Expected [key1=value1], got [%v,%v]", actual, err)
+	}
+	// Validate it's working with more than one =
+	if actual, err := ValidateLabel("key1=value1=value2"); err != nil {
+		t.Fatalf("Expected [key1=value1=value2], got [%v,%v]", actual, err)
+	}
+	// Validate it's working with one more
+	if actual, err := ValidateLabel("key1=value1=value2=value3"); err != nil {
+		t.Fatalf("Expected [key1=value1=value2=value2], got [%v,%v]", actual, err)
+	}
+}
+
+func TestValidateHost(t *testing.T) {
+	invalid := map[string]string{
+		"anything":              "Invalid bind address format: anything",
+		"something with spaces": "Invalid bind address format: something with spaces",
+		"://":                "Invalid bind address format: ://",
+		"unknown://":         "Invalid bind address format: unknown://",
+		"tcp://":             "Invalid proto, expected tcp: ",
+		"tcp://:port":        "Invalid bind address format: :port",
+		"tcp://invalid":      "Invalid bind address format: invalid",
+		"tcp://invalid:port": "Invalid bind address format: invalid:port",
+	}
+	valid := map[string]string{
+		"fd://":                    "fd://",
+		"fd://something":           "fd://something",
+		"tcp://:2375":              "tcp://127.0.0.1:2375", // default ip address
+		"tcp://:2376":              "tcp://127.0.0.1:2376", // default ip address
+		"tcp://0.0.0.0:8080":       "tcp://0.0.0.0:8080",
+		"tcp://192.168.0.0:12000":  "tcp://192.168.0.0:12000",
+		"tcp://192.168:8080":       "tcp://192.168:8080",
+		"tcp://0.0.0.0:1234567890": "tcp://0.0.0.0:1234567890", // yeah it's valid :P
+		"tcp://docker.com:2375":    "tcp://docker.com:2375",
+		"unix://":                  "unix:///var/run/docker.sock", // default unix:// value
+		"unix://path/to/socket":    "unix://path/to/socket",
+	}
+
+	for value, errorMessage := range invalid {
+		if _, err := ValidateHost(value); err == nil || err.Error() != errorMessage {
+			t.Fatalf("Expected an error for %v with [%v], got [%v]", value, errorMessage, err)
+		}
+	}
+	for value, expected := range valid {
+		if actual, err := ValidateHost(value); err != nil || actual != expected {
+			t.Fatalf("Expected for %v [%v], got [%v, %v]", value, expected, actual, err)
 		}
 		}
 	}
 	}
 }
 }

+ 42 - 0
opts/ulimit_test.go

@@ -0,0 +1,42 @@
+package opts
+
+import (
+	"testing"
+
+	"github.com/docker/docker/pkg/ulimit"
+)
+
+func TestUlimitOpt(t *testing.T) {
+	ulimitMap := map[string]*ulimit.Ulimit{
+		"nofile": {"nofile", 1024, 512},
+	}
+
+	ulimitOpt := NewUlimitOpt(ulimitMap)
+
+	expected := "[nofile=512:1024]"
+	if ulimitOpt.String() != expected {
+		t.Fatalf("Expected %v, got %v", expected, ulimitOpt)
+	}
+
+	// Valid ulimit append to opts
+	if err := ulimitOpt.Set("core=1024:1024"); err != nil {
+		t.Fatal(err)
+	}
+
+	// Invalid ulimit type returns an error and do not append to opts
+	if err := ulimitOpt.Set("notavalidtype=1024:1024"); err == nil {
+		t.Fatalf("Expected error on invalid ulimit type")
+	}
+	expected = "[nofile=512:1024 core=1024:1024]"
+	expected2 := "[core=1024:1024 nofile=512:1024]"
+	result := ulimitOpt.String()
+	if result != expected && result != expected2 {
+		t.Fatalf("Expected %v or %v, got %v", expected, expected2, ulimitOpt)
+	}
+
+	// And test GetList
+	ulimits := ulimitOpt.GetList()
+	if len(ulimits) != 2 {
+		t.Fatalf("Expected a ulimit list of 2, got %v", ulimits)
+	}
+}

+ 2 - 2
runconfig/parse.go

@@ -45,7 +45,7 @@ func Parse(cmd *flag.FlagSet, args []string) (*Config, *HostConfig, *flag.FlagSe
 		flLinks   = opts.NewListOpts(opts.ValidateLink)
 		flLinks   = opts.NewListOpts(opts.ValidateLink)
 		flEnv     = opts.NewListOpts(opts.ValidateEnv)
 		flEnv     = opts.NewListOpts(opts.ValidateEnv)
 		flLabels  = opts.NewListOpts(opts.ValidateEnv)
 		flLabels  = opts.NewListOpts(opts.ValidateEnv)
-		flDevices = opts.NewListOpts(opts.ValidatePath)
+		flDevices = opts.NewListOpts(opts.ValidateDevice)
 
 
 		ulimits   = make(map[string]*ulimit.Ulimit)
 		ulimits   = make(map[string]*ulimit.Ulimit)
 		flUlimits = opts.NewUlimitOpt(ulimits)
 		flUlimits = opts.NewUlimitOpt(ulimits)
@@ -53,7 +53,7 @@ func Parse(cmd *flag.FlagSet, args []string) (*Config, *HostConfig, *flag.FlagSe
 		flPublish     = opts.NewListOpts(nil)
 		flPublish     = opts.NewListOpts(nil)
 		flExpose      = opts.NewListOpts(nil)
 		flExpose      = opts.NewListOpts(nil)
 		flDns         = opts.NewListOpts(opts.ValidateIPAddress)
 		flDns         = opts.NewListOpts(opts.ValidateIPAddress)
-		flDnsSearch   = opts.NewListOpts(opts.ValidateDnsSearch)
+		flDnsSearch   = opts.NewListOpts(opts.ValidateDNSSearch)
 		flExtraHosts  = opts.NewListOpts(opts.ValidateExtraHost)
 		flExtraHosts  = opts.NewListOpts(opts.ValidateExtraHost)
 		flVolumesFrom = opts.NewListOpts(nil)
 		flVolumesFrom = opts.NewListOpts(nil)
 		flLxcOpts     = opts.NewListOpts(nil)
 		flLxcOpts     = opts.NewListOpts(nil)

+ 6 - 5
runconfig/parse_test.go

@@ -124,8 +124,12 @@ func TestParseRunVolumes(t *testing.T) {
 		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)
 		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)
 	}
 	}
 
 
-	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 {
-		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)
+	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 {
+		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)
+	}
+
+	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 {
+		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)
 	}
 	}
 
 
 	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 {
 	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) {
 	if _, _, err := parse(t, "-v /tmp:"); err == nil {
 	if _, _, err := parse(t, "-v /tmp:"); err == nil {
 		t.Fatalf("Error parsing volume flags, `-v /tmp:` should fail but didn't")
 		t.Fatalf("Error parsing volume flags, `-v /tmp:` should fail but didn't")
 	}
 	}
-	if _, _, err := parse(t, "-v /tmp:ro"); err == nil {
-		t.Fatalf("Error parsing volume flags, `-v /tmp:ro` should fail but didn't")
-	}
 	if _, _, err := parse(t, "-v /tmp::"); err == nil {
 	if _, _, err := parse(t, "-v /tmp::"); err == nil {
 		t.Fatalf("Error parsing volume flags, `-v /tmp::` should fail but didn't")
 		t.Fatalf("Error parsing volume flags, `-v /tmp::` should fail but didn't")
 	}
 	}

+ 26 - 0
volume/volume.go

@@ -24,3 +24,29 @@ type Volume interface {
 	// Unmount unmounts the volume when it is no longer in use.
 	// Unmount unmounts the volume when it is no longer in use.
 	Unmount() error
 	Unmount() error
 }
 }
+
+// read-write modes
+var rwModes = map[string]bool{
+	"rw":   true,
+	"rw,Z": true,
+	"rw,z": true,
+	"z,rw": true,
+	"Z,rw": true,
+	"Z":    true,
+	"z":    true,
+}
+
+// read-only modes
+var roModes = map[string]bool{
+	"ro":   true,
+	"ro,Z": true,
+	"ro,z": true,
+	"z,ro": true,
+	"Z,ro": true,
+}
+
+// ValidateMountMode will make sure the mount mode is valid.
+// returns if it's a valid mount mode and if it's read-write or not.
+func ValidateMountMode(mode string) (bool, bool) {
+	return roModes[mode] || rwModes[mode], rwModes[mode]
+}