mirror of
https://github.com/moby/moby.git
synced 2022-11-09 12:21:53 -05:00
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>
This commit is contained in:
parent
9264d38424
commit
dfc6c04fa3
12 changed files with 589 additions and 110 deletions
|
@ -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")
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -4,12 +4,18 @@ import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
/*
|
var (
|
||||||
Read in a line delimited file with environment variables enumerated
|
// 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
|
// trim the front of a variable, but nothing else
|
||||||
variable := strings.TrimLeft(data[0], whiteSpaces)
|
variable := strings.TrimLeft(data[0], whiteSpaces)
|
||||||
if strings.ContainsAny(variable, whiteSpaces) {
|
|
||||||
return []string{}, ErrBadEnvVariable{fmt.Sprintf("variable '%s' has white spaces", variable)}
|
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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
tmpFile := tmpFileWithContent(content, t)
|
||||||
if err != nil {
|
|
||||||
t.Fatal("failed to create test data file")
|
|
||||||
}
|
|
||||||
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("")
|
tmpFile := tmpFileWithContent("", t)
|
||||||
if err != nil {
|
|
||||||
t.Fatal("failed to create test data file")
|
|
||||||
}
|
|
||||||
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)
|
tmpFile := tmpFileWithContent(content, t)
|
||||||
if err != nil {
|
|
||||||
t.Fatal("failed to create test data file")
|
|
||||||
}
|
|
||||||
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)
|
tmpFile := tmpFileWithContent(content, t)
|
||||||
if err != nil {
|
|
||||||
t.Fatal("failed to create test data file")
|
|
||||||
}
|
|
||||||
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())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"net"
|
"net"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// IpOpt type that hold an IP
|
||||||
type IpOpt struct {
|
type IpOpt struct {
|
||||||
*net.IP
|
*net.IP
|
||||||
}
|
}
|
||||||
|
|
54
opts/ip_test.go
Normal file
54
opts/ip_test.go
Normal file
|
@ -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())
|
||||||
|
}
|
||||||
|
}
|
108
opts/opts.go
108
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]`)
|
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*$`)
|
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
|
// 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
|
DefaultHTTPPort = 2375 // Default HTTP Port
|
||||||
DefaultUnixSocket = "/var/run/docker.sock" // Docker daemon by default always listens on the default unix socket
|
// 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) {
|
// DNSSearchListVar Defines a flag with the specified names and usage and put the
|
||||||
flag.Var(newListOptsRef(values, ValidateDnsSearch), names, usage)
|
// 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:
|
// ValidateDevice Validate a path for devices
|
||||||
// [host-dir:]container-path[:rw|ro] - but doesn't validate the mode part
|
// 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)
|
splited := strings.SplitN(val, ":", 3)
|
||||||
if len(splited) == 1 {
|
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])
|
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 {
|
} else {
|
||||||
containerPath = splited[1]
|
containerPath = splited[1]
|
||||||
val = fmt.Sprintf("%s:%s", splited[0], path.Clean(splited[1]))
|
val = fmt.Sprintf("%s:%s", splited[0], path.Clean(containerPath))
|
||||||
|
}
|
||||||
|
case 3:
|
||||||
|
containerPath = 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 {
|
||||||
|
|
|
@ -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]" {
|
if o.String() != "[bar bar]" {
|
||||||
t.Errorf("%s != [bar]", o.String())
|
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 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) {
|
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 == "" {
|
if ret, err := ValidateDNSSearch(domain); err != nil || ret == "" {
|
||||||
t.Fatalf("ValidateDnsSearch(`"+domain+"`) got %s %s", ret, err)
|
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 != "" {
|
if ret, err := ValidateDNSSearch(domain); err == nil || ret != "" {
|
||||||
t.Fatalf("ValidateDnsSearch(`"+domain+"`) got %s %s", ret, err)
|
t.Fatalf("ValidateDNSSearch(`"+domain+"`) got %s %s", ret, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -180,14 +220,251 @@ func TestValidateExtraHosts(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestIpOptString(t *testing.T) {
|
func TestValidateAttach(t *testing.T) {
|
||||||
addresses := []string{"", "0.0.0.0"}
|
valid := []string{
|
||||||
var ip net.IP
|
"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 _, address := range addresses {
|
for _, attach := range valid {
|
||||||
stringAddress := NewIpOpt(&ip, address).String()
|
value, err := ValidateAttach(attach)
|
||||||
if stringAddress != address {
|
if err != nil {
|
||||||
t.Fatalf("IpOpt string should be `%s`, not `%s`", address, stringAddress)
|
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 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
opts/ulimit_test.go
Normal file
42
opts/ulimit_test.go
Normal file
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)
|
||||||
|
|
|
@ -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 {
|
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:roZ -v /hostVar:/containerVar:rwZ` 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: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")
|
||||||
}
|
}
|
||||||
|
|
|
@ -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]
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue