From c6c287cddc398d438c7f1d8893e6a7427ca8ca6c Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Mon, 10 Mar 2014 21:10:23 +0000 Subject: [PATCH 001/123] move opts out of pkg because it's related to docker Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) --- opts/opts.go | 138 ++++++++++++++++++++++++++++++++++++++++++++++ opts/opts_test.go | 24 ++++++++ 2 files changed, 162 insertions(+) create mode 100644 opts/opts.go create mode 100644 opts/opts_test.go diff --git a/opts/opts.go b/opts/opts.go new file mode 100644 index 000000000000..4f5897c796c5 --- /dev/null +++ b/opts/opts.go @@ -0,0 +1,138 @@ +package opts + +import ( + "fmt" + "github.com/dotcloud/docker/utils" + "os" + "path/filepath" + "regexp" + "strings" +) + +// ListOpts type +type ListOpts struct { + values []string + validator ValidatorFctType +} + +func NewListOpts(validator ValidatorFctType) ListOpts { + return ListOpts{ + validator: validator, + } +} + +func (opts *ListOpts) String() string { + return fmt.Sprintf("%v", []string(opts.values)) +} + +// Set validates if needed the input value and add it to the +// internal slice. +func (opts *ListOpts) Set(value string) error { + if opts.validator != nil { + v, err := opts.validator(value) + if err != nil { + return err + } + value = v + } + opts.values = append(opts.values, value) + return nil +} + +// Delete remove the given element from the slice. +func (opts *ListOpts) Delete(key string) { + for i, k := range opts.values { + if k == key { + opts.values = append(opts.values[:i], opts.values[i+1:]...) + return + } + } +} + +// GetMap returns the content of values in a map in order to avoid +// duplicates. +// FIXME: can we remove this? +func (opts *ListOpts) GetMap() map[string]struct{} { + ret := make(map[string]struct{}) + for _, k := range opts.values { + ret[k] = struct{}{} + } + return ret +} + +// GetAll returns the values' slice. +// FIXME: Can we remove this? +func (opts *ListOpts) GetAll() []string { + return opts.values +} + +// Get checks the existence of the given key. +func (opts *ListOpts) Get(key string) bool { + for _, k := range opts.values { + if k == key { + return true + } + } + return false +} + +// Len returns the amount of element in the slice. +func (opts *ListOpts) Len() int { + return len(opts.values) +} + +// Validators +type ValidatorFctType func(val string) (string, error) + +func ValidateAttach(val string) (string, error) { + if val != "stdin" && val != "stdout" && val != "stderr" { + return val, fmt.Errorf("Unsupported stream name: %s", val) + } + return val, nil +} + +func ValidateLink(val string) (string, error) { + if _, err := utils.PartParser("name:alias", val); err != nil { + return val, err + } + return val, nil +} + +func ValidatePath(val string) (string, error) { + var containerPath string + + if strings.Count(val, ":") > 2 { + return val, fmt.Errorf("bad format for volumes: %s", val) + } + + splited := strings.SplitN(val, ":", 2) + if len(splited) == 1 { + containerPath = splited[0] + val = filepath.Clean(splited[0]) + } else { + containerPath = splited[1] + val = fmt.Sprintf("%s:%s", splited[0], filepath.Clean(splited[1])) + } + + if !filepath.IsAbs(containerPath) { + return val, fmt.Errorf("%s is not an absolute path", containerPath) + } + return val, nil +} + +func ValidateEnv(val string) (string, error) { + arr := strings.Split(val, "=") + if len(arr) > 1 { + return val, nil + } + return fmt.Sprintf("%s=%s", val, os.Getenv(val)), nil +} + +func ValidateIp4Address(val string) (string, error) { + re := regexp.MustCompile(`^(([0-9]+\.){3}([0-9]+))\s*$`) + var ns = re.FindSubmatch([]byte(val)) + if len(ns) > 0 { + return string(ns[1]), nil + } + return "", fmt.Errorf("%s is not an ip4 address", val) +} diff --git a/opts/opts_test.go b/opts/opts_test.go new file mode 100644 index 000000000000..a5c1fac9ca9d --- /dev/null +++ b/opts/opts_test.go @@ -0,0 +1,24 @@ +package opts + +import ( + "testing" +) + +func TestValidateIP4(t *testing.T) { + if ret, err := ValidateIp4Address(`1.2.3.4`); err != nil || ret == "" { + t.Fatalf("ValidateIp4Address(`1.2.3.4`) got %s %s", ret, err) + } + + if ret, err := ValidateIp4Address(`127.0.0.1`); err != nil || ret == "" { + t.Fatalf("ValidateIp4Address(`127.0.0.1`) got %s %s", ret, err) + } + + if ret, err := ValidateIp4Address(`127`); err == nil || ret != "" { + t.Fatalf("ValidateIp4Address(`127`) got %s %s", ret, err) + } + + if ret, err := ValidateIp4Address(`random invalid string`); err == nil || ret != "" { + t.Fatalf("ValidateIp4Address(`random invalid string`) got %s %s", ret, err) + } + +} From bdc62769d35783a16dfb0d59012741d44d9f2041 Mon Sep 17 00:00:00 2001 From: Daniel Norberg Date: Fri, 7 Feb 2014 11:48:14 -0500 Subject: [PATCH 002/123] configurable dns search domains Add a --dns-search parameter and a DnsSearch configuration field for specifying dns search domains. Docker-DCO-1.1-Signed-off-by: Daniel Norberg (github: danielnorberg) --- opts/opts.go | 13 ++++++++++++ opts/opts_test.go | 54 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+) diff --git a/opts/opts.go b/opts/opts.go index 4f5897c796c5..b2f21db30b64 100644 --- a/opts/opts.go +++ b/opts/opts.go @@ -136,3 +136,16 @@ func ValidateIp4Address(val string) (string, error) { } return "", fmt.Errorf("%s is not an ip4 address", val) } + +func ValidateDomain(val string) (string, error) { + alpha := regexp.MustCompile(`[a-zA-Z]`) + if alpha.FindString(val) == "" { + return "", fmt.Errorf("%s is not a valid domain", val) + } + re := 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*$`) + var ns = re.FindSubmatch([]byte(val)) + if len(ns) > 0 { + return string(ns[1]), nil + } + return "", fmt.Errorf("%s is not a valid domain", val) +} diff --git a/opts/opts_test.go b/opts/opts_test.go index a5c1fac9ca9d..299cbfe503f3 100644 --- a/opts/opts_test.go +++ b/opts/opts_test.go @@ -22,3 +22,57 @@ func TestValidateIP4(t *testing.T) { } } + +func TestValidateDomain(t *testing.T) { + valid := []string{ + `a`, + `a.`, + `1.foo`, + `17.foo`, + `foo.bar`, + `foo.bar.baz`, + `foo.bar.`, + `foo.bar.baz`, + `foo1.bar2`, + `foo1.bar2.baz`, + `1foo.2bar.`, + `1foo.2bar.baz`, + `foo-1.bar-2`, + `foo-1.bar-2.baz`, + `foo-1.bar-2.`, + `foo-1.bar-2.baz`, + `1-foo.2-bar`, + `1-foo.2-bar.baz`, + `1-foo.2-bar.`, + `1-foo.2-bar.baz`, + } + + invalid := []string{ + ``, + `.`, + `17`, + `17.`, + `.17`, + `17-.`, + `17-.foo`, + `.foo`, + `foo-.bar`, + `-foo.bar`, + `foo.bar-`, + `foo.bar-.baz`, + `foo.-bar`, + `foo.-bar.baz`, + } + + for _, domain := range valid { + if ret, err := ValidateDomain(domain); err != nil || ret == "" { + t.Fatalf("ValidateDomain(`"+domain+"`) got %s %s", ret, err) + } + } + + for _, domain := range invalid { + if ret, err := ValidateDomain(domain); err == nil || ret != "" { + t.Fatalf("ValidateDomain(`"+domain+"`) got %s %s", ret, err) + } + } +} From 5684997e54085fbb1ab16ac3bfe15b6470acd795 Mon Sep 17 00:00:00 2001 From: Daniel Norberg Date: Wed, 19 Mar 2014 16:00:46 -0400 Subject: [PATCH 003/123] variable declaration cleanup Docker-DCO-1.1-Signed-off-by: Daniel Norberg (github: danielnorberg) --- opts/opts.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opts/opts.go b/opts/opts.go index b2f21db30b64..67f1c8fd48eb 100644 --- a/opts/opts.go +++ b/opts/opts.go @@ -143,7 +143,7 @@ func ValidateDomain(val string) (string, error) { return "", fmt.Errorf("%s is not a valid domain", val) } re := 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*$`) - var ns = re.FindSubmatch([]byte(val)) + ns := re.FindSubmatch([]byte(val)) if len(ns) > 0 { return string(ns[1]), nil } From 176347382af4dd78ceeb9476dd3b92439f6876cb Mon Sep 17 00:00:00 2001 From: Vincent Batts Date: Tue, 11 Mar 2014 16:22:58 -0400 Subject: [PATCH 004/123] --env-file: simple line-delimited match dock functionality, and not try to achieve shell-sourcing compatibility Docker-DCO-1.1-Signed-off-by: Vincent Batts (github: vbatts) --- opts/envfile.go | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 opts/envfile.go diff --git a/opts/envfile.go b/opts/envfile.go new file mode 100644 index 000000000000..99a713e761ea --- /dev/null +++ b/opts/envfile.go @@ -0,0 +1,35 @@ +package opts + +import ( + "bufio" + "fmt" + "os" + "strings" +) + +/* +Read in a line delimited file with environment variables enumerated +*/ +func ParseEnvFile(filename string) ([]string, error) { + fh, err := os.Open(filename) + if err != nil { + return []string{}, err + } + defer fh.Close() + + lines := []string{} + scanner := bufio.NewScanner(fh) + for scanner.Scan() { + line := scanner.Text() + // line is not empty, and not starting with '#' + if len(line) > 0 && !strings.HasPrefix(line, "#") { + if strings.Contains(line, "=") { + data := strings.SplitN(line, "=", 2) + lines = append(lines, fmt.Sprintf("%s=%s", data[0], data[1])) + } else { + lines = append(lines, fmt.Sprintf("%s=%s", line, os.Getenv(line))) + } + } + } + return lines, nil +} From 7297bfab3093e526f59b9d006b012c3f85ce087f Mon Sep 17 00:00:00 2001 From: Vincent Batts Date: Mon, 17 Mar 2014 17:11:27 -0400 Subject: [PATCH 005/123] env-file: variable behavior trim the front of variables. Error if there are other spaces present. Leave the value alone. Docker-DCO-1.1-Signed-off-by: Vincent Batts (github: vbatts) --- opts/envfile.go | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/opts/envfile.go b/opts/envfile.go index 99a713e761ea..19ee8955f96a 100644 --- a/opts/envfile.go +++ b/opts/envfile.go @@ -25,11 +25,30 @@ func ParseEnvFile(filename string) ([]string, error) { if len(line) > 0 && !strings.HasPrefix(line, "#") { if strings.Contains(line, "=") { data := strings.SplitN(line, "=", 2) - lines = append(lines, fmt.Sprintf("%s=%s", data[0], data[1])) + + // 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)} + } + + // pass the value through, no trimming + lines = append(lines, fmt.Sprintf("%s=%s", variable, data[1])) } else { - lines = append(lines, fmt.Sprintf("%s=%s", line, os.Getenv(line))) + // if only a pass-through variable is given, clean it up. + lines = append(lines, fmt.Sprintf("%s=%s", strings.TrimSpace(line), os.Getenv(line))) } } } return lines, nil } + +var whiteSpaces = " \t" + +type ErrBadEnvVariable struct { + msg string +} + +func (e ErrBadEnvVariable) Error() string { + return fmt.Sprintf("poorly formatted environment: %s", e.msg) +} From 9fdc86ac55646015a8421645e1cab7b51bc58380 Mon Sep 17 00:00:00 2001 From: Fabio Falci Date: Thu, 26 Jun 2014 12:03:23 +0100 Subject: [PATCH 006/123] Relax dns search to accept empty domain In that case /etc/resolv.conf will be generated with no search option. Usage: --dns-search=. Docker-DCO-1.1-Signed-off-by: Fabio Falci (github: fabiofalci) --- opts/opts.go | 11 ++++++++++- opts/opts_test.go | 14 ++++++++------ 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/opts/opts.go b/opts/opts.go index 67f1c8fd48eb..d17b57e07cc2 100644 --- a/opts/opts.go +++ b/opts/opts.go @@ -137,7 +137,16 @@ func ValidateIp4Address(val string) (string, error) { return "", fmt.Errorf("%s is not an ip4 address", val) } -func ValidateDomain(val string) (string, error) { +// Validates domain for resolvconf search configuration. +// A zero length domain is represented by . +func ValidateDnsSearch(val string) (string, error) { + if val = strings.Trim(val, " "); val == "." { + return val, nil + } + return validateDomain(val) +} + +func validateDomain(val string) (string, error) { alpha := regexp.MustCompile(`[a-zA-Z]`) if alpha.FindString(val) == "" { return "", fmt.Errorf("%s is not a valid domain", val) diff --git a/opts/opts_test.go b/opts/opts_test.go index 299cbfe503f3..b18088b9349c 100644 --- a/opts/opts_test.go +++ b/opts/opts_test.go @@ -23,8 +23,9 @@ func TestValidateIP4(t *testing.T) { } -func TestValidateDomain(t *testing.T) { +func TestValidateDnsSearch(t *testing.T) { valid := []string{ + `.`, `a`, `a.`, `1.foo`, @@ -49,7 +50,8 @@ func TestValidateDomain(t *testing.T) { invalid := []string{ ``, - `.`, + ` `, + ` `, `17`, `17.`, `.17`, @@ -65,14 +67,14 @@ func TestValidateDomain(t *testing.T) { } for _, domain := range valid { - if ret, err := ValidateDomain(domain); err != nil || ret == "" { - t.Fatalf("ValidateDomain(`"+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 { - if ret, err := ValidateDomain(domain); err == nil || ret != "" { - t.Fatalf("ValidateDomain(`"+domain+"`) got %s %s", ret, err) + if ret, err := ValidateDnsSearch(domain); err == nil || ret != "" { + t.Fatalf("ValidateDnsSearch(`"+domain+"`) got %s %s", ret, err) } } } From e56ff6bda9b6be1bdcf6940542a7516e1d607263 Mon Sep 17 00:00:00 2001 From: Jan Pazdziora Date: Fri, 13 Jun 2014 14:02:12 +0200 Subject: [PATCH 007/123] Add support for IPv6 addresses in --dns parameters. Docker-DCO-1.1-Signed-off-by: Jan Pazdziora (github: adelton) --- opts/opts.go | 12 ++++++------ opts/opts_test.go | 20 ++++++++++++-------- 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/opts/opts.go b/opts/opts.go index d17b57e07cc2..b8018c13bf1a 100644 --- a/opts/opts.go +++ b/opts/opts.go @@ -3,6 +3,7 @@ package opts import ( "fmt" "github.com/dotcloud/docker/utils" + "net" "os" "path/filepath" "regexp" @@ -128,13 +129,12 @@ func ValidateEnv(val string) (string, error) { return fmt.Sprintf("%s=%s", val, os.Getenv(val)), nil } -func ValidateIp4Address(val string) (string, error) { - re := regexp.MustCompile(`^(([0-9]+\.){3}([0-9]+))\s*$`) - var ns = re.FindSubmatch([]byte(val)) - if len(ns) > 0 { - return string(ns[1]), nil +func ValidateIpAddress(val string) (string, error) { + var ip = net.ParseIP(strings.TrimSpace(val)) + if ip != nil { + return ip.String(), nil } - return "", fmt.Errorf("%s is not an ip4 address", val) + return "", fmt.Errorf("%s is not an ip address", val) } // Validates domain for resolvconf search configuration. diff --git a/opts/opts_test.go b/opts/opts_test.go index b18088b9349c..4d37fcaf3a7e 100644 --- a/opts/opts_test.go +++ b/opts/opts_test.go @@ -5,20 +5,24 @@ import ( ) func TestValidateIP4(t *testing.T) { - if ret, err := ValidateIp4Address(`1.2.3.4`); err != nil || ret == "" { - t.Fatalf("ValidateIp4Address(`1.2.3.4`) got %s %s", ret, err) + if ret, err := ValidateIpAddress(`1.2.3.4`); err != nil || ret == "" { + t.Fatalf("ValidateIpAddress(`1.2.3.4`) got %s %s", ret, err) } - if ret, err := ValidateIp4Address(`127.0.0.1`); err != nil || ret == "" { - t.Fatalf("ValidateIp4Address(`127.0.0.1`) got %s %s", ret, err) + if ret, err := ValidateIpAddress(`127.0.0.1`); err != nil || ret == "" { + t.Fatalf("ValidateIpAddress(`127.0.0.1`) got %s %s", ret, err) } - if ret, err := ValidateIp4Address(`127`); err == nil || ret != "" { - t.Fatalf("ValidateIp4Address(`127`) got %s %s", ret, err) + if ret, err := ValidateIpAddress(`::1`); err != nil || ret == "" { + t.Fatalf("ValidateIpAddress(`::1`) got %s %s", ret, err) } - if ret, err := ValidateIp4Address(`random invalid string`); err == nil || ret != "" { - t.Fatalf("ValidateIp4Address(`random invalid string`) got %s %s", ret, err) + if ret, err := ValidateIpAddress(`127`); err == nil || ret != "" { + t.Fatalf("ValidateIpAddress(`127`) got %s %s", ret, err) + } + + if ret, err := ValidateIpAddress(`random invalid string`); err == nil || ret != "" { + t.Fatalf("ValidateIpAddress(`random invalid string`) got %s %s", ret, err) } } From 84c187fe0cf38b452902518fde172597a8562c71 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Wed, 9 Jul 2014 21:47:55 +0000 Subject: [PATCH 008/123] update for consistency Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) --- opts/opts.go | 5 +++-- opts/opts_test.go | 22 +++++++++++----------- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/opts/opts.go b/opts/opts.go index b8018c13bf1a..b3ceeffda603 100644 --- a/opts/opts.go +++ b/opts/opts.go @@ -2,12 +2,13 @@ package opts import ( "fmt" - "github.com/dotcloud/docker/utils" "net" "os" "path/filepath" "regexp" "strings" + + "github.com/dotcloud/docker/utils" ) // ListOpts type @@ -129,7 +130,7 @@ func ValidateEnv(val string) (string, error) { return fmt.Sprintf("%s=%s", val, os.Getenv(val)), nil } -func ValidateIpAddress(val string) (string, error) { +func ValidateIPAddress(val string) (string, error) { var ip = net.ParseIP(strings.TrimSpace(val)) if ip != nil { return ip.String(), nil diff --git a/opts/opts_test.go b/opts/opts_test.go index 4d37fcaf3a7e..9494e27a754e 100644 --- a/opts/opts_test.go +++ b/opts/opts_test.go @@ -4,25 +4,25 @@ import ( "testing" ) -func TestValidateIP4(t *testing.T) { - if ret, err := ValidateIpAddress(`1.2.3.4`); err != nil || ret == "" { - t.Fatalf("ValidateIpAddress(`1.2.3.4`) got %s %s", ret, err) +func TestValidateIPAddress(t *testing.T) { + if ret, err := ValidateIPAddress(`1.2.3.4`); err != nil || ret == "" { + t.Fatalf("ValidateIPAddress(`1.2.3.4`) got %s %s", ret, err) } - if ret, err := ValidateIpAddress(`127.0.0.1`); err != nil || ret == "" { - t.Fatalf("ValidateIpAddress(`127.0.0.1`) got %s %s", ret, err) + if ret, err := ValidateIPAddress(`127.0.0.1`); err != nil || ret == "" { + t.Fatalf("ValidateIPAddress(`127.0.0.1`) got %s %s", ret, err) } - if ret, err := ValidateIpAddress(`::1`); err != nil || ret == "" { - t.Fatalf("ValidateIpAddress(`::1`) got %s %s", ret, err) + if ret, err := ValidateIPAddress(`::1`); err != nil || ret == "" { + t.Fatalf("ValidateIPAddress(`::1`) got %s %s", ret, err) } - if ret, err := ValidateIpAddress(`127`); err == nil || ret != "" { - t.Fatalf("ValidateIpAddress(`127`) got %s %s", ret, err) + if ret, err := ValidateIPAddress(`127`); err == nil || ret != "" { + t.Fatalf("ValidateIPAddress(`127`) got %s %s", ret, err) } - if ret, err := ValidateIpAddress(`random invalid string`); err == nil || ret != "" { - t.Fatalf("ValidateIpAddress(`random invalid string`) got %s %s", ret, err) + if ret, err := ValidateIPAddress(`random invalid string`); err == nil || ret != "" { + t.Fatalf("ValidateIPAddress(`random invalid string`) got %s %s", ret, err) } } From 1ed63a0f852063b8ee7a62bcb8e5377525a045e2 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Thu, 24 Jul 2014 22:19:50 +0000 Subject: [PATCH 009/123] update go import path and libcontainer Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) --- opts/opts.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opts/opts.go b/opts/opts.go index b3ceeffda603..434ea037221e 100644 --- a/opts/opts.go +++ b/opts/opts.go @@ -8,7 +8,7 @@ import ( "regexp" "strings" - "github.com/dotcloud/docker/utils" + "github.com/docker/docker/utils" ) // ListOpts type From 29adea2e423c4c41f3440ce205d09dafca94f422 Mon Sep 17 00:00:00 2001 From: Erik Hollensbe Date: Mon, 28 Jul 2014 17:23:38 -0700 Subject: [PATCH 010/123] Move parsing functions to pkg/parsers and the specific kernel handling functions to pkg/parsers/kernel, and parsing filters to pkg/parsers/filter. Adjust imports and package references. Docker-DCO-1.1-Signed-off-by: Erik Hollensbe (github: erikh) --- opts/opts.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/opts/opts.go b/opts/opts.go index 434ea037221e..43bef370684d 100644 --- a/opts/opts.go +++ b/opts/opts.go @@ -8,7 +8,7 @@ import ( "regexp" "strings" - "github.com/docker/docker/utils" + "github.com/docker/docker/pkg/parsers" ) // ListOpts type @@ -94,7 +94,7 @@ func ValidateAttach(val string) (string, error) { } func ValidateLink(val string) (string, error) { - if _, err := utils.PartParser("name:alias", val); err != nil { + if _, err := parsers.PartParser("name:alias", val); err != nil { return val, err } return val, nil From 67a518b70c76c9d55b0f551f76d3c999f05377d1 Mon Sep 17 00:00:00 2001 From: Tibor Vass Date: Wed, 30 Jul 2014 10:20:52 -0400 Subject: [PATCH 011/123] Make --attach case-insensitive Docker-DCO-1.1-Signed-off-by: Tibor Vass (github: tiborvass) --- opts/opts.go | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/opts/opts.go b/opts/opts.go index 43bef370684d..bfee2ad21741 100644 --- a/opts/opts.go +++ b/opts/opts.go @@ -87,10 +87,13 @@ func (opts *ListOpts) Len() int { type ValidatorFctType func(val string) (string, error) func ValidateAttach(val string) (string, error) { - if val != "stdin" && val != "stdout" && val != "stderr" { - return val, fmt.Errorf("Unsupported stream name: %s", val) + s := strings.ToLower(val) + for _, str := range []string{"stdin", "stdout", "stderr"} { + if s == str { + return s, nil + } } - return val, nil + return val, fmt.Errorf("valid streams are STDIN, STDOUT and STDERR.") } func ValidateLink(val string) (string, error) { From f34ca0a35408b066da00ab461f2fc6b963be9714 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Sun, 10 Aug 2014 01:12:52 +0000 Subject: [PATCH 012/123] opts.IpOpt: a helper to parse IP addressed from the command line Signed-off-by: Solomon Hykes --- opts/ip.go | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 opts/ip.go diff --git a/opts/ip.go b/opts/ip.go new file mode 100644 index 000000000000..3610b1453878 --- /dev/null +++ b/opts/ip.go @@ -0,0 +1,28 @@ +package opts + +import ( + "net" +) + +type IpOpt struct { + *net.IP +} + +func NewIpOpt(ref *net.IP, defaultVal string) *IpOpt { + o := &IpOpt{ + IP: ref, + } + o.Set(defaultVal) + return o +} + +func (o *IpOpt) Set(val string) error { + // FIXME: return a parse error if the value is not a valid IP? + // We are not changing this now to preserve behavior while refactoring. + (*o.IP) = net.ParseIP(val) + return nil +} + +func (o *IpOpt) String() string { + return (*o.IP).String() +} From a6487884e5baa88c52b00c931340eaa0014d3049 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Sun, 10 Aug 2014 01:13:44 +0000 Subject: [PATCH 013/123] Helpers to parse lists, IPs, hosts, dns searches from the command line Signed-off-by: Solomon Hykes --- opts/opts.go | 48 +++++++++++++++++++++++++++++++++++++---------- opts/opts_test.go | 6 ++++++ 2 files changed, 44 insertions(+), 10 deletions(-) diff --git a/opts/opts.go b/opts/opts.go index bfee2ad21741..65806f369842 100644 --- a/opts/opts.go +++ b/opts/opts.go @@ -8,23 +8,51 @@ import ( "regexp" "strings" + "github.com/docker/docker/api" + flag "github.com/docker/docker/pkg/mflag" "github.com/docker/docker/pkg/parsers" ) +func ListVar(values *[]string, names []string, usage string) { + flag.Var(newListOptsRef(values, nil), names, usage) +} + +func HostListVar(values *[]string, names []string, usage string) { + flag.Var(newListOptsRef(values, api.ValidateHost), names, usage) +} + +func IPListVar(values *[]string, names []string, usage string) { + flag.Var(newListOptsRef(values, ValidateIPAddress), names, usage) +} + +func DnsSearchListVar(values *[]string, names []string, usage string) { + flag.Var(newListOptsRef(values, ValidateDnsSearch), names, usage) +} + +func IPVar(value *net.IP, names []string, defaultValue, usage string) { + flag.Var(NewIpOpt(value, defaultValue), names, usage) +} + // ListOpts type type ListOpts struct { - values []string + values *[]string validator ValidatorFctType } func NewListOpts(validator ValidatorFctType) ListOpts { - return ListOpts{ + var values []string + return *newListOptsRef(&values, validator) +} + +func newListOptsRef(values *[]string, validator ValidatorFctType) *ListOpts { + return &ListOpts{ + values: values, validator: validator, } } func (opts *ListOpts) String() string { - return fmt.Sprintf("%v", []string(opts.values)) + return fmt.Sprintf("%v", []string((*opts.values))) } // Set validates if needed the input value and add it to the @@ -37,15 +65,15 @@ func (opts *ListOpts) Set(value string) error { } value = v } - opts.values = append(opts.values, value) + (*opts.values) = append((*opts.values), value) return nil } // Delete remove the given element from the slice. func (opts *ListOpts) Delete(key string) { - for i, k := range opts.values { + for i, k := range *opts.values { if k == key { - opts.values = append(opts.values[:i], opts.values[i+1:]...) + (*opts.values) = append((*opts.values)[:i], (*opts.values)[i+1:]...) return } } @@ -56,7 +84,7 @@ func (opts *ListOpts) Delete(key string) { // FIXME: can we remove this? func (opts *ListOpts) GetMap() map[string]struct{} { ret := make(map[string]struct{}) - for _, k := range opts.values { + for _, k := range *opts.values { ret[k] = struct{}{} } return ret @@ -65,12 +93,12 @@ func (opts *ListOpts) GetMap() map[string]struct{} { // GetAll returns the values' slice. // FIXME: Can we remove this? func (opts *ListOpts) GetAll() []string { - return opts.values + return (*opts.values) } // Get checks the existence of the given key. func (opts *ListOpts) Get(key string) bool { - for _, k := range opts.values { + for _, k := range *opts.values { if k == key { return true } @@ -80,7 +108,7 @@ func (opts *ListOpts) Get(key string) bool { // Len returns the amount of element in the slice. func (opts *ListOpts) Len() int { - return len(opts.values) + return len((*opts.values)) } // Validators diff --git a/opts/opts_test.go b/opts/opts_test.go index 9494e27a754e..09b5aa780b29 100644 --- a/opts/opts_test.go +++ b/opts/opts_test.go @@ -27,6 +27,12 @@ func TestValidateIPAddress(t *testing.T) { } +func TestListOpts(t *testing.T) { + o := NewListOpts(nil) + o.Set("foo") + o.String() +} + func TestValidateDnsSearch(t *testing.T) { valid := []string{ `.`, From 1edb726c0bdc3110e3962bcd845bc106de860515 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Sun, 10 Aug 2014 03:50:46 +0000 Subject: [PATCH 014/123] opts.IPVal returns an error on incorrect input Signed-off-by: Solomon Hykes --- opts/ip.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/opts/ip.go b/opts/ip.go index 3610b1453878..f0c29750bce4 100644 --- a/opts/ip.go +++ b/opts/ip.go @@ -1,6 +1,7 @@ package opts import ( + "fmt" "net" ) @@ -17,8 +18,10 @@ func NewIpOpt(ref *net.IP, defaultVal string) *IpOpt { } func (o *IpOpt) Set(val string) error { - // FIXME: return a parse error if the value is not a valid IP? - // We are not changing this now to preserve behavior while refactoring. + ip := net.ParseIP(val) + if ip == nil { + return fmt.Errorf("incorrect IP format") + } (*o.IP) = net.ParseIP(val) return nil } From 561b98067f90326cc3c1d73cab6836a2e50728cd Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Wed, 13 Aug 2014 06:45:40 +0000 Subject: [PATCH 015/123] Fix inconsistency in IP address parsing errors Signed-off-by: Solomon Hykes --- opts/ip.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opts/ip.go b/opts/ip.go index f0c29750bce4..f8a493e68a63 100644 --- a/opts/ip.go +++ b/opts/ip.go @@ -20,7 +20,7 @@ func NewIpOpt(ref *net.IP, defaultVal string) *IpOpt { func (o *IpOpt) Set(val string) error { ip := net.ParseIP(val) if ip == nil { - return fmt.Errorf("incorrect IP format") + return fmt.Errorf("%s is not an ip address", val) } (*o.IP) = net.ParseIP(val) return nil From cc0954586a520b00f6de8a45bd69c26f15ef9c83 Mon Sep 17 00:00:00 2001 From: Tim Smith Date: Fri, 18 Jul 2014 18:48:19 +0000 Subject: [PATCH 016/123] Add daemon flag to specify public registry mirrors Adds support for a --registry-mirror=scheme://[:port] daemon flag. The flag may be present multiple times. If provided, mirrors are prepended to the list of endpoints used for image pull. Note that only mirrors of the public index.docker.io registry are supported, and image/tag resolution is still performed via the official index. Docker-DCO-1.1-Signed-off-by: Tim Smith (github: timbot) --- opts/opts.go | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/opts/opts.go b/opts/opts.go index 65806f369842..0ed0f62bcd41 100644 --- a/opts/opts.go +++ b/opts/opts.go @@ -3,6 +3,7 @@ package opts import ( "fmt" "net" + "net/url" "os" "path/filepath" "regexp" @@ -33,6 +34,10 @@ func IPVar(value *net.IP, names []string, defaultValue, usage string) { flag.Var(NewIpOpt(value, defaultValue), names, usage) } +func MirrorListVar(values *[]string, names []string, usage string) { + flag.Var(newListOptsRef(values, ValidateMirror), names, usage) +} + // ListOpts type type ListOpts struct { values *[]string @@ -190,3 +195,21 @@ func validateDomain(val string) (string, error) { } return "", fmt.Errorf("%s is not a valid domain", val) } + +// Validates an HTTP(S) registry mirror +func ValidateMirror(val string) (string, error) { + uri, err := url.Parse(val) + if err != nil { + return "", fmt.Errorf("%s is not a valid URI", val) + } + + if uri.Scheme != "http" && uri.Scheme != "https" { + return "", fmt.Errorf("Unsupported scheme %s", uri.Scheme) + } + + if uri.Path != "" || uri.RawQuery != "" || uri.Fragment != "" { + return "", fmt.Errorf("Unsupported path/query/fragment at end of the URI") + } + + return fmt.Sprintf("%s://%s/v1/", uri.Scheme, uri.Host), nil +} From 8b27eee0f0229f5eb10bcfc83883279548160d0a Mon Sep 17 00:00:00 2001 From: Phil Estes Date: Mon, 15 Sep 2014 23:30:10 -0400 Subject: [PATCH 017/123] Refactor all pre-compiled regexp to package level vars Addresses #8057 Docker-DCO-1.1-Signed-off-by: Phil Estes --- opts/opts.go | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/opts/opts.go b/opts/opts.go index 0ed0f62bcd41..f57ef8b6d18d 100644 --- a/opts/opts.go +++ b/opts/opts.go @@ -14,6 +14,11 @@ import ( "github.com/docker/docker/pkg/parsers" ) +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*$`) +) + func ListVar(values *[]string, names []string, usage string) { flag.Var(newListOptsRef(values, nil), names, usage) } @@ -184,12 +189,10 @@ func ValidateDnsSearch(val string) (string, error) { } func validateDomain(val string) (string, error) { - alpha := regexp.MustCompile(`[a-zA-Z]`) - if alpha.FindString(val) == "" { + if alphaRegexp.FindString(val) == "" { return "", fmt.Errorf("%s is not a valid domain", val) } - re := 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*$`) - ns := re.FindSubmatch([]byte(val)) + ns := domainRegexp.FindSubmatch([]byte(val)) if len(ns) > 0 { return string(ns[1]), nil } From 4731b1ebc867755333a21525630bf002cf48e74f Mon Sep 17 00:00:00 2001 From: Tim Hockin Date: Sat, 13 Sep 2014 04:35:59 +0000 Subject: [PATCH 018/123] Allow extra lines in /etc/hosts This adds a --add-host host:ip flag which appends lines to /etc/hosts. This is needed in places where you want the container to get a different name resolution than it would through DNS. This was submitted before as #5525, closed, and now I am re-opening. It has come up 2 or 3 times in the last couple days. Signed-off-by: Tim Hockin --- opts/opts.go | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/opts/opts.go b/opts/opts.go index f57ef8b6d18d..4ca7ec58ce0d 100644 --- a/opts/opts.go +++ b/opts/opts.go @@ -199,6 +199,17 @@ func validateDomain(val string) (string, error) { return "", fmt.Errorf("%s is not a valid domain", val) } +func ValidateExtraHost(val string) (string, error) { + arr := strings.Split(val, ":") + if len(arr) != 2 || len(arr[0]) == 0 { + return "", fmt.Errorf("bad format for add-host: %s", val) + } + if _, err := ValidateIPAddress(arr[1]); err != nil { + return "", fmt.Errorf("bad format for add-host: %s", val) + } + return val, nil +} + // Validates an HTTP(S) registry mirror func ValidateMirror(val string) (string, error) { uri, err := url.Parse(val) From bcae148da2094e44efcc9cd5dac73014ad2d413c Mon Sep 17 00:00:00 2001 From: Ahmet Alp Balkan Date: Wed, 29 Oct 2014 15:46:45 -0700 Subject: [PATCH 019/123] Fix input volume path check on Windows used path package instead of path/filepath so that --volumes and --device parameters to always validate paths as unix paths instead of OS-dependent path convention Signed-off-by: Ahmet Alp Balkan --- opts/opts.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/opts/opts.go b/opts/opts.go index 4ca7ec58ce0d..d3202969b464 100644 --- a/opts/opts.go +++ b/opts/opts.go @@ -5,7 +5,7 @@ import ( "net" "net/url" "os" - "path/filepath" + "path" "regexp" "strings" @@ -151,13 +151,13 @@ func ValidatePath(val string) (string, error) { splited := strings.SplitN(val, ":", 2) if len(splited) == 1 { containerPath = splited[0] - val = filepath.Clean(splited[0]) + val = path.Clean(splited[0]) } else { containerPath = splited[1] - val = fmt.Sprintf("%s:%s", splited[0], filepath.Clean(splited[1])) + val = fmt.Sprintf("%s:%s", splited[0], path.Clean(splited[1])) } - if !filepath.IsAbs(containerPath) { + if !path.IsAbs(containerPath) { return val, fmt.Errorf("%s is not an absolute path", containerPath) } return val, nil From 1eda63e7e4ac69c4fe011f61ff8b79ef0ba4ba29 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Thu, 20 Nov 2014 18:36:05 +0000 Subject: [PATCH 020/123] add daemon labels Signed-off-by: Victor Vieux --- opts/opts.go | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/opts/opts.go b/opts/opts.go index d3202969b464..f15064ac69db 100644 --- a/opts/opts.go +++ b/opts/opts.go @@ -43,6 +43,10 @@ func MirrorListVar(values *[]string, names []string, usage string) { flag.Var(newListOptsRef(values, ValidateMirror), names, usage) } +func LabelListVar(values *[]string, names []string, usage string) { + flag.Var(newListOptsRef(values, ValidateLabel), names, usage) +} + // ListOpts type type ListOpts struct { values *[]string @@ -227,3 +231,10 @@ func ValidateMirror(val string) (string, error) { return fmt.Sprintf("%s://%s/v1/", uri.Scheme, uri.Host), nil } + +func ValidateLabel(val string) (string, error) { + if strings.Count(val, "=") != 1 { + return "", fmt.Errorf("bad attribute format: %s", val) + } + return val, nil +} From 4440f5aa3f164bced82146ba1c19122b2b958a1f Mon Sep 17 00:00:00 2001 From: Don Kjer Date: Tue, 7 Oct 2014 01:54:52 +0000 Subject: [PATCH 021/123] Deprecating ResolveRepositoryName Passing RepositoryInfo to ResolveAuthConfig, pullRepository, and pushRepository Moving --registry-mirror configuration to registry config Created resolve_repository job Repo names with 'index.docker.io' or 'docker.io' are now synonymous with omitting an index name. Adding test for RepositoryInfo Adding tests for opts.StringSetOpts and registry.ValidateMirror Fixing search term use of repoInfo Adding integration tests for registry mirror configuration Normalizing LookupImage image name to match LocalName parsing rules Normalizing repository LocalName to avoid multiple references to an official image Removing errorOut use in tests Removing TODO comment gofmt changes golint comments cleanup. renaming RegistryOptions => registry.Options, and RegistryServiceConfig => registry.ServiceConfig Splitting out builtins.Registry and registry.NewService calls Stray whitespace cleanup Moving integration tests for Mirrors and InsecureRegistries into TestNewIndexInfo unit test Factoring out ValidateRepositoryName from NewRepositoryInfo Removing unused IndexServerURL Allowing json marshaling of ServiceConfig. Exposing ServiceConfig in /info Switching to CamelCase for json marshaling PR cleanup; removing 'Is' prefix from boolean members. Removing unneeded json tags. Removing non-cleanup related fix for 'localhost:[port]' in splitReposName Merge fixes for gh9735 Fixing integration test Reapplying #9754 Adding comment on config.IndexConfigs use from isSecureIndex Remove unused error return value from isSecureIndex Signed-off-by: Don Kjer Adding back comment in isSecureIndex Signed-off-by: Don Kjer --- opts/opts.go | 24 +----------------------- opts/opts_test.go | 18 +++++++++++++++++- 2 files changed, 18 insertions(+), 24 deletions(-) diff --git a/opts/opts.go b/opts/opts.go index f15064ac69db..3d8c23ff77df 100644 --- a/opts/opts.go +++ b/opts/opts.go @@ -3,7 +3,6 @@ package opts import ( "fmt" "net" - "net/url" "os" "path" "regexp" @@ -39,10 +38,6 @@ func IPVar(value *net.IP, names []string, defaultValue, usage string) { flag.Var(NewIpOpt(value, defaultValue), names, usage) } -func MirrorListVar(values *[]string, names []string, usage string) { - flag.Var(newListOptsRef(values, ValidateMirror), names, usage) -} - func LabelListVar(values *[]string, names []string, usage string) { flag.Var(newListOptsRef(values, ValidateLabel), names, usage) } @@ -127,6 +122,7 @@ func (opts *ListOpts) Len() int { // Validators type ValidatorFctType func(val string) (string, error) +type ValidatorFctListType func(val string) ([]string, error) func ValidateAttach(val string) (string, error) { s := strings.ToLower(val) @@ -214,24 +210,6 @@ func ValidateExtraHost(val string) (string, error) { return val, nil } -// Validates an HTTP(S) registry mirror -func ValidateMirror(val string) (string, error) { - uri, err := url.Parse(val) - if err != nil { - return "", fmt.Errorf("%s is not a valid URI", val) - } - - if uri.Scheme != "http" && uri.Scheme != "https" { - return "", fmt.Errorf("Unsupported scheme %s", uri.Scheme) - } - - if uri.Path != "" || uri.RawQuery != "" || uri.Fragment != "" { - return "", fmt.Errorf("Unsupported path/query/fragment at end of the URI") - } - - return fmt.Sprintf("%s://%s/v1/", uri.Scheme, uri.Host), nil -} - func ValidateLabel(val string) (string, error) { if strings.Count(val, "=") != 1 { return "", fmt.Errorf("bad attribute format: %s", val) diff --git a/opts/opts_test.go b/opts/opts_test.go index 09b5aa780b29..e813c443262c 100644 --- a/opts/opts_test.go +++ b/opts/opts_test.go @@ -30,7 +30,23 @@ func TestValidateIPAddress(t *testing.T) { func TestListOpts(t *testing.T) { o := NewListOpts(nil) o.Set("foo") - o.String() + if o.String() != "[foo]" { + t.Errorf("%s != [foo]", o.String()) + } + o.Set("bar") + if o.Len() != 2 { + t.Errorf("%d != 2", o.Len()) + } + if !o.Get("bar") { + t.Error("o.Get(\"bar\") == false") + } + if o.Get("baz") { + t.Error("o.Get(\"baz\") == true") + } + o.Delete("foo") + if o.String() != "[bar]" { + t.Errorf("%s != [bar]", o.String()) + } } func TestValidateDnsSearch(t *testing.T) { From 67735d2a1674293bf43acc34c0bb789218874573 Mon Sep 17 00:00:00 2001 From: Doug Davis Date: Fri, 16 Jan 2015 12:57:08 -0800 Subject: [PATCH 022/123] Docker run -e FOO should erase FOO if FOO isn't set in client env See #10141 for more info, but the main point of this is to make sure that if you do "docker run -e FOO ..." that FOO from the current env is passed into the container. This means that if there's a value, its set. But it also means that if FOO isn't set then it should be unset in the container too - even if it has to remove it from the env. So, unset HOSTNAME docker run -e HOSTNAME busybox env should _NOT_ show HOSTNAME in the list at all Closes #10141 Signed-off-by: Doug Davis --- opts/opts.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/opts/opts.go b/opts/opts.go index 3d8c23ff77df..7f401934128b 100644 --- a/opts/opts.go +++ b/opts/opts.go @@ -11,6 +11,7 @@ import ( "github.com/docker/docker/api" flag "github.com/docker/docker/pkg/mflag" "github.com/docker/docker/pkg/parsers" + "github.com/docker/docker/utils" ) var ( @@ -168,6 +169,9 @@ func ValidateEnv(val string) (string, error) { if len(arr) > 1 { return val, nil } + if !utils.DoesEnvExist(val) { + return val, nil + } return fmt.Sprintf("%s=%s", val, os.Getenv(val)), nil } From d656c63b21076546d0a74fff11b0fa941ad0c094 Mon Sep 17 00:00:00 2001 From: Phil Estes Date: Wed, 4 Feb 2015 10:20:28 -0500 Subject: [PATCH 023/123] Allow IPv6 addresses in ExtraHosts option settings Since the separator for extra host settings (for /etc/hosts in a container) is a ":", the code that handles extra hosts needed to only split on the first ":" to preserve IPv6 addresses which are passed via the command line settings as well as stored in the JSON container config. Docker-DCO-1.1-Signed-off-by: Phil Estes (github: estesp) --- opts/opts.go | 3 ++- opts/opts_test.go | 28 ++++++++++++++++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/opts/opts.go b/opts/opts.go index 7f401934128b..1b57b7753642 100644 --- a/opts/opts.go +++ b/opts/opts.go @@ -204,7 +204,8 @@ func validateDomain(val string) (string, error) { } func ValidateExtraHost(val string) (string, error) { - arr := strings.Split(val, ":") + // allow for IPv6 addresses in extra hosts by only splitting on first ":" + arr := strings.SplitN(val, ":", 2) if len(arr) != 2 || len(arr[0]) == 0 { return "", fmt.Errorf("bad format for add-host: %s", val) } diff --git a/opts/opts_test.go b/opts/opts_test.go index e813c443262c..68a715567c1f 100644 --- a/opts/opts_test.go +++ b/opts/opts_test.go @@ -104,3 +104,31 @@ func TestValidateDnsSearch(t *testing.T) { } } } + +func TestValidateExtraHosts(t *testing.T) { + valid := []string{ + `myhost:192.168.0.1`, + `thathost:10.0.2.1`, + `anipv6host:2003:ab34:e::1`, + `ipv6local:::1`, + } + + invalid := []string{ + `myhost:192.notanipaddress.1`, + `thathost-nosemicolon10.0.0.1`, + `anipv6host:::::1`, + `ipv6local:::0::`, + } + + for _, extrahost := range valid { + if _, err := ValidateExtraHost(extrahost); err != nil { + t.Fatalf("ValidateExtraHost(`"+extrahost+"`) should succeed: error %v", err) + } + } + + for _, extrahost := range invalid { + if _, err := ValidateExtraHost(extrahost); err == nil { + t.Fatalf("ValidateExtraHost(`" + extrahost + "`) should have failed validation") + } + } +} From 196bc32878cdf6bfc6c432c61b06785c19580e27 Mon Sep 17 00:00:00 2001 From: Phil Estes Date: Mon, 9 Feb 2015 09:59:05 -0500 Subject: [PATCH 024/123] Add more helpful error message for -add-host Fixes: #10655 As noted in the issue, bad format was being returned even if the format was appropriate, but the IP was invalid. This adds a better error message for when the IP address fails validation. Docker-DCO-1.1-Signed-off-by: Phil Estes (github: estesp) --- opts/opts.go | 4 ++-- opts/opts_test.go | 21 +++++++++++++-------- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/opts/opts.go b/opts/opts.go index 1b57b7753642..e2596983cf53 100644 --- a/opts/opts.go +++ b/opts/opts.go @@ -207,10 +207,10 @@ func ValidateExtraHost(val string) (string, error) { // allow for IPv6 addresses in extra hosts by only splitting on first ":" arr := strings.SplitN(val, ":", 2) if len(arr) != 2 || len(arr[0]) == 0 { - return "", fmt.Errorf("bad format for add-host: %s", val) + return "", fmt.Errorf("bad format for add-host: %q", val) } if _, err := ValidateIPAddress(arr[1]); err != nil { - return "", fmt.Errorf("bad format for add-host: %s", val) + return "", fmt.Errorf("invalid IP address in add-host: %q", arr[1]) } return val, nil } diff --git a/opts/opts_test.go b/opts/opts_test.go index 68a715567c1f..0f6bc4c28dcb 100644 --- a/opts/opts_test.go +++ b/opts/opts_test.go @@ -1,6 +1,7 @@ package opts import ( + "strings" "testing" ) @@ -113,11 +114,11 @@ func TestValidateExtraHosts(t *testing.T) { `ipv6local:::1`, } - invalid := []string{ - `myhost:192.notanipaddress.1`, - `thathost-nosemicolon10.0.0.1`, - `anipv6host:::::1`, - `ipv6local:::0::`, + invalid := map[string]string{ + `myhost:192.notanipaddress.1`: `invalid IP`, + `thathost-nosemicolon10.0.0.1`: `bad format`, + `anipv6host:::::1`: `invalid IP`, + `ipv6local:::0::`: `invalid IP`, } for _, extrahost := range valid { @@ -126,9 +127,13 @@ func TestValidateExtraHosts(t *testing.T) { } } - for _, extrahost := range invalid { - if _, err := ValidateExtraHost(extrahost); err == nil { - t.Fatalf("ValidateExtraHost(`" + extrahost + "`) should have failed validation") + for extraHost, expectedError := range invalid { + if _, err := ValidateExtraHost(extraHost); err == nil { + t.Fatalf("ValidateExtraHost(`%q`) should have failed validation", extraHost) + } else { + if !strings.Contains(err.Error(), expectedError) { + t.Fatalf("ValidateExtraHost(`%q`) error should contain %q", extraHost, expectedError) + } } } } From 473eb9a2c575f88578b674661e48ce2154eb6023 Mon Sep 17 00:00:00 2001 From: Brian Goff Date: Wed, 11 Feb 2015 14:21:38 -0500 Subject: [PATCH 025/123] Allow setting ulimits for containers Signed-off-by: Brian Goff --- opts/opts.go | 5 +++++ opts/ulimit.go | 44 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+) create mode 100644 opts/ulimit.go diff --git a/opts/opts.go b/opts/opts.go index e2596983cf53..6b42fa687118 100644 --- a/opts/opts.go +++ b/opts/opts.go @@ -11,6 +11,7 @@ import ( "github.com/docker/docker/api" flag "github.com/docker/docker/pkg/mflag" "github.com/docker/docker/pkg/parsers" + "github.com/docker/docker/pkg/ulimit" "github.com/docker/docker/utils" ) @@ -43,6 +44,10 @@ func LabelListVar(values *[]string, names []string, usage string) { flag.Var(newListOptsRef(values, ValidateLabel), names, usage) } +func UlimitMapVar(values map[string]*ulimit.Ulimit, names []string, usage string) { + flag.Var(NewUlimitOpt(values), names, usage) +} + // ListOpts type type ListOpts struct { values *[]string diff --git a/opts/ulimit.go b/opts/ulimit.go new file mode 100644 index 000000000000..361eadf220e2 --- /dev/null +++ b/opts/ulimit.go @@ -0,0 +1,44 @@ +package opts + +import ( + "fmt" + + "github.com/docker/docker/pkg/ulimit" +) + +type UlimitOpt struct { + values map[string]*ulimit.Ulimit +} + +func NewUlimitOpt(ref map[string]*ulimit.Ulimit) *UlimitOpt { + return &UlimitOpt{ref} +} + +func (o *UlimitOpt) Set(val string) error { + l, err := ulimit.Parse(val) + if err != nil { + return err + } + + o.values[l.Name] = l + + return nil +} + +func (o *UlimitOpt) String() string { + var out []string + for _, v := range o.values { + out = append(out, v.String()) + } + + return fmt.Sprintf("%v", out) +} + +func (o *UlimitOpt) GetList() []*ulimit.Ulimit { + var ulimits []*ulimit.Ulimit + for _, v := range o.values { + ulimits = append(ulimits, v) + } + + return ulimits +} From 8972795de7653aea732bfe097b1ae7aed1e904a1 Mon Sep 17 00:00:00 2001 From: Lei Jitang Date: Fri, 27 Feb 2015 07:27:12 -0800 Subject: [PATCH 026/123] Add validate the input mac address on docker run command Signed-off-by: Lei Jitang --- opts/opts.go | 9 +++++++++ opts/opts_test.go | 14 ++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/opts/opts.go b/opts/opts.go index 6b42fa687118..cd720c9a928a 100644 --- a/opts/opts.go +++ b/opts/opts.go @@ -188,6 +188,15 @@ func ValidateIPAddress(val string) (string, error) { return "", fmt.Errorf("%s is not an ip address", val) } +func ValidateMACAddress(val string) (string, error) { + _, err := net.ParseMAC(strings.TrimSpace(val)) + if err != nil { + return "", err + } else { + return val, nil + } +} + // Validates domain for resolvconf search configuration. // A zero length domain is represented by . func ValidateDnsSearch(val string) (string, error) { diff --git a/opts/opts_test.go b/opts/opts_test.go index 0f6bc4c28dcb..631d4c6b60c0 100644 --- a/opts/opts_test.go +++ b/opts/opts_test.go @@ -28,6 +28,20 @@ func TestValidateIPAddress(t *testing.T) { } +func TestValidateMACAddress(t *testing.T) { + if _, err := ValidateMACAddress(`92:d0:c6:0a:29:33`); err != nil { + t.Fatalf("ValidateMACAddress(`92:d0:c6:0a:29:33`) got %s", err) + } + + if _, err := ValidateMACAddress(`92:d0:c6:0a:33`); err == nil { + t.Fatalf("ValidateMACAddress(`92:d0:c6:0a:33`) succeeded; expected failure on invalid MAC") + } + + if _, err := ValidateMACAddress(`random invalid string`); err == nil { + t.Fatalf("ValidateMACAddress(`random invalid string`) succeeded; expected failure on invalid MAC") + } +} + func TestListOpts(t *testing.T) { o := NewListOpts(nil) o.Set("foo") From 01245d261939885b838622ed4a07ca7cb83f95fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Hr=C4=8Dka?= Date: Tue, 25 Nov 2014 12:45:20 +0100 Subject: [PATCH 027/123] Restrict domain name to 255 characters MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Tomáš Hrčka Docker-DCO-1.1-Signed-off-by: Jessie Frazelle (github: jfrazelle) --- opts/opts.go | 2 +- opts/opts_test.go | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/opts/opts.go b/opts/opts.go index cd720c9a928a..e867c0a21d40 100644 --- a/opts/opts.go +++ b/opts/opts.go @@ -211,7 +211,7 @@ func validateDomain(val string) (string, error) { return "", fmt.Errorf("%s is not a valid domain", val) } ns := domainRegexp.FindSubmatch([]byte(val)) - if len(ns) > 0 { + if len(ns) > 0 && len(ns[1]) < 255 { return string(ns[1]), nil } return "", fmt.Errorf("%s is not a valid domain", val) diff --git a/opts/opts_test.go b/opts/opts_test.go index 631d4c6b60c0..8370926da591 100644 --- a/opts/opts_test.go +++ b/opts/opts_test.go @@ -105,6 +105,7 @@ func TestValidateDnsSearch(t *testing.T) { `foo.bar-.baz`, `foo.-bar`, `foo.-bar.baz`, + `foo.bar.baz.this.should.fail.on.long.name.beause.it.is.longer.thanisshouldbethis.should.fail.on.long.name.beause.it.is.longer.thanisshouldbethis.should.fail.on.long.name.beause.it.is.longer.thanisshouldbethis.should.fail.on.long.name.beause.it.is.longer.thanisshouldbe`, } for _, domain := range valid { From cd08d97a64f1775884e69e3e344347f5a222b2e0 Mon Sep 17 00:00:00 2001 From: George MacRorie Date: Mon, 23 Mar 2015 19:21:37 +0000 Subject: [PATCH 028/123] Cleanup redundant else statements find via golint #11602 Signed-off-by: George MacRorie --- opts/opts.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/opts/opts.go b/opts/opts.go index e867c0a21d40..df9decf61fa0 100644 --- a/opts/opts.go +++ b/opts/opts.go @@ -192,9 +192,8 @@ func ValidateMACAddress(val string) (string, error) { _, err := net.ParseMAC(strings.TrimSpace(val)) if err != nil { return "", err - } else { - return val, nil } + return val, nil } // Validates domain for resolvconf search configuration. From 64a9942188c01a8ad1fbdfc6dccd89e5381169e7 Mon Sep 17 00:00:00 2001 From: Antonio Murdaca Date: Mon, 13 Apr 2015 16:17:14 +0200 Subject: [PATCH 029/123] Remove job from container_inspect Signed-off-by: Antonio Murdaca --- opts/opts.go | 30 ++++++++++++++++++++++++------ 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/opts/opts.go b/opts/opts.go index df9decf61fa0..d2c32f13c722 100644 --- a/opts/opts.go +++ b/opts/opts.go @@ -8,16 +8,16 @@ import ( "regexp" "strings" - "github.com/docker/docker/api" flag "github.com/docker/docker/pkg/mflag" "github.com/docker/docker/pkg/parsers" "github.com/docker/docker/pkg/ulimit" - "github.com/docker/docker/utils" ) 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*$`) + 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 + DefaultUnixSocket = "/var/run/docker.sock" // Docker daemon by default always listens on the default unix socket ) func ListVar(values *[]string, names []string, usage string) { @@ -25,7 +25,7 @@ func ListVar(values *[]string, names []string, usage string) { } func HostListVar(values *[]string, names []string, usage string) { - flag.Var(newListOptsRef(values, api.ValidateHost), names, usage) + flag.Var(newListOptsRef(values, ValidateHost), names, usage) } func IPListVar(values *[]string, names []string, usage string) { @@ -174,7 +174,7 @@ func ValidateEnv(val string) (string, error) { if len(arr) > 1 { return val, nil } - if !utils.DoesEnvExist(val) { + if !doesEnvExist(val) { return val, nil } return fmt.Sprintf("%s=%s", val, os.Getenv(val)), nil @@ -234,3 +234,21 @@ func ValidateLabel(val string) (string, error) { } return val, nil } + +func ValidateHost(val string) (string, error) { + host, err := parsers.ParseHost(DefaultHTTPHost, DefaultUnixSocket, val) + if err != nil { + return val, err + } + return host, nil +} + +func doesEnvExist(name string) bool { + for _, entry := range os.Environ() { + parts := strings.SplitN(entry, "=", 2) + if parts[0] == name { + return true + } + } + return false +} From 7da142413e7dec5abf4e8cdfa49eb2931661fb10 Mon Sep 17 00:00:00 2001 From: jhowardmsft Date: Thu, 23 Apr 2015 13:45:34 -0700 Subject: [PATCH 030/123] Windows: Change default listener to HTTP Signed-off-by: jhowardmsft --- opts/opts.go | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/opts/opts.go b/opts/opts.go index d2c32f13c722..1db454736e5b 100644 --- a/opts/opts.go +++ b/opts/opts.go @@ -14,9 +14,13 @@ import ( ) 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 = "127.0.0.1" // Default HTTP Host used if only port is provided to -H flag e.g. docker -d -H tcp://:8080 + // 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 + // 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 ) From d0c32b1efa3f22532f4645f11806643fae0db8e5 Mon Sep 17 00:00:00 2001 From: wlan0 Date: Mon, 4 May 2015 14:39:48 -0700 Subject: [PATCH 031/123] Add log opts flag to pass in logging options Signed-off-by: wlan0 --- opts/opts.go | 51 +++++++++++++++++++++++++++++++++++++++++++++++ opts/opts_test.go | 35 ++++++++++++++++++++++++++++++++ 2 files changed, 86 insertions(+) diff --git a/opts/opts.go b/opts/opts.go index 1db454736e5b..380159663164 100644 --- a/opts/opts.go +++ b/opts/opts.go @@ -28,6 +28,14 @@ func ListVar(values *[]string, names []string, usage string) { flag.Var(newListOptsRef(values, nil), names, usage) } +func MapVar(values map[string]string, names []string, usage string) { + flag.Var(newMapOpt(values, nil), names, usage) +} + +func LogOptsVar(values map[string]string, names []string, usage string) { + flag.Var(newMapOpt(values, ValidateLogOpts), names, usage) +} + func HostListVar(values *[]string, names []string, usage string) { flag.Var(newListOptsRef(values, ValidateHost), names, usage) } @@ -130,10 +138,53 @@ func (opts *ListOpts) Len() int { return len((*opts.values)) } +//MapOpts type +type MapOpts struct { + values map[string]string + validator ValidatorFctType +} + +func (opts *MapOpts) Set(value string) error { + if opts.validator != nil { + v, err := opts.validator(value) + if err != nil { + return err + } + value = v + } + vals := strings.SplitN(value, "=", 2) + if len(vals) == 1 { + (opts.values)[vals[0]] = "" + } else { + (opts.values)[vals[0]] = vals[1] + } + return nil +} + +func (opts *MapOpts) String() string { + return fmt.Sprintf("%v", map[string]string((opts.values))) +} + +func newMapOpt(values map[string]string, validator ValidatorFctType) *MapOpts { + return &MapOpts{ + values: values, + validator: validator, + } +} + // Validators type ValidatorFctType func(val string) (string, error) type ValidatorFctListType func(val string) ([]string, error) +func ValidateLogOpts(val string) (string, error) { + allowedKeys := map[string]string{} + vals := strings.Split(val, "=") + if allowedKeys[vals[0]] != "" { + return val, nil + } + return "", fmt.Errorf("%s is not a valid log opt", vals[0]) +} + func ValidateAttach(val string) (string, error) { s := strings.ToLower(val) for _, str := range []string{"stdin", "stdout", "stderr"} { diff --git a/opts/opts_test.go b/opts/opts_test.go index 8370926da591..dfad430ac427 100644 --- a/opts/opts_test.go +++ b/opts/opts_test.go @@ -1,6 +1,7 @@ package opts import ( + "fmt" "strings" "testing" ) @@ -28,6 +29,31 @@ func TestValidateIPAddress(t *testing.T) { } +func TestMapOpts(t *testing.T) { + tmpMap := make(map[string]string) + o := newMapOpt(tmpMap, logOptsValidator) + o.Set("max-size=1") + if o.String() != "map[max-size:1]" { + t.Errorf("%s != [map[max-size:1]", o.String()) + } + + o.Set("max-file=2") + if len(tmpMap) != 2 { + t.Errorf("map length %d != 2", len(tmpMap)) + } + + if tmpMap["max-file"] != "2" { + t.Errorf("max-file = %s != 2", tmpMap["max-file"]) + } + + if tmpMap["max-size"] != "1" { + t.Errorf("max-size = %s != 1", tmpMap["max-size"]) + } + if o.Set("dummy-val=3") == nil { + t.Errorf("validator is not being called") + } +} + func TestValidateMACAddress(t *testing.T) { if _, err := ValidateMACAddress(`92:d0:c6:0a:29:33`); err != nil { t.Fatalf("ValidateMACAddress(`92:d0:c6:0a:29:33`) got %s", err) @@ -152,3 +178,12 @@ func TestValidateExtraHosts(t *testing.T) { } } } + +func logOptsValidator(val string) (string, error) { + allowedKeys := map[string]string{"max-size": "1", "max-file": "2"} + vals := strings.Split(val, "=") + if allowedKeys[vals[0]] != "" { + return val, nil + } + return "", fmt.Errorf("invalid key %s", vals[0]) +} From e8e612405093242c29306f9f80baf5537e15ee0f Mon Sep 17 00:00:00 2001 From: Antonio Murdaca Date: Thu, 7 May 2015 22:02:14 +0200 Subject: [PATCH 032/123] Allow links to be specified with only the name if this matches the alias Signed-off-by: Antonio Murdaca --- opts/opts.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opts/opts.go b/opts/opts.go index 380159663164..c330c27a5de7 100644 --- a/opts/opts.go +++ b/opts/opts.go @@ -196,7 +196,7 @@ func ValidateAttach(val string) (string, error) { } func ValidateLink(val string) (string, error) { - if _, err := parsers.PartParser("name:alias", val); err != nil { + if _, _, err := parsers.ParseLink(val); err != nil { return val, err } return val, nil From 51c57063264a7c7bc93e6e08a3be7cf7ef353a33 Mon Sep 17 00:00:00 2001 From: Antonio Murdaca Date: Thu, 21 May 2015 22:20:25 +0200 Subject: [PATCH 033/123] Add syslog-address log-opt Signed-off-by: Antonio Murdaca --- opts/opts.go | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/opts/opts.go b/opts/opts.go index c330c27a5de7..8dcc8c598891 100644 --- a/opts/opts.go +++ b/opts/opts.go @@ -33,7 +33,7 @@ func MapVar(values map[string]string, names []string, usage string) { } func LogOptsVar(values map[string]string, names []string, usage string) { - flag.Var(newMapOpt(values, ValidateLogOpts), names, usage) + flag.Var(newMapOpt(values, nil), names, usage) } func HostListVar(values *[]string, names []string, usage string) { @@ -176,15 +176,6 @@ func newMapOpt(values map[string]string, validator ValidatorFctType) *MapOpts { type ValidatorFctType func(val string) (string, error) type ValidatorFctListType func(val string) ([]string, error) -func ValidateLogOpts(val string) (string, error) { - allowedKeys := map[string]string{} - vals := strings.Split(val, "=") - if allowedKeys[vals[0]] != "" { - return val, nil - } - return "", fmt.Errorf("%s is not a valid log opt", vals[0]) -} - func ValidateAttach(val string) (string, error) { s := strings.ToLower(val) for _, str := range []string{"stdin", "stdout", "stderr"} { From 292e38a1443b030d2b41e3bc570e25f104d6ecca Mon Sep 17 00:00:00 2001 From: Doug Davis Date: Fri, 5 Jun 2015 09:44:10 -0700 Subject: [PATCH 034/123] Remove duplicate call to net.ParseIP and a little cleanup Signed-off-by: Doug Davis --- opts/ip.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/opts/ip.go b/opts/ip.go index f8a493e68a63..90360056f714 100644 --- a/opts/ip.go +++ b/opts/ip.go @@ -22,10 +22,10 @@ func (o *IpOpt) Set(val string) error { if ip == nil { return fmt.Errorf("%s is not an ip address", val) } - (*o.IP) = net.ParseIP(val) + *o.IP = ip return nil } func (o *IpOpt) String() string { - return (*o.IP).String() + return o.IP.String() } From d1be0bd11e50aff7b62e3b2327b0290e485dc369 Mon Sep 17 00:00:00 2001 From: Doug Davis Date: Fri, 5 Jun 2015 12:42:48 -0700 Subject: [PATCH 035/123] Minor doc edit to add clarity around the --volume path format Also add a comment to the ValidatePath func so devs/reviewers know exactly what its looking for. Signed-off-by: Doug Davis --- opts/opts.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/opts/opts.go b/opts/opts.go index 8dcc8c598891..e40c1a334ab1 100644 --- a/opts/opts.go +++ b/opts/opts.go @@ -193,6 +193,8 @@ func ValidateLink(val string) (string, error) { 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 func ValidatePath(val string) (string, error) { var containerPath string From 0c721e1ad5b8070accb49ce8f339c2adc9a8ee7f Mon Sep 17 00:00:00 2001 From: Eric-Olivier Lamey Date: Thu, 11 Jun 2015 07:53:39 +0000 Subject: [PATCH 036/123] Display empty string instead of when IP opt is nil. Fixes #13878. Signed-off-by: Eric-Olivier Lamey --- opts/ip.go | 3 +++ opts/opts_test.go | 13 +++++++++++++ 2 files changed, 16 insertions(+) diff --git a/opts/ip.go b/opts/ip.go index 90360056f714..d960dcd935e7 100644 --- a/opts/ip.go +++ b/opts/ip.go @@ -27,5 +27,8 @@ func (o *IpOpt) Set(val string) error { } func (o *IpOpt) String() string { + if *o.IP == nil { + return "" + } return o.IP.String() } diff --git a/opts/opts_test.go b/opts/opts_test.go index dfad430ac427..921009c6b374 100644 --- a/opts/opts_test.go +++ b/opts/opts_test.go @@ -2,6 +2,7 @@ package opts import ( "fmt" + "net" "strings" "testing" ) @@ -179,6 +180,18 @@ func TestValidateExtraHosts(t *testing.T) { } } +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 logOptsValidator(val string) (string, error) { allowedKeys := map[string]string{"max-size": "1", "max-file": "2"} vals := strings.Split(val, "=") From 0ede41afbd9bcff16fe81b71de96f9c5c863722b Mon Sep 17 00:00:00 2001 From: Matthieu Hauglustaine Date: Wed, 1 Jul 2015 16:54:20 +0200 Subject: [PATCH 037/123] Return bufio error if set in ParseEnvFile Return an error value if bufio failed to properly read a token. Avoids running a container with partial environment. Fixes: #14266 Signed-off-by: Matthieu Hauglustaine --- opts/envfile.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opts/envfile.go b/opts/envfile.go index 19ee8955f96a..74d7f96f34d9 100644 --- a/opts/envfile.go +++ b/opts/envfile.go @@ -40,7 +40,7 @@ func ParseEnvFile(filename string) ([]string, error) { } } } - return lines, nil + return lines, scanner.Err() } var whiteSpaces = " \t" From f49f44a3a3b51091ab6808b1837af14942393aad Mon Sep 17 00:00:00 2001 From: Matthieu Hauglustaine Date: Wed, 1 Jul 2015 17:02:40 +0200 Subject: [PATCH 038/123] Add unit tests for ParseEnvFile. Add unit tests of the ParseEnvFile function. Test for: * good file * empty file * non existent file * badly formatted file Signed-off-by: Matthieu Hauglustaine --- opts/envfile_test.go | 114 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 114 insertions(+) create mode 100644 opts/envfile_test.go diff --git a/opts/envfile_test.go b/opts/envfile_test.go new file mode 100644 index 000000000000..3748481d1b76 --- /dev/null +++ b/opts/envfile_test.go @@ -0,0 +1,114 @@ +package opts + +import ( + "bufio" + "fmt" + "io/ioutil" + "os" + "reflect" + "strings" + "testing" +) + +func tmpFileWithContent(content string) (string, error) { + tmpFile, err := ioutil.TempFile("", "envfile-test") + if err != nil { + return "", err + } + defer tmpFile.Close() + + tmpFile.WriteString(content) + return tmpFile.Name(), nil +} + +// Test ParseEnvFile for a file with a few well formatted lines +func TestParseEnvFileGoodFile(t *testing.T) { + content := `foo=bar + baz=quux +# comment + +foobar=foobaz +` + + tmpFile, err := tmpFileWithContent(content) + if err != nil { + t.Fatal("failed to create test data file") + } + defer os.Remove(tmpFile) + + lines, err := ParseEnvFile(tmpFile) + if err != nil { + t.Fatal("ParseEnvFile failed; expected success") + } + + expected_lines := []string{ + "foo=bar", + "baz=quux", + "foobar=foobaz", + } + + if !reflect.DeepEqual(lines, expected_lines) { + t.Fatal("lines not equal to expected_lines") + } +} + +// Test ParseEnvFile for an empty file +func TestParseEnvFileEmptyFile(t *testing.T) { + tmpFile, err := tmpFileWithContent("") + if err != nil { + t.Fatal("failed to create test data file") + } + defer os.Remove(tmpFile) + + lines, err := ParseEnvFile(tmpFile) + if err != nil { + t.Fatal("ParseEnvFile failed; expected success") + } + + if len(lines) != 0 { + t.Fatal("lines not empty; expected empty") + } +} + +// Test ParseEnvFile for a non existent file +func TestParseEnvFileNonExistentFile(t *testing.T) { + _, err := ParseEnvFile("foo_bar_baz") + if err == nil { + t.Fatal("ParseEnvFile succeeded; expected failure") + } +} + +// Test ParseEnvFile for a badly formatted file +func TestParseEnvFileBadlyFormattedFile(t *testing.T) { + content := `foo=bar + f =quux +` + + tmpFile, err := tmpFileWithContent(content) + if err != nil { + t.Fatal("failed to create test data file") + } + defer os.Remove(tmpFile) + + _, err = ParseEnvFile(tmpFile) + if err == nil { + t.Fatal("ParseEnvFile succeeded; expected failure") + } +} + +// Test ParseEnvFile for a file with a line exeeding bufio.MaxScanTokenSize +func TestParseEnvFileLineTooLongFile(t *testing.T) { + content := strings.Repeat("a", bufio.MaxScanTokenSize+42) + content = fmt.Sprint("foo=", content) + + tmpFile, err := tmpFileWithContent(content) + if err != nil { + t.Fatal("failed to create test data file") + } + defer os.Remove(tmpFile) + + _, err = ParseEnvFile(tmpFile) + if err == nil { + t.Fatal("ParseEnvFile succeeded; expected failure") + } +} From 7b9ceadc4db9c14f42d19c9a22ceca48d2891f1d Mon Sep 17 00:00:00 2001 From: Vincent Demeester Date: Sun, 12 Jul 2015 10:33:30 +0200 Subject: [PATCH 039/123] 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 --- opts/envfile.go | 28 ++-- opts/envfile_test.go | 75 +++++++---- opts/ip.go | 1 + opts/ip_test.go | 54 ++++++++ opts/opts.go | 116 +++++++++++++--- opts/opts_test.go | 309 ++++++++++++++++++++++++++++++++++++++++--- opts/ulimit_test.go | 42 ++++++ 7 files changed, 550 insertions(+), 75 deletions(-) create mode 100644 opts/ip_test.go create mode 100644 opts/ulimit_test.go diff --git a/opts/envfile.go b/opts/envfile.go index 74d7f96f34d9..b854227e86da 100644 --- a/opts/envfile.go +++ b/opts/envfile.go @@ -4,12 +4,18 @@ import ( "bufio" "fmt" "os" + "regexp" "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) { fh, err := os.Open(filename) if err != nil { @@ -23,14 +29,15 @@ func ParseEnvFile(filename string) ([]string, error) { line := scanner.Text() // line is not empty, and not starting with '#' 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 lines = append(lines, fmt.Sprintf("%s=%s", variable, data[1])) @@ -45,6 +52,7 @@ func ParseEnvFile(filename string) ([]string, error) { var whiteSpaces = " \t" +// ErrBadEnvVariable typed error for bad environment variable type ErrBadEnvVariable struct { msg string } diff --git a/opts/envfile_test.go b/opts/envfile_test.go index 3748481d1b76..cd0ca8f32590 100644 --- a/opts/envfile_test.go +++ b/opts/envfile_test.go @@ -10,15 +10,15 @@ import ( "testing" ) -func tmpFileWithContent(content string) (string, error) { +func tmpFileWithContent(content string, t *testing.T) string { tmpFile, err := ioutil.TempFile("", "envfile-test") if err != nil { - return "", err + t.Fatal(err) } defer tmpFile.Close() tmpFile.WriteString(content) - return tmpFile.Name(), nil + return tmpFile.Name() } // Test ParseEnvFile for a file with a few well formatted lines @@ -27,42 +27,36 @@ func TestParseEnvFileGoodFile(t *testing.T) { baz=quux # 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) lines, err := ParseEnvFile(tmpFile) if err != nil { - t.Fatal("ParseEnvFile failed; expected success") + t.Fatal(err) } - expected_lines := []string{ + expectedLines := []string{ "foo=bar", "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") } } // Test ParseEnvFile for an empty file 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) lines, err := ParseEnvFile(tmpFile) if err != nil { - t.Fatal("ParseEnvFile failed; expected success") + t.Fatal(err) } if len(lines) != 0 { @@ -76,6 +70,9 @@ func TestParseEnvFileNonExistentFile(t *testing.T) { if err == nil { 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 @@ -84,15 +81,19 @@ func TestParseEnvFileBadlyFormattedFile(t *testing.T) { 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) - _, err = ParseEnvFile(tmpFile) + _, err := ParseEnvFile(tmpFile) 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 = 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) - _, err = ParseEnvFile(tmpFile) + _, err := ParseEnvFile(tmpFile) if err == nil { 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()) + } +} diff --git a/opts/ip.go b/opts/ip.go index d960dcd935e7..b1f9587555eb 100644 --- a/opts/ip.go +++ b/opts/ip.go @@ -5,6 +5,7 @@ import ( "net" ) +// IpOpt type that hold an IP type IpOpt struct { *net.IP } diff --git a/opts/ip_test.go b/opts/ip_test.go new file mode 100644 index 000000000000..b6b526a578b3 --- /dev/null +++ b/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()) + } +} diff --git a/opts/opts.go b/opts/opts.go index e40c1a334ab1..b73901279189 100644 --- a/opts/opts.go +++ b/opts/opts.go @@ -11,61 +11,85 @@ import ( flag "github.com/docker/docker/pkg/mflag" "github.com/docker/docker/pkg/parsers" "github.com/docker/docker/pkg/ulimit" + "github.com/docker/docker/volume" ) 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 // is not supplied. A better longer term solution would be to use a named // 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) { 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) { 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) { 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) { 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) { 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) { 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) { 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) { flag.Var(NewUlimitOpt(values), names, usage) } -// ListOpts type +// ListOpts type that hold a list of values and a validation function. type ListOpts struct { values *[]string validator ValidatorFctType } +// NewListOpts Create a new ListOpts with the specified validator. func NewListOpts(validator ValidatorFctType) ListOpts { var values []string return *newListOptsRef(&values, validator) @@ -138,12 +162,14 @@ func (opts *ListOpts) Len() int { return len((*opts.values)) } -//MapOpts type +//MapOpts type that holds a map of values and a validation function. type MapOpts struct { values map[string]string 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 { if opts.validator != nil { 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) + +// ValidatorFctListType validator that return a validate list of string and/or an 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) { s := strings.ToLower(val) for _, str := range []string{"stdin", "stdout", "stderr"} { @@ -183,9 +212,10 @@ func ValidateAttach(val string) (string, error) { 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) { if _, _, err := parsers.ParseLink(val); err != nil { return val, err @@ -193,22 +223,53 @@ func ValidateLink(val string) (string, error) { 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) { + return validatePath(val, true) +} + +func validatePath(val string, validateMountMode bool) (string, error) { var containerPath string + var mode string if strings.Count(val, ":") > 2 { 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] - 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] - 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) { @@ -217,17 +278,24 @@ func ValidatePath(val string) (string, error) { 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) { arr := strings.Split(val, "=") if len(arr) > 1 { 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) { return val, nil } return fmt.Sprintf("%s=%s", val, os.Getenv(val)), nil } +// ValidateIPAddress Validates an Ip address func ValidateIPAddress(val string) (string, error) { var ip = net.ParseIP(strings.TrimSpace(val)) if ip != nil { @@ -236,6 +304,7 @@ func ValidateIPAddress(val string) (string, error) { return "", fmt.Errorf("%s is not an ip address", val) } +// ValidateMACAddress Validates a MAC address func ValidateMACAddress(val string) (string, error) { _, err := net.ParseMAC(strings.TrimSpace(val)) if err != nil { @@ -244,9 +313,9 @@ func ValidateMACAddress(val string) (string, error) { return val, nil } -// Validates domain for resolvconf search configuration. +// ValidateDNSSearch Validates domain for resolvconf search configuration. // 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 == "." { return val, nil } @@ -264,6 +333,8 @@ func validateDomain(val string) (string, error) { 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) { // allow for IPv6 addresses in extra hosts by only splitting on first ":" arr := strings.SplitN(val, ":", 2) @@ -276,13 +347,16 @@ func ValidateExtraHost(val string) (string, error) { 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) { - if strings.Count(val, "=") != 1 { + if strings.Count(val, "=") < 1 { return "", fmt.Errorf("bad attribute format: %s", val) } return val, nil } +// ValidateHost Validate that the given string is a valid host and returns it func ValidateHost(val string) (string, error) { host, err := parsers.ParseHost(DefaultHTTPHost, DefaultUnixSocket, val) if err != nil { diff --git a/opts/opts_test.go b/opts/opts_test.go index 921009c6b374..3e639c1fa0d0 100644 --- a/opts/opts_test.go +++ b/opts/opts_test.go @@ -2,7 +2,7 @@ package opts import ( "fmt" - "net" + "os" "strings" "testing" ) @@ -69,7 +69,7 @@ func TestValidateMACAddress(t *testing.T) { } } -func TestListOpts(t *testing.T) { +func TestListOptsWithoutValidator(t *testing.T) { o := NewListOpts(nil) o.Set("foo") if o.String() != "[foo]" { @@ -79,6 +79,10 @@ func TestListOpts(t *testing.T) { if o.Len() != 2 { t.Errorf("%d != 2", o.Len()) } + o.Set("bar") + if o.Len() != 3 { + t.Errorf("%d != 3", o.Len()) + } if !o.Get("bar") { t.Error("o.Get(\"bar\") == false") } @@ -86,12 +90,48 @@ func TestListOpts(t *testing.T) { t.Error("o.Get(\"baz\") == true") } 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{ `.`, `a`, @@ -136,14 +176,14 @@ func TestValidateDnsSearch(t *testing.T) { } 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 { - 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) } } } diff --git a/opts/ulimit_test.go b/opts/ulimit_test.go new file mode 100644 index 000000000000..e72be7a813db --- /dev/null +++ b/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) + } +} From f55d8241cfc8e436a1271e653adfa57a29883adf Mon Sep 17 00:00:00 2001 From: Tibor Vass Date: Tue, 5 May 2015 00:18:28 -0400 Subject: [PATCH 040/123] cli: new daemon command and new cli package This patch creates a new cli package that allows to combine both client and daemon commands (there is only one daemon command: docker daemon). The `-d` and `--daemon` top-level flags are deprecated and a special message is added to prompt the user to use `docker daemon`. Providing top-level daemon-specific flags for client commands result in an error message prompting the user to use `docker daemon`. This patch does not break any old but correct usages. This also makes `-d` and `--daemon` flags, as well as the `daemon` command illegal in client-only binaries. Signed-off-by: Tibor Vass --- opts/hosts_unix.go | 7 +++++++ opts/hosts_windows.go | 7 +++++++ opts/opts.go | 25 ++++++++++++++----------- opts/opts_test.go | 2 +- opts/ulimit.go | 13 ++++++++----- 5 files changed, 37 insertions(+), 17 deletions(-) create mode 100644 opts/hosts_unix.go create mode 100644 opts/hosts_windows.go diff --git a/opts/hosts_unix.go b/opts/hosts_unix.go new file mode 100644 index 000000000000..a29335e605a7 --- /dev/null +++ b/opts/hosts_unix.go @@ -0,0 +1,7 @@ +// +build !windows + +package opts + +import "fmt" + +var DefaultHost = fmt.Sprintf("unix://%s", DefaultUnixSocket) diff --git a/opts/hosts_windows.go b/opts/hosts_windows.go new file mode 100644 index 000000000000..55eac2aaca44 --- /dev/null +++ b/opts/hosts_windows.go @@ -0,0 +1,7 @@ +// +build windows + +package opts + +import "fmt" + +var DefaultHost = fmt.Sprintf("tcp://%s:%d", DefaultHTTPHost, DefaultHTTPPort) diff --git a/opts/opts.go b/opts/opts.go index b73901279189..6d1b2f7b99b8 100644 --- a/opts/opts.go +++ b/opts/opts.go @@ -32,37 +32,37 @@ var ( // 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) { - 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) { - flag.Var(newMapOpt(values, nil), names, usage) + flag.Var(NewMapOpts(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) { - flag.Var(newMapOpt(values, nil), names, usage) + flag.Var(NewMapOpts(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) { - 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) { - flag.Var(newListOptsRef(values, ValidateIPAddress), names, usage) + flag.Var(NewListOptsRef(values, ValidateIPAddress), 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) + flag.Var(NewListOptsRef(values, ValidateDNSSearch), names, usage) } // IPVar Defines a flag with the specified names and usage for IP and will use @@ -74,12 +74,12 @@ func IPVar(value *net.IP, names []string, defaultValue, usage string) { // 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) { - 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) } @@ -92,10 +92,10 @@ type ListOpts struct { // NewListOpts Create a new ListOpts with the specified validator. func NewListOpts(validator ValidatorFctType) ListOpts { var values []string - return *newListOptsRef(&values, validator) + return *NewListOptsRef(&values, validator) } -func newListOptsRef(values *[]string, validator ValidatorFctType) *ListOpts { +func NewListOptsRef(values *[]string, validator ValidatorFctType) *ListOpts { return &ListOpts{ values: values, validator: validator, @@ -191,7 +191,10 @@ func (opts *MapOpts) String() string { return fmt.Sprintf("%v", map[string]string((opts.values))) } -func newMapOpt(values map[string]string, validator ValidatorFctType) *MapOpts { +func NewMapOpts(values map[string]string, validator ValidatorFctType) *MapOpts { + if values == nil { + values = make(map[string]string) + } return &MapOpts{ values: values, validator: validator, diff --git a/opts/opts_test.go b/opts/opts_test.go index 3e639c1fa0d0..f08df30be633 100644 --- a/opts/opts_test.go +++ b/opts/opts_test.go @@ -32,7 +32,7 @@ func TestValidateIPAddress(t *testing.T) { func TestMapOpts(t *testing.T) { tmpMap := make(map[string]string) - o := newMapOpt(tmpMap, logOptsValidator) + o := NewMapOpts(tmpMap, logOptsValidator) o.Set("max-size=1") if o.String() != "map[max-size:1]" { t.Errorf("%s != [map[max-size:1]", o.String()) diff --git a/opts/ulimit.go b/opts/ulimit.go index 361eadf220e2..f8d34365f5aa 100644 --- a/opts/ulimit.go +++ b/opts/ulimit.go @@ -7,10 +7,13 @@ import ( ) type UlimitOpt struct { - values map[string]*ulimit.Ulimit + values *map[string]*ulimit.Ulimit } -func NewUlimitOpt(ref map[string]*ulimit.Ulimit) *UlimitOpt { +func NewUlimitOpt(ref *map[string]*ulimit.Ulimit) *UlimitOpt { + if ref == nil { + ref = &map[string]*ulimit.Ulimit{} + } return &UlimitOpt{ref} } @@ -20,14 +23,14 @@ func (o *UlimitOpt) Set(val string) error { return err } - o.values[l.Name] = l + (*o.values)[l.Name] = l return nil } func (o *UlimitOpt) String() string { var out []string - for _, v := range o.values { + for _, v := range *o.values { out = append(out, v.String()) } @@ -36,7 +39,7 @@ func (o *UlimitOpt) String() string { func (o *UlimitOpt) GetList() []*ulimit.Ulimit { var ulimits []*ulimit.Ulimit - for _, v := range o.values { + for _, v := range *o.values { ulimits = append(ulimits, v) } From f3841a1d27e4f3b4260eae7933dc7c54950d0179 Mon Sep 17 00:00:00 2001 From: Shishir Mahajan Date: Fri, 1 May 2015 10:00:43 -0400 Subject: [PATCH 041/123] Add and modify tests for legacy and new daemon invokations Signed-off-by: Shishir Mahajan Signed-off-by: Tibor Vass --- opts/ulimit_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opts/ulimit_test.go b/opts/ulimit_test.go index e72be7a813db..3845d1ec18a1 100644 --- a/opts/ulimit_test.go +++ b/opts/ulimit_test.go @@ -11,7 +11,7 @@ func TestUlimitOpt(t *testing.T) { "nofile": {"nofile", 1024, 512}, } - ulimitOpt := NewUlimitOpt(ulimitMap) + ulimitOpt := NewUlimitOpt(&ulimitMap) expected := "[nofile=512:1024]" if ulimitOpt.String() != expected { From 351ac873e077f89017d5670a44d273e959e75648 Mon Sep 17 00:00:00 2001 From: Antonio Murdaca Date: Sat, 25 Jul 2015 19:54:05 +0200 Subject: [PATCH 042/123] Remove unused functions Signed-off-by: Antonio Murdaca --- opts/opts.go | 56 ---------------------------------------------------- 1 file changed, 56 deletions(-) diff --git a/opts/opts.go b/opts/opts.go index 6d1b2f7b99b8..115ed5783bcb 100644 --- a/opts/opts.go +++ b/opts/opts.go @@ -8,9 +8,7 @@ import ( "regexp" "strings" - flag "github.com/docker/docker/pkg/mflag" "github.com/docker/docker/pkg/parsers" - "github.com/docker/docker/pkg/ulimit" "github.com/docker/docker/volume" ) @@ -29,60 +27,6 @@ var ( 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) { - 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) { - flag.Var(NewMapOpts(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) { - flag.Var(NewMapOpts(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) { - 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) { - flag.Var(NewListOptsRef(values, ValidateIPAddress), 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) { - 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) { - 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) { - flag.Var(NewUlimitOpt(values), names, usage) -} - // ListOpts type that hold a list of values and a validation function. type ListOpts struct { values *[]string From 091f8000695cb620dfc081f095720e7b789d14a9 Mon Sep 17 00:00:00 2001 From: Qiang Huang Date: Mon, 10 Aug 2015 20:48:08 +0800 Subject: [PATCH 043/123] Change all docker -d to docker daemon Signed-off-by: Qiang Huang --- opts/opts.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/opts/opts.go b/opts/opts.go index 115ed5783bcb..398b8b4f37ff 100644 --- a/opts/opts.go +++ b/opts/opts.go @@ -15,9 +15,9 @@ import ( 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 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 daemon -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:// + // DefaultHTTPPort Default HTTP Port used if only the protocol is provided to -H flag e.g. docker daemon -H tcp:// // 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 // pipe as the default on the Windows daemon. From 869e08a12bf3bf614e74af34f46a6c1d3d061afb Mon Sep 17 00:00:00 2001 From: Matt Robenolt Date: Fri, 21 Aug 2015 13:28:01 -0700 Subject: [PATCH 044/123] opts/envfile: trim all leading whitespace in a line Signed-off-by: Matt Robenolt --- opts/envfile.go | 7 +++---- opts/envfile_test.go | 7 ++++++- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/opts/envfile.go b/opts/envfile.go index b854227e86da..1a7284f56c31 100644 --- a/opts/envfile.go +++ b/opts/envfile.go @@ -26,13 +26,12 @@ func ParseEnvFile(filename string) ([]string, error) { lines := []string{} scanner := bufio.NewScanner(fh) for scanner.Scan() { - line := scanner.Text() + // trim the line from all leading whitespace first + line := strings.TrimLeft(scanner.Text(), whiteSpaces) // line is not empty, and not starting with '#' if len(line) > 0 && !strings.HasPrefix(line, "#") { data := strings.SplitN(line, "=", 2) - - // trim the front of a variable, but nothing else - variable := strings.TrimLeft(data[0], whiteSpaces) + variable := data[0] if !EnvironmentVariableRegexp.MatchString(variable) { return []string{}, ErrBadEnvVariable{fmt.Sprintf("variable '%s' is not a valid environment variable", variable)} diff --git a/opts/envfile_test.go b/opts/envfile_test.go index cd0ca8f32590..b82435155ad2 100644 --- a/opts/envfile_test.go +++ b/opts/envfile_test.go @@ -29,7 +29,12 @@ func TestParseEnvFileGoodFile(t *testing.T) { _foobar=foobaz ` - + // Adding a newline + a line with pure whitespace. + // This is being done like this instead of the block above + // because it's common for editors to trim trailing whitespace + // from lines, which becomes annoying since that's the + // exact thing we need to test. + content += "\n \t " tmpFile := tmpFileWithContent(content, t) defer os.Remove(tmpFile) From 6a31056ff64c29c8e8fb853a1c282513a9d80141 Mon Sep 17 00:00:00 2001 From: Qiang Huang Date: Mon, 24 Aug 2015 17:28:19 +0800 Subject: [PATCH 045/123] Change return value for ValidateMountMode 1. rename it from ValidateMountMode to ValidMountMode Because it's a function simply check mount mode is valid or not. 2. remove the rw check return value It's not supposed to be combined into this function, and we already have a function for that check. Signed-off-by: Qiang Huang --- opts/opts.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/opts/opts.go b/opts/opts.go index 398b8b4f37ff..28ed604e752f 100644 --- a/opts/opts.go +++ b/opts/opts.go @@ -202,7 +202,7 @@ func validatePath(val string, validateMountMode bool) (string, error) { containerPath = splited[0] val = path.Clean(containerPath) case 2: - if isValid, _ := volume.ValidateMountMode(splited[1]); validateMountMode && isValid { + if isValid := volume.ValidMountMode(splited[1]); validateMountMode && isValid { containerPath = splited[0] mode = splited[1] val = fmt.Sprintf("%s:%s", path.Clean(containerPath), mode) @@ -213,7 +213,7 @@ func validatePath(val string, validateMountMode bool) (string, error) { case 3: containerPath = splited[1] mode = splited[2] - if isValid, _ := volume.ValidateMountMode(splited[2]); validateMountMode && !isValid { + if isValid := volume.ValidMountMode(splited[2]); validateMountMode && !isValid { return val, fmt.Errorf("bad mount mode specified : %s", mode) } val = fmt.Sprintf("%s:%s:%s", splited[0], containerPath, mode) From 22e1ac49663509afb7a5ab031ff7e4aeca4d6e14 Mon Sep 17 00:00:00 2001 From: Qiang Huang Date: Mon, 24 Aug 2015 17:57:12 +0800 Subject: [PATCH 046/123] Add mode check for device This fixes two problems: 1. docker run --device /dev/sda:rw ubuntu bash doesn't work 2. --device /dev/zero:/dev/noro:ro doesn't show clear error message, but fail when writing to cgroup file. Signed-off-by: Qiang Huang --- opts/opts.go | 37 +++++++++++++++++++++++++++++-------- opts/opts_test.go | 45 ++++++++++++++++++++++++--------------------- 2 files changed, 53 insertions(+), 29 deletions(-) diff --git a/opts/opts.go b/opts/opts.go index 28ed604e752f..aa3de81a4d70 100644 --- a/opts/opts.go +++ b/opts/opts.go @@ -170,11 +170,32 @@ func ValidateLink(val string) (string, error) { return val, nil } +// ValidDeviceMode checks if the mode for device is valid or not. +// Valid mode is a composition of r (read), w (write), and m (mknod). +func ValidDeviceMode(mode string) bool { + var legalDeviceMode = map[rune]bool{ + 'r': true, + 'w': true, + 'm': true, + } + if mode == "" { + return false + } + for _, c := range mode { + if !legalDeviceMode[c] { + return false + } + legalDeviceMode[c] = false + } + return true +} + // ValidateDevice Validate a path for devices // It will make sure 'val' is in the form: // [host-dir:]container-path[:mode] +// It will also validate the device mode. func ValidateDevice(val string) (string, error) { - return validatePath(val, false) + return validatePath(val, ValidDeviceMode) } // ValidatePath Validate a path for volumes @@ -182,27 +203,27 @@ func ValidateDevice(val string) (string, error) { // [host-dir:]container-path[:rw|ro] // It will also validate the mount mode. func ValidatePath(val string) (string, error) { - return validatePath(val, true) + return validatePath(val, volume.ValidMountMode) } -func validatePath(val string, validateMountMode bool) (string, error) { +func validatePath(val string, validator func(string) bool) (string, error) { var containerPath string var mode string if strings.Count(val, ":") > 2 { - return val, fmt.Errorf("bad format for volumes: %s", val) + return val, fmt.Errorf("bad format for path: %s", val) } splited := strings.SplitN(val, ":", 3) if splited[0] == "" { - return val, fmt.Errorf("bad format for volumes: %s", val) + return val, fmt.Errorf("bad format for path: %s", val) } switch len(splited) { case 1: containerPath = splited[0] val = path.Clean(containerPath) case 2: - if isValid := volume.ValidMountMode(splited[1]); validateMountMode && isValid { + if isValid := validator(splited[1]); isValid { containerPath = splited[0] mode = splited[1] val = fmt.Sprintf("%s:%s", path.Clean(containerPath), mode) @@ -213,8 +234,8 @@ func validatePath(val string, validateMountMode bool) (string, error) { case 3: containerPath = splited[1] mode = splited[2] - if isValid := volume.ValidMountMode(splited[2]); validateMountMode && !isValid { - return val, fmt.Errorf("bad mount mode specified : %s", mode) + if isValid := validator(splited[2]); !isValid { + return val, fmt.Errorf("bad mode specified: %s", mode) } val = fmt.Sprintf("%s:%s:%s", splited[0], containerPath, mode) } diff --git a/opts/opts_test.go b/opts/opts_test.go index f08df30be633..e3ff970b32c9 100644 --- a/opts/opts_test.go +++ b/opts/opts_test.go @@ -289,24 +289,24 @@ func TestValidatePath(t *testing.T) { "/rw:rw", } invalid := map[string]string{ - "": "bad format for volumes: ", + "": "bad format for path: ", "./": "./ 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: :", + ":": "bad format for path: :", "/tmp:": " is not an absolute path", - ":test": "bad format for volumes: :test", - ":/test": "bad format for volumes: :/test", + ":test": "bad format for path: :test", + ":/test": "bad format for path: :/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::", + ":test:": "bad format for path: :test:", + "::": "bad format for path: ::", + ":::": "bad format for path: :::", + "/tmp:::": "bad format for path: /tmp:::", + ":/tmp::": "bad format for path: :/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", + "/path:/path:sw": "bad mode specified: sw", + "/path:/path:rwz": "bad mode specified: rwz", } for _, path := range valid { @@ -333,27 +333,30 @@ func TestValidateDevice(t *testing.T) { "/with space", "/home:/with space", "relative:/absolute-path", - "hostPath:/containerPath:ro", + "hostPath:/containerPath:r", "/hostPath:/containerPath:rw", "/hostPath:/containerPath:mrw", } invalid := map[string]string{ - "": "bad format for volumes: ", + "": "bad format for path: ", "./": "./ 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: :", + ":": "bad format for path: :", "/tmp:": " is not an absolute path", - ":test": "bad format for volumes: :test", - ":/test": "bad format for volumes: :/test", + ":test": "bad format for path: :test", + ":/test": "bad format for path: :/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::", + ":test:": "bad format for path: :test:", + "::": "bad format for path: ::", + ":::": "bad format for path: :::", + "/tmp:::": "bad format for path: /tmp:::", + ":/tmp::": "bad format for path: :/tmp::", "path:ro": "ro is not an absolute path", + "path:rr": "rr is not an absolute path", + "a:/b:ro": "bad mode specified: ro", + "a:/b:rr": "bad mode specified: rr", } for _, path := range valid { From f2934dbacf37306f20bc53ba8576b73154d22180 Mon Sep 17 00:00:00 2001 From: Brian Goff Date: Fri, 12 Jun 2015 09:25:32 -0400 Subject: [PATCH 047/123] Add volume API/CLI Signed-off-by: Brian Goff --- opts/opts.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/opts/opts.go b/opts/opts.go index aa3de81a4d70..aa8d593b7e7f 100644 --- a/opts/opts.go +++ b/opts/opts.go @@ -131,6 +131,10 @@ func (opts *MapOpts) Set(value string) error { return nil } +func (opts *MapOpts) GetAll() map[string]string { + return opts.values +} + func (opts *MapOpts) String() string { return fmt.Sprintf("%v", map[string]string((opts.values))) } From 409920de5cdf308a2c96edbc287f7fead0b8ccb9 Mon Sep 17 00:00:00 2001 From: Sevki Hasirci Date: Tue, 21 Jul 2015 19:15:36 +0300 Subject: [PATCH 048/123] Opts lint issues, ip and ulimit Signed-off-by: Sevki Hasirci --- opts/ip.go | 17 +++++++++++------ opts/ip_test.go | 4 ++-- opts/ulimit.go | 5 +++++ 3 files changed, 18 insertions(+), 8 deletions(-) diff --git a/opts/ip.go b/opts/ip.go index b1f9587555eb..1268789fb123 100644 --- a/opts/ip.go +++ b/opts/ip.go @@ -5,20 +5,23 @@ import ( "net" ) -// IpOpt type that hold an IP -type IpOpt struct { +// IPOpt type that hold an IP +type IPOpt struct { *net.IP } -func NewIpOpt(ref *net.IP, defaultVal string) *IpOpt { - o := &IpOpt{ +// NewIPOpt returns a new IPOpt from a string of an IP. +func NewIPOpt(ref *net.IP, defaultVal string) *IPOpt { + o := &IPOpt{ IP: ref, } o.Set(defaultVal) return o } -func (o *IpOpt) Set(val string) error { +// Set sets an IPv4 or IPv6 address from a given string. If the given +// string is not parsable as an IP address it will return an error. +func (o *IPOpt) Set(val string) error { ip := net.ParseIP(val) if ip == nil { return fmt.Errorf("%s is not an ip address", val) @@ -27,7 +30,9 @@ func (o *IpOpt) Set(val string) error { return nil } -func (o *IpOpt) String() string { +// String returns the IP address stored in the IPOpt. If IPOpt is a +// nil pointer, it returns an empty string. +func (o *IPOpt) String() string { if *o.IP == nil { return "" } diff --git a/opts/ip_test.go b/opts/ip_test.go index b6b526a578b3..0bbee7616953 100644 --- a/opts/ip_test.go +++ b/opts/ip_test.go @@ -45,9 +45,9 @@ func TestIpOptSetInvalidVal(t *testing.T) { ip := net.IPv4(127, 0, 0, 1) ipOpt := &IpOpt{IP: &ip} - invalidIp := "invalid ip" + invalidIP := "invalid ip" expectedError := "invalid ip is not an ip address" - err := ipOpt.Set(invalidIp) + err := ipOpt.Set(invalidIP) if err == nil || err.Error() != expectedError { t.Fatalf("Expected an Error with [%v], got [%v]", expectedError, err.Error()) } diff --git a/opts/ulimit.go b/opts/ulimit.go index f8d34365f5aa..b41f475bd5b3 100644 --- a/opts/ulimit.go +++ b/opts/ulimit.go @@ -6,10 +6,12 @@ import ( "github.com/docker/docker/pkg/ulimit" ) +// UlimitOpt defines a map of Ulimits type UlimitOpt struct { values *map[string]*ulimit.Ulimit } +// NewUlimitOpt creates a new UlimitOpt func NewUlimitOpt(ref *map[string]*ulimit.Ulimit) *UlimitOpt { if ref == nil { ref = &map[string]*ulimit.Ulimit{} @@ -17,6 +19,7 @@ func NewUlimitOpt(ref *map[string]*ulimit.Ulimit) *UlimitOpt { return &UlimitOpt{ref} } +// Set validates a Ulimit and sets its name as a key in UlimitOpt func (o *UlimitOpt) Set(val string) error { l, err := ulimit.Parse(val) if err != nil { @@ -28,6 +31,7 @@ func (o *UlimitOpt) Set(val string) error { return nil } +// String returns Ulimit values as a string. func (o *UlimitOpt) String() string { var out []string for _, v := range *o.values { @@ -37,6 +41,7 @@ func (o *UlimitOpt) String() string { return fmt.Sprintf("%v", out) } +// GetList returns a slice of pointers to Ulimits. func (o *UlimitOpt) GetList() []*ulimit.Ulimit { var ulimits []*ulimit.Ulimit for _, v := range *o.values { From 1c5cd03b1b22b87e33cd8b7ded269218384c139e Mon Sep 17 00:00:00 2001 From: Sevki Hasirci Date: Tue, 28 Jul 2015 21:18:04 +0300 Subject: [PATCH 049/123] golint: trust contributes to #14756 Signed-off-by: Sevki Hasirci --- opts/ip_test.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/opts/ip_test.go b/opts/ip_test.go index 0bbee7616953..1027d84a057e 100644 --- a/opts/ip_test.go +++ b/opts/ip_test.go @@ -10,7 +10,7 @@ func TestIpOptString(t *testing.T) { var ip net.IP for _, address := range addresses { - stringAddress := NewIpOpt(&ip, address).String() + stringAddress := NewIPOpt(&ip, address).String() if stringAddress != address { t.Fatalf("IpOpt string should be `%s`, not `%s`", address, stringAddress) } @@ -21,7 +21,7 @@ func TestNewIpOptInvalidDefaultVal(t *testing.T) { ip := net.IPv4(127, 0, 0, 1) defaultVal := "Not an ip" - ipOpt := NewIpOpt(&ip, defaultVal) + ipOpt := NewIPOpt(&ip, defaultVal) expected := "127.0.0.1" if ipOpt.String() != expected { @@ -33,7 +33,7 @@ func TestNewIpOptValidDefaultVal(t *testing.T) { ip := net.IPv4(127, 0, 0, 1) defaultVal := "192.168.1.1" - ipOpt := NewIpOpt(&ip, defaultVal) + ipOpt := NewIPOpt(&ip, defaultVal) expected := "192.168.1.1" if ipOpt.String() != expected { @@ -43,7 +43,7 @@ func TestNewIpOptValidDefaultVal(t *testing.T) { func TestIpOptSetInvalidVal(t *testing.T) { ip := net.IPv4(127, 0, 0, 1) - ipOpt := &IpOpt{IP: &ip} + ipOpt := &IPOpt{IP: &ip} invalidIP := "invalid ip" expectedError := "invalid ip is not an ip address" From 59ffb24c8f6efc7c83043b8b4c37cca91e9c0e21 Mon Sep 17 00:00:00 2001 From: Vincent Demeester Date: Thu, 27 Aug 2015 09:33:21 +0200 Subject: [PATCH 050/123] Finish linting opts and trust package. Signed-off-by: Vincent Demeester --- opts/envfile.go | 4 ++-- opts/hosts_unix.go | 1 + opts/hosts_windows.go | 1 + opts/ip.go | 10 ++++---- opts/opts.go | 53 +++++++++++++++++++++++-------------------- 5 files changed, 38 insertions(+), 31 deletions(-) diff --git a/opts/envfile.go b/opts/envfile.go index 1a7284f56c31..a1cfbae5e42e 100644 --- a/opts/envfile.go +++ b/opts/envfile.go @@ -9,13 +9,13 @@ import ( ) var ( - // EnvironmentVariableRegexp A regexp to validate correct environment variables + // EnvironmentVariableRegexp is 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 +// ParseEnvFile reads a file with environment variables enumerated by lines func ParseEnvFile(filename string) ([]string, error) { fh, err := os.Open(filename) if err != nil { diff --git a/opts/hosts_unix.go b/opts/hosts_unix.go index a29335e605a7..611407a9d94b 100644 --- a/opts/hosts_unix.go +++ b/opts/hosts_unix.go @@ -4,4 +4,5 @@ package opts import "fmt" +// DefaultHost constant defines the default host string used by docker on other hosts than Windows var DefaultHost = fmt.Sprintf("unix://%s", DefaultUnixSocket) diff --git a/opts/hosts_windows.go b/opts/hosts_windows.go index 55eac2aaca44..0b5c8a5b8d55 100644 --- a/opts/hosts_windows.go +++ b/opts/hosts_windows.go @@ -4,4 +4,5 @@ package opts import "fmt" +// DefaultHost constant defines the default host string used by docker on Windows var DefaultHost = fmt.Sprintf("tcp://%s:%d", DefaultHTTPHost, DefaultHTTPPort) diff --git a/opts/ip.go b/opts/ip.go index 1268789fb123..d787b56ca6a0 100644 --- a/opts/ip.go +++ b/opts/ip.go @@ -5,12 +5,14 @@ import ( "net" ) -// IPOpt type that hold an IP +// IPOpt holds an IP. It is used to store values from CLI flags. type IPOpt struct { *net.IP } -// NewIPOpt returns a new IPOpt from a string of an IP. +// NewIPOpt creates a new IPOpt from a reference net.IP and a +// string representation of an IP. If the string is not a valid +// IP it will fallback to the specified reference. func NewIPOpt(ref *net.IP, defaultVal string) *IPOpt { o := &IPOpt{ IP: ref, @@ -20,7 +22,7 @@ func NewIPOpt(ref *net.IP, defaultVal string) *IPOpt { } // Set sets an IPv4 or IPv6 address from a given string. If the given -// string is not parsable as an IP address it will return an error. +// string is not parsable as an IP address it returns an error. func (o *IPOpt) Set(val string) error { ip := net.ParseIP(val) if ip == nil { @@ -30,7 +32,7 @@ func (o *IPOpt) Set(val string) error { return nil } -// String returns the IP address stored in the IPOpt. If IPOpt is a +// String returns the IP address stored in the IPOpt. If stored IP is a // nil pointer, it returns an empty string. func (o *IPOpt) String() string { if *o.IP == nil { diff --git a/opts/opts.go b/opts/opts.go index aa8d593b7e7f..392bc4973568 100644 --- a/opts/opts.go +++ b/opts/opts.go @@ -27,18 +27,19 @@ var ( DefaultUnixSocket = "/var/run/docker.sock" ) -// ListOpts type that hold a list of values and a validation function. +// ListOpts holds a list of values and a validation function. type ListOpts struct { values *[]string validator ValidatorFctType } -// NewListOpts Create a new ListOpts with the specified validator. +// NewListOpts creates a new ListOpts with the specified validator. func NewListOpts(validator ValidatorFctType) ListOpts { var values []string return *NewListOptsRef(&values, validator) } +// NewListOptsRef creates a new ListOpts with the specified values and validator. func NewListOptsRef(values *[]string, validator ValidatorFctType) *ListOpts { return &ListOpts{ values: values, @@ -64,7 +65,7 @@ func (opts *ListOpts) Set(value string) error { return nil } -// Delete remove the given element from the slice. +// Delete removes the specified element from the slice. func (opts *ListOpts) Delete(key string) { for i, k := range *opts.values { if k == key { @@ -85,13 +86,13 @@ func (opts *ListOpts) GetMap() map[string]struct{} { return ret } -// GetAll returns the values' slice. +// GetAll returns the values of slice. // FIXME: Can we remove this? func (opts *ListOpts) GetAll() []string { return (*opts.values) } -// Get checks the existence of the given key. +// Get checks the existence of the specified key. func (opts *ListOpts) Get(key string) bool { for _, k := range *opts.values { if k == key { @@ -106,7 +107,7 @@ func (opts *ListOpts) Len() int { return len((*opts.values)) } -//MapOpts type that holds a map of values and a validation function. +//MapOpts holds a map of values and a validation function. type MapOpts struct { values map[string]string validator ValidatorFctType @@ -131,6 +132,7 @@ func (opts *MapOpts) Set(value string) error { return nil } +// GetAll returns the values of MapOpts as a map. func (opts *MapOpts) GetAll() map[string]string { return opts.values } @@ -139,6 +141,7 @@ func (opts *MapOpts) String() string { return fmt.Sprintf("%v", map[string]string((opts.values))) } +// NewMapOpts creates a new MapOpts with the specified map of values and a validator. func NewMapOpts(values map[string]string, validator ValidatorFctType) *MapOpts { if values == nil { values = make(map[string]string) @@ -149,13 +152,13 @@ func NewMapOpts(values map[string]string, validator ValidatorFctType) *MapOpts { } } -// ValidatorFctType validator that return a validate string and/or an error +// ValidatorFctType defines a validator function that returns a validated string and/or an error. type ValidatorFctType func(val string) (string, error) -// ValidatorFctListType validator that return a validate list of string and/or an error +// ValidatorFctListType defines a validator function that returns a validated list of string and/or an error type ValidatorFctListType func(val string) ([]string, error) -// ValidateAttach Validates that the specified string is a valid attach option. +// ValidateAttach validates that the specified string is a valid attach option. func ValidateAttach(val string) (string, error) { s := strings.ToLower(val) for _, str := range []string{"stdin", "stdout", "stderr"} { @@ -166,7 +169,7 @@ func ValidateAttach(val string) (string, error) { return val, fmt.Errorf("valid streams are STDIN, STDOUT and STDERR") } -// ValidateLink Validates that the specified string has a valid link format (containerName:alias). +// ValidateLink validates that the specified string has a valid link format (containerName:alias). func ValidateLink(val string) (string, error) { if _, _, err := parsers.ParseLink(val); err != nil { return val, err @@ -194,18 +197,18 @@ func ValidDeviceMode(mode string) bool { return true } -// ValidateDevice Validate a path for devices +// ValidateDevice validates a path for devices // It will make sure 'val' is in the form: // [host-dir:]container-path[:mode] -// It will also validate the device mode. +// It also validates the device mode. func ValidateDevice(val string) (string, error) { return validatePath(val, ValidDeviceMode) } -// ValidatePath Validate a path for volumes +// ValidatePath validates 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. +// It also validates the mount mode. func ValidatePath(val string) (string, error) { return validatePath(val, volume.ValidMountMode) } @@ -250,8 +253,8 @@ func validatePath(val string, validator func(string) bool) (string, error) { 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. +// ValidateEnv validates an environment variable and returns it. +// It uses 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) { arr := strings.Split(val, "=") @@ -267,7 +270,7 @@ func ValidateEnv(val string) (string, error) { return fmt.Sprintf("%s=%s", val, os.Getenv(val)), nil } -// ValidateIPAddress Validates an Ip address +// ValidateIPAddress validates an Ip address. func ValidateIPAddress(val string) (string, error) { var ip = net.ParseIP(strings.TrimSpace(val)) if ip != nil { @@ -276,7 +279,7 @@ func ValidateIPAddress(val string) (string, error) { return "", fmt.Errorf("%s is not an ip address", val) } -// ValidateMACAddress Validates a MAC address +// ValidateMACAddress validates a MAC address. func ValidateMACAddress(val string) (string, error) { _, err := net.ParseMAC(strings.TrimSpace(val)) if err != nil { @@ -285,8 +288,8 @@ func ValidateMACAddress(val string) (string, error) { return val, nil } -// ValidateDNSSearch Validates domain for resolvconf search configuration. -// A zero length domain is represented by . +// ValidateDNSSearch validates domain for resolvconf search configuration. +// A zero length domain is represented by a dot (.). func ValidateDNSSearch(val string) (string, error) { if val = strings.Trim(val, " "); val == "." { return val, nil @@ -305,8 +308,8 @@ func validateDomain(val string) (string, error) { 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) +// ValidateExtraHost validates that the specified 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) { // allow for IPv6 addresses in extra hosts by only splitting on first ":" arr := strings.SplitN(val, ":", 2) @@ -319,8 +322,8 @@ func ValidateExtraHost(val string) (string, error) { return val, nil } -// ValidateLabel Validate that the given string is a valid label, and returns it -// Labels are in the form on key=value +// ValidateLabel validates that the specified string is a valid label, and returns it. +// Labels are in the form on key=value. func ValidateLabel(val string) (string, error) { if strings.Count(val, "=") < 1 { return "", fmt.Errorf("bad attribute format: %s", val) @@ -328,7 +331,7 @@ func ValidateLabel(val string) (string, error) { return val, nil } -// ValidateHost Validate that the given string is a valid host and returns it +// ValidateHost validates that the specified string is a valid host and returns it. func ValidateHost(val string) (string, error) { host, err := parsers.ParseHost(DefaultHTTPHost, DefaultUnixSocket, val) if err != nil { From 2d2863ebd5bcbb3a5964506ce58f58d13f5e4842 Mon Sep 17 00:00:00 2001 From: Sven Dowideit Date: Sat, 29 Aug 2015 03:05:18 +0000 Subject: [PATCH 051/123] Change ParseTCPAddr to use tcp://127.0.0.0:2375 format as default consistently Signed-off-by: Sven Dowideit --- opts/hosts_windows.go | 4 +--- opts/opts.go | 4 +++- opts/opts_test.go | 3 ++- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/opts/hosts_windows.go b/opts/hosts_windows.go index 0b5c8a5b8d55..ec52e9a70ae5 100644 --- a/opts/hosts_windows.go +++ b/opts/hosts_windows.go @@ -2,7 +2,5 @@ package opts -import "fmt" - // DefaultHost constant defines the default host string used by docker on Windows -var DefaultHost = fmt.Sprintf("tcp://%s:%d", DefaultHTTPHost, DefaultHTTPPort) +var DefaultHost = DefaultTCPHost diff --git a/opts/opts.go b/opts/opts.go index 392bc4973568..2601c00cf09b 100644 --- a/opts/opts.go +++ b/opts/opts.go @@ -25,6 +25,8 @@ var ( // DefaultUnixSocket Path for the unix socket. // Docker daemon by default always listens on the default unix socket DefaultUnixSocket = "/var/run/docker.sock" + // DefaultTCPHost constant defines the default host string used by docker on Windows + DefaultTCPHost = fmt.Sprintf("tcp://%s:%d", DefaultHTTPHost, DefaultHTTPPort) ) // ListOpts holds a list of values and a validation function. @@ -333,7 +335,7 @@ func ValidateLabel(val string) (string, error) { // ValidateHost validates that the specified string is a valid host and returns it. func ValidateHost(val string) (string, error) { - host, err := parsers.ParseHost(DefaultHTTPHost, DefaultUnixSocket, val) + host, err := parsers.ParseDockerDaemonHost(DefaultTCPHost, DefaultUnixSocket, val) if err != nil { return val, err } diff --git a/opts/opts_test.go b/opts/opts_test.go index e3ff970b32c9..48b21f344205 100644 --- a/opts/opts_test.go +++ b/opts/opts_test.go @@ -441,7 +441,6 @@ func TestValidateHost(t *testing.T) { "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", @@ -449,6 +448,8 @@ func TestValidateHost(t *testing.T) { valid := map[string]string{ "fd://": "fd://", "fd://something": "fd://something", + "tcp://host:": "tcp://host:2375", + "tcp://": "tcp://127.0.0.1:2375", "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", From 863be38db7ab6c1b8edd9f042c0b52a898602b59 Mon Sep 17 00:00:00 2001 From: Ma Shimiao Date: Mon, 14 Sep 2015 13:42:33 +0800 Subject: [PATCH 052/123] opts/opts.go: fix typo Signed-off-by: Ma Shimiao --- opts/opts.go | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/opts/opts.go b/opts/opts.go index 2601c00cf09b..330e6b250ddd 100644 --- a/opts/opts.go +++ b/opts/opts.go @@ -223,30 +223,30 @@ func validatePath(val string, validator func(string) bool) (string, error) { return val, fmt.Errorf("bad format for path: %s", val) } - splited := strings.SplitN(val, ":", 3) - if splited[0] == "" { + split := strings.SplitN(val, ":", 3) + if split[0] == "" { return val, fmt.Errorf("bad format for path: %s", val) } - switch len(splited) { + switch len(split) { case 1: - containerPath = splited[0] + containerPath = split[0] val = path.Clean(containerPath) case 2: - if isValid := validator(splited[1]); isValid { - containerPath = splited[0] - mode = splited[1] + if isValid := validator(split[1]); isValid { + containerPath = split[0] + mode = split[1] val = fmt.Sprintf("%s:%s", path.Clean(containerPath), mode) } else { - containerPath = splited[1] - val = fmt.Sprintf("%s:%s", splited[0], path.Clean(containerPath)) + containerPath = split[1] + val = fmt.Sprintf("%s:%s", split[0], path.Clean(containerPath)) } case 3: - containerPath = splited[1] - mode = splited[2] - if isValid := validator(splited[2]); !isValid { + containerPath = split[1] + mode = split[2] + if isValid := validator(split[2]); !isValid { return val, fmt.Errorf("bad mode specified: %s", mode) } - val = fmt.Sprintf("%s:%s:%s", splited[0], containerPath, mode) + val = fmt.Sprintf("%s:%s:%s", split[0], containerPath, mode) } if !path.IsAbs(containerPath) { From aca36e11f2b4688cd8057def6710048521559095 Mon Sep 17 00:00:00 2001 From: Vincent Demeester Date: Mon, 28 Sep 2015 20:26:20 +0200 Subject: [PATCH 053/123] Revert environment regexp from 13694 Signed-off-by: Vincent Demeester --- opts/envfile.go | 28 +++++++++++++++++----------- opts/envfile_test.go | 8 ++++++-- opts/opts.go | 8 ++++---- opts/opts_test.go | 44 ++++++++++++++++---------------------------- 4 files changed, 43 insertions(+), 45 deletions(-) diff --git a/opts/envfile.go b/opts/envfile.go index a1cfbae5e42e..ba8b4f20165d 100644 --- a/opts/envfile.go +++ b/opts/envfile.go @@ -4,18 +4,22 @@ import ( "bufio" "fmt" "os" - "regexp" "strings" ) -var ( - // EnvironmentVariableRegexp is 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 reads a file with environment variables enumerated by lines +// +// ``Environment variable names used by the utilities in the Shell and +// Utilities volume of IEEE Std 1003.1-2001 consist solely of uppercase +// letters, digits, and the '_' (underscore) from the characters defined in +// Portable Character Set and do not begin with a digit. *But*, other +// characters may be permitted by an implementation; applications shall +// tolerate the presence of such names.'' +// -- http://pubs.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap08.html +// +// As of #16585, it's up to application inside docker to validate or not +// environment variables, that's why we just strip leading whitespace and +// nothing more. func ParseEnvFile(filename string) ([]string, error) { fh, err := os.Open(filename) if err != nil { @@ -31,11 +35,13 @@ func ParseEnvFile(filename string) ([]string, error) { // line is not empty, and not starting with '#' if len(line) > 0 && !strings.HasPrefix(line, "#") { data := strings.SplitN(line, "=", 2) - variable := data[0] - if !EnvironmentVariableRegexp.MatchString(variable) { - return []string{}, ErrBadEnvVariable{fmt.Sprintf("variable '%s' is not a valid environment variable", variable)} + // 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)} } + if len(data) > 1 { // pass the value through, no trimming diff --git a/opts/envfile_test.go b/opts/envfile_test.go index b82435155ad2..a172267b5bbb 100644 --- a/opts/envfile_test.go +++ b/opts/envfile_test.go @@ -28,6 +28,8 @@ func TestParseEnvFileGoodFile(t *testing.T) { # comment _foobar=foobaz +with.dots=working +and_underscore=working too ` // Adding a newline + a line with pure whitespace. // This is being done like this instead of the block above @@ -47,6 +49,8 @@ _foobar=foobaz "foo=bar", "baz=quux", "_foobar=foobaz", + "with.dots=working", + "and_underscore=working too", } if !reflect.DeepEqual(lines, expectedLines) { @@ -96,7 +100,7 @@ func TestParseEnvFileBadlyFormattedFile(t *testing.T) { 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" + expectedMessage := "poorly formatted environment: variable 'f ' has white spaces" if err.Error() != expectedMessage { t.Fatalf("Expected [%v], got [%v]", expectedMessage, err.Error()) } @@ -131,7 +135,7 @@ another invalid line` 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" + expectedMessage := "poorly formatted environment: variable 'first line' has white spaces" if err.Error() != expectedMessage { t.Fatalf("Expected [%v], got [%v]", expectedMessage, err.Error()) } diff --git a/opts/opts.go b/opts/opts.go index 330e6b250ddd..6b44e8d801bb 100644 --- a/opts/opts.go +++ b/opts/opts.go @@ -256,16 +256,16 @@ func validatePath(val string, validator func(string) bool) (string, error) { } // ValidateEnv validates an environment variable and returns it. -// It uses EnvironmentVariableRegexp to ensure the name of the environment variable is valid. // If no value is specified, it returns the current value using os.Getenv. +// +// As on ParseEnvFile and related to #16585, environment variable names +// are not validate what so ever, it's up to application inside docker +// to validate them or not. func ValidateEnv(val string) (string, error) { arr := strings.Split(val, "=") if len(arr) > 1 { 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) { return val, nil } diff --git a/opts/opts_test.go b/opts/opts_test.go index 48b21f344205..237b470b951f 100644 --- a/opts/opts_test.go +++ b/opts/opts_test.go @@ -377,35 +377,23 @@ func TestValidateDevice(t *testing.T) { } 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()) - } + "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", + "asd!qwe": "asd!qwe", + "1asd": "1asd", + "123": "123", + "some space": "some space", + " some space before": " some space before", + "some space after ": "some space after ", } for value, expected := range valids { actual, err := ValidateEnv(value) From 6ad0875074f055fe5aea0f628b42a52be52692e0 Mon Sep 17 00:00:00 2001 From: Sven Dowideit Date: Fri, 21 Aug 2015 23:28:49 +1000 Subject: [PATCH 054/123] Default the tcp port to 2376 if tls is on, and 2375 if not Refactor so that the Host flag validation doesn't destroy the user's input, and then post process the flags when we know the TLS options Signed-off-by: Sven Dowideit --- opts/opts.go | 18 ++++++++++++++++++ opts/opts_test.go | 14 +++++++++++--- 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/opts/opts.go b/opts/opts.go index 6b44e8d801bb..48b9d842aa56 100644 --- a/opts/opts.go +++ b/opts/opts.go @@ -17,16 +17,23 @@ var ( 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 daemon -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 daemon -H tcp:// // 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 // pipe as the default on the Windows daemon. + // These are the IANA registered port numbers for use with Docker + // see http://www.iana.org/assignments/service-names-port-numbers/service-names-port-numbers.xhtml?search=docker DefaultHTTPPort = 2375 // Default HTTP Port + // DefaultTLSHTTPPort Default HTTP Port used when TLS enabled + DefaultTLSHTTPPort = 2376 // Default TLS encrypted HTTP Port // DefaultUnixSocket Path for the unix socket. // Docker daemon by default always listens on the default unix socket DefaultUnixSocket = "/var/run/docker.sock" // DefaultTCPHost constant defines the default host string used by docker on Windows DefaultTCPHost = fmt.Sprintf("tcp://%s:%d", DefaultHTTPHost, DefaultHTTPPort) + // DefaultTLSHost constant defines the default host string used by docker for TLS sockets + DefaultTLSHost = fmt.Sprintf("tcp://%s:%d", DefaultHTTPHost, DefaultTLSHTTPPort) ) // ListOpts holds a list of values and a validation function. @@ -335,6 +342,17 @@ func ValidateLabel(val string) (string, error) { // ValidateHost validates that the specified string is a valid host and returns it. func ValidateHost(val string) (string, error) { + _, err := parsers.ParseDockerDaemonHost(DefaultTCPHost, DefaultUnixSocket, val) + if err != nil { + return val, err + } + // Note: unlike most flag validators, we don't return the mutated value here + // we need to know what the user entered later (using ParseHost) to adjust for tls + return val, nil +} + +// ParseHost and set defaults for a Daemon host string +func ParseHost(defaultHTTPHost, val string) (string, error) { host, err := parsers.ParseDockerDaemonHost(DefaultTCPHost, DefaultUnixSocket, val) if err != nil { return val, err diff --git a/opts/opts_test.go b/opts/opts_test.go index 237b470b951f..f1a4bea9f28d 100644 --- a/opts/opts_test.go +++ b/opts/opts_test.go @@ -3,6 +3,7 @@ package opts import ( "fmt" "os" + "runtime" "strings" "testing" ) @@ -423,7 +424,7 @@ func TestValidateLabel(t *testing.T) { } } -func TestValidateHost(t *testing.T) { +func TestParseHost(t *testing.T) { invalid := map[string]string{ "anything": "Invalid bind address format: anything", "something with spaces": "Invalid bind address format: something with spaces", @@ -433,7 +434,14 @@ func TestValidateHost(t *testing.T) { "tcp://invalid": "Invalid bind address format: invalid", "tcp://invalid:port": "Invalid bind address format: invalid:port", } + const defaultHTTPHost = "tcp://127.0.0.1:2375" + var defaultHOST = "unix:///var/run/docker.sock" + + if runtime.GOOS == "windows" { + defaultHOST = defaultHTTPHost + } valid := map[string]string{ + "": defaultHOST, "fd://": "fd://", "fd://something": "fd://something", "tcp://host:": "tcp://host:2375", @@ -450,12 +458,12 @@ func TestValidateHost(t *testing.T) { } for value, errorMessage := range invalid { - if _, err := ValidateHost(value); err == nil || err.Error() != errorMessage { + if _, err := ParseHost(defaultHTTPHost, 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 { + if actual, err := ParseHost(defaultHTTPHost, value); err != nil || actual != expected { t.Fatalf("Expected for %v [%v], got [%v, %v]", value, expected, actual, err) } } From 1398bd9861dae7ccd0ad295b3c311cd0991d0966 Mon Sep 17 00:00:00 2001 From: Doug Davis Date: Sun, 11 Oct 2015 20:45:17 -0700 Subject: [PATCH 055/123] Remove used param on ParseHost The first param on opts.ParseHost() wasn't being used for anything. Once we get rid of that param we can then also clean-up some code that calls ParseHost() because the param that was passed in wasn't being used for anything else. Signed-off-by: Doug Davis --- opts/opts.go | 2 +- opts/opts_test.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/opts/opts.go b/opts/opts.go index 48b9d842aa56..78059739a860 100644 --- a/opts/opts.go +++ b/opts/opts.go @@ -352,7 +352,7 @@ func ValidateHost(val string) (string, error) { } // ParseHost and set defaults for a Daemon host string -func ParseHost(defaultHTTPHost, val string) (string, error) { +func ParseHost(val string) (string, error) { host, err := parsers.ParseDockerDaemonHost(DefaultTCPHost, DefaultUnixSocket, val) if err != nil { return val, err diff --git a/opts/opts_test.go b/opts/opts_test.go index f1a4bea9f28d..0e4e9562e79a 100644 --- a/opts/opts_test.go +++ b/opts/opts_test.go @@ -458,12 +458,12 @@ func TestParseHost(t *testing.T) { } for value, errorMessage := range invalid { - if _, err := ParseHost(defaultHTTPHost, value); err == nil || err.Error() != errorMessage { + if _, err := ParseHost(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 := ParseHost(defaultHTTPHost, value); err != nil || actual != expected { + if actual, err := ParseHost(value); err != nil || actual != expected { t.Fatalf("Expected for %v [%v], got [%v, %v]", value, expected, actual, err) } } From 74d0203af05460f040a02352dcddc9e69f94975c Mon Sep 17 00:00:00 2001 From: Lei Jitang Date: Mon, 19 Oct 2015 21:17:37 +0800 Subject: [PATCH 056/123] Make default tls host work Signed-off-by: Lei Jitang --- opts/opts.go | 8 ++++---- opts/opts_test.go | 10 +++++----- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/opts/opts.go b/opts/opts.go index 78059739a860..3d8e4b481623 100644 --- a/opts/opts.go +++ b/opts/opts.go @@ -16,7 +16,7 @@ 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 Default HTTP Host used if only port is provided to -H flag e.g. docker daemon -H tcp://:8080 - DefaultHTTPHost = "127.0.0.1" + DefaultHTTPHost = "localhost" // DefaultHTTPPort Default HTTP Port used if only the protocol is provided to -H flag e.g. docker daemon -H tcp:// // TODO Windows. DefaultHTTPPort is only used on Windows if a -H parameter @@ -342,7 +342,7 @@ func ValidateLabel(val string) (string, error) { // ValidateHost validates that the specified string is a valid host and returns it. func ValidateHost(val string) (string, error) { - _, err := parsers.ParseDockerDaemonHost(DefaultTCPHost, DefaultUnixSocket, val) + _, err := parsers.ParseDockerDaemonHost(DefaultTCPHost, DefaultTLSHost, DefaultUnixSocket, "", val) if err != nil { return val, err } @@ -352,8 +352,8 @@ func ValidateHost(val string) (string, error) { } // ParseHost and set defaults for a Daemon host string -func ParseHost(val string) (string, error) { - host, err := parsers.ParseDockerDaemonHost(DefaultTCPHost, DefaultUnixSocket, val) +func ParseHost(defaultHost, val string) (string, error) { + host, err := parsers.ParseDockerDaemonHost(DefaultTCPHost, DefaultTLSHost, DefaultUnixSocket, defaultHost, val) if err != nil { return val, err } diff --git a/opts/opts_test.go b/opts/opts_test.go index 0e4e9562e79a..baf5f53362af 100644 --- a/opts/opts_test.go +++ b/opts/opts_test.go @@ -445,9 +445,9 @@ func TestParseHost(t *testing.T) { "fd://": "fd://", "fd://something": "fd://something", "tcp://host:": "tcp://host:2375", - "tcp://": "tcp://127.0.0.1:2375", - "tcp://:2375": "tcp://127.0.0.1:2375", // default ip address - "tcp://:2376": "tcp://127.0.0.1:2376", // default ip address + "tcp://": "tcp://localhost:2375", + "tcp://:2375": "tcp://localhost:2375", // default ip address + "tcp://:2376": "tcp://localhost: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", @@ -458,12 +458,12 @@ func TestParseHost(t *testing.T) { } for value, errorMessage := range invalid { - if _, err := ParseHost(value); err == nil || err.Error() != errorMessage { + if _, err := ParseHost(defaultHTTPHost, 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 := ParseHost(value); err != nil || actual != expected { + if actual, err := ParseHost(defaultHTTPHost, value); err != nil || actual != expected { t.Fatalf("Expected for %v [%v], got [%v, %v]", value, expected, actual, err) } } From af6e54516488fb0707d220f4e01f261df835ff27 Mon Sep 17 00:00:00 2001 From: John Howard Date: Wed, 9 Sep 2015 19:23:06 -0700 Subject: [PATCH 057/123] Windows: Add volume support Signed-off-by: John Howard --- opts/opts.go | 9 -------- opts/opts_test.go | 52 ----------------------------------------------- 2 files changed, 61 deletions(-) diff --git a/opts/opts.go b/opts/opts.go index 3d8e4b481623..46adef1d8580 100644 --- a/opts/opts.go +++ b/opts/opts.go @@ -9,7 +9,6 @@ import ( "strings" "github.com/docker/docker/pkg/parsers" - "github.com/docker/docker/volume" ) var ( @@ -214,14 +213,6 @@ func ValidateDevice(val string) (string, error) { return validatePath(val, ValidDeviceMode) } -// ValidatePath validates a path for volumes -// It will make sure 'val' is in the form: -// [host-dir:]container-path[:rw|ro] -// It also validates the mount mode. -func ValidatePath(val string) (string, error) { - return validatePath(val, volume.ValidMountMode) -} - func validatePath(val string, validator func(string) bool) (string, error) { var containerPath string var mode string diff --git a/opts/opts_test.go b/opts/opts_test.go index baf5f53362af..e02d3f8ea61c 100644 --- a/opts/opts_test.go +++ b/opts/opts_test.go @@ -274,58 +274,6 @@ func TestValidateLink(t *testing.T) { } } -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 path: ", - "./": "./ 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 path: :", - "/tmp:": " is not an absolute path", - ":test": "bad format for path: :test", - ":/test": "bad format for path: :/test", - "tmp:": " is not an absolute path", - ":test:": "bad format for path: :test:", - "::": "bad format for path: ::", - ":::": "bad format for path: :::", - "/tmp:::": "bad format for path: /tmp:::", - ":/tmp::": "bad format for path: :/tmp::", - "path:ro": "path is not an absolute path", - "/path:/path:sw": "bad mode specified: sw", - "/path:/path:rwz": "bad 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", From 36030a9a8b26b189eecea2611933b1d9331c28b5 Mon Sep 17 00:00:00 2001 From: Shijiang Wei Date: Sun, 30 Aug 2015 21:48:03 +0800 Subject: [PATCH 058/123] Add ability to add multiple tags with docker build Signed-off-by: Shijiang Wei --- opts/opts.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/opts/opts.go b/opts/opts.go index 46adef1d8580..ebfcfd1fc770 100644 --- a/opts/opts.go +++ b/opts/opts.go @@ -85,7 +85,6 @@ func (opts *ListOpts) Delete(key string) { // GetMap returns the content of values in a map in order to avoid // duplicates. -// FIXME: can we remove this? func (opts *ListOpts) GetMap() map[string]struct{} { ret := make(map[string]struct{}) for _, k := range *opts.values { @@ -95,7 +94,6 @@ func (opts *ListOpts) GetMap() map[string]struct{} { } // GetAll returns the values of slice. -// FIXME: Can we remove this? func (opts *ListOpts) GetAll() []string { return (*opts.values) } From d02ac2a69448451b5c33bcef754b34b2d1071220 Mon Sep 17 00:00:00 2001 From: John Howard Date: Fri, 6 Nov 2015 13:52:59 -0800 Subject: [PATCH 059/123] Windows [TP4] localhost mitigation Signed-off-by: John Howard --- opts/opts.go | 3 --- opts/opts_unix.go | 6 +++++ opts/opts_windows.go | 56 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 62 insertions(+), 3 deletions(-) create mode 100644 opts/opts_unix.go create mode 100644 opts/opts_windows.go diff --git a/opts/opts.go b/opts/opts.go index ebfcfd1fc770..a61b82cdee6e 100644 --- a/opts/opts.go +++ b/opts/opts.go @@ -14,9 +14,6 @@ import ( 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 Default HTTP Host used if only port is provided to -H flag e.g. docker daemon -H tcp://:8080 - DefaultHTTPHost = "localhost" - // DefaultHTTPPort Default HTTP Port used if only the protocol is provided to -H flag e.g. docker daemon -H tcp:// // 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 diff --git a/opts/opts_unix.go b/opts/opts_unix.go new file mode 100644 index 000000000000..f1ce844a8f60 --- /dev/null +++ b/opts/opts_unix.go @@ -0,0 +1,6 @@ +// +build !windows + +package opts + +// DefaultHTTPHost Default HTTP Host used if only port is provided to -H flag e.g. docker daemon -H tcp://:8080 +const DefaultHTTPHost = "localhost" diff --git a/opts/opts_windows.go b/opts/opts_windows.go new file mode 100644 index 000000000000..b9ff2bae6408 --- /dev/null +++ b/opts/opts_windows.go @@ -0,0 +1,56 @@ +package opts + +// TODO Windows. Identify bug in GOLang 1.5.1 and/or Windows Server 2016 TP4. +// @jhowardmsft, @swernli. +// +// On Windows, this mitigates a problem with the default options of running +// a docker client against a local docker daemon on TP4. +// +// What was found that if the default host is "localhost", even if the client +// (and daemon as this is local) is not physically on a network, and the DNS +// cache is flushed (ipconfig /flushdns), then the client will pause for +// exactly one second when connecting to the daemon for calls. For example +// using docker run windowsservercore cmd, the CLI will send a create followed +// by an attach. You see the delay between the attach finishing and the attach +// being seen by the daemon. +// +// Here's some daemon debug logs with additional debug spew put in. The +// AfterWriteJSON log is the very last thing the daemon does as part of the +// create call. The POST /attach is the second CLI call. Notice the second +// time gap. +// +// time="2015-11-06T13:38:37.259627400-08:00" level=debug msg="After createRootfs" +// time="2015-11-06T13:38:37.263626300-08:00" level=debug msg="After setHostConfig" +// time="2015-11-06T13:38:37.267631200-08:00" level=debug msg="before createContainerPl...." +// time="2015-11-06T13:38:37.271629500-08:00" level=debug msg=toDiskLocking.... +// time="2015-11-06T13:38:37.275643200-08:00" level=debug msg="loggin event...." +// time="2015-11-06T13:38:37.277627600-08:00" level=debug msg="logged event...." +// time="2015-11-06T13:38:37.279631800-08:00" level=debug msg="In defer func" +// time="2015-11-06T13:38:37.282628100-08:00" level=debug msg="After daemon.create" +// time="2015-11-06T13:38:37.286651700-08:00" level=debug msg="return 2" +// time="2015-11-06T13:38:37.289629500-08:00" level=debug msg="Returned from daemon.ContainerCreate" +// time="2015-11-06T13:38:37.311629100-08:00" level=debug msg="After WriteJSON" +// ... 1 second gap here.... +// time="2015-11-06T13:38:38.317866200-08:00" level=debug msg="Calling POST /v1.22/containers/984758282b842f779e805664b2c95d563adc9a979c8a3973e68c807843ee4757/attach" +// time="2015-11-06T13:38:38.326882500-08:00" level=info msg="POST /v1.22/containers/984758282b842f779e805664b2c95d563adc9a979c8a3973e68c807843ee4757/attach?stderr=1&stdin=1&stdout=1&stream=1" +// +// We suspect this is either a bug introduced in GOLang 1.5.1, or that a change +// in GOLang 1.5.1 (from 1.4.3) is exposing a bug in Windows TP4. In theory, +// the Windows networking stack is supposed to resolve "localhost" internally, +// without hitting DNS, or even reading the hosts file (which is why localhost +// is commented out in the hosts file on Windows). +// +// We have validated that working around this using the actual IPv4 localhost +// address does not cause the delay. +// +// This does not occur with the docker client built with 1.4.3 on the same +// Windows TP4 build, regardless of whether the daemon is built using 1.5.1 +// or 1.4.3. It does not occur on Linux. We also verified we see the same thing +// on a cross-compiled Windows binary (from Linux). +// +// Final note: This is a mitigation, not a 'real' fix. It is still susceptible +// to the delay in TP4 if a user were to do 'docker run -H=tcp://localhost:2375...' +// explicitly. + +// DefaultHTTPHost Default HTTP Host used if only port is provided to -H flag e.g. docker daemon -H tcp://:8080 +const DefaultHTTPHost = "127.0.0.1" From 8c734c8cde079e6b0178f7e4af0146ca49f06dff Mon Sep 17 00:00:00 2001 From: David Calavera Date: Fri, 6 Nov 2015 17:22:48 -0500 Subject: [PATCH 060/123] Use an empty slice as default value for DNS, DNSSearch and DNSOptions So we don't print those in the client and we don't fail executing inspect templates with API field names. Make sure those fields are initialized as empty slices when a container is loaded from disk and their values are nil. Signed-off-by: David Calavera --- opts/opts.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/opts/opts.go b/opts/opts.go index a61b82cdee6e..ed93002b2414 100644 --- a/opts/opts.go +++ b/opts/opts.go @@ -95,6 +95,16 @@ func (opts *ListOpts) GetAll() []string { return (*opts.values) } +// GetAllOrEmpty returns the values of the slice +// or an empty slice when there are no values. +func (opts *ListOpts) GetAllOrEmpty() []string { + v := *opts.values + if v == nil { + return make([]string, 0) + } + return v +} + // Get checks the existence of the specified key. func (opts *ListOpts) Get(key string) bool { for _, k := range *opts.values { From 8a67a18fd8f87f8155b212baf87d8f85d459e617 Mon Sep 17 00:00:00 2001 From: Ma Shimiao Date: Fri, 12 Jun 2015 08:34:20 +0800 Subject: [PATCH 061/123] Add support for blkio.weight_device Signed-off-by: Ma Shimiao --- opts/opts.go | 28 ++++++++++++++++++++++ opts/weightdevice.go | 56 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 84 insertions(+) create mode 100644 opts/weightdevice.go diff --git a/opts/opts.go b/opts/opts.go index ed93002b2414..b0271cda4cb5 100644 --- a/opts/opts.go +++ b/opts/opts.go @@ -6,8 +6,10 @@ import ( "os" "path" "regexp" + "strconv" "strings" + "github.com/docker/docker/pkg/blkiodev" "github.com/docker/docker/pkg/parsers" ) @@ -168,6 +170,9 @@ func NewMapOpts(values map[string]string, validator ValidatorFctType) *MapOpts { // ValidatorFctType defines a validator function that returns a validated string and/or an error. type ValidatorFctType func(val string) (string, error) +// ValidatorWeightFctType defines a validator function that returns a validated struct and/or an error. +type ValidatorWeightFctType func(val string) (*blkiodev.WeightDevice, error) + // ValidatorFctListType defines a validator function that returns a validated list of string and/or an error type ValidatorFctListType func(val string) ([]string, error) @@ -182,6 +187,29 @@ func ValidateAttach(val string) (string, error) { return val, fmt.Errorf("valid streams are STDIN, STDOUT and STDERR") } +// ValidateWeightDevice validates that the specified string has a valid device-weight format. +func ValidateWeightDevice(val string) (*blkiodev.WeightDevice, error) { + split := strings.SplitN(val, ":", 2) + if len(split) != 2 { + return nil, fmt.Errorf("bad format: %s", val) + } + if !strings.HasPrefix(split[0], "/dev/") { + return nil, fmt.Errorf("bad format for device path: %s", val) + } + weight, err := strconv.ParseUint(split[1], 10, 0) + if err != nil { + return nil, fmt.Errorf("invalid weight for device: %s", val) + } + if weight > 0 && (weight < 10 || weight > 1000) { + return nil, fmt.Errorf("invalid weight for device: %s", val) + } + + return &blkiodev.WeightDevice{ + Path: split[0], + Weight: uint16(weight), + }, nil +} + // ValidateLink validates that the specified string has a valid link format (containerName:alias). func ValidateLink(val string) (string, error) { if _, _, err := parsers.ParseLink(val); err != nil { diff --git a/opts/weightdevice.go b/opts/weightdevice.go new file mode 100644 index 000000000000..2b0d6e38c98c --- /dev/null +++ b/opts/weightdevice.go @@ -0,0 +1,56 @@ +package opts + +import ( + "fmt" + + "github.com/docker/docker/pkg/blkiodev" +) + +// WeightdeviceOpt defines a map of WeightDevices +type WeightdeviceOpt struct { + values []*blkiodev.WeightDevice + validator ValidatorWeightFctType +} + +// NewWeightdeviceOpt creates a new WeightdeviceOpt +func NewWeightdeviceOpt(validator ValidatorWeightFctType) WeightdeviceOpt { + values := []*blkiodev.WeightDevice{} + return WeightdeviceOpt{ + values: values, + validator: validator, + } +} + +// Set validates a WeightDevice and sets its name as a key in WeightdeviceOpt +func (opt *WeightdeviceOpt) Set(val string) error { + var value *blkiodev.WeightDevice + if opt.validator != nil { + v, err := opt.validator(val) + if err != nil { + return err + } + value = v + } + (opt.values) = append((opt.values), value) + return nil +} + +// String returns Ulimit values as a string. +func (opt *WeightdeviceOpt) String() string { + var out []string + for _, v := range opt.values { + out = append(out, v.String()) + } + + return fmt.Sprintf("%v", out) +} + +// GetList returns a slice of pointers to WeightDevices. +func (opt *WeightdeviceOpt) GetList() []*blkiodev.WeightDevice { + var weightdevice []*blkiodev.WeightDevice + for _, v := range opt.values { + weightdevice = append(weightdevice, v) + } + + return weightdevice +} From 35c223629bb71618d2c8ac82a0dc3baf67ad3a12 Mon Sep 17 00:00:00 2001 From: Ma Shimiao Date: Thu, 19 Nov 2015 13:01:58 +0800 Subject: [PATCH 062/123] opts/weightdevice: fix typo Signed-off-by: Ma Shimiao --- opts/weightdevice.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opts/weightdevice.go b/opts/weightdevice.go index 2b0d6e38c98c..4c6288f6afc4 100644 --- a/opts/weightdevice.go +++ b/opts/weightdevice.go @@ -35,7 +35,7 @@ func (opt *WeightdeviceOpt) Set(val string) error { return nil } -// String returns Ulimit values as a string. +// String returns WeightdeviceOpt values as a string. func (opt *WeightdeviceOpt) String() string { var out []string for _, v := range opt.values { From 9837646cd171ad7fe145aacd304a273fb1812297 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Thu, 12 Nov 2015 11:55:17 -0800 Subject: [PATCH 063/123] Move Container to its own package. So other packages don't need to import the daemon package when they want to use this struct. Signed-off-by: David Calavera Signed-off-by: Tibor Vass --- opts/opts_windows.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opts/opts_windows.go b/opts/opts_windows.go index b9ff2bae6408..2a9e2be7447e 100644 --- a/opts/opts_windows.go +++ b/opts/opts_windows.go @@ -22,7 +22,7 @@ package opts // time="2015-11-06T13:38:37.259627400-08:00" level=debug msg="After createRootfs" // time="2015-11-06T13:38:37.263626300-08:00" level=debug msg="After setHostConfig" // time="2015-11-06T13:38:37.267631200-08:00" level=debug msg="before createContainerPl...." -// time="2015-11-06T13:38:37.271629500-08:00" level=debug msg=toDiskLocking.... +// time="2015-11-06T13:38:37.271629500-08:00" level=debug msg=ToDiskLocking.... // time="2015-11-06T13:38:37.275643200-08:00" level=debug msg="loggin event...." // time="2015-11-06T13:38:37.277627600-08:00" level=debug msg="logged event...." // time="2015-11-06T13:38:37.279631800-08:00" level=debug msg="In defer func" From 02a6d3c5e6849d9c622a290d85e2ba5dc4512cf7 Mon Sep 17 00:00:00 2001 From: Ma Shimiao Date: Wed, 8 Jul 2015 19:06:48 +0800 Subject: [PATCH 064/123] Add support for blkio read/write bps device Signed-off-by: Ma Shimiao --- opts/opts.go | 27 ++++++++++++++++++++ opts/throttledevice.go | 56 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 83 insertions(+) create mode 100644 opts/throttledevice.go diff --git a/opts/opts.go b/opts/opts.go index b0271cda4cb5..6c75a9c9b115 100644 --- a/opts/opts.go +++ b/opts/opts.go @@ -11,6 +11,7 @@ import ( "github.com/docker/docker/pkg/blkiodev" "github.com/docker/docker/pkg/parsers" + "github.com/docker/docker/pkg/units" ) var ( @@ -173,6 +174,9 @@ type ValidatorFctType func(val string) (string, error) // ValidatorWeightFctType defines a validator function that returns a validated struct and/or an error. type ValidatorWeightFctType func(val string) (*blkiodev.WeightDevice, error) +// ValidatorThrottleFctType defines a validator function that returns a validated struct and/or an error. +type ValidatorThrottleFctType func(val string) (*blkiodev.ThrottleDevice, error) + // ValidatorFctListType defines a validator function that returns a validated list of string and/or an error type ValidatorFctListType func(val string) ([]string, error) @@ -210,6 +214,29 @@ func ValidateWeightDevice(val string) (*blkiodev.WeightDevice, error) { }, nil } +// ValidateThrottleBpsDevice validates that the specified string has a valid device-rate format. +func ValidateThrottleBpsDevice(val string) (*blkiodev.ThrottleDevice, error) { + split := strings.SplitN(val, ":", 2) + if len(split) != 2 { + return nil, fmt.Errorf("bad format: %s", val) + } + if !strings.HasPrefix(split[0], "/dev/") { + return nil, fmt.Errorf("bad format for device path: %s", val) + } + rate, err := units.RAMInBytes(split[1]) + if err != nil { + return nil, fmt.Errorf("invalid rate for device: %s. The correct format is :[]. Number must be a positive integer. Unit is optional and can be kb, mb, or gb", val) + } + if rate < 0 { + return nil, fmt.Errorf("invalid rate for device: %s. The correct format is :[]. Number must be a positive integer. Unit is optional and can be kb, mb, or gb", val) + } + + return &blkiodev.ThrottleDevice{ + Path: split[0], + Rate: uint64(rate), + }, nil +} + // ValidateLink validates that the specified string has a valid link format (containerName:alias). func ValidateLink(val string) (string, error) { if _, _, err := parsers.ParseLink(val); err != nil { diff --git a/opts/throttledevice.go b/opts/throttledevice.go new file mode 100644 index 000000000000..fb1180232692 --- /dev/null +++ b/opts/throttledevice.go @@ -0,0 +1,56 @@ +package opts + +import ( + "fmt" + + "github.com/docker/docker/pkg/blkiodev" +) + +// ThrottledeviceOpt defines a map of ThrottleDevices +type ThrottledeviceOpt struct { + values []*blkiodev.ThrottleDevice + validator ValidatorThrottleFctType +} + +// NewThrottledeviceOpt creates a new ThrottledeviceOpt +func NewThrottledeviceOpt(validator ValidatorThrottleFctType) ThrottledeviceOpt { + values := []*blkiodev.ThrottleDevice{} + return ThrottledeviceOpt{ + values: values, + validator: validator, + } +} + +// Set validates a ThrottleDevice and sets its name as a key in ThrottledeviceOpt +func (opt *ThrottledeviceOpt) Set(val string) error { + var value *blkiodev.ThrottleDevice + if opt.validator != nil { + v, err := opt.validator(val) + if err != nil { + return err + } + value = v + } + (opt.values) = append((opt.values), value) + return nil +} + +// String returns ThrottledeviceOpt values as a string. +func (opt *ThrottledeviceOpt) String() string { + var out []string + for _, v := range opt.values { + out = append(out, v.String()) + } + + return fmt.Sprintf("%v", out) +} + +// GetList returns a slice of pointers to ThrottleDevices. +func (opt *ThrottledeviceOpt) GetList() []*blkiodev.ThrottleDevice { + var throttledevice []*blkiodev.ThrottleDevice + for _, v := range opt.values { + throttledevice = append(throttledevice, v) + } + + return throttledevice +} From f599afe64efceb8e892b764a2f31d7892231c462 Mon Sep 17 00:00:00 2001 From: Justas Brazauskas Date: Sun, 13 Dec 2015 18:00:39 +0200 Subject: [PATCH 065/123] Fix typos found across repository Signed-off-by: Justas Brazauskas --- opts/envfile_test.go | 2 +- opts/ip.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/opts/envfile_test.go b/opts/envfile_test.go index a172267b5bbb..a2e2200fa1bc 100644 --- a/opts/envfile_test.go +++ b/opts/envfile_test.go @@ -106,7 +106,7 @@ func TestParseEnvFileBadlyFormattedFile(t *testing.T) { } } -// Test ParseEnvFile for a file with a line exeeding bufio.MaxScanTokenSize +// Test ParseEnvFile for a file with a line exceeding bufio.MaxScanTokenSize func TestParseEnvFileLineTooLongFile(t *testing.T) { content := strings.Repeat("a", bufio.MaxScanTokenSize+42) content = fmt.Sprint("foo=", content) diff --git a/opts/ip.go b/opts/ip.go index d787b56ca6a0..c7b0dc99473a 100644 --- a/opts/ip.go +++ b/opts/ip.go @@ -22,7 +22,7 @@ func NewIPOpt(ref *net.IP, defaultVal string) *IPOpt { } // Set sets an IPv4 or IPv6 address from a given string. If the given -// string is not parsable as an IP address it returns an error. +// string is not parseable as an IP address it returns an error. func (o *IPOpt) Set(val string) error { ip := net.ParseIP(val) if ip == nil { From 7dc7bff9a0ef954e4f942a2bb851239766972abb Mon Sep 17 00:00:00 2001 From: Daniel Nephin Date: Tue, 15 Dec 2015 20:53:17 -0500 Subject: [PATCH 066/123] Move ParseDockerDaemonHost to opts/ package. This function was only being used from a single place opts/opts.go. This change moves it from a incohesive package (parsers) to the single place it is used. Also made a bunch of the helper methods private because they are not used by any external modules. Signed-off-by: Daniel Nephin --- opts/hosts.go | 146 ++++++++++++++++++++++++++++++++++++++++ opts/hosts_test.go | 164 +++++++++++++++++++++++++++++++++++++++++++++ opts/opts.go | 36 ---------- opts/opts_test.go | 46 ------------- 4 files changed, 310 insertions(+), 82 deletions(-) create mode 100644 opts/hosts.go create mode 100644 opts/hosts_test.go diff --git a/opts/hosts.go b/opts/hosts.go new file mode 100644 index 000000000000..d1b69854152d --- /dev/null +++ b/opts/hosts.go @@ -0,0 +1,146 @@ +package opts + +import ( + "fmt" + "net" + "net/url" + "runtime" + "strconv" + "strings" +) + +var ( + // DefaultHTTPPort Default HTTP Port used if only the protocol is provided to -H flag e.g. docker daemon -H tcp:// + // 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 + // pipe as the default on the Windows daemon. + // These are the IANA registered port numbers for use with Docker + // see http://www.iana.org/assignments/service-names-port-numbers/service-names-port-numbers.xhtml?search=docker + DefaultHTTPPort = 2375 // Default HTTP Port + // DefaultTLSHTTPPort Default HTTP Port used when TLS enabled + DefaultTLSHTTPPort = 2376 // Default TLS encrypted HTTP Port + // DefaultUnixSocket Path for the unix socket. + // Docker daemon by default always listens on the default unix socket + DefaultUnixSocket = "/var/run/docker.sock" + // DefaultTCPHost constant defines the default host string used by docker on Windows + DefaultTCPHost = fmt.Sprintf("tcp://%s:%d", DefaultHTTPHost, DefaultHTTPPort) + // DefaultTLSHost constant defines the default host string used by docker for TLS sockets + DefaultTLSHost = fmt.Sprintf("tcp://%s:%d", DefaultHTTPHost, DefaultTLSHTTPPort) +) + +// ValidateHost validates that the specified string is a valid host and returns it. +func ValidateHost(val string) (string, error) { + _, err := parseDockerDaemonHost(DefaultTCPHost, DefaultTLSHost, DefaultUnixSocket, "", val) + if err != nil { + return val, err + } + // Note: unlike most flag validators, we don't return the mutated value here + // we need to know what the user entered later (using ParseHost) to adjust for tls + return val, nil +} + +// ParseHost and set defaults for a Daemon host string +func ParseHost(defaultHost, val string) (string, error) { + host, err := parseDockerDaemonHost(DefaultTCPHost, DefaultTLSHost, DefaultUnixSocket, defaultHost, val) + if err != nil { + return val, err + } + return host, nil +} + +// parseDockerDaemonHost parses the specified address and returns an address that will be used as the host. +// Depending of the address specified, will use the defaultTCPAddr or defaultUnixAddr +// defaultUnixAddr must be a absolute file path (no `unix://` prefix) +// defaultTCPAddr must be the full `tcp://host:port` form +func parseDockerDaemonHost(defaultTCPAddr, defaultTLSHost, defaultUnixAddr, defaultAddr, addr string) (string, error) { + addr = strings.TrimSpace(addr) + if addr == "" { + if defaultAddr == defaultTLSHost { + return defaultTLSHost, nil + } + if runtime.GOOS != "windows" { + return fmt.Sprintf("unix://%s", defaultUnixAddr), nil + } + return defaultTCPAddr, nil + } + addrParts := strings.Split(addr, "://") + if len(addrParts) == 1 { + addrParts = []string{"tcp", addrParts[0]} + } + + switch addrParts[0] { + case "tcp": + return parseTCPAddr(addrParts[1], defaultTCPAddr) + case "unix": + return parseUnixAddr(addrParts[1], defaultUnixAddr) + case "fd": + return addr, nil + default: + return "", fmt.Errorf("Invalid bind address format: %s", addr) + } +} + +// parseUnixAddr parses and validates that the specified address is a valid UNIX +// socket address. It returns a formatted UNIX socket address, either using the +// address parsed from addr, or the contents of defaultAddr if addr is a blank +// string. +func parseUnixAddr(addr string, defaultAddr string) (string, error) { + addr = strings.TrimPrefix(addr, "unix://") + if strings.Contains(addr, "://") { + return "", fmt.Errorf("Invalid proto, expected unix: %s", addr) + } + if addr == "" { + addr = defaultAddr + } + return fmt.Sprintf("unix://%s", addr), nil +} + +// parseTCPAddr parses and validates that the specified address is a valid TCP +// address. It returns a formatted TCP address, either using the address parsed +// from tryAddr, or the contents of defaultAddr if tryAddr is a blank string. +// tryAddr is expected to have already been Trim()'d +// defaultAddr must be in the full `tcp://host:port` form +func parseTCPAddr(tryAddr string, defaultAddr string) (string, error) { + if tryAddr == "" || tryAddr == "tcp://" { + return defaultAddr, nil + } + addr := strings.TrimPrefix(tryAddr, "tcp://") + if strings.Contains(addr, "://") || addr == "" { + return "", fmt.Errorf("Invalid proto, expected tcp: %s", tryAddr) + } + + defaultAddr = strings.TrimPrefix(defaultAddr, "tcp://") + defaultHost, defaultPort, err := net.SplitHostPort(defaultAddr) + if err != nil { + return "", err + } + // url.Parse fails for trailing colon on IPv6 brackets on Go 1.5, but + // not 1.4. See https://github.com/golang/go/issues/12200 and + // https://github.com/golang/go/issues/6530. + if strings.HasSuffix(addr, "]:") { + addr += defaultPort + } + + u, err := url.Parse("tcp://" + addr) + if err != nil { + return "", err + } + + host, port, err := net.SplitHostPort(u.Host) + if err != nil { + return "", fmt.Errorf("Invalid bind address format: %s", tryAddr) + } + + if host == "" { + host = defaultHost + } + if port == "" { + port = defaultPort + } + p, err := strconv.Atoi(port) + if err != nil && p == 0 { + return "", fmt.Errorf("Invalid bind address format: %s", tryAddr) + } + + return fmt.Sprintf("tcp://%s%s", net.JoinHostPort(host, port), u.Path), nil +} diff --git a/opts/hosts_test.go b/opts/hosts_test.go new file mode 100644 index 000000000000..e497e2865643 --- /dev/null +++ b/opts/hosts_test.go @@ -0,0 +1,164 @@ +package opts + +import ( + "runtime" + "testing" +) + +func TestParseHost(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://:port": "Invalid bind address format: :port", + "tcp://invalid": "Invalid bind address format: invalid", + "tcp://invalid:port": "Invalid bind address format: invalid:port", + } + const defaultHTTPHost = "tcp://127.0.0.1:2375" + var defaultHOST = "unix:///var/run/docker.sock" + + if runtime.GOOS == "windows" { + defaultHOST = defaultHTTPHost + } + valid := map[string]string{ + "": defaultHOST, + "fd://": "fd://", + "fd://something": "fd://something", + "tcp://host:": "tcp://host:2375", + "tcp://": "tcp://localhost:2375", + "tcp://:2375": "tcp://localhost:2375", // default ip address + "tcp://:2376": "tcp://localhost: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 := ParseHost(defaultHTTPHost, 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 := ParseHost(defaultHTTPHost, value); err != nil || actual != expected { + t.Fatalf("Expected for %v [%v], got [%v, %v]", value, expected, actual, err) + } + } +} + +func TestParseDockerDaemonHost(t *testing.T) { + var ( + defaultHTTPHost = "tcp://localhost:2375" + defaultHTTPSHost = "tcp://localhost:2376" + defaultUnix = "/var/run/docker.sock" + defaultHOST = "unix:///var/run/docker.sock" + ) + if runtime.GOOS == "windows" { + defaultHOST = defaultHTTPHost + } + invalids := map[string]string{ + "0.0.0.0": "Invalid bind address format: 0.0.0.0", + "tcp:a.b.c.d": "Invalid bind address format: tcp:a.b.c.d", + "tcp:a.b.c.d/path": "Invalid bind address format: tcp:a.b.c.d/path", + "udp://127.0.0.1": "Invalid bind address format: udp://127.0.0.1", + "udp://127.0.0.1:2375": "Invalid bind address format: udp://127.0.0.1:2375", + "tcp://unix:///run/docker.sock": "Invalid bind address format: unix", + "tcp": "Invalid bind address format: tcp", + "unix": "Invalid bind address format: unix", + "fd": "Invalid bind address format: fd", + } + valids := map[string]string{ + "0.0.0.1:": "tcp://0.0.0.1:2375", + "0.0.0.1:5555": "tcp://0.0.0.1:5555", + "0.0.0.1:5555/path": "tcp://0.0.0.1:5555/path", + "[::1]:": "tcp://[::1]:2375", + "[::1]:5555/path": "tcp://[::1]:5555/path", + "[0:0:0:0:0:0:0:1]:": "tcp://[0:0:0:0:0:0:0:1]:2375", + "[0:0:0:0:0:0:0:1]:5555/path": "tcp://[0:0:0:0:0:0:0:1]:5555/path", + ":6666": "tcp://localhost:6666", + ":6666/path": "tcp://localhost:6666/path", + "": defaultHOST, + " ": defaultHOST, + " ": defaultHOST, + "tcp://": defaultHTTPHost, + "tcp://:7777": "tcp://localhost:7777", + "tcp://:7777/path": "tcp://localhost:7777/path", + " tcp://:7777/path ": "tcp://localhost:7777/path", + "unix:///run/docker.sock": "unix:///run/docker.sock", + "unix://": "unix:///var/run/docker.sock", + "fd://": "fd://", + "fd://something": "fd://something", + "localhost:": "tcp://localhost:2375", + "localhost:5555": "tcp://localhost:5555", + "localhost:5555/path": "tcp://localhost:5555/path", + } + for invalidAddr, expectedError := range invalids { + if addr, err := parseDockerDaemonHost(defaultHTTPHost, defaultHTTPSHost, defaultUnix, "", invalidAddr); err == nil || err.Error() != expectedError { + t.Errorf("tcp %v address expected error %v return, got %s and addr %v", invalidAddr, expectedError, err, addr) + } + } + for validAddr, expectedAddr := range valids { + if addr, err := parseDockerDaemonHost(defaultHTTPHost, defaultHTTPSHost, defaultUnix, "", validAddr); err != nil || addr != expectedAddr { + t.Errorf("%v -> expected %v, got (%v) addr (%v)", validAddr, expectedAddr, err, addr) + } + } +} + +func TestParseTCP(t *testing.T) { + var ( + defaultHTTPHost = "tcp://127.0.0.1:2376" + ) + invalids := map[string]string{ + "0.0.0.0": "Invalid bind address format: 0.0.0.0", + "tcp:a.b.c.d": "Invalid bind address format: tcp:a.b.c.d", + "tcp:a.b.c.d/path": "Invalid bind address format: tcp:a.b.c.d/path", + "udp://127.0.0.1": "Invalid proto, expected tcp: udp://127.0.0.1", + "udp://127.0.0.1:2375": "Invalid proto, expected tcp: udp://127.0.0.1:2375", + } + valids := map[string]string{ + "": defaultHTTPHost, + "tcp://": defaultHTTPHost, + "0.0.0.1:": "tcp://0.0.0.1:2376", + "0.0.0.1:5555": "tcp://0.0.0.1:5555", + "0.0.0.1:5555/path": "tcp://0.0.0.1:5555/path", + ":6666": "tcp://127.0.0.1:6666", + ":6666/path": "tcp://127.0.0.1:6666/path", + "tcp://:7777": "tcp://127.0.0.1:7777", + "tcp://:7777/path": "tcp://127.0.0.1:7777/path", + "[::1]:": "tcp://[::1]:2376", + "[::1]:5555": "tcp://[::1]:5555", + "[::1]:5555/path": "tcp://[::1]:5555/path", + "[0:0:0:0:0:0:0:1]:": "tcp://[0:0:0:0:0:0:0:1]:2376", + "[0:0:0:0:0:0:0:1]:5555": "tcp://[0:0:0:0:0:0:0:1]:5555", + "[0:0:0:0:0:0:0:1]:5555/path": "tcp://[0:0:0:0:0:0:0:1]:5555/path", + "localhost:": "tcp://localhost:2376", + "localhost:5555": "tcp://localhost:5555", + "localhost:5555/path": "tcp://localhost:5555/path", + } + for invalidAddr, expectedError := range invalids { + if addr, err := parseTCPAddr(invalidAddr, defaultHTTPHost); err == nil || err.Error() != expectedError { + t.Errorf("tcp %v address expected error %v return, got %s and addr %v", invalidAddr, expectedError, err, addr) + } + } + for validAddr, expectedAddr := range valids { + if addr, err := parseTCPAddr(validAddr, defaultHTTPHost); err != nil || addr != expectedAddr { + t.Errorf("%v -> expected %v, got %v and addr %v", validAddr, expectedAddr, err, addr) + } + } +} + +func TestParseInvalidUnixAddrInvalid(t *testing.T) { + if _, err := parseUnixAddr("tcp://127.0.0.1", "unix:///var/run/docker.sock"); err == nil || err.Error() != "Invalid proto, expected unix: tcp://127.0.0.1" { + t.Fatalf("Expected an error, got %v", err) + } + if _, err := parseUnixAddr("unix://tcp://127.0.0.1", "/var/run/docker.sock"); err == nil || err.Error() != "Invalid proto, expected unix: tcp://127.0.0.1" { + t.Fatalf("Expected an error, got %v", err) + } + if v, err := parseUnixAddr("", "/var/run/docker.sock"); err != nil || v != "unix:///var/run/docker.sock" { + t.Fatalf("Expected an %v, got %v", v, "unix:///var/run/docker.sock") + } +} diff --git a/opts/opts.go b/opts/opts.go index 6c75a9c9b115..fd2997db05b7 100644 --- a/opts/opts.go +++ b/opts/opts.go @@ -17,22 +17,6 @@ import ( 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*$`) - // DefaultHTTPPort Default HTTP Port used if only the protocol is provided to -H flag e.g. docker daemon -H tcp:// - // 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 - // pipe as the default on the Windows daemon. - // These are the IANA registered port numbers for use with Docker - // see http://www.iana.org/assignments/service-names-port-numbers/service-names-port-numbers.xhtml?search=docker - DefaultHTTPPort = 2375 // Default HTTP Port - // DefaultTLSHTTPPort Default HTTP Port used when TLS enabled - DefaultTLSHTTPPort = 2376 // Default TLS encrypted HTTP Port - // DefaultUnixSocket Path for the unix socket. - // Docker daemon by default always listens on the default unix socket - DefaultUnixSocket = "/var/run/docker.sock" - // DefaultTCPHost constant defines the default host string used by docker on Windows - DefaultTCPHost = fmt.Sprintf("tcp://%s:%d", DefaultHTTPHost, DefaultHTTPPort) - // DefaultTLSHost constant defines the default host string used by docker for TLS sockets - DefaultTLSHost = fmt.Sprintf("tcp://%s:%d", DefaultHTTPHost, DefaultTLSHTTPPort) ) // ListOpts holds a list of values and a validation function. @@ -391,26 +375,6 @@ func ValidateLabel(val string) (string, error) { return val, nil } -// ValidateHost validates that the specified string is a valid host and returns it. -func ValidateHost(val string) (string, error) { - _, err := parsers.ParseDockerDaemonHost(DefaultTCPHost, DefaultTLSHost, DefaultUnixSocket, "", val) - if err != nil { - return val, err - } - // Note: unlike most flag validators, we don't return the mutated value here - // we need to know what the user entered later (using ParseHost) to adjust for tls - return val, nil -} - -// ParseHost and set defaults for a Daemon host string -func ParseHost(defaultHost, val string) (string, error) { - host, err := parsers.ParseDockerDaemonHost(DefaultTCPHost, DefaultTLSHost, DefaultUnixSocket, defaultHost, val) - if err != nil { - return val, err - } - return host, nil -} - func doesEnvExist(name string) bool { for _, entry := range os.Environ() { parts := strings.SplitN(entry, "=", 2) diff --git a/opts/opts_test.go b/opts/opts_test.go index e02d3f8ea61c..2528525f5661 100644 --- a/opts/opts_test.go +++ b/opts/opts_test.go @@ -3,7 +3,6 @@ package opts import ( "fmt" "os" - "runtime" "strings" "testing" ) @@ -372,51 +371,6 @@ func TestValidateLabel(t *testing.T) { } } -func TestParseHost(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://:port": "Invalid bind address format: :port", - "tcp://invalid": "Invalid bind address format: invalid", - "tcp://invalid:port": "Invalid bind address format: invalid:port", - } - const defaultHTTPHost = "tcp://127.0.0.1:2375" - var defaultHOST = "unix:///var/run/docker.sock" - - if runtime.GOOS == "windows" { - defaultHOST = defaultHTTPHost - } - valid := map[string]string{ - "": defaultHOST, - "fd://": "fd://", - "fd://something": "fd://something", - "tcp://host:": "tcp://host:2375", - "tcp://": "tcp://localhost:2375", - "tcp://:2375": "tcp://localhost:2375", // default ip address - "tcp://:2376": "tcp://localhost: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 := ParseHost(defaultHTTPHost, 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 := ParseHost(defaultHTTPHost, value); err != nil || actual != expected { - t.Fatalf("Expected for %v [%v], got [%v, %v]", value, expected, actual, err) - } - } -} - func logOptsValidator(val string) (string, error) { allowedKeys := map[string]string{"max-size": "1", "max-file": "2"} vals := strings.Split(val, "=") From bfba6ec0701d8f9549e1c0eaaaaa5a25484ddf2e Mon Sep 17 00:00:00 2001 From: David Calavera Date: Wed, 16 Dec 2015 12:26:49 -0500 Subject: [PATCH 067/123] Replace pkg/units with docker/go-units. Signed-off-by: David Calavera --- opts/opts.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opts/opts.go b/opts/opts.go index fd2997db05b7..59abb3d9bf91 100644 --- a/opts/opts.go +++ b/opts/opts.go @@ -11,7 +11,7 @@ import ( "github.com/docker/docker/pkg/blkiodev" "github.com/docker/docker/pkg/parsers" - "github.com/docker/docker/pkg/units" + "github.com/docker/go-units" ) var ( From 659fac261b3bd3508f8606edc3708c1639baee3a Mon Sep 17 00:00:00 2001 From: Daniel Nephin Date: Tue, 15 Dec 2015 21:36:35 -0500 Subject: [PATCH 068/123] Move ParseLink and validators into runconfig.parse where they are used. Signed-off-by: Daniel Nephin --- opts/opts.go | 78 --------------------------------------------- opts/opts_test.go | 80 ----------------------------------------------- 2 files changed, 158 deletions(-) diff --git a/opts/opts.go b/opts/opts.go index 59abb3d9bf91..0dd42a6ce0e1 100644 --- a/opts/opts.go +++ b/opts/opts.go @@ -4,13 +4,11 @@ import ( "fmt" "net" "os" - "path" "regexp" "strconv" "strings" "github.com/docker/docker/pkg/blkiodev" - "github.com/docker/docker/pkg/parsers" "github.com/docker/go-units" ) @@ -221,82 +219,6 @@ func ValidateThrottleBpsDevice(val string) (*blkiodev.ThrottleDevice, error) { }, nil } -// ValidateLink validates that the specified string has a valid link format (containerName:alias). -func ValidateLink(val string) (string, error) { - if _, _, err := parsers.ParseLink(val); err != nil { - return val, err - } - return val, nil -} - -// ValidDeviceMode checks if the mode for device is valid or not. -// Valid mode is a composition of r (read), w (write), and m (mknod). -func ValidDeviceMode(mode string) bool { - var legalDeviceMode = map[rune]bool{ - 'r': true, - 'w': true, - 'm': true, - } - if mode == "" { - return false - } - for _, c := range mode { - if !legalDeviceMode[c] { - return false - } - legalDeviceMode[c] = false - } - return true -} - -// ValidateDevice validates a path for devices -// It will make sure 'val' is in the form: -// [host-dir:]container-path[:mode] -// It also validates the device mode. -func ValidateDevice(val string) (string, error) { - return validatePath(val, ValidDeviceMode) -} - -func validatePath(val string, validator func(string) bool) (string, error) { - var containerPath string - var mode string - - if strings.Count(val, ":") > 2 { - return val, fmt.Errorf("bad format for path: %s", val) - } - - split := strings.SplitN(val, ":", 3) - if split[0] == "" { - return val, fmt.Errorf("bad format for path: %s", val) - } - switch len(split) { - case 1: - containerPath = split[0] - val = path.Clean(containerPath) - case 2: - if isValid := validator(split[1]); isValid { - containerPath = split[0] - mode = split[1] - val = fmt.Sprintf("%s:%s", path.Clean(containerPath), mode) - } else { - containerPath = split[1] - val = fmt.Sprintf("%s:%s", split[0], path.Clean(containerPath)) - } - case 3: - containerPath = split[1] - mode = split[2] - if isValid := validator(split[2]); !isValid { - return val, fmt.Errorf("bad mode specified: %s", mode) - } - val = fmt.Sprintf("%s:%s:%s", split[0], containerPath, mode) - } - - if !path.IsAbs(containerPath) { - return val, fmt.Errorf("%s is not an absolute path", containerPath) - } - return val, nil -} - // ValidateEnv validates an environment variable and returns it. // If no value is specified, it returns the current value using os.Getenv. // diff --git a/opts/opts_test.go b/opts/opts_test.go index 2528525f5661..e2af1c11d07a 100644 --- a/opts/opts_test.go +++ b/opts/opts_test.go @@ -244,86 +244,6 @@ func TestValidateAttach(t *testing.T) { } } -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 TestValidateDevice(t *testing.T) { - valid := []string{ - "/home", - "/home:/home", - "/home:/something/else", - "/with space", - "/home:/with space", - "relative:/absolute-path", - "hostPath:/containerPath:r", - "/hostPath:/containerPath:rw", - "/hostPath:/containerPath:mrw", - } - invalid := map[string]string{ - "": "bad format for path: ", - "./": "./ 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 path: :", - "/tmp:": " is not an absolute path", - ":test": "bad format for path: :test", - ":/test": "bad format for path: :/test", - "tmp:": " is not an absolute path", - ":test:": "bad format for path: :test:", - "::": "bad format for path: ::", - ":::": "bad format for path: :::", - "/tmp:::": "bad format for path: /tmp:::", - ":/tmp::": "bad format for path: :/tmp::", - "path:ro": "ro is not an absolute path", - "path:rr": "rr is not an absolute path", - "a:/b:ro": "bad mode specified: ro", - "a:/b:rr": "bad mode specified: rr", - } - - 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) { valids := map[string]string{ "a": "a", From 369f05781892d5c083ed624bb936726efc20d1c8 Mon Sep 17 00:00:00 2001 From: Ma Shimiao Date: Wed, 8 Jul 2015 19:06:48 +0800 Subject: [PATCH 069/123] Add support for blkio read/write iops device Signed-off-by: Ma Shimiao --- opts/opts.go | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/opts/opts.go b/opts/opts.go index 0dd42a6ce0e1..1a77e20c7a66 100644 --- a/opts/opts.go +++ b/opts/opts.go @@ -219,6 +219,29 @@ func ValidateThrottleBpsDevice(val string) (*blkiodev.ThrottleDevice, error) { }, nil } +// ValidateThrottleIOpsDevice validates that the specified string has a valid device-rate format. +func ValidateThrottleIOpsDevice(val string) (*blkiodev.ThrottleDevice, error) { + split := strings.SplitN(val, ":", 2) + if len(split) != 2 { + return nil, fmt.Errorf("bad format: %s", val) + } + if !strings.HasPrefix(split[0], "/dev/") { + return nil, fmt.Errorf("bad format for device path: %s", val) + } + rate, err := strconv.ParseUint(split[1], 10, 64) + if err != nil { + return nil, fmt.Errorf("invalid rate for device: %s. The correct format is :. Number must be a positive integer", val) + } + if rate < 0 { + return nil, fmt.Errorf("invalid rate for device: %s. The correct format is :. Number must be a positive integer", val) + } + + return &blkiodev.ThrottleDevice{ + Path: split[0], + Rate: uint64(rate), + }, nil +} + // ValidateEnv validates an environment variable and returns it. // If no value is specified, it returns the current value using os.Getenv. // From 757d284759549d1efc0e0539643d1ec72c6f2cf4 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Fri, 18 Dec 2015 12:44:39 -0500 Subject: [PATCH 070/123] Move blkiodev package to types. Signed-off-by: David Calavera --- opts/opts.go | 2 +- opts/throttledevice.go | 2 +- opts/weightdevice.go | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/opts/opts.go b/opts/opts.go index 1a77e20c7a66..98a03197501e 100644 --- a/opts/opts.go +++ b/opts/opts.go @@ -8,7 +8,7 @@ import ( "strconv" "strings" - "github.com/docker/docker/pkg/blkiodev" + "github.com/docker/docker/api/types/blkiodev" "github.com/docker/go-units" ) diff --git a/opts/throttledevice.go b/opts/throttledevice.go index fb1180232692..6d3e31bc045f 100644 --- a/opts/throttledevice.go +++ b/opts/throttledevice.go @@ -3,7 +3,7 @@ package opts import ( "fmt" - "github.com/docker/docker/pkg/blkiodev" + "github.com/docker/docker/api/types/blkiodev" ) // ThrottledeviceOpt defines a map of ThrottleDevices diff --git a/opts/weightdevice.go b/opts/weightdevice.go index 4c6288f6afc4..2c94af3bf07c 100644 --- a/opts/weightdevice.go +++ b/opts/weightdevice.go @@ -3,7 +3,7 @@ package opts import ( "fmt" - "github.com/docker/docker/pkg/blkiodev" + "github.com/docker/docker/api/types/blkiodev" ) // WeightdeviceOpt defines a map of WeightDevices From 5d99388c2318b57f78bdca8ce13949a93c6c7735 Mon Sep 17 00:00:00 2001 From: Daniel Nephin Date: Mon, 21 Dec 2015 16:34:05 -0500 Subject: [PATCH 071/123] Move runconfig blkiodev options and parsing into runconfig/opts package. Signed-off-by: Daniel Nephin --- opts/opts.go | 79 ------------------------------------------ opts/throttledevice.go | 56 ------------------------------ opts/weightdevice.go | 56 ------------------------------ 3 files changed, 191 deletions(-) delete mode 100644 opts/throttledevice.go delete mode 100644 opts/weightdevice.go diff --git a/opts/opts.go b/opts/opts.go index 98a03197501e..b244f5a3a9cf 100644 --- a/opts/opts.go +++ b/opts/opts.go @@ -5,11 +5,7 @@ import ( "net" "os" "regexp" - "strconv" "strings" - - "github.com/docker/docker/api/types/blkiodev" - "github.com/docker/go-units" ) var ( @@ -153,12 +149,6 @@ func NewMapOpts(values map[string]string, validator ValidatorFctType) *MapOpts { // ValidatorFctType defines a validator function that returns a validated string and/or an error. type ValidatorFctType func(val string) (string, error) -// ValidatorWeightFctType defines a validator function that returns a validated struct and/or an error. -type ValidatorWeightFctType func(val string) (*blkiodev.WeightDevice, error) - -// ValidatorThrottleFctType defines a validator function that returns a validated struct and/or an error. -type ValidatorThrottleFctType func(val string) (*blkiodev.ThrottleDevice, error) - // ValidatorFctListType defines a validator function that returns a validated list of string and/or an error type ValidatorFctListType func(val string) ([]string, error) @@ -173,75 +163,6 @@ func ValidateAttach(val string) (string, error) { return val, fmt.Errorf("valid streams are STDIN, STDOUT and STDERR") } -// ValidateWeightDevice validates that the specified string has a valid device-weight format. -func ValidateWeightDevice(val string) (*blkiodev.WeightDevice, error) { - split := strings.SplitN(val, ":", 2) - if len(split) != 2 { - return nil, fmt.Errorf("bad format: %s", val) - } - if !strings.HasPrefix(split[0], "/dev/") { - return nil, fmt.Errorf("bad format for device path: %s", val) - } - weight, err := strconv.ParseUint(split[1], 10, 0) - if err != nil { - return nil, fmt.Errorf("invalid weight for device: %s", val) - } - if weight > 0 && (weight < 10 || weight > 1000) { - return nil, fmt.Errorf("invalid weight for device: %s", val) - } - - return &blkiodev.WeightDevice{ - Path: split[0], - Weight: uint16(weight), - }, nil -} - -// ValidateThrottleBpsDevice validates that the specified string has a valid device-rate format. -func ValidateThrottleBpsDevice(val string) (*blkiodev.ThrottleDevice, error) { - split := strings.SplitN(val, ":", 2) - if len(split) != 2 { - return nil, fmt.Errorf("bad format: %s", val) - } - if !strings.HasPrefix(split[0], "/dev/") { - return nil, fmt.Errorf("bad format for device path: %s", val) - } - rate, err := units.RAMInBytes(split[1]) - if err != nil { - return nil, fmt.Errorf("invalid rate for device: %s. The correct format is :[]. Number must be a positive integer. Unit is optional and can be kb, mb, or gb", val) - } - if rate < 0 { - return nil, fmt.Errorf("invalid rate for device: %s. The correct format is :[]. Number must be a positive integer. Unit is optional and can be kb, mb, or gb", val) - } - - return &blkiodev.ThrottleDevice{ - Path: split[0], - Rate: uint64(rate), - }, nil -} - -// ValidateThrottleIOpsDevice validates that the specified string has a valid device-rate format. -func ValidateThrottleIOpsDevice(val string) (*blkiodev.ThrottleDevice, error) { - split := strings.SplitN(val, ":", 2) - if len(split) != 2 { - return nil, fmt.Errorf("bad format: %s", val) - } - if !strings.HasPrefix(split[0], "/dev/") { - return nil, fmt.Errorf("bad format for device path: %s", val) - } - rate, err := strconv.ParseUint(split[1], 10, 64) - if err != nil { - return nil, fmt.Errorf("invalid rate for device: %s. The correct format is :. Number must be a positive integer", val) - } - if rate < 0 { - return nil, fmt.Errorf("invalid rate for device: %s. The correct format is :. Number must be a positive integer", val) - } - - return &blkiodev.ThrottleDevice{ - Path: split[0], - Rate: uint64(rate), - }, nil -} - // ValidateEnv validates an environment variable and returns it. // If no value is specified, it returns the current value using os.Getenv. // diff --git a/opts/throttledevice.go b/opts/throttledevice.go deleted file mode 100644 index 6d3e31bc045f..000000000000 --- a/opts/throttledevice.go +++ /dev/null @@ -1,56 +0,0 @@ -package opts - -import ( - "fmt" - - "github.com/docker/docker/api/types/blkiodev" -) - -// ThrottledeviceOpt defines a map of ThrottleDevices -type ThrottledeviceOpt struct { - values []*blkiodev.ThrottleDevice - validator ValidatorThrottleFctType -} - -// NewThrottledeviceOpt creates a new ThrottledeviceOpt -func NewThrottledeviceOpt(validator ValidatorThrottleFctType) ThrottledeviceOpt { - values := []*blkiodev.ThrottleDevice{} - return ThrottledeviceOpt{ - values: values, - validator: validator, - } -} - -// Set validates a ThrottleDevice and sets its name as a key in ThrottledeviceOpt -func (opt *ThrottledeviceOpt) Set(val string) error { - var value *blkiodev.ThrottleDevice - if opt.validator != nil { - v, err := opt.validator(val) - if err != nil { - return err - } - value = v - } - (opt.values) = append((opt.values), value) - return nil -} - -// String returns ThrottledeviceOpt values as a string. -func (opt *ThrottledeviceOpt) String() string { - var out []string - for _, v := range opt.values { - out = append(out, v.String()) - } - - return fmt.Sprintf("%v", out) -} - -// GetList returns a slice of pointers to ThrottleDevices. -func (opt *ThrottledeviceOpt) GetList() []*blkiodev.ThrottleDevice { - var throttledevice []*blkiodev.ThrottleDevice - for _, v := range opt.values { - throttledevice = append(throttledevice, v) - } - - return throttledevice -} diff --git a/opts/weightdevice.go b/opts/weightdevice.go deleted file mode 100644 index 2c94af3bf07c..000000000000 --- a/opts/weightdevice.go +++ /dev/null @@ -1,56 +0,0 @@ -package opts - -import ( - "fmt" - - "github.com/docker/docker/api/types/blkiodev" -) - -// WeightdeviceOpt defines a map of WeightDevices -type WeightdeviceOpt struct { - values []*blkiodev.WeightDevice - validator ValidatorWeightFctType -} - -// NewWeightdeviceOpt creates a new WeightdeviceOpt -func NewWeightdeviceOpt(validator ValidatorWeightFctType) WeightdeviceOpt { - values := []*blkiodev.WeightDevice{} - return WeightdeviceOpt{ - values: values, - validator: validator, - } -} - -// Set validates a WeightDevice and sets its name as a key in WeightdeviceOpt -func (opt *WeightdeviceOpt) Set(val string) error { - var value *blkiodev.WeightDevice - if opt.validator != nil { - v, err := opt.validator(val) - if err != nil { - return err - } - value = v - } - (opt.values) = append((opt.values), value) - return nil -} - -// String returns WeightdeviceOpt values as a string. -func (opt *WeightdeviceOpt) String() string { - var out []string - for _, v := range opt.values { - out = append(out, v.String()) - } - - return fmt.Sprintf("%v", out) -} - -// GetList returns a slice of pointers to WeightDevices. -func (opt *WeightdeviceOpt) GetList() []*blkiodev.WeightDevice { - var weightdevice []*blkiodev.WeightDevice - for _, v := range opt.values { - weightdevice = append(weightdevice, v) - } - - return weightdevice -} From cef8b71ff4da5054a093afa619375e5a3133e9aa Mon Sep 17 00:00:00 2001 From: Daniel Nephin Date: Mon, 21 Dec 2015 15:06:46 -0500 Subject: [PATCH 072/123] Move ulimit options to runconfig opts Signed-off-by: Daniel Nephin --- opts/ulimit.go | 52 --------------------------------------------- opts/ulimit_test.go | 42 ------------------------------------ 2 files changed, 94 deletions(-) delete mode 100644 opts/ulimit.go delete mode 100644 opts/ulimit_test.go diff --git a/opts/ulimit.go b/opts/ulimit.go deleted file mode 100644 index b41f475bd5b3..000000000000 --- a/opts/ulimit.go +++ /dev/null @@ -1,52 +0,0 @@ -package opts - -import ( - "fmt" - - "github.com/docker/docker/pkg/ulimit" -) - -// UlimitOpt defines a map of Ulimits -type UlimitOpt struct { - values *map[string]*ulimit.Ulimit -} - -// NewUlimitOpt creates a new UlimitOpt -func NewUlimitOpt(ref *map[string]*ulimit.Ulimit) *UlimitOpt { - if ref == nil { - ref = &map[string]*ulimit.Ulimit{} - } - return &UlimitOpt{ref} -} - -// Set validates a Ulimit and sets its name as a key in UlimitOpt -func (o *UlimitOpt) Set(val string) error { - l, err := ulimit.Parse(val) - if err != nil { - return err - } - - (*o.values)[l.Name] = l - - return nil -} - -// String returns Ulimit values as a string. -func (o *UlimitOpt) String() string { - var out []string - for _, v := range *o.values { - out = append(out, v.String()) - } - - return fmt.Sprintf("%v", out) -} - -// GetList returns a slice of pointers to Ulimits. -func (o *UlimitOpt) GetList() []*ulimit.Ulimit { - var ulimits []*ulimit.Ulimit - for _, v := range *o.values { - ulimits = append(ulimits, v) - } - - return ulimits -} diff --git a/opts/ulimit_test.go b/opts/ulimit_test.go deleted file mode 100644 index 3845d1ec18a1..000000000000 --- a/opts/ulimit_test.go +++ /dev/null @@ -1,42 +0,0 @@ -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) - } -} From e73db35cbf485361fdf737f30d4d505e9b4c4f59 Mon Sep 17 00:00:00 2001 From: Daniel Nephin Date: Tue, 29 Dec 2015 11:28:21 -0500 Subject: [PATCH 073/123] Move some validators from opts to runconfig/opts. These validators are only used by runconfig.Parse() or some other part of the client, so move them into the client-side package. Signed-off-by: Daniel Nephin --- opts/envfile.go | 67 -------------------- opts/envfile_test.go | 142 ------------------------------------------- opts/opts.go | 62 ------------------- opts/opts_test.go | 101 ------------------------------ 4 files changed, 372 deletions(-) delete mode 100644 opts/envfile.go delete mode 100644 opts/envfile_test.go diff --git a/opts/envfile.go b/opts/envfile.go deleted file mode 100644 index ba8b4f20165d..000000000000 --- a/opts/envfile.go +++ /dev/null @@ -1,67 +0,0 @@ -package opts - -import ( - "bufio" - "fmt" - "os" - "strings" -) - -// ParseEnvFile reads a file with environment variables enumerated by lines -// -// ``Environment variable names used by the utilities in the Shell and -// Utilities volume of IEEE Std 1003.1-2001 consist solely of uppercase -// letters, digits, and the '_' (underscore) from the characters defined in -// Portable Character Set and do not begin with a digit. *But*, other -// characters may be permitted by an implementation; applications shall -// tolerate the presence of such names.'' -// -- http://pubs.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap08.html -// -// As of #16585, it's up to application inside docker to validate or not -// environment variables, that's why we just strip leading whitespace and -// nothing more. -func ParseEnvFile(filename string) ([]string, error) { - fh, err := os.Open(filename) - if err != nil { - return []string{}, err - } - defer fh.Close() - - lines := []string{} - scanner := bufio.NewScanner(fh) - for scanner.Scan() { - // trim the line from all leading whitespace first - line := strings.TrimLeft(scanner.Text(), whiteSpaces) - // line is not empty, and not starting with '#' - if len(line) > 0 && !strings.HasPrefix(line, "#") { - 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)} - } - - if len(data) > 1 { - - // pass the value through, no trimming - lines = append(lines, fmt.Sprintf("%s=%s", variable, data[1])) - } else { - // if only a pass-through variable is given, clean it up. - lines = append(lines, fmt.Sprintf("%s=%s", strings.TrimSpace(line), os.Getenv(line))) - } - } - } - return lines, scanner.Err() -} - -var whiteSpaces = " \t" - -// ErrBadEnvVariable typed error for bad environment variable -type ErrBadEnvVariable struct { - msg string -} - -func (e ErrBadEnvVariable) Error() string { - return fmt.Sprintf("poorly formatted environment: %s", e.msg) -} diff --git a/opts/envfile_test.go b/opts/envfile_test.go deleted file mode 100644 index a2e2200fa1bc..000000000000 --- a/opts/envfile_test.go +++ /dev/null @@ -1,142 +0,0 @@ -package opts - -import ( - "bufio" - "fmt" - "io/ioutil" - "os" - "reflect" - "strings" - "testing" -) - -func tmpFileWithContent(content string, t *testing.T) string { - tmpFile, err := ioutil.TempFile("", "envfile-test") - if err != nil { - t.Fatal(err) - } - defer tmpFile.Close() - - tmpFile.WriteString(content) - return tmpFile.Name() -} - -// Test ParseEnvFile for a file with a few well formatted lines -func TestParseEnvFileGoodFile(t *testing.T) { - content := `foo=bar - baz=quux -# comment - -_foobar=foobaz -with.dots=working -and_underscore=working too -` - // Adding a newline + a line with pure whitespace. - // This is being done like this instead of the block above - // because it's common for editors to trim trailing whitespace - // from lines, which becomes annoying since that's the - // exact thing we need to test. - content += "\n \t " - tmpFile := tmpFileWithContent(content, t) - defer os.Remove(tmpFile) - - lines, err := ParseEnvFile(tmpFile) - if err != nil { - t.Fatal(err) - } - - expectedLines := []string{ - "foo=bar", - "baz=quux", - "_foobar=foobaz", - "with.dots=working", - "and_underscore=working too", - } - - if !reflect.DeepEqual(lines, expectedLines) { - t.Fatal("lines not equal to expected_lines") - } -} - -// Test ParseEnvFile for an empty file -func TestParseEnvFileEmptyFile(t *testing.T) { - tmpFile := tmpFileWithContent("", t) - defer os.Remove(tmpFile) - - lines, err := ParseEnvFile(tmpFile) - if err != nil { - t.Fatal(err) - } - - if len(lines) != 0 { - t.Fatal("lines not empty; expected empty") - } -} - -// Test ParseEnvFile for a non existent file -func TestParseEnvFileNonExistentFile(t *testing.T) { - _, err := ParseEnvFile("foo_bar_baz") - if err == nil { - 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 -func TestParseEnvFileBadlyFormattedFile(t *testing.T) { - content := `foo=bar - f =quux -` - - 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 'f ' has white spaces" - if err.Error() != expectedMessage { - t.Fatalf("Expected [%v], got [%v]", expectedMessage, err.Error()) - } -} - -// Test ParseEnvFile for a file with a line exceeding bufio.MaxScanTokenSize -func TestParseEnvFileLineTooLongFile(t *testing.T) { - content := strings.Repeat("a", bufio.MaxScanTokenSize+42) - content = fmt.Sprint("foo=", content) - - tmpFile := tmpFileWithContent(content, t) - defer os.Remove(tmpFile) - - _, err := ParseEnvFile(tmpFile) - if err == nil { - 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' has white spaces" - if err.Error() != expectedMessage { - t.Fatalf("Expected [%v], got [%v]", expectedMessage, err.Error()) - } -} diff --git a/opts/opts.go b/opts/opts.go index b244f5a3a9cf..abc9ab8a1811 100644 --- a/opts/opts.go +++ b/opts/opts.go @@ -3,7 +3,6 @@ package opts import ( "fmt" "net" - "os" "regexp" "strings" ) @@ -152,34 +151,6 @@ type ValidatorFctType func(val string) (string, error) // ValidatorFctListType defines a validator function that returns a validated list of string and/or an 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) { - s := strings.ToLower(val) - for _, str := range []string{"stdin", "stdout", "stderr"} { - if s == str { - return s, nil - } - } - return val, fmt.Errorf("valid streams are STDIN, STDOUT and STDERR") -} - -// ValidateEnv validates an environment variable and returns it. -// If no value is specified, it returns the current value using os.Getenv. -// -// As on ParseEnvFile and related to #16585, environment variable names -// are not validate what so ever, it's up to application inside docker -// to validate them or not. -func ValidateEnv(val string) (string, error) { - arr := strings.Split(val, "=") - if len(arr) > 1 { - return val, nil - } - if !doesEnvExist(val) { - return val, nil - } - return fmt.Sprintf("%s=%s", val, os.Getenv(val)), nil -} - // ValidateIPAddress validates an Ip address. func ValidateIPAddress(val string) (string, error) { var ip = net.ParseIP(strings.TrimSpace(val)) @@ -189,15 +160,6 @@ func ValidateIPAddress(val string) (string, error) { return "", fmt.Errorf("%s is not an ip address", val) } -// ValidateMACAddress validates a MAC address. -func ValidateMACAddress(val string) (string, error) { - _, err := net.ParseMAC(strings.TrimSpace(val)) - if err != nil { - return "", err - } - return val, nil -} - // ValidateDNSSearch validates domain for resolvconf search configuration. // A zero length domain is represented by a dot (.). func ValidateDNSSearch(val string) (string, error) { @@ -218,20 +180,6 @@ func validateDomain(val string) (string, error) { return "", fmt.Errorf("%s is not a valid domain", val) } -// ValidateExtraHost validates that the specified 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) { - // allow for IPv6 addresses in extra hosts by only splitting on first ":" - arr := strings.SplitN(val, ":", 2) - if len(arr) != 2 || len(arr[0]) == 0 { - return "", fmt.Errorf("bad format for add-host: %q", val) - } - if _, err := ValidateIPAddress(arr[1]); err != nil { - return "", fmt.Errorf("invalid IP address in add-host: %q", arr[1]) - } - return val, nil -} - // ValidateLabel validates that the specified string is a valid label, and returns it. // Labels are in the form on key=value. func ValidateLabel(val string) (string, error) { @@ -240,13 +188,3 @@ func ValidateLabel(val string) (string, error) { } return val, nil } - -func doesEnvExist(name string) bool { - for _, entry := range os.Environ() { - parts := strings.SplitN(entry, "=", 2) - if parts[0] == name { - return true - } - } - return false -} diff --git a/opts/opts_test.go b/opts/opts_test.go index e2af1c11d07a..da86b21fa393 100644 --- a/opts/opts_test.go +++ b/opts/opts_test.go @@ -2,7 +2,6 @@ package opts import ( "fmt" - "os" "strings" "testing" ) @@ -55,20 +54,6 @@ func TestMapOpts(t *testing.T) { } } -func TestValidateMACAddress(t *testing.T) { - if _, err := ValidateMACAddress(`92:d0:c6:0a:29:33`); err != nil { - t.Fatalf("ValidateMACAddress(`92:d0:c6:0a:29:33`) got %s", err) - } - - if _, err := ValidateMACAddress(`92:d0:c6:0a:33`); err == nil { - t.Fatalf("ValidateMACAddress(`92:d0:c6:0a:33`) succeeded; expected failure on invalid MAC") - } - - if _, err := ValidateMACAddress(`random invalid string`); err == nil { - t.Fatalf("ValidateMACAddress(`random invalid string`) succeeded; expected failure on invalid MAC") - } -} - func TestListOptsWithoutValidator(t *testing.T) { o := NewListOpts(nil) o.Set("foo") @@ -188,92 +173,6 @@ func TestValidateDNSSearch(t *testing.T) { } } -func TestValidateExtraHosts(t *testing.T) { - valid := []string{ - `myhost:192.168.0.1`, - `thathost:10.0.2.1`, - `anipv6host:2003:ab34:e::1`, - `ipv6local:::1`, - } - - invalid := map[string]string{ - `myhost:192.notanipaddress.1`: `invalid IP`, - `thathost-nosemicolon10.0.0.1`: `bad format`, - `anipv6host:::::1`: `invalid IP`, - `ipv6local:::0::`: `invalid IP`, - } - - for _, extrahost := range valid { - if _, err := ValidateExtraHost(extrahost); err != nil { - t.Fatalf("ValidateExtraHost(`"+extrahost+"`) should succeed: error %v", err) - } - } - - for extraHost, expectedError := range invalid { - if _, err := ValidateExtraHost(extraHost); err == nil { - t.Fatalf("ValidateExtraHost(`%q`) should have failed validation", extraHost) - } else { - if !strings.Contains(err.Error(), expectedError) { - t.Fatalf("ValidateExtraHost(`%q`) error should contain %q", extraHost, expectedError) - } - } - } -} - -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 TestValidateEnv(t *testing.T) { - 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", - "asd!qwe": "asd!qwe", - "1asd": "1asd", - "123": "123", - "some space": "some space", - " some space before": " some space before", - "some space after ": "some space after ", - } - 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) From d611dc46c0e43a865f883ed8436bf61ef846a30d Mon Sep 17 00:00:00 2001 From: David Calavera Date: Thu, 10 Dec 2015 18:35:10 -0500 Subject: [PATCH 074/123] Allow to set daemon and server configurations in a file. Read configuration after flags making this the priority: 1- Apply configuration from file. 2- Apply configuration from flags. Reload configuration when a signal is received, USR2 in Linux: - Reload router if the debug configuration changes. - Reload daemon labels. - Reload cluster discovery. Signed-off-by: David Calavera --- opts/opts.go | 52 +++++++++++++++++++++++++++++++++++++++++++++++ opts/opts_test.go | 32 +++++++++++++++++++++++++++++ 2 files changed, 84 insertions(+) diff --git a/opts/opts.go b/opts/opts.go index abc9ab8a1811..05aadbe74b76 100644 --- a/opts/opts.go +++ b/opts/opts.go @@ -100,6 +100,35 @@ func (opts *ListOpts) Len() int { return len((*opts.values)) } +// NamedOption is an interface that list and map options +// with names implement. +type NamedOption interface { + Name() string +} + +// NamedListOpts is a ListOpts with a configuration name. +// This struct is useful to keep reference to the assigned +// field name in the internal configuration struct. +type NamedListOpts struct { + name string + ListOpts +} + +var _ NamedOption = &NamedListOpts{} + +// NewNamedListOptsRef creates a reference to a new NamedListOpts struct. +func NewNamedListOptsRef(name string, values *[]string, validator ValidatorFctType) *NamedListOpts { + return &NamedListOpts{ + name: name, + ListOpts: *NewListOptsRef(values, validator), + } +} + +// Name returns the name of the NamedListOpts in the configuration. +func (o *NamedListOpts) Name() string { + return o.name +} + //MapOpts holds a map of values and a validation function. type MapOpts struct { values map[string]string @@ -145,6 +174,29 @@ func NewMapOpts(values map[string]string, validator ValidatorFctType) *MapOpts { } } +// NamedMapOpts is a MapOpts struct with a configuration name. +// This struct is useful to keep reference to the assigned +// field name in the internal configuration struct. +type NamedMapOpts struct { + name string + MapOpts +} + +var _ NamedOption = &NamedMapOpts{} + +// NewNamedMapOpts creates a reference to a new NamedMapOpts struct. +func NewNamedMapOpts(name string, values map[string]string, validator ValidatorFctType) *NamedMapOpts { + return &NamedMapOpts{ + name: name, + MapOpts: *NewMapOpts(values, validator), + } +} + +// Name returns the name of the NamedMapOpts in the configuration. +func (o *NamedMapOpts) Name() string { + return o.name +} + // ValidatorFctType defines a validator function that returns a validated string and/or an error. type ValidatorFctType func(val string) (string, error) diff --git a/opts/opts_test.go b/opts/opts_test.go index da86b21fa393..9f41e4786484 100644 --- a/opts/opts_test.go +++ b/opts/opts_test.go @@ -198,3 +198,35 @@ func logOptsValidator(val string) (string, error) { } return "", fmt.Errorf("invalid key %s", vals[0]) } + +func TestNamedListOpts(t *testing.T) { + var v []string + o := NewNamedListOptsRef("foo-name", &v, nil) + + o.Set("foo") + if o.String() != "[foo]" { + t.Errorf("%s != [foo]", o.String()) + } + if o.Name() != "foo-name" { + t.Errorf("%s != foo-name", o.Name()) + } + if len(v) != 1 { + t.Errorf("expected foo to be in the values, got %v", v) + } +} + +func TestNamedMapOpts(t *testing.T) { + tmpMap := make(map[string]string) + o := NewNamedMapOpts("max-name", tmpMap, nil) + + o.Set("max-size=1") + if o.String() != "map[max-size:1]" { + t.Errorf("%s != [map[max-size:1]", o.String()) + } + if o.Name() != "max-name" { + t.Errorf("%s != max-name", o.Name()) + } + if _, exist := tmpMap["max-size"]; !exist { + t.Errorf("expected map-size to be in the values, got %v", tmpMap) + } +} From 5d648746e806431461411b0172105bfae318c93a Mon Sep 17 00:00:00 2001 From: John Starks Date: Sat, 30 Jan 2016 18:45:49 -0800 Subject: [PATCH 075/123] Windows: Add support for named pipe protocol This adds an npipe protocol option for Windows hosts, akin to unix sockets for Linux hosts. This should become the default transport for Windows, but this change does not yet do that. It also does not add support for the client side yet since that code is in engine-api, which will have to be revendored separately. Signed-off-by: John Starks --- opts/hosts.go | 74 ++++++++++++++++++++++---------------------- opts/hosts_test.go | 76 +++++++++++++++++++--------------------------- 2 files changed, 70 insertions(+), 80 deletions(-) diff --git a/opts/hosts.go b/opts/hosts.go index d1b69854152d..ad16759236e0 100644 --- a/opts/hosts.go +++ b/opts/hosts.go @@ -4,16 +4,12 @@ import ( "fmt" "net" "net/url" - "runtime" "strconv" "strings" ) var ( // DefaultHTTPPort Default HTTP Port used if only the protocol is provided to -H flag e.g. docker daemon -H tcp:// - // 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 - // pipe as the default on the Windows daemon. // These are the IANA registered port numbers for use with Docker // see http://www.iana.org/assignments/service-names-port-numbers/service-names-port-numbers.xhtml?search=docker DefaultHTTPPort = 2375 // Default HTTP Port @@ -26,13 +22,19 @@ var ( DefaultTCPHost = fmt.Sprintf("tcp://%s:%d", DefaultHTTPHost, DefaultHTTPPort) // DefaultTLSHost constant defines the default host string used by docker for TLS sockets DefaultTLSHost = fmt.Sprintf("tcp://%s:%d", DefaultHTTPHost, DefaultTLSHTTPPort) + // DefaultNamedPipe defines the default named pipe used by docker on Windows + DefaultNamedPipe = `//./pipe/docker_engine` ) // ValidateHost validates that the specified string is a valid host and returns it. func ValidateHost(val string) (string, error) { - _, err := parseDockerDaemonHost(DefaultTCPHost, DefaultTLSHost, DefaultUnixSocket, "", val) - if err != nil { - return val, err + host := strings.TrimSpace(val) + // The empty string means default and is not handled by parseDockerDaemonHost + if host != "" { + _, err := parseDockerDaemonHost(host) + if err != nil { + return val, err + } } // Note: unlike most flag validators, we don't return the mutated value here // we need to know what the user entered later (using ParseHost) to adjust for tls @@ -40,39 +42,39 @@ func ValidateHost(val string) (string, error) { } // ParseHost and set defaults for a Daemon host string -func ParseHost(defaultHost, val string) (string, error) { - host, err := parseDockerDaemonHost(DefaultTCPHost, DefaultTLSHost, DefaultUnixSocket, defaultHost, val) - if err != nil { - return val, err +func ParseHost(defaultToTLS bool, val string) (string, error) { + host := strings.TrimSpace(val) + if host == "" { + if defaultToTLS { + host = DefaultTLSHost + } else { + host = DefaultHost + } + } else { + var err error + host, err = parseDockerDaemonHost(host) + if err != nil { + return val, err + } } return host, nil } // parseDockerDaemonHost parses the specified address and returns an address that will be used as the host. -// Depending of the address specified, will use the defaultTCPAddr or defaultUnixAddr -// defaultUnixAddr must be a absolute file path (no `unix://` prefix) -// defaultTCPAddr must be the full `tcp://host:port` form -func parseDockerDaemonHost(defaultTCPAddr, defaultTLSHost, defaultUnixAddr, defaultAddr, addr string) (string, error) { - addr = strings.TrimSpace(addr) - if addr == "" { - if defaultAddr == defaultTLSHost { - return defaultTLSHost, nil - } - if runtime.GOOS != "windows" { - return fmt.Sprintf("unix://%s", defaultUnixAddr), nil - } - return defaultTCPAddr, nil - } +// Depending of the address specified, this may return one of the global Default* strings defined in hosts.go. +func parseDockerDaemonHost(addr string) (string, error) { addrParts := strings.Split(addr, "://") - if len(addrParts) == 1 { + if len(addrParts) == 1 && addrParts[0] != "" { addrParts = []string{"tcp", addrParts[0]} } switch addrParts[0] { case "tcp": - return parseTCPAddr(addrParts[1], defaultTCPAddr) + return parseTCPAddr(addrParts[1], DefaultTCPHost) case "unix": - return parseUnixAddr(addrParts[1], defaultUnixAddr) + return parseSimpleProtoAddr("unix", addrParts[1], DefaultUnixSocket) + case "npipe": + return parseSimpleProtoAddr("npipe", addrParts[1], DefaultNamedPipe) case "fd": return addr, nil default: @@ -80,19 +82,19 @@ func parseDockerDaemonHost(defaultTCPAddr, defaultTLSHost, defaultUnixAddr, defa } } -// parseUnixAddr parses and validates that the specified address is a valid UNIX -// socket address. It returns a formatted UNIX socket address, either using the -// address parsed from addr, or the contents of defaultAddr if addr is a blank -// string. -func parseUnixAddr(addr string, defaultAddr string) (string, error) { - addr = strings.TrimPrefix(addr, "unix://") +// parseSimpleProtoAddr parses and validates that the specified address is a valid +// socket address for simple protocols like unix and npipe. It returns a formatted +// socket address, either using the address parsed from addr, or the contents of +// defaultAddr if addr is a blank string. +func parseSimpleProtoAddr(proto, addr, defaultAddr string) (string, error) { + addr = strings.TrimPrefix(addr, proto+"://") if strings.Contains(addr, "://") { - return "", fmt.Errorf("Invalid proto, expected unix: %s", addr) + return "", fmt.Errorf("Invalid proto, expected %s: %s", proto, addr) } if addr == "" { addr = defaultAddr } - return fmt.Sprintf("unix://%s", addr), nil + return fmt.Sprintf("%s://%s", proto, addr), nil } // parseTCPAddr parses and validates that the specified address is a valid TCP diff --git a/opts/hosts_test.go b/opts/hosts_test.go index e497e2865643..8856f95fedcb 100644 --- a/opts/hosts_test.go +++ b/opts/hosts_test.go @@ -1,7 +1,7 @@ package opts import ( - "runtime" + "fmt" "testing" ) @@ -15,51 +15,41 @@ func TestParseHost(t *testing.T) { "tcp://invalid": "Invalid bind address format: invalid", "tcp://invalid:port": "Invalid bind address format: invalid:port", } - const defaultHTTPHost = "tcp://127.0.0.1:2375" - var defaultHOST = "unix:///var/run/docker.sock" - - if runtime.GOOS == "windows" { - defaultHOST = defaultHTTPHost - } valid := map[string]string{ - "": defaultHOST, + "": DefaultHost, + " ": DefaultHost, + " ": DefaultHost, "fd://": "fd://", "fd://something": "fd://something", - "tcp://host:": "tcp://host:2375", - "tcp://": "tcp://localhost:2375", - "tcp://:2375": "tcp://localhost:2375", // default ip address - "tcp://:2376": "tcp://localhost:2376", // default ip address + "tcp://host:": fmt.Sprintf("tcp://host:%d", DefaultHTTPPort), + "tcp://": DefaultTCPHost, + "tcp://:2375": fmt.Sprintf("tcp://%s:2375", DefaultHTTPHost), + "tcp://:2376": fmt.Sprintf("tcp://%s:2376", DefaultHTTPHost), "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://:7777/path ": fmt.Sprintf("tcp://%s:7777/path", DefaultHTTPHost), "tcp://docker.com:2375": "tcp://docker.com:2375", - "unix://": "unix:///var/run/docker.sock", // default unix:// value + "unix://": "unix://" + DefaultUnixSocket, "unix://path/to/socket": "unix://path/to/socket", + "npipe://": "npipe://" + DefaultNamedPipe, + "npipe:////./pipe/foo": "npipe:////./pipe/foo", } for value, errorMessage := range invalid { - if _, err := ParseHost(defaultHTTPHost, value); err == nil || err.Error() != errorMessage { - t.Fatalf("Expected an error for %v with [%v], got [%v]", value, errorMessage, err) + if _, err := ParseHost(false, value); err == nil || err.Error() != errorMessage { + t.Errorf("Expected an error for %v with [%v], got [%v]", value, errorMessage, err) } } for value, expected := range valid { - if actual, err := ParseHost(defaultHTTPHost, value); err != nil || actual != expected { - t.Fatalf("Expected for %v [%v], got [%v, %v]", value, expected, actual, err) + if actual, err := ParseHost(false, value); err != nil || actual != expected { + t.Errorf("Expected for %v [%v], got [%v, %v]", value, expected, actual, err) } } } func TestParseDockerDaemonHost(t *testing.T) { - var ( - defaultHTTPHost = "tcp://localhost:2375" - defaultHTTPSHost = "tcp://localhost:2376" - defaultUnix = "/var/run/docker.sock" - defaultHOST = "unix:///var/run/docker.sock" - ) - if runtime.GOOS == "windows" { - defaultHOST = defaultHTTPHost - } invalids := map[string]string{ "0.0.0.0": "Invalid bind address format: 0.0.0.0", "tcp:a.b.c.d": "Invalid bind address format: tcp:a.b.c.d", @@ -67,9 +57,11 @@ func TestParseDockerDaemonHost(t *testing.T) { "udp://127.0.0.1": "Invalid bind address format: udp://127.0.0.1", "udp://127.0.0.1:2375": "Invalid bind address format: udp://127.0.0.1:2375", "tcp://unix:///run/docker.sock": "Invalid bind address format: unix", - "tcp": "Invalid bind address format: tcp", - "unix": "Invalid bind address format: unix", - "fd": "Invalid bind address format: fd", + " tcp://:7777/path ": "Invalid bind address format: tcp://:7777/path ", + "tcp": "Invalid bind address format: tcp", + "unix": "Invalid bind address format: unix", + "fd": "Invalid bind address format: fd", + "": "Invalid bind address format: ", } valids := map[string]string{ "0.0.0.1:": "tcp://0.0.0.1:2375", @@ -79,17 +71,13 @@ func TestParseDockerDaemonHost(t *testing.T) { "[::1]:5555/path": "tcp://[::1]:5555/path", "[0:0:0:0:0:0:0:1]:": "tcp://[0:0:0:0:0:0:0:1]:2375", "[0:0:0:0:0:0:0:1]:5555/path": "tcp://[0:0:0:0:0:0:0:1]:5555/path", - ":6666": "tcp://localhost:6666", - ":6666/path": "tcp://localhost:6666/path", - "": defaultHOST, - " ": defaultHOST, - " ": defaultHOST, - "tcp://": defaultHTTPHost, - "tcp://:7777": "tcp://localhost:7777", - "tcp://:7777/path": "tcp://localhost:7777/path", - " tcp://:7777/path ": "tcp://localhost:7777/path", + ":6666": fmt.Sprintf("tcp://%s:6666", DefaultHTTPHost), + ":6666/path": fmt.Sprintf("tcp://%s:6666/path", DefaultHTTPHost), + "tcp://": DefaultTCPHost, + "tcp://:7777": fmt.Sprintf("tcp://%s:7777", DefaultHTTPHost), + "tcp://:7777/path": fmt.Sprintf("tcp://%s:7777/path", DefaultHTTPHost), "unix:///run/docker.sock": "unix:///run/docker.sock", - "unix://": "unix:///var/run/docker.sock", + "unix://": "unix://" + DefaultUnixSocket, "fd://": "fd://", "fd://something": "fd://something", "localhost:": "tcp://localhost:2375", @@ -97,12 +85,12 @@ func TestParseDockerDaemonHost(t *testing.T) { "localhost:5555/path": "tcp://localhost:5555/path", } for invalidAddr, expectedError := range invalids { - if addr, err := parseDockerDaemonHost(defaultHTTPHost, defaultHTTPSHost, defaultUnix, "", invalidAddr); err == nil || err.Error() != expectedError { + if addr, err := parseDockerDaemonHost(invalidAddr); err == nil || err.Error() != expectedError { t.Errorf("tcp %v address expected error %v return, got %s and addr %v", invalidAddr, expectedError, err, addr) } } for validAddr, expectedAddr := range valids { - if addr, err := parseDockerDaemonHost(defaultHTTPHost, defaultHTTPSHost, defaultUnix, "", validAddr); err != nil || addr != expectedAddr { + if addr, err := parseDockerDaemonHost(validAddr); err != nil || addr != expectedAddr { t.Errorf("%v -> expected %v, got (%v) addr (%v)", validAddr, expectedAddr, err, addr) } } @@ -152,13 +140,13 @@ func TestParseTCP(t *testing.T) { } func TestParseInvalidUnixAddrInvalid(t *testing.T) { - if _, err := parseUnixAddr("tcp://127.0.0.1", "unix:///var/run/docker.sock"); err == nil || err.Error() != "Invalid proto, expected unix: tcp://127.0.0.1" { + if _, err := parseSimpleProtoAddr("unix", "tcp://127.0.0.1", "unix:///var/run/docker.sock"); err == nil || err.Error() != "Invalid proto, expected unix: tcp://127.0.0.1" { t.Fatalf("Expected an error, got %v", err) } - if _, err := parseUnixAddr("unix://tcp://127.0.0.1", "/var/run/docker.sock"); err == nil || err.Error() != "Invalid proto, expected unix: tcp://127.0.0.1" { + if _, err := parseSimpleProtoAddr("unix", "unix://tcp://127.0.0.1", "/var/run/docker.sock"); err == nil || err.Error() != "Invalid proto, expected unix: tcp://127.0.0.1" { t.Fatalf("Expected an error, got %v", err) } - if v, err := parseUnixAddr("", "/var/run/docker.sock"); err != nil || v != "unix:///var/run/docker.sock" { + if v, err := parseSimpleProtoAddr("unix", "", "/var/run/docker.sock"); err != nil || v != "unix:///var/run/docker.sock" { t.Fatalf("Expected an %v, got %v", v, "unix:///var/run/docker.sock") } } From 2b140819b30e3bfdd6f22e129eb45018f30be221 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Wed, 17 Feb 2016 19:05:52 -0500 Subject: [PATCH 076/123] Upgrade Go to 1.6. Signed-off-by: David Calavera --- opts/hosts_test.go | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/opts/hosts_test.go b/opts/hosts_test.go index 8856f95fedcb..dc527e6388b6 100644 --- a/opts/hosts_test.go +++ b/opts/hosts_test.go @@ -6,15 +6,16 @@ import ( ) func TestParseHost(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://:port": "Invalid bind address format: :port", - "tcp://invalid": "Invalid bind address format: invalid", - "tcp://invalid:port": "Invalid bind address format: invalid:port", + invalid := []string{ + "anything", + "something with spaces", + "://", + "unknown://", + "tcp://:port", + "tcp://invalid", + "tcp://invalid:port", } + valid := map[string]string{ "": DefaultHost, " ": DefaultHost, @@ -37,11 +38,12 @@ func TestParseHost(t *testing.T) { "npipe:////./pipe/foo": "npipe:////./pipe/foo", } - for value, errorMessage := range invalid { - if _, err := ParseHost(false, value); err == nil || err.Error() != errorMessage { - t.Errorf("Expected an error for %v with [%v], got [%v]", value, errorMessage, err) + for _, value := range invalid { + if _, err := ParseHost(false, value); err == nil { + t.Errorf("Expected an error for %v, got [nil]", value) } } + for value, expected := range valid { if actual, err := ParseHost(false, value); err != nil || actual != expected { t.Errorf("Expected for %v [%v], got [%v, %v]", value, expected, actual, err) From 6ad28f55d580a4e9cc870972b0b3a28bfb596aa2 Mon Sep 17 00:00:00 2001 From: John Starks Date: Tue, 1 Mar 2016 18:25:04 -0800 Subject: [PATCH 077/123] Windows: Default to npipe transport This changes the default transport for Windows from unencrypted TCP to npipe. This is similar to how Linux runs with the unix socket transport by default. Signed-off-by: John Starks --- opts/hosts_windows.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opts/hosts_windows.go b/opts/hosts_windows.go index ec52e9a70ae5..7c239e00f1e4 100644 --- a/opts/hosts_windows.go +++ b/opts/hosts_windows.go @@ -3,4 +3,4 @@ package opts // DefaultHost constant defines the default host string used by docker on Windows -var DefaultHost = DefaultTCPHost +var DefaultHost = "npipe://" + DefaultNamedPipe From a5f0686bf09b059f0edad00b0895bae7f396b13b Mon Sep 17 00:00:00 2001 From: allencloud Date: Mon, 28 Mar 2016 18:57:55 +0800 Subject: [PATCH 078/123] fix typos Signed-off-by: allencloud --- opts/opts.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opts/opts.go b/opts/opts.go index 05aadbe74b76..a56c0cc42ea2 100644 --- a/opts/opts.go +++ b/opts/opts.go @@ -36,7 +36,7 @@ func (opts *ListOpts) String() string { return fmt.Sprintf("%v", []string((*opts.values))) } -// Set validates if needed the input value and add it to the +// Set validates if needed the input value and adds it to the // internal slice. func (opts *ListOpts) Set(value string) error { if opts.validator != nil { From 1688110fd627262497b1828760539080ea9612c9 Mon Sep 17 00:00:00 2001 From: John Howard Date: Wed, 6 Apr 2016 12:01:29 -0700 Subject: [PATCH 079/123] Windows: Remove TP4 support from main code Signed-off-by: John Howard --- opts/opts_windows.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/opts/opts_windows.go b/opts/opts_windows.go index 2a9e2be7447e..ebe40c969c92 100644 --- a/opts/opts_windows.go +++ b/opts/opts_windows.go @@ -1,10 +1,10 @@ package opts -// TODO Windows. Identify bug in GOLang 1.5.1 and/or Windows Server 2016 TP4. +// TODO Windows. Identify bug in GOLang 1.5.1+ and/or Windows Server 2016 TP5. // @jhowardmsft, @swernli. // // On Windows, this mitigates a problem with the default options of running -// a docker client against a local docker daemon on TP4. +// a docker client against a local docker daemon on TP5. // // What was found that if the default host is "localhost", even if the client // (and daemon as this is local) is not physically on a network, and the DNS @@ -35,7 +35,7 @@ package opts // time="2015-11-06T13:38:38.326882500-08:00" level=info msg="POST /v1.22/containers/984758282b842f779e805664b2c95d563adc9a979c8a3973e68c807843ee4757/attach?stderr=1&stdin=1&stdout=1&stream=1" // // We suspect this is either a bug introduced in GOLang 1.5.1, or that a change -// in GOLang 1.5.1 (from 1.4.3) is exposing a bug in Windows TP4. In theory, +// in GOLang 1.5.1 (from 1.4.3) is exposing a bug in Windows. In theory, // the Windows networking stack is supposed to resolve "localhost" internally, // without hitting DNS, or even reading the hosts file (which is why localhost // is commented out in the hosts file on Windows). @@ -44,12 +44,12 @@ package opts // address does not cause the delay. // // This does not occur with the docker client built with 1.4.3 on the same -// Windows TP4 build, regardless of whether the daemon is built using 1.5.1 +// Windows build, regardless of whether the daemon is built using 1.5.1 // or 1.4.3. It does not occur on Linux. We also verified we see the same thing // on a cross-compiled Windows binary (from Linux). // // Final note: This is a mitigation, not a 'real' fix. It is still susceptible -// to the delay in TP4 if a user were to do 'docker run -H=tcp://localhost:2375...' +// to the delay if a user were to do 'docker run -H=tcp://localhost:2375...' // explicitly. // DefaultHTTPHost Default HTTP Host used if only port is provided to -H flag e.g. docker daemon -H tcp://:8080 From 1419abf7b24eb4dcc9ed2671e7d7e9761ec44840 Mon Sep 17 00:00:00 2001 From: Dan Walsh Date: Tue, 29 Mar 2016 08:24:28 -0400 Subject: [PATCH 080/123] Add support for setting sysctls This patch will allow users to specify namespace specific "kernel parameters" for running inside of a container. Signed-off-by: Dan Walsh --- opts/opts.go | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/opts/opts.go b/opts/opts.go index a56c0cc42ea2..0b09981778fd 100644 --- a/opts/opts.go +++ b/opts/opts.go @@ -240,3 +240,35 @@ func ValidateLabel(val string) (string, error) { } return val, nil } + +// ValidateSysctl validates an sysctl and returns it. +func ValidateSysctl(val string) (string, error) { + validSysctlMap := map[string]bool{ + "kernel.msgmax": true, + "kernel.msgmnb": true, + "kernel.msgmni": true, + "kernel.sem": true, + "kernel.shmall": true, + "kernel.shmmax": true, + "kernel.shmmni": true, + "kernel.shm_rmid_forced": true, + } + validSysctlPrefixes := []string{ + "net.", + "fs.mqueue.", + } + arr := strings.Split(val, "=") + if len(arr) < 2 { + return "", fmt.Errorf("sysctl '%s' is not whitelisted", val) + } + if validSysctlMap[arr[0]] { + return val, nil + } + + for _, vp := range validSysctlPrefixes { + if strings.HasPrefix(arr[0], vp) { + return val, nil + } + } + return "", fmt.Errorf("sysctl '%s' is not whitelisted", val) +} From b9fe27025960b8e488bfe6d1390a0f8275c2ddf2 Mon Sep 17 00:00:00 2001 From: Daniel Nephin Date: Tue, 19 Apr 2016 12:59:48 -0400 Subject: [PATCH 081/123] Migrate volume commands to cobra. Signed-off-by: Daniel Nephin --- opts/opts.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/opts/opts.go b/opts/opts.go index 0b09981778fd..05d5497161c8 100644 --- a/opts/opts.go +++ b/opts/opts.go @@ -163,6 +163,11 @@ func (opts *MapOpts) String() string { return fmt.Sprintf("%v", map[string]string((opts.values))) } +// Type returns a string name for this Option type +func (opts *MapOpts) Type() string { + return "map" +} + // NewMapOpts creates a new MapOpts with the specified map of values and a validator. func NewMapOpts(values map[string]string, validator ValidatorFctType) *MapOpts { if values == nil { From 17b3d05419453a83e9f84911214888bd3c99ba5a Mon Sep 17 00:00:00 2001 From: Daniel Nephin Date: Mon, 16 May 2016 17:20:29 -0400 Subject: [PATCH 082/123] Update usage and help to (almost) match the existing docker behaviour Signed-off-by: Daniel Nephin --- opts/opts.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/opts/opts.go b/opts/opts.go index 05d5497161c8..e1d3c2db15ce 100644 --- a/opts/opts.go +++ b/opts/opts.go @@ -100,6 +100,11 @@ func (opts *ListOpts) Len() int { return len((*opts.values)) } +// Type returns a string name for this Option type +func (opts *ListOpts) Type() string { + return "list" +} + // NamedOption is an interface that list and map options // with names implement. type NamedOption interface { From 662b3de10a965452c616fb9a3c8edd3119cb54ec Mon Sep 17 00:00:00 2001 From: allencloud Date: Sun, 8 May 2016 09:36:10 +0800 Subject: [PATCH 083/123] fix typos Signed-off-by: allencloud --- opts/opts.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opts/opts.go b/opts/opts.go index e1d3c2db15ce..9bd8040d257b 100644 --- a/opts/opts.go +++ b/opts/opts.go @@ -251,7 +251,7 @@ func ValidateLabel(val string) (string, error) { return val, nil } -// ValidateSysctl validates an sysctl and returns it. +// ValidateSysctl validates a sysctl and returns it. func ValidateSysctl(val string) (string, error) { validSysctlMap := map[string]bool{ "kernel.msgmax": true, From 4f87181ba9f5fb58f40d0b86c3b98e31f26437ab Mon Sep 17 00:00:00 2001 From: Tonis Tiigi Date: Mon, 13 Jun 2016 19:52:49 -0700 Subject: [PATCH 084/123] Add Swarm management backend As described in our ROADMAP.md, introduce new Swarm management API endpoints relying on swarmkit to deploy services. It currently vendors docker/engine-api changes. This PR is fully backward compatible (joining a Swarm is an optional feature of the Engine, and existing commands are not impacted). Signed-off-by: Tonis Tiigi Signed-off-by: Victor Vieux Signed-off-by: Daniel Nephin Signed-off-by: Jana Radhakrishnan Signed-off-by: Madhu Venugopal --- opts/opts.go | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/opts/opts.go b/opts/opts.go index 9bd8040d257b..1b9d6b294a84 100644 --- a/opts/opts.go +++ b/opts/opts.go @@ -5,6 +5,8 @@ import ( "net" "regexp" "strings" + + "github.com/docker/engine-api/types/filters" ) var ( @@ -282,3 +284,38 @@ func ValidateSysctl(val string) (string, error) { } return "", fmt.Errorf("sysctl '%s' is not whitelisted", val) } + +// FilterOpt is a flag type for validating filters +type FilterOpt struct { + filter filters.Args +} + +// NewFilterOpt returns a new FilterOpt +func NewFilterOpt() FilterOpt { + return FilterOpt{filter: filters.NewArgs()} +} + +func (o *FilterOpt) String() string { + repr, err := filters.ToParam(o.filter) + if err != nil { + return "invalid filters" + } + return repr +} + +// Set sets the value of the opt by parsing the command line value +func (o *FilterOpt) Set(value string) error { + var err error + o.filter, err = filters.ParseFlag(value, o.filter) + return err +} + +// Type returns the option type +func (o *FilterOpt) Type() string { + return "filter" +} + +// Value returns the value of this option +func (o *FilterOpt) Value() filters.Args { + return o.filter +} From 881833232e91f9840f2164f89f89dd30ee247ca6 Mon Sep 17 00:00:00 2001 From: Tonis Tiigi Date: Tue, 21 Jun 2016 14:27:04 -0700 Subject: [PATCH 085/123] Unify swarm init and update options Add api side validation and defaults for init and join requests. Signed-off-by: Tonis Tiigi --- opts/hosts.go | 11 +++++++---- opts/hosts_test.go | 4 ++-- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/opts/hosts.go b/opts/hosts.go index ad16759236e0..957b24ce7d20 100644 --- a/opts/hosts.go +++ b/opts/hosts.go @@ -70,7 +70,7 @@ func parseDockerDaemonHost(addr string) (string, error) { switch addrParts[0] { case "tcp": - return parseTCPAddr(addrParts[1], DefaultTCPHost) + return ParseTCPAddr(addrParts[1], DefaultTCPHost) case "unix": return parseSimpleProtoAddr("unix", addrParts[1], DefaultUnixSocket) case "npipe": @@ -97,12 +97,12 @@ func parseSimpleProtoAddr(proto, addr, defaultAddr string) (string, error) { return fmt.Sprintf("%s://%s", proto, addr), nil } -// parseTCPAddr parses and validates that the specified address is a valid TCP +// ParseTCPAddr parses and validates that the specified address is a valid TCP // address. It returns a formatted TCP address, either using the address parsed // from tryAddr, or the contents of defaultAddr if tryAddr is a blank string. // tryAddr is expected to have already been Trim()'d // defaultAddr must be in the full `tcp://host:port` form -func parseTCPAddr(tryAddr string, defaultAddr string) (string, error) { +func ParseTCPAddr(tryAddr string, defaultAddr string) (string, error) { if tryAddr == "" || tryAddr == "tcp://" { return defaultAddr, nil } @@ -127,8 +127,11 @@ func parseTCPAddr(tryAddr string, defaultAddr string) (string, error) { if err != nil { return "", err } - host, port, err := net.SplitHostPort(u.Host) + if err != nil { + // try port addition once + host, port, err = net.SplitHostPort(net.JoinHostPort(u.Host, defaultPort)) + } if err != nil { return "", fmt.Errorf("Invalid bind address format: %s", tryAddr) } diff --git a/opts/hosts_test.go b/opts/hosts_test.go index dc527e6388b6..57161eaf4614 100644 --- a/opts/hosts_test.go +++ b/opts/hosts_test.go @@ -130,12 +130,12 @@ func TestParseTCP(t *testing.T) { "localhost:5555/path": "tcp://localhost:5555/path", } for invalidAddr, expectedError := range invalids { - if addr, err := parseTCPAddr(invalidAddr, defaultHTTPHost); err == nil || err.Error() != expectedError { + if addr, err := ParseTCPAddr(invalidAddr, defaultHTTPHost); err == nil || err.Error() != expectedError { t.Errorf("tcp %v address expected error %v return, got %s and addr %v", invalidAddr, expectedError, err, addr) } } for validAddr, expectedAddr := range valids { - if addr, err := parseTCPAddr(validAddr, defaultHTTPHost); err != nil || addr != expectedAddr { + if addr, err := ParseTCPAddr(validAddr, defaultHTTPHost); err != nil || addr != expectedAddr { t.Errorf("%v -> expected %v, got %v and addr %v", validAddr, expectedAddr, err, addr) } } From 63d756f34796f183373118a3512986831cfc7096 Mon Sep 17 00:00:00 2001 From: Tonis Tiigi Date: Tue, 21 Jun 2016 17:14:55 -0700 Subject: [PATCH 086/123] Fix opts tests after default port fix MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The code for default port was already there but it didn’t work because split function errored out before. This should be the desired behavior that matches daemon listen address with swarm listen address. Signed-off-by: Tonis Tiigi --- opts/hosts.go | 2 +- opts/hosts_test.go | 12 +++--------- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/opts/hosts.go b/opts/hosts.go index 957b24ce7d20..266df1e5374e 100644 --- a/opts/hosts.go +++ b/opts/hosts.go @@ -63,7 +63,7 @@ func ParseHost(defaultToTLS bool, val string) (string, error) { // parseDockerDaemonHost parses the specified address and returns an address that will be used as the host. // Depending of the address specified, this may return one of the global Default* strings defined in hosts.go. func parseDockerDaemonHost(addr string) (string, error) { - addrParts := strings.Split(addr, "://") + addrParts := strings.SplitN(addr, "://", 2) if len(addrParts) == 1 && addrParts[0] != "" { addrParts = []string{"tcp", addrParts[0]} } diff --git a/opts/hosts_test.go b/opts/hosts_test.go index 57161eaf4614..a5bec30d4c40 100644 --- a/opts/hosts_test.go +++ b/opts/hosts_test.go @@ -7,12 +7,10 @@ import ( func TestParseHost(t *testing.T) { invalid := []string{ - "anything", "something with spaces", "://", "unknown://", "tcp://:port", - "tcp://invalid", "tcp://invalid:port", } @@ -53,16 +51,13 @@ func TestParseHost(t *testing.T) { func TestParseDockerDaemonHost(t *testing.T) { invalids := map[string]string{ - "0.0.0.0": "Invalid bind address format: 0.0.0.0", + "tcp:a.b.c.d": "Invalid bind address format: tcp:a.b.c.d", "tcp:a.b.c.d/path": "Invalid bind address format: tcp:a.b.c.d/path", "udp://127.0.0.1": "Invalid bind address format: udp://127.0.0.1", "udp://127.0.0.1:2375": "Invalid bind address format: udp://127.0.0.1:2375", - "tcp://unix:///run/docker.sock": "Invalid bind address format: unix", + "tcp://unix:///run/docker.sock": "Invalid proto, expected tcp: unix:///run/docker.sock", " tcp://:7777/path ": "Invalid bind address format: tcp://:7777/path ", - "tcp": "Invalid bind address format: tcp", - "unix": "Invalid bind address format: unix", - "fd": "Invalid bind address format: fd", "": "Invalid bind address format: ", } valids := map[string]string{ @@ -88,7 +83,7 @@ func TestParseDockerDaemonHost(t *testing.T) { } for invalidAddr, expectedError := range invalids { if addr, err := parseDockerDaemonHost(invalidAddr); err == nil || err.Error() != expectedError { - t.Errorf("tcp %v address expected error %v return, got %s and addr %v", invalidAddr, expectedError, err, addr) + t.Errorf("tcp %v address expected error %q return, got %q and addr %v", invalidAddr, expectedError, err, addr) } } for validAddr, expectedAddr := range valids { @@ -103,7 +98,6 @@ func TestParseTCP(t *testing.T) { defaultHTTPHost = "tcp://127.0.0.1:2376" ) invalids := map[string]string{ - "0.0.0.0": "Invalid bind address format: 0.0.0.0", "tcp:a.b.c.d": "Invalid bind address format: tcp:a.b.c.d", "tcp:a.b.c.d/path": "Invalid bind address format: tcp:a.b.c.d/path", "udp://127.0.0.1": "Invalid proto, expected tcp: udp://127.0.0.1", From ac76967dbac4e98ffea03a2a1f35a322a4cc88de Mon Sep 17 00:00:00 2001 From: Daniel Nephin Date: Tue, 21 Jun 2016 16:42:47 -0400 Subject: [PATCH 087/123] Convert dockerd to use cobra and pflag Signed-off-by: Daniel Nephin --- opts/ip.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/opts/ip.go b/opts/ip.go index c7b0dc99473a..fb03b50111fd 100644 --- a/opts/ip.go +++ b/opts/ip.go @@ -40,3 +40,8 @@ func (o *IPOpt) String() string { } return o.IP.String() } + +// Type returns the type of the option +func (o *IPOpt) Type() string { + return "ip" +} From 6083de889117cc48f8bd3522f15560396d983740 Mon Sep 17 00:00:00 2001 From: allencloud Date: Mon, 29 Aug 2016 18:37:14 +0800 Subject: [PATCH 088/123] correct some nits in comment and test files Signed-off-by: allencloud --- opts/opts.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opts/opts.go b/opts/opts.go index 1b9d6b294a84..20f4c655a171 100644 --- a/opts/opts.go +++ b/opts/opts.go @@ -136,7 +136,7 @@ func (o *NamedListOpts) Name() string { return o.name } -//MapOpts holds a map of values and a validation function. +// MapOpts holds a map of values and a validation function. type MapOpts struct { values map[string]string validator ValidatorFctType From 11d7f42b49d8f3a57881a20f90a673d27524ef98 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Tue, 6 Sep 2016 11:18:12 -0700 Subject: [PATCH 089/123] Add engine-api types to docker This moves the types for the `engine-api` repo to the existing types package. Signed-off-by: Michael Crosby --- opts/opts.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opts/opts.go b/opts/opts.go index 20f4c655a171..f8bb3ba74522 100644 --- a/opts/opts.go +++ b/opts/opts.go @@ -6,7 +6,7 @@ import ( "regexp" "strings" - "github.com/docker/engine-api/types/filters" + "github.com/docker/docker/api/types/filters" ) var ( From 3375ba2acd1176db7c2f2028f6b6910a52f6981f Mon Sep 17 00:00:00 2001 From: Akihiro Suda Date: Tue, 25 Oct 2016 03:26:54 +0000 Subject: [PATCH 090/123] cli: add `--mount` to `docker run` Signed-off-by: Akihiro Suda --- opts/mount.go | 147 +++++++++++++++++++++++++++++++++++++++++++ opts/mount_test.go | 153 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 300 insertions(+) create mode 100644 opts/mount.go create mode 100644 opts/mount_test.go diff --git a/opts/mount.go b/opts/mount.go new file mode 100644 index 000000000000..b6fccade18d7 --- /dev/null +++ b/opts/mount.go @@ -0,0 +1,147 @@ +package opts + +import ( + "encoding/csv" + "fmt" + "strconv" + "strings" + + mounttypes "github.com/docker/docker/api/types/mount" +) + +// MountOpt is a Value type for parsing mounts +type MountOpt struct { + values []mounttypes.Mount +} + +// Set a new mount value +func (m *MountOpt) Set(value string) error { + csvReader := csv.NewReader(strings.NewReader(value)) + fields, err := csvReader.Read() + if err != nil { + return err + } + + mount := mounttypes.Mount{} + + volumeOptions := func() *mounttypes.VolumeOptions { + if mount.VolumeOptions == nil { + mount.VolumeOptions = &mounttypes.VolumeOptions{ + Labels: make(map[string]string), + } + } + if mount.VolumeOptions.DriverConfig == nil { + mount.VolumeOptions.DriverConfig = &mounttypes.Driver{} + } + return mount.VolumeOptions + } + + bindOptions := func() *mounttypes.BindOptions { + if mount.BindOptions == nil { + mount.BindOptions = new(mounttypes.BindOptions) + } + return mount.BindOptions + } + + setValueOnMap := func(target map[string]string, value string) { + parts := strings.SplitN(value, "=", 2) + if len(parts) == 1 { + target[value] = "" + } else { + target[parts[0]] = parts[1] + } + } + + mount.Type = mounttypes.TypeVolume // default to volume mounts + // Set writable as the default + for _, field := range fields { + parts := strings.SplitN(field, "=", 2) + key := strings.ToLower(parts[0]) + + if len(parts) == 1 { + switch key { + case "readonly", "ro": + mount.ReadOnly = true + continue + case "volume-nocopy": + volumeOptions().NoCopy = true + continue + } + } + + if len(parts) != 2 { + return fmt.Errorf("invalid field '%s' must be a key=value pair", field) + } + + value := parts[1] + switch key { + case "type": + mount.Type = mounttypes.Type(strings.ToLower(value)) + case "source", "src": + mount.Source = value + case "target", "dst", "destination": + mount.Target = value + case "readonly", "ro": + mount.ReadOnly, err = strconv.ParseBool(value) + if err != nil { + return fmt.Errorf("invalid value for %s: %s", key, value) + } + case "bind-propagation": + bindOptions().Propagation = mounttypes.Propagation(strings.ToLower(value)) + case "volume-nocopy": + volumeOptions().NoCopy, err = strconv.ParseBool(value) + if err != nil { + return fmt.Errorf("invalid value for populate: %s", value) + } + case "volume-label": + setValueOnMap(volumeOptions().Labels, value) + case "volume-driver": + volumeOptions().DriverConfig.Name = value + case "volume-opt": + if volumeOptions().DriverConfig.Options == nil { + volumeOptions().DriverConfig.Options = make(map[string]string) + } + setValueOnMap(volumeOptions().DriverConfig.Options, value) + default: + return fmt.Errorf("unexpected key '%s' in '%s'", key, field) + } + } + + if mount.Type == "" { + return fmt.Errorf("type is required") + } + + if mount.Target == "" { + return fmt.Errorf("target is required") + } + + if mount.Type == mounttypes.TypeBind && mount.VolumeOptions != nil { + return fmt.Errorf("cannot mix 'volume-*' options with mount type '%s'", mounttypes.TypeBind) + } + if mount.Type == mounttypes.TypeVolume && mount.BindOptions != nil { + return fmt.Errorf("cannot mix 'bind-*' options with mount type '%s'", mounttypes.TypeVolume) + } + + m.values = append(m.values, mount) + return nil +} + +// Type returns the type of this option +func (m *MountOpt) Type() string { + return "mount" +} + +// String returns a string repr of this option +func (m *MountOpt) String() string { + mounts := []string{} + for _, mount := range m.values { + repr := fmt.Sprintf("%s %s %s", mount.Type, mount.Source, mount.Target) + mounts = append(mounts, repr) + } + return strings.Join(mounts, ", ") +} + +// Value returns the mounts +func (m *MountOpt) Value() []mounttypes.Mount { + return m.values +} diff --git a/opts/mount_test.go b/opts/mount_test.go new file mode 100644 index 000000000000..28c551bcc638 --- /dev/null +++ b/opts/mount_test.go @@ -0,0 +1,153 @@ +package opts + +import ( + "testing" + + mounttypes "github.com/docker/docker/api/types/mount" + "github.com/docker/docker/pkg/testutil/assert" +) + +func TestMountOptString(t *testing.T) { + mount := MountOpt{ + values: []mounttypes.Mount{ + { + Type: mounttypes.TypeBind, + Source: "/home/path", + Target: "/target", + }, + { + Type: mounttypes.TypeVolume, + Source: "foo", + Target: "/target/foo", + }, + }, + } + expected := "bind /home/path /target, volume foo /target/foo" + assert.Equal(t, mount.String(), expected) +} + +func TestMountOptSetBindNoErrorBind(t *testing.T) { + for _, testcase := range []string{ + // tests several aliases that should have same result. + "type=bind,target=/target,source=/source", + "type=bind,src=/source,dst=/target", + "type=bind,source=/source,dst=/target", + "type=bind,src=/source,target=/target", + } { + var mount MountOpt + + assert.NilError(t, mount.Set(testcase)) + + mounts := mount.Value() + assert.Equal(t, len(mounts), 1) + assert.Equal(t, mounts[0], mounttypes.Mount{ + Type: mounttypes.TypeBind, + Source: "/source", + Target: "/target", + }) + } +} + +func TestMountOptSetVolumeNoError(t *testing.T) { + for _, testcase := range []string{ + // tests several aliases that should have same result. + "type=volume,target=/target,source=/source", + "type=volume,src=/source,dst=/target", + "type=volume,source=/source,dst=/target", + "type=volume,src=/source,target=/target", + } { + var mount MountOpt + + assert.NilError(t, mount.Set(testcase)) + + mounts := mount.Value() + assert.Equal(t, len(mounts), 1) + assert.Equal(t, mounts[0], mounttypes.Mount{ + Type: mounttypes.TypeVolume, + Source: "/source", + Target: "/target", + }) + } +} + +// TestMountOptDefaultType ensures that a mount without the type defaults to a +// volume mount. +func TestMountOptDefaultType(t *testing.T) { + var mount MountOpt + assert.NilError(t, mount.Set("target=/target,source=/foo")) + assert.Equal(t, mount.values[0].Type, mounttypes.TypeVolume) +} + +func TestMountOptSetErrorNoTarget(t *testing.T) { + var mount MountOpt + assert.Error(t, mount.Set("type=volume,source=/foo"), "target is required") +} + +func TestMountOptSetErrorInvalidKey(t *testing.T) { + var mount MountOpt + assert.Error(t, mount.Set("type=volume,bogus=foo"), "unexpected key 'bogus'") +} + +func TestMountOptSetErrorInvalidField(t *testing.T) { + var mount MountOpt + assert.Error(t, mount.Set("type=volume,bogus"), "invalid field 'bogus'") +} + +func TestMountOptSetErrorInvalidReadOnly(t *testing.T) { + var mount MountOpt + assert.Error(t, mount.Set("type=volume,readonly=no"), "invalid value for readonly: no") + assert.Error(t, mount.Set("type=volume,readonly=invalid"), "invalid value for readonly: invalid") +} + +func TestMountOptDefaultEnableReadOnly(t *testing.T) { + var m MountOpt + assert.NilError(t, m.Set("type=bind,target=/foo,source=/foo")) + assert.Equal(t, m.values[0].ReadOnly, false) + + m = MountOpt{} + assert.NilError(t, m.Set("type=bind,target=/foo,source=/foo,readonly")) + assert.Equal(t, m.values[0].ReadOnly, true) + + m = MountOpt{} + assert.NilError(t, m.Set("type=bind,target=/foo,source=/foo,readonly=1")) + assert.Equal(t, m.values[0].ReadOnly, true) + + m = MountOpt{} + assert.NilError(t, m.Set("type=bind,target=/foo,source=/foo,readonly=true")) + assert.Equal(t, m.values[0].ReadOnly, true) + + m = MountOpt{} + assert.NilError(t, m.Set("type=bind,target=/foo,source=/foo,readonly=0")) + assert.Equal(t, m.values[0].ReadOnly, false) +} + +func TestMountOptVolumeNoCopy(t *testing.T) { + var m MountOpt + assert.NilError(t, m.Set("type=volume,target=/foo,volume-nocopy")) + assert.Equal(t, m.values[0].Source, "") + + m = MountOpt{} + assert.NilError(t, m.Set("type=volume,target=/foo,source=foo")) + assert.Equal(t, m.values[0].VolumeOptions == nil, true) + + m = MountOpt{} + assert.NilError(t, m.Set("type=volume,target=/foo,source=foo,volume-nocopy=true")) + assert.Equal(t, m.values[0].VolumeOptions != nil, true) + assert.Equal(t, m.values[0].VolumeOptions.NoCopy, true) + + m = MountOpt{} + assert.NilError(t, m.Set("type=volume,target=/foo,source=foo,volume-nocopy")) + assert.Equal(t, m.values[0].VolumeOptions != nil, true) + assert.Equal(t, m.values[0].VolumeOptions.NoCopy, true) + + m = MountOpt{} + assert.NilError(t, m.Set("type=volume,target=/foo,source=foo,volume-nocopy=1")) + assert.Equal(t, m.values[0].VolumeOptions != nil, true) + assert.Equal(t, m.values[0].VolumeOptions.NoCopy, true) +} + +func TestMountOptTypeConflict(t *testing.T) { + var m MountOpt + assert.Error(t, m.Set("type=bind,target=/foo,source=/foo,volume-nocopy=true"), "cannot mix") + assert.Error(t, m.Set("type=volume,target=/foo,source=/foo,bind-propagation=rprivate"), "cannot mix") +} From 17e9503bbb5922279692f4d5f8bcb942f7657455 Mon Sep 17 00:00:00 2001 From: Yong Tang Date: Tue, 1 Nov 2016 10:12:29 -0700 Subject: [PATCH 091/123] Add `--cpus` flag to control cpu resources This fix tries to address the proposal raised in 27921 and add `--cpus` flag for `docker run/create`. Basically, `--cpus` will allow user to specify a number (possibly partial) about how many CPUs the container will use. For example, on a 2-CPU system `--cpus 1.5` means the container will take 75% (1.5/2) of the CPU share. This fix adds a `NanoCPUs` field to `HostConfig` since swarmkit alreay have a concept of NanoCPUs for tasks. The `--cpus` flag will translate the number into reused `NanoCPUs` to be consistent. This fix adds integration tests to cover the changes. Related docs (`docker run` and Remote APIs) have been updated. This fix fixes 27921. Signed-off-by: Yong Tang --- opts/opts.go | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/opts/opts.go b/opts/opts.go index f8bb3ba74522..e452943ffc50 100644 --- a/opts/opts.go +++ b/opts/opts.go @@ -2,6 +2,7 @@ package opts import ( "fmt" + "math/big" "net" "regexp" "strings" @@ -319,3 +320,35 @@ func (o *FilterOpt) Type() string { func (o *FilterOpt) Value() filters.Args { return o.filter } + +// NanoCPUs is a type for fixed point fractional number. +type NanoCPUs int64 + +// String returns the string format of the number +func (c *NanoCPUs) String() string { + return big.NewRat(c.Value(), 1e9).FloatString(3) +} + +// Set sets the value of the NanoCPU by passing a string +func (c *NanoCPUs) Set(value string) error { + cpu, ok := new(big.Rat).SetString(value) + if !ok { + return fmt.Errorf("Failed to parse %v as a rational number", value) + } + nano := cpu.Mul(cpu, big.NewRat(1e9, 1)) + if !nano.IsInt() { + return fmt.Errorf("value is too precise") + } + *c = NanoCPUs(nano.Num().Int64()) + return nil +} + +// Type returns the type +func (c *NanoCPUs) Type() string { + return "NanoCPUs" +} + +// Value returns the value in int64 +func (c *NanoCPUs) Value() int64 { + return int64(*c) +} From 547dc2052c7df96f92fdeb4de908267af0d1c1d2 Mon Sep 17 00:00:00 2001 From: Akihiro Suda Date: Tue, 8 Nov 2016 05:32:21 +0000 Subject: [PATCH 092/123] opts/mount: add tmpfs-specific options added following options: * tmpfs-size * tmpfs-mode Signed-off-by: Akihiro Suda --- opts/mount.go | 32 ++++++++++++++++++++++++++++---- opts/mount_test.go | 31 +++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+), 4 deletions(-) diff --git a/opts/mount.go b/opts/mount.go index b6fccade18d7..ce6383ddca37 100644 --- a/opts/mount.go +++ b/opts/mount.go @@ -3,10 +3,12 @@ package opts import ( "encoding/csv" "fmt" + "os" "strconv" "strings" mounttypes "github.com/docker/docker/api/types/mount" + "github.com/docker/go-units" ) // MountOpt is a Value type for parsing mounts @@ -43,6 +45,13 @@ func (m *MountOpt) Set(value string) error { return mount.BindOptions } + tmpfsOptions := func() *mounttypes.TmpfsOptions { + if mount.TmpfsOptions == nil { + mount.TmpfsOptions = new(mounttypes.TmpfsOptions) + } + return mount.TmpfsOptions + } + setValueOnMap := func(target map[string]string, value string) { parts := strings.SplitN(value, "=", 2) if len(parts) == 1 { @@ -102,6 +111,18 @@ func (m *MountOpt) Set(value string) error { volumeOptions().DriverConfig.Options = make(map[string]string) } setValueOnMap(volumeOptions().DriverConfig.Options, value) + case "tmpfs-size": + sizeBytes, err := units.RAMInBytes(value) + if err != nil { + return fmt.Errorf("invalid value for %s: %s", key, value) + } + tmpfsOptions().SizeBytes = sizeBytes + case "tmpfs-mode": + ui64, err := strconv.ParseUint(value, 8, 32) + if err != nil { + return fmt.Errorf("invalid value for %s: %s", key, value) + } + tmpfsOptions().Mode = os.FileMode(ui64) default: return fmt.Errorf("unexpected key '%s' in '%s'", key, field) } @@ -115,11 +136,14 @@ func (m *MountOpt) Set(value string) error { return fmt.Errorf("target is required") } - if mount.Type == mounttypes.TypeBind && mount.VolumeOptions != nil { - return fmt.Errorf("cannot mix 'volume-*' options with mount type '%s'", mounttypes.TypeBind) + if mount.VolumeOptions != nil && mount.Type != mounttypes.TypeVolume { + return fmt.Errorf("cannot mix 'volume-*' options with mount type '%s'", mount.Type) + } + if mount.BindOptions != nil && mount.Type != mounttypes.TypeBind { + return fmt.Errorf("cannot mix 'bind-*' options with mount type '%s'", mount.Type) } - if mount.Type == mounttypes.TypeVolume && mount.BindOptions != nil { - return fmt.Errorf("cannot mix 'bind-*' options with mount type '%s'", mounttypes.TypeVolume) + if mount.TmpfsOptions != nil && mount.Type != mounttypes.TypeTmpfs { + return fmt.Errorf("cannot mix 'tmpfs-*' options with mount type '%s'", mount.Type) } m.values = append(m.values, mount) diff --git a/opts/mount_test.go b/opts/mount_test.go index 28c551bcc638..59606c38e2c3 100644 --- a/opts/mount_test.go +++ b/opts/mount_test.go @@ -1,6 +1,7 @@ package opts import ( + "os" "testing" mounttypes "github.com/docker/docker/api/types/mount" @@ -151,3 +152,33 @@ func TestMountOptTypeConflict(t *testing.T) { assert.Error(t, m.Set("type=bind,target=/foo,source=/foo,volume-nocopy=true"), "cannot mix") assert.Error(t, m.Set("type=volume,target=/foo,source=/foo,bind-propagation=rprivate"), "cannot mix") } + +func TestMountOptSetTmpfsNoError(t *testing.T) { + for _, testcase := range []string{ + // tests several aliases that should have same result. + "type=tmpfs,target=/target,tmpfs-size=1m,tmpfs-mode=0700", + "type=tmpfs,target=/target,tmpfs-size=1MB,tmpfs-mode=700", + } { + var mount MountOpt + + assert.NilError(t, mount.Set(testcase)) + + mounts := mount.Value() + assert.Equal(t, len(mounts), 1) + assert.DeepEqual(t, mounts[0], mounttypes.Mount{ + Type: mounttypes.TypeTmpfs, + Target: "/target", + TmpfsOptions: &mounttypes.TmpfsOptions{ + SizeBytes: 1024 * 1024, // not 1000 * 1000 + Mode: os.FileMode(0700), + }, + }) + } +} + +func TestMountOptSetTmpfsError(t *testing.T) { + var m MountOpt + assert.Error(t, m.Set("type=tmpfs,target=/foo,tmpfs-size=foo"), "invalid value for tmpfs-size") + assert.Error(t, m.Set("type=tmpfs,target=/foo,tmpfs-mode=foo"), "invalid value for tmpfs-mode") + assert.Error(t, m.Set("type=tmpfs"), "target is required") +} From 583dd837278ab8999a45ba9be109f706d57485ae Mon Sep 17 00:00:00 2001 From: Yong Tang Date: Tue, 8 Nov 2016 07:06:07 -0800 Subject: [PATCH 093/123] Remove `-ptr` from the help output of `service create` This fix is based on the comment: https://github.com/docker/docker/pull/28147#discussion_r86996347 Previously the output string of the `DurationOpt` is `duration-ptr` and `Uint64Opt` is `uint64-ptr`. While it is clear to developers, for a normal user `-ptr` might not be very informative. On the other hand, the default value of `DurationOpt` and `Uint64Opt` has already been quite informative: `none`. That means if no flag provided, the value will be treated as none. (like a ptr with nil as the default) For that reason this fix removes the `-ptr`. Also, the output in the docs of `service create` has been quite out-of-sync with the true output. So this fix updates the docs to have the most up-to-date help output of `service create --help`. This fix is related to #28147. Signed-off-by: Yong Tang --- opts/opts.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opts/opts.go b/opts/opts.go index e452943ffc50..0ac391add8f4 100644 --- a/opts/opts.go +++ b/opts/opts.go @@ -345,7 +345,7 @@ func (c *NanoCPUs) Set(value string) error { // Type returns the type func (c *NanoCPUs) Type() string { - return "NanoCPUs" + return "decimal" } // Value returns the value in int64 From 20525b5695126fbf6c8dba970bb7011d2d9d0731 Mon Sep 17 00:00:00 2001 From: Evan Hazlett Date: Thu, 3 Nov 2016 11:08:22 -0400 Subject: [PATCH 094/123] move secretopt to opts pkg Signed-off-by: Evan Hazlett --- opts/secret.go | 95 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 95 insertions(+) create mode 100644 opts/secret.go diff --git a/opts/secret.go b/opts/secret.go new file mode 100644 index 000000000000..34ed42a68005 --- /dev/null +++ b/opts/secret.go @@ -0,0 +1,95 @@ +package opts + +import ( + "encoding/csv" + "fmt" + "os" + "path/filepath" + "strconv" + "strings" + + "github.com/docker/docker/api/types" +) + +// SecretOpt is a Value type for parsing secrets +type SecretOpt struct { + values []*types.SecretRequestOptions +} + +// Set a new secret value +func (o *SecretOpt) Set(value string) error { + csvReader := csv.NewReader(strings.NewReader(value)) + fields, err := csvReader.Read() + if err != nil { + return err + } + + options := &types.SecretRequestOptions{ + Source: "", + Target: "", + UID: "0", + GID: "0", + Mode: 0444, + } + + for _, field := range fields { + parts := strings.SplitN(field, "=", 2) + key := strings.ToLower(parts[0]) + + if len(parts) != 2 { + return fmt.Errorf("invalid field '%s' must be a key=value pair", field) + } + + value := parts[1] + switch key { + case "source": + options.Source = value + case "target": + tDir, _ := filepath.Split(value) + if tDir != "" { + return fmt.Errorf("target must not be a path") + } + options.Target = value + case "uid": + options.UID = value + case "gid": + options.GID = value + case "mode": + m, err := strconv.ParseUint(value, 0, 32) + if err != nil { + return fmt.Errorf("invalid mode specified: %v", err) + } + + options.Mode = os.FileMode(m) + default: + return fmt.Errorf("invalid field in secret request: %s", key) + } + } + + if options.Source == "" { + return fmt.Errorf("source is required") + } + + o.values = append(o.values, options) + return nil +} + +// Type returns the type of this option +func (o *SecretOpt) Type() string { + return "secret" +} + +// String returns a string repr of this option +func (o *SecretOpt) String() string { + secrets := []string{} + for _, secret := range o.values { + repr := fmt.Sprintf("%s -> %s", secret.Source, secret.Target) + secrets = append(secrets, repr) + } + return strings.Join(secrets, ", ") +} + +// Value returns the secret requests +func (o *SecretOpt) Value() []*types.SecretRequestOptions { + return o.values +} From 7180c8afed1996557eab653bc6fab7d548a5b5b8 Mon Sep 17 00:00:00 2001 From: Evan Hazlett Date: Thu, 3 Nov 2016 15:56:05 -0400 Subject: [PATCH 095/123] secrets: support simple syntax --secret foo Signed-off-by: Evan Hazlett --- opts/secret.go | 14 +++++++++- opts/secret_test.go | 67 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 80 insertions(+), 1 deletion(-) create mode 100644 opts/secret_test.go diff --git a/opts/secret.go b/opts/secret.go index 34ed42a68005..9475d9f506ae 100644 --- a/opts/secret.go +++ b/opts/secret.go @@ -32,6 +32,14 @@ func (o *SecretOpt) Set(value string) error { Mode: 0444, } + // support a simple syntax of --secret foo + if len(fields) == 1 { + options.Source = fields[0] + options.Target = fields[0] + o.values = append(o.values, options) + return nil + } + for _, field := range fields { parts := strings.SplitN(field, "=", 2) key := strings.ToLower(parts[0]) @@ -62,7 +70,11 @@ func (o *SecretOpt) Set(value string) error { options.Mode = os.FileMode(m) default: - return fmt.Errorf("invalid field in secret request: %s", key) + if len(fields) == 1 && value == "" { + + } else { + return fmt.Errorf("invalid field in secret request: %s", key) + } } } diff --git a/opts/secret_test.go b/opts/secret_test.go new file mode 100644 index 000000000000..ce4418a0bcaa --- /dev/null +++ b/opts/secret_test.go @@ -0,0 +1,67 @@ +package opts + +import ( + "os" + "testing" + + "github.com/docker/docker/pkg/testutil/assert" +) + +func TestSecretOptionsSimple(t *testing.T) { + var opt SecretOpt + + testCase := "app-secret" + assert.NilError(t, opt.Set(testCase)) + + reqs := opt.Value() + assert.Equal(t, len(reqs), 1) + req := reqs[0] + assert.Equal(t, req.Source, "app-secret") + assert.Equal(t, req.Target, "app-secret") + assert.Equal(t, req.UID, "0") + assert.Equal(t, req.GID, "0") +} + +func TestSecretOptionsSourceTarget(t *testing.T) { + var opt SecretOpt + + testCase := "source=foo,target=testing" + assert.NilError(t, opt.Set(testCase)) + + reqs := opt.Value() + assert.Equal(t, len(reqs), 1) + req := reqs[0] + assert.Equal(t, req.Source, "foo") + assert.Equal(t, req.Target, "testing") +} + +func TestSecretOptionsCustomUidGid(t *testing.T) { + var opt SecretOpt + + testCase := "source=foo,target=testing,uid=1000,gid=1001" + assert.NilError(t, opt.Set(testCase)) + + reqs := opt.Value() + assert.Equal(t, len(reqs), 1) + req := reqs[0] + assert.Equal(t, req.Source, "foo") + assert.Equal(t, req.Target, "testing") + assert.Equal(t, req.UID, "1000") + assert.Equal(t, req.GID, "1001") +} + +func TestSecretOptionsCustomMode(t *testing.T) { + var opt SecretOpt + + testCase := "source=foo,target=testing,uid=1000,gid=1001,mode=0444" + assert.NilError(t, opt.Set(testCase)) + + reqs := opt.Value() + assert.Equal(t, len(reqs), 1) + req := reqs[0] + assert.Equal(t, req.Source, "foo") + assert.Equal(t, req.Target, "testing") + assert.Equal(t, req.UID, "1000") + assert.Equal(t, req.GID, "1001") + assert.Equal(t, req.Mode, os.FileMode(0444)) +} From 9d3c317c9559e417e0759b654c0510346f1a36b5 Mon Sep 17 00:00:00 2001 From: Evan Hazlett Date: Fri, 4 Nov 2016 14:24:44 -0400 Subject: [PATCH 096/123] SecretRequestOptions -> SecretRequestOption Signed-off-by: Evan Hazlett --- opts/secret.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/opts/secret.go b/opts/secret.go index 9475d9f506ae..b77a33f685a4 100644 --- a/opts/secret.go +++ b/opts/secret.go @@ -13,7 +13,7 @@ import ( // SecretOpt is a Value type for parsing secrets type SecretOpt struct { - values []*types.SecretRequestOptions + values []*types.SecretRequestOption } // Set a new secret value @@ -24,7 +24,7 @@ func (o *SecretOpt) Set(value string) error { return err } - options := &types.SecretRequestOptions{ + options := &types.SecretRequestOption{ Source: "", Target: "", UID: "0", @@ -102,6 +102,6 @@ func (o *SecretOpt) String() string { } // Value returns the secret requests -func (o *SecretOpt) Value() []*types.SecretRequestOptions { +func (o *SecretOpt) Value() []*types.SecretRequestOption { return o.values } From 18ad4598a8c654da2a76808b761034b376e8bbbc Mon Sep 17 00:00:00 2001 From: Daniel Nephin Date: Fri, 28 Oct 2016 17:30:20 -0400 Subject: [PATCH 097/123] Add swarmkit fields to stack service. Signed-off-by: Daniel Nephin --- opts/opts.go | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/opts/opts.go b/opts/opts.go index 0ac391add8f4..ae851537ec8a 100644 --- a/opts/opts.go +++ b/opts/opts.go @@ -331,16 +331,9 @@ func (c *NanoCPUs) String() string { // Set sets the value of the NanoCPU by passing a string func (c *NanoCPUs) Set(value string) error { - cpu, ok := new(big.Rat).SetString(value) - if !ok { - return fmt.Errorf("Failed to parse %v as a rational number", value) - } - nano := cpu.Mul(cpu, big.NewRat(1e9, 1)) - if !nano.IsInt() { - return fmt.Errorf("value is too precise") - } - *c = NanoCPUs(nano.Num().Int64()) - return nil + cpus, err := ParseCPUs(value) + *c = NanoCPUs(cpus) + return err } // Type returns the type @@ -352,3 +345,16 @@ func (c *NanoCPUs) Type() string { func (c *NanoCPUs) Value() int64 { return int64(*c) } + +// ParseCPUs takes a string ratio and returns an integer value of nano cpus +func ParseCPUs(value string) (int64, error) { + cpu, ok := new(big.Rat).SetString(value) + if !ok { + return 0, fmt.Errorf("failed to parse %v as a rational number", value) + } + nano := cpu.Mul(cpu, big.NewRat(1e9, 1)) + if !nano.IsInt() { + return 0, fmt.Errorf("value is too precise") + } + return nano.Num().Int64(), nil +} From 7426b9d61bf2b23b39df8b6453ef04eff85e595d Mon Sep 17 00:00:00 2001 From: Jana Radhakrishnan Date: Thu, 10 Nov 2016 12:13:26 -0800 Subject: [PATCH 098/123] Add support for host port PublishMode in services Add api/cli support for adding host port PublishMode in services. Signed-off-by: Jana Radhakrishnan --- opts/port.go | 99 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 99 insertions(+) create mode 100644 opts/port.go diff --git a/opts/port.go b/opts/port.go new file mode 100644 index 000000000000..ef3f12a4653a --- /dev/null +++ b/opts/port.go @@ -0,0 +1,99 @@ +package opts + +import ( + "encoding/csv" + "fmt" + "strconv" + "strings" + + "github.com/docker/docker/api/types/swarm" +) + +const ( + portOptTargetPort = "target" + portOptPublishedPort = "published" + portOptProtocol = "protocol" + portOptMode = "mode" +) + +type PortOpt struct { + ports []swarm.PortConfig +} + +// Set a new port value +func (p *PortOpt) Set(value string) error { + csvReader := csv.NewReader(strings.NewReader(value)) + fields, err := csvReader.Read() + if err != nil { + return err + } + + pConfig := swarm.PortConfig{} + for _, field := range fields { + parts := strings.SplitN(field, "=", 2) + if len(parts) != 2 { + return fmt.Errorf("invalid field %s", field) + } + + key := strings.ToLower(parts[0]) + value := strings.ToLower(parts[1]) + + switch key { + case portOptProtocol: + if value != string(swarm.PortConfigProtocolTCP) && value != string(swarm.PortConfigProtocolUDP) { + return fmt.Errorf("invalid protocol value %s", value) + } + + pConfig.Protocol = swarm.PortConfigProtocol(value) + case portOptMode: + if value != string(swarm.PortConfigPublishModeIngress) && value != string(swarm.PortConfigPublishModeHost) { + return fmt.Errorf("invalid publish mode value %s", value) + } + + pConfig.PublishMode = swarm.PortConfigPublishMode(value) + case portOptTargetPort: + tPort, err := strconv.ParseUint(value, 10, 16) + if err != nil { + return err + } + + pConfig.TargetPort = uint32(tPort) + case portOptPublishedPort: + pPort, err := strconv.ParseUint(value, 10, 16) + if err != nil { + return err + } + + pConfig.PublishedPort = uint32(pPort) + default: + return fmt.Errorf("invalid field key %s", key) + } + } + + if pConfig.TargetPort == 0 { + return fmt.Errorf("missing mandatory field %q", portOptTargetPort) + } + + p.ports = append(p.ports, pConfig) + return nil +} + +// Type returns the type of this option +func (p *PortOpt) Type() string { + return "port" +} + +// String returns a string repr of this option +func (p *PortOpt) String() string { + ports := []string{} + for _, port := range p.ports { + repr := fmt.Sprintf("%v:%v/%s/%s", port.PublishedPort, port.TargetPort, port.Protocol, port.PublishMode) + ports = append(ports, repr) + } + return strings.Join(ports, ", ") +} + +// Value returns the ports +func (p *PortOpt) Value() []swarm.PortConfig { + return p.ports +} From 9806df060b00e55aa366a18a61ddad6c1d97af06 Mon Sep 17 00:00:00 2001 From: wefine Date: Mon, 14 Nov 2016 17:01:17 +0800 Subject: [PATCH 099/123] fix t.Errorf to t.Error in serveral _test.go Signed-off-by: wefine --- opts/opts_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opts/opts_test.go b/opts/opts_test.go index 9f41e4786484..46cb9e0b03a1 100644 --- a/opts/opts_test.go +++ b/opts/opts_test.go @@ -50,7 +50,7 @@ func TestMapOpts(t *testing.T) { t.Errorf("max-size = %s != 1", tmpMap["max-size"]) } if o.Set("dummy-val=3") == nil { - t.Errorf("validator is not being called") + t.Error("validator is not being called") } } From 4d5944cd5155a4c2ef5661c7c07d3fb389f4c4d7 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Fri, 18 Nov 2016 15:57:11 -0800 Subject: [PATCH 100/123] fix a few golint errors Signed-off-by: Victor Vieux --- opts/port.go | 1 + 1 file changed, 1 insertion(+) diff --git a/opts/port.go b/opts/port.go index ef3f12a4653a..d337cb1a43ba 100644 --- a/opts/port.go +++ b/opts/port.go @@ -16,6 +16,7 @@ const ( portOptMode = "mode" ) +// PortOpt represents a port config in swarm mode. type PortOpt struct { ports []swarm.PortConfig } From 1b400f6284b17fffa0495e1c801155fdd0716e98 Mon Sep 17 00:00:00 2001 From: Yong Tang Date: Thu, 18 Aug 2016 18:09:07 -0700 Subject: [PATCH 101/123] Return error for incorrect argument of `service update --publish-rm ` Currently `--publish-rm` only accepts `` or `[/Protocol]` though there are some confusions. Since `--publish-add` accepts `:[/Protocol]`, some user may provide `--publish-rm 80:80`. However, there is no error checking so the incorrect provided argument is ignored silently. This fix adds the check to make sure `--publish-rm` only accepts `[/Protocol]` and returns error if the format is invalid. The `--publish-rm` itself may needs to be revisited to have a better UI/UX experience, see discussions on: https://github.com/docker/swarmkit/issues/1396 https://github.com/docker/docker/issues/25200#issuecomment-236213242 https://github.com/docker/docker/issues/25338#issuecomment-240787002 This fix is short term measure so that end users are not misled by the silently ignored error of `--publish-rm`. This fix is related to (but is not a complete fix): https://github.com/docker/swarmkit/issues/1396 Signed-off-by: Yong Tang --- opts/opts.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/opts/opts.go b/opts/opts.go index ae851537ec8a..9f66c039e7f5 100644 --- a/opts/opts.go +++ b/opts/opts.go @@ -108,6 +108,12 @@ func (opts *ListOpts) Type() string { return "list" } +// WithValidator returns the ListOpts with validator set. +func (opts *ListOpts) WithValidator(validator ValidatorFctType) *ListOpts { + opts.validator = validator + return opts +} + // NamedOption is an interface that list and map options // with names implement. type NamedOption interface { From 016a56b06440d8c53106488714f50ce09bcaa2a4 Mon Sep 17 00:00:00 2001 From: Vincent Demeester Date: Thu, 8 Dec 2016 22:32:10 +0100 Subject: [PATCH 102/123] Remove --port and update --publish for services to support syntaxes Add support for simple and complex syntax to `--publish` through the use of `PortOpt`. Signed-off-by: Vincent Demeester --- opts/port.go | 117 ++++++++++++++-------- opts/port_test.go | 243 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 320 insertions(+), 40 deletions(-) create mode 100644 opts/port_test.go diff --git a/opts/port.go b/opts/port.go index d337cb1a43ba..e1c4449a385f 100644 --- a/opts/port.go +++ b/opts/port.go @@ -3,10 +3,12 @@ package opts import ( "encoding/csv" "fmt" + "regexp" "strconv" "strings" "github.com/docker/docker/api/types/swarm" + "github.com/docker/go-connections/nat" ) const ( @@ -23,59 +25,75 @@ type PortOpt struct { // Set a new port value func (p *PortOpt) Set(value string) error { - csvReader := csv.NewReader(strings.NewReader(value)) - fields, err := csvReader.Read() + longSyntax, err := regexp.MatchString(`\w+=\w+(,\w+=\w+)*`, value) if err != nil { return err } - - pConfig := swarm.PortConfig{} - for _, field := range fields { - parts := strings.SplitN(field, "=", 2) - if len(parts) != 2 { - return fmt.Errorf("invalid field %s", field) + if longSyntax { + csvReader := csv.NewReader(strings.NewReader(value)) + fields, err := csvReader.Read() + if err != nil { + return err } - key := strings.ToLower(parts[0]) - value := strings.ToLower(parts[1]) - - switch key { - case portOptProtocol: - if value != string(swarm.PortConfigProtocolTCP) && value != string(swarm.PortConfigProtocolUDP) { - return fmt.Errorf("invalid protocol value %s", value) + pConfig := swarm.PortConfig{} + for _, field := range fields { + parts := strings.SplitN(field, "=", 2) + if len(parts) != 2 { + return fmt.Errorf("invalid field %s", field) } - pConfig.Protocol = swarm.PortConfigProtocol(value) - case portOptMode: - if value != string(swarm.PortConfigPublishModeIngress) && value != string(swarm.PortConfigPublishModeHost) { - return fmt.Errorf("invalid publish mode value %s", value) + key := strings.ToLower(parts[0]) + value := strings.ToLower(parts[1]) + + switch key { + case portOptProtocol: + if value != string(swarm.PortConfigProtocolTCP) && value != string(swarm.PortConfigProtocolUDP) { + return fmt.Errorf("invalid protocol value %s", value) + } + + pConfig.Protocol = swarm.PortConfigProtocol(value) + case portOptMode: + if value != string(swarm.PortConfigPublishModeIngress) && value != string(swarm.PortConfigPublishModeHost) { + return fmt.Errorf("invalid publish mode value %s", value) + } + + pConfig.PublishMode = swarm.PortConfigPublishMode(value) + case portOptTargetPort: + tPort, err := strconv.ParseUint(value, 10, 16) + if err != nil { + return err + } + + pConfig.TargetPort = uint32(tPort) + case portOptPublishedPort: + pPort, err := strconv.ParseUint(value, 10, 16) + if err != nil { + return err + } + + pConfig.PublishedPort = uint32(pPort) + default: + return fmt.Errorf("invalid field key %s", key) } + } - pConfig.PublishMode = swarm.PortConfigPublishMode(value) - case portOptTargetPort: - tPort, err := strconv.ParseUint(value, 10, 16) - if err != nil { - return err - } + if pConfig.TargetPort == 0 { + return fmt.Errorf("missing mandatory field %q", portOptTargetPort) + } - pConfig.TargetPort = uint32(tPort) - case portOptPublishedPort: - pPort, err := strconv.ParseUint(value, 10, 16) - if err != nil { - return err - } + p.ports = append(p.ports, pConfig) + } else { + // short syntax + portConfigs := []swarm.PortConfig{} + // We can ignore errors because the format was already validated by ValidatePort + ports, portBindings, _ := nat.ParsePortSpecs([]string{value}) - pConfig.PublishedPort = uint32(pPort) - default: - return fmt.Errorf("invalid field key %s", key) + for port := range ports { + portConfigs = append(portConfigs, ConvertPortToPortConfig(port, portBindings)...) } + p.ports = append(p.ports, portConfigs...) } - - if pConfig.TargetPort == 0 { - return fmt.Errorf("missing mandatory field %q", portOptTargetPort) - } - - p.ports = append(p.ports, pConfig) return nil } @@ -98,3 +116,22 @@ func (p *PortOpt) String() string { func (p *PortOpt) Value() []swarm.PortConfig { return p.ports } + +// ConvertPortToPortConfig converts ports to the swarm type +func ConvertPortToPortConfig( + port nat.Port, + portBindings map[nat.Port][]nat.PortBinding, +) []swarm.PortConfig { + ports := []swarm.PortConfig{} + + for _, binding := range portBindings[port] { + hostPort, _ := strconv.ParseUint(binding.HostPort, 10, 16) + ports = append(ports, swarm.PortConfig{ + //TODO Name: ? + Protocol: swarm.PortConfigProtocol(strings.ToLower(port.Proto())), + TargetPort: uint32(port.Int()), + PublishedPort: uint32(hostPort), + }) + } + return ports +} diff --git a/opts/port_test.go b/opts/port_test.go new file mode 100644 index 000000000000..8046b4428eb4 --- /dev/null +++ b/opts/port_test.go @@ -0,0 +1,243 @@ +package opts + +import ( + "testing" + + "github.com/docker/docker/api/types/swarm" + "github.com/docker/docker/pkg/testutil/assert" +) + +func TestPortOptValidSimpleSyntax(t *testing.T) { + testCases := []struct { + value string + expected []swarm.PortConfig + }{ + { + value: "80", + expected: []swarm.PortConfig{ + { + Protocol: "tcp", + TargetPort: 80, + }, + }, + }, + { + value: "80:8080", + expected: []swarm.PortConfig{ + { + Protocol: "tcp", + TargetPort: 8080, + PublishedPort: 80, + }, + }, + }, + { + value: "8080:80/tcp", + expected: []swarm.PortConfig{ + { + Protocol: "tcp", + TargetPort: 80, + PublishedPort: 8080, + }, + }, + }, + { + value: "80:8080/udp", + expected: []swarm.PortConfig{ + { + Protocol: "udp", + TargetPort: 8080, + PublishedPort: 80, + }, + }, + }, + { + value: "80-81:8080-8081/tcp", + expected: []swarm.PortConfig{ + { + Protocol: "tcp", + TargetPort: 8080, + PublishedPort: 80, + }, + { + Protocol: "tcp", + TargetPort: 8081, + PublishedPort: 81, + }, + }, + }, + { + value: "80-82:8080-8082/udp", + expected: []swarm.PortConfig{ + { + Protocol: "udp", + TargetPort: 8080, + PublishedPort: 80, + }, + { + Protocol: "udp", + TargetPort: 8081, + PublishedPort: 81, + }, + { + Protocol: "udp", + TargetPort: 8082, + PublishedPort: 82, + }, + }, + }, + } + for _, tc := range testCases { + var port PortOpt + assert.NilError(t, port.Set(tc.value)) + assert.Equal(t, len(port.Value()), len(tc.expected)) + for _, expectedPortConfig := range tc.expected { + assertContains(t, port.Value(), expectedPortConfig) + } + } +} + +func TestPortOptValidComplexSyntax(t *testing.T) { + testCases := []struct { + value string + expected []swarm.PortConfig + }{ + { + value: "target=80", + expected: []swarm.PortConfig{ + { + TargetPort: 80, + }, + }, + }, + { + value: "target=80,protocol=tcp", + expected: []swarm.PortConfig{ + { + Protocol: "tcp", + TargetPort: 80, + }, + }, + }, + { + value: "target=80,published=8080,protocol=tcp", + expected: []swarm.PortConfig{ + { + Protocol: "tcp", + TargetPort: 80, + PublishedPort: 8080, + }, + }, + }, + { + value: "published=80,target=8080,protocol=tcp", + expected: []swarm.PortConfig{ + { + Protocol: "tcp", + TargetPort: 8080, + PublishedPort: 80, + }, + }, + }, + { + value: "target=80,published=8080,protocol=tcp,mode=host", + expected: []swarm.PortConfig{ + { + Protocol: "tcp", + TargetPort: 80, + PublishedPort: 8080, + PublishMode: "host", + }, + }, + }, + { + value: "target=80,published=8080,mode=host", + expected: []swarm.PortConfig{ + { + TargetPort: 80, + PublishedPort: 8080, + PublishMode: "host", + }, + }, + }, + { + value: "target=80,published=8080,mode=ingress", + expected: []swarm.PortConfig{ + { + TargetPort: 80, + PublishedPort: 8080, + PublishMode: "ingress", + }, + }, + }, + } + for _, tc := range testCases { + var port PortOpt + assert.NilError(t, port.Set(tc.value)) + assert.Equal(t, len(port.Value()), len(tc.expected)) + for _, expectedPortConfig := range tc.expected { + assertContains(t, port.Value(), expectedPortConfig) + } + } +} + +func TestPortOptInvalidComplexSyntax(t *testing.T) { + testCases := []struct { + value string + expectedError string + }{ + { + value: "invalid,target=80", + expectedError: "invalid field", + }, + { + value: "invalid=field", + expectedError: "invalid field", + }, + { + value: "protocol=invalid", + expectedError: "invalid protocol value", + }, + { + value: "target=invalid", + expectedError: "invalid syntax", + }, + { + value: "published=invalid", + expectedError: "invalid syntax", + }, + { + value: "mode=invalid", + expectedError: "invalid publish mode value", + }, + { + value: "published=8080,protocol=tcp,mode=ingress", + expectedError: "missing mandatory field", + }, + { + value: `target=80,protocol="tcp,mode=ingress"`, + expectedError: "non-quoted-field", + }, + { + value: `target=80,"protocol=tcp,mode=ingress"`, + expectedError: "invalid protocol value", + }, + } + for _, tc := range testCases { + var port PortOpt + assert.Error(t, port.Set(tc.value), tc.expectedError) + } +} + +func assertContains(t *testing.T, portConfigs []swarm.PortConfig, expected swarm.PortConfig) { + var contains = false + for _, portConfig := range portConfigs { + if portConfig == expected { + contains = true + break + } + } + if !contains { + t.Errorf("expected %v to contain %v, did not", portConfigs, expected) + } +} From 16bbd3441ade6495352faa5ac4442e92b1f633d7 Mon Sep 17 00:00:00 2001 From: Vincent Demeester Date: Fri, 9 Dec 2016 21:17:57 +0100 Subject: [PATCH 103/123] Make --publish-rm precedes --publish-add, so that add wins `--publish-add 8081:81 --publish-add 8082:82 --publish-rm 80 --publish-rm 81/tcp --publish-rm 82/tcp` would thus result in 81 and 82 to be published. Signed-off-by: Vincent Demeester --- opts/port.go | 9 +++++++++ opts/port_test.go | 26 +++++++++++++++++++++----- 2 files changed, 30 insertions(+), 5 deletions(-) diff --git a/opts/port.go b/opts/port.go index e1c4449a385f..020a5d1e1c45 100644 --- a/opts/port.go +++ b/opts/port.go @@ -82,6 +82,14 @@ func (p *PortOpt) Set(value string) error { return fmt.Errorf("missing mandatory field %q", portOptTargetPort) } + if pConfig.PublishMode == "" { + pConfig.PublishMode = swarm.PortConfigPublishModeIngress + } + + if pConfig.Protocol == "" { + pConfig.Protocol = swarm.PortConfigProtocolTCP + } + p.ports = append(p.ports, pConfig) } else { // short syntax @@ -131,6 +139,7 @@ func ConvertPortToPortConfig( Protocol: swarm.PortConfigProtocol(strings.ToLower(port.Proto())), TargetPort: uint32(port.Int()), PublishedPort: uint32(hostPort), + PublishMode: swarm.PortConfigPublishModeIngress, }) } return ports diff --git a/opts/port_test.go b/opts/port_test.go index 8046b4428eb4..67bcf8f1d931 100644 --- a/opts/port_test.go +++ b/opts/port_test.go @@ -16,8 +16,9 @@ func TestPortOptValidSimpleSyntax(t *testing.T) { value: "80", expected: []swarm.PortConfig{ { - Protocol: "tcp", - TargetPort: 80, + Protocol: "tcp", + TargetPort: 80, + PublishMode: swarm.PortConfigPublishModeIngress, }, }, }, @@ -28,6 +29,7 @@ func TestPortOptValidSimpleSyntax(t *testing.T) { Protocol: "tcp", TargetPort: 8080, PublishedPort: 80, + PublishMode: swarm.PortConfigPublishModeIngress, }, }, }, @@ -38,6 +40,7 @@ func TestPortOptValidSimpleSyntax(t *testing.T) { Protocol: "tcp", TargetPort: 80, PublishedPort: 8080, + PublishMode: swarm.PortConfigPublishModeIngress, }, }, }, @@ -48,6 +51,7 @@ func TestPortOptValidSimpleSyntax(t *testing.T) { Protocol: "udp", TargetPort: 8080, PublishedPort: 80, + PublishMode: swarm.PortConfigPublishModeIngress, }, }, }, @@ -58,11 +62,13 @@ func TestPortOptValidSimpleSyntax(t *testing.T) { Protocol: "tcp", TargetPort: 8080, PublishedPort: 80, + PublishMode: swarm.PortConfigPublishModeIngress, }, { Protocol: "tcp", TargetPort: 8081, PublishedPort: 81, + PublishMode: swarm.PortConfigPublishModeIngress, }, }, }, @@ -73,16 +79,19 @@ func TestPortOptValidSimpleSyntax(t *testing.T) { Protocol: "udp", TargetPort: 8080, PublishedPort: 80, + PublishMode: swarm.PortConfigPublishModeIngress, }, { Protocol: "udp", TargetPort: 8081, PublishedPort: 81, + PublishMode: swarm.PortConfigPublishModeIngress, }, { Protocol: "udp", TargetPort: 8082, PublishedPort: 82, + PublishMode: swarm.PortConfigPublishModeIngress, }, }, }, @@ -106,7 +115,9 @@ func TestPortOptValidComplexSyntax(t *testing.T) { value: "target=80", expected: []swarm.PortConfig{ { - TargetPort: 80, + TargetPort: 80, + Protocol: "tcp", + PublishMode: swarm.PortConfigPublishModeIngress, }, }, }, @@ -114,8 +125,9 @@ func TestPortOptValidComplexSyntax(t *testing.T) { value: "target=80,protocol=tcp", expected: []swarm.PortConfig{ { - Protocol: "tcp", - TargetPort: 80, + Protocol: "tcp", + TargetPort: 80, + PublishMode: swarm.PortConfigPublishModeIngress, }, }, }, @@ -126,6 +138,7 @@ func TestPortOptValidComplexSyntax(t *testing.T) { Protocol: "tcp", TargetPort: 80, PublishedPort: 8080, + PublishMode: swarm.PortConfigPublishModeIngress, }, }, }, @@ -136,6 +149,7 @@ func TestPortOptValidComplexSyntax(t *testing.T) { Protocol: "tcp", TargetPort: 8080, PublishedPort: 80, + PublishMode: swarm.PortConfigPublishModeIngress, }, }, }, @@ -157,6 +171,7 @@ func TestPortOptValidComplexSyntax(t *testing.T) { TargetPort: 80, PublishedPort: 8080, PublishMode: "host", + Protocol: "tcp", }, }, }, @@ -167,6 +182,7 @@ func TestPortOptValidComplexSyntax(t *testing.T) { TargetPort: 80, PublishedPort: 8080, PublishMode: "ingress", + Protocol: "tcp", }, }, }, From a58827b0c203dc2358af0cb25e8c1c7175dd8424 Mon Sep 17 00:00:00 2001 From: yuexiao-wang Date: Tue, 20 Dec 2016 19:14:41 +0800 Subject: [PATCH 104/123] Change tls to TLS Signed-off-by: yuexiao-wang --- opts/hosts.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opts/hosts.go b/opts/hosts.go index 266df1e5374e..1107b7209f84 100644 --- a/opts/hosts.go +++ b/opts/hosts.go @@ -37,7 +37,7 @@ func ValidateHost(val string) (string, error) { } } // Note: unlike most flag validators, we don't return the mutated value here - // we need to know what the user entered later (using ParseHost) to adjust for tls + // we need to know what the user entered later (using ParseHost) to adjust for TLS return val, nil } From 8b725e10e72baa99f367ae05d1126681f0b81206 Mon Sep 17 00:00:00 2001 From: Vincent Demeester Date: Fri, 23 Dec 2016 20:09:12 +0100 Subject: [PATCH 105/123] =?UTF-8?q?Clean=20some=20stuff=20from=20runconfig?= =?UTF-8?q?=20that=20are=20cli=20only=E2=80=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit … or could be in `opts` package. Having `runconfig/opts` and `opts` doesn't really make sense and make it difficult to know where to put some code. Signed-off-by: Vincent Demeester --- opts/env.go | 46 +++++++++++++++++ opts/env_test.go | 42 ++++++++++++++++ opts/hosts.go | 14 ++++++ opts/hosts_test.go | 33 ++++++++++++ opts/opts.go | 38 ++++++++++++++ opts/opts_test.go | 75 ++++++++++++++++++++++++++++ opts/runtime.go | 79 +++++++++++++++++++++++++++++ opts/throttledevice.go | 111 +++++++++++++++++++++++++++++++++++++++++ opts/ulimit.go | 57 +++++++++++++++++++++ opts/ulimit_test.go | 42 ++++++++++++++++ opts/weightdevice.go | 89 +++++++++++++++++++++++++++++++++ 11 files changed, 626 insertions(+) create mode 100644 opts/env.go create mode 100644 opts/env_test.go create mode 100644 opts/runtime.go create mode 100644 opts/throttledevice.go create mode 100644 opts/ulimit.go create mode 100644 opts/ulimit_test.go create mode 100644 opts/weightdevice.go diff --git a/opts/env.go b/opts/env.go new file mode 100644 index 000000000000..e6ddd733090a --- /dev/null +++ b/opts/env.go @@ -0,0 +1,46 @@ +package opts + +import ( + "fmt" + "os" + "runtime" + "strings" +) + +// ValidateEnv validates an environment variable and returns it. +// If no value is specified, it returns the current value using os.Getenv. +// +// As on ParseEnvFile and related to #16585, environment variable names +// are not validate what so ever, it's up to application inside docker +// to validate them or not. +// +// The only validation here is to check if name is empty, per #25099 +func ValidateEnv(val string) (string, error) { + arr := strings.Split(val, "=") + if arr[0] == "" { + return "", fmt.Errorf("invalid environment variable: %s", val) + } + if len(arr) > 1 { + return val, nil + } + if !doesEnvExist(val) { + return val, nil + } + return fmt.Sprintf("%s=%s", val, os.Getenv(val)), nil +} + +func doesEnvExist(name string) bool { + for _, entry := range os.Environ() { + parts := strings.SplitN(entry, "=", 2) + if runtime.GOOS == "windows" { + // Environment variable are case-insensitive on Windows. PaTh, path and PATH are equivalent. + if strings.EqualFold(parts[0], name) { + return true + } + } + if parts[0] == name { + return true + } + } + return false +} diff --git a/opts/env_test.go b/opts/env_test.go new file mode 100644 index 000000000000..6f6c7a7a29ed --- /dev/null +++ b/opts/env_test.go @@ -0,0 +1,42 @@ +package opts + +import ( + "fmt" + "os" + "runtime" + "testing" +) + +func TestValidateEnv(t *testing.T) { + 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", + "asd!qwe": "asd!qwe", + "1asd": "1asd", + "123": "123", + "some space": "some space", + " some space before": " some space before", + "some space after ": "some space after ", + } + // Environment variables are case in-sensitive on Windows + if runtime.GOOS == "windows" { + valids["PaTh"] = fmt.Sprintf("PaTh=%v", os.Getenv("PATH")) + } + 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) + } + } +} diff --git a/opts/hosts.go b/opts/hosts.go index 1107b7209f84..7a948fb55ca1 100644 --- a/opts/hosts.go +++ b/opts/hosts.go @@ -149,3 +149,17 @@ func ParseTCPAddr(tryAddr string, defaultAddr string) (string, error) { return fmt.Sprintf("tcp://%s%s", net.JoinHostPort(host, port), u.Path), nil } + +// ValidateExtraHost validates that the specified string is a valid extrahost and returns it. +// ExtraHost is in the form of name:ip where the ip has to be a valid ip (IPv4 or IPv6). +func ValidateExtraHost(val string) (string, error) { + // allow for IPv6 addresses in extra hosts by only splitting on first ":" + arr := strings.SplitN(val, ":", 2) + if len(arr) != 2 || len(arr[0]) == 0 { + return "", fmt.Errorf("bad format for add-host: %q", val) + } + if _, err := ValidateIPAddress(arr[1]); err != nil { + return "", fmt.Errorf("invalid IP address in add-host: %q", arr[1]) + } + return val, nil +} diff --git a/opts/hosts_test.go b/opts/hosts_test.go index a5bec30d4c40..8aada6a95301 100644 --- a/opts/hosts_test.go +++ b/opts/hosts_test.go @@ -2,6 +2,7 @@ package opts import ( "fmt" + "strings" "testing" ) @@ -146,3 +147,35 @@ func TestParseInvalidUnixAddrInvalid(t *testing.T) { t.Fatalf("Expected an %v, got %v", v, "unix:///var/run/docker.sock") } } + +func TestValidateExtraHosts(t *testing.T) { + valid := []string{ + `myhost:192.168.0.1`, + `thathost:10.0.2.1`, + `anipv6host:2003:ab34:e::1`, + `ipv6local:::1`, + } + + invalid := map[string]string{ + `myhost:192.notanipaddress.1`: `invalid IP`, + `thathost-nosemicolon10.0.0.1`: `bad format`, + `anipv6host:::::1`: `invalid IP`, + `ipv6local:::0::`: `invalid IP`, + } + + for _, extrahost := range valid { + if _, err := ValidateExtraHost(extrahost); err != nil { + t.Fatalf("ValidateExtraHost(`"+extrahost+"`) should succeed: error %v", err) + } + } + + for extraHost, expectedError := range invalid { + if _, err := ValidateExtraHost(extraHost); err == nil { + t.Fatalf("ValidateExtraHost(`%q`) should have failed validation", extraHost) + } else { + if !strings.Contains(err.Error(), expectedError) { + t.Fatalf("ValidateExtraHost(`%q`) error should contain %q", extraHost, expectedError) + } + } + } +} diff --git a/opts/opts.go b/opts/opts.go index 9f66c039e7f5..c0f9cebf6024 100644 --- a/opts/opts.go +++ b/opts/opts.go @@ -4,6 +4,7 @@ import ( "fmt" "math/big" "net" + "path" "regexp" "strings" @@ -231,6 +232,15 @@ func ValidateIPAddress(val string) (string, error) { return "", fmt.Errorf("%s is not an ip address", val) } +// ValidateMACAddress validates a MAC address. +func ValidateMACAddress(val string) (string, error) { + _, err := net.ParseMAC(strings.TrimSpace(val)) + if err != nil { + return "", err + } + return val, nil +} + // ValidateDNSSearch validates domain for resolvconf search configuration. // A zero length domain is represented by a dot (.). func ValidateDNSSearch(val string) (string, error) { @@ -364,3 +374,31 @@ func ParseCPUs(value string) (int64, error) { } return nano.Num().Int64(), nil } + +// ParseLink parses and validates the specified string as a link format (name:alias) +func ParseLink(val string) (string, string, error) { + if val == "" { + return "", "", fmt.Errorf("empty string specified for links") + } + arr := strings.Split(val, ":") + if len(arr) > 2 { + return "", "", fmt.Errorf("bad format for links: %s", val) + } + if len(arr) == 1 { + return val, val, nil + } + // This is kept because we can actually get a HostConfig with links + // from an already created container and the format is not `foo:bar` + // but `/foo:/c1/bar` + if strings.HasPrefix(arr[0], "/") { + _, alias := path.Split(arr[1]) + return arr[0][1:], alias, nil + } + return arr[0], arr[1], nil +} + +// ValidateLink validates that the specified string has a valid link format (containerName:alias). +func ValidateLink(val string) (string, error) { + _, _, err := ParseLink(val) + return val, err +} diff --git a/opts/opts_test.go b/opts/opts_test.go index 46cb9e0b03a1..e137127156c1 100644 --- a/opts/opts_test.go +++ b/opts/opts_test.go @@ -230,3 +230,78 @@ func TestNamedMapOpts(t *testing.T) { t.Errorf("expected map-size to be in the values, got %v", tmpMap) } } + +func TestValidateMACAddress(t *testing.T) { + if _, err := ValidateMACAddress(`92:d0:c6:0a:29:33`); err != nil { + t.Fatalf("ValidateMACAddress(`92:d0:c6:0a:29:33`) got %s", err) + } + + if _, err := ValidateMACAddress(`92:d0:c6:0a:33`); err == nil { + t.Fatalf("ValidateMACAddress(`92:d0:c6:0a:33`) succeeded; expected failure on invalid MAC") + } + + if _, err := ValidateMACAddress(`random invalid string`); err == nil { + t.Fatalf("ValidateMACAddress(`random invalid string`) succeeded; expected failure on invalid MAC") + } +} + +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 TestParseLink(t *testing.T) { + name, alias, err := ParseLink("name:alias") + if err != nil { + t.Fatalf("Expected not to error out on a valid name:alias format but got: %v", err) + } + if name != "name" { + t.Fatalf("Link name should have been name, got %s instead", name) + } + if alias != "alias" { + t.Fatalf("Link alias should have been alias, got %s instead", alias) + } + // short format definition + name, alias, err = ParseLink("name") + if err != nil { + t.Fatalf("Expected not to error out on a valid name only format but got: %v", err) + } + if name != "name" { + t.Fatalf("Link name should have been name, got %s instead", name) + } + if alias != "name" { + t.Fatalf("Link alias should have been name, got %s instead", alias) + } + // empty string link definition is not allowed + if _, _, err := ParseLink(""); err == nil || !strings.Contains(err.Error(), "empty string specified for links") { + t.Fatalf("Expected error 'empty string specified for links' but got: %v", err) + } + // more than two colons are not allowed + if _, _, err := ParseLink("link:alias:wrong"); err == nil || !strings.Contains(err.Error(), "bad format for links: link:alias:wrong") { + t.Fatalf("Expected error 'bad format for links: link:alias:wrong' but got: %v", err) + } +} diff --git a/opts/runtime.go b/opts/runtime.go new file mode 100644 index 000000000000..4361b3ce0949 --- /dev/null +++ b/opts/runtime.go @@ -0,0 +1,79 @@ +package opts + +import ( + "fmt" + "strings" + + "github.com/docker/docker/api/types" +) + +// RuntimeOpt defines a map of Runtimes +type RuntimeOpt struct { + name string + stockRuntimeName string + values *map[string]types.Runtime +} + +// NewNamedRuntimeOpt creates a new RuntimeOpt +func NewNamedRuntimeOpt(name string, ref *map[string]types.Runtime, stockRuntime string) *RuntimeOpt { + if ref == nil { + ref = &map[string]types.Runtime{} + } + return &RuntimeOpt{name: name, values: ref, stockRuntimeName: stockRuntime} +} + +// Name returns the name of the NamedListOpts in the configuration. +func (o *RuntimeOpt) Name() string { + return o.name +} + +// Set validates and updates the list of Runtimes +func (o *RuntimeOpt) Set(val string) error { + parts := strings.SplitN(val, "=", 2) + if len(parts) != 2 { + return fmt.Errorf("invalid runtime argument: %s", val) + } + + parts[0] = strings.TrimSpace(parts[0]) + parts[1] = strings.TrimSpace(parts[1]) + if parts[0] == "" || parts[1] == "" { + return fmt.Errorf("invalid runtime argument: %s", val) + } + + parts[0] = strings.ToLower(parts[0]) + if parts[0] == o.stockRuntimeName { + return fmt.Errorf("runtime name '%s' is reserved", o.stockRuntimeName) + } + + if _, ok := (*o.values)[parts[0]]; ok { + return fmt.Errorf("runtime '%s' was already defined", parts[0]) + } + + (*o.values)[parts[0]] = types.Runtime{Path: parts[1]} + + return nil +} + +// String returns Runtime values as a string. +func (o *RuntimeOpt) String() string { + var out []string + for k := range *o.values { + out = append(out, k) + } + + return fmt.Sprintf("%v", out) +} + +// GetMap returns a map of Runtimes (name: path) +func (o *RuntimeOpt) GetMap() map[string]types.Runtime { + if o.values != nil { + return *o.values + } + + return map[string]types.Runtime{} +} + +// Type returns the type of the option +func (o *RuntimeOpt) Type() string { + return "runtime" +} diff --git a/opts/throttledevice.go b/opts/throttledevice.go new file mode 100644 index 000000000000..5024324298f9 --- /dev/null +++ b/opts/throttledevice.go @@ -0,0 +1,111 @@ +package opts + +import ( + "fmt" + "strconv" + "strings" + + "github.com/docker/docker/api/types/blkiodev" + "github.com/docker/go-units" +) + +// ValidatorThrottleFctType defines a validator function that returns a validated struct and/or an error. +type ValidatorThrottleFctType func(val string) (*blkiodev.ThrottleDevice, error) + +// ValidateThrottleBpsDevice validates that the specified string has a valid device-rate format. +func ValidateThrottleBpsDevice(val string) (*blkiodev.ThrottleDevice, error) { + split := strings.SplitN(val, ":", 2) + if len(split) != 2 { + return nil, fmt.Errorf("bad format: %s", val) + } + if !strings.HasPrefix(split[0], "/dev/") { + return nil, fmt.Errorf("bad format for device path: %s", val) + } + rate, err := units.RAMInBytes(split[1]) + if err != nil { + return nil, fmt.Errorf("invalid rate for device: %s. The correct format is :[]. Number must be a positive integer. Unit is optional and can be kb, mb, or gb", val) + } + if rate < 0 { + return nil, fmt.Errorf("invalid rate for device: %s. The correct format is :[]. Number must be a positive integer. Unit is optional and can be kb, mb, or gb", val) + } + + return &blkiodev.ThrottleDevice{ + Path: split[0], + Rate: uint64(rate), + }, nil +} + +// ValidateThrottleIOpsDevice validates that the specified string has a valid device-rate format. +func ValidateThrottleIOpsDevice(val string) (*blkiodev.ThrottleDevice, error) { + split := strings.SplitN(val, ":", 2) + if len(split) != 2 { + return nil, fmt.Errorf("bad format: %s", val) + } + if !strings.HasPrefix(split[0], "/dev/") { + return nil, fmt.Errorf("bad format for device path: %s", val) + } + rate, err := strconv.ParseUint(split[1], 10, 64) + if err != nil { + return nil, fmt.Errorf("invalid rate for device: %s. The correct format is :. Number must be a positive integer", val) + } + if rate < 0 { + return nil, fmt.Errorf("invalid rate for device: %s. The correct format is :. Number must be a positive integer", val) + } + + return &blkiodev.ThrottleDevice{ + Path: split[0], + Rate: uint64(rate), + }, nil +} + +// ThrottledeviceOpt defines a map of ThrottleDevices +type ThrottledeviceOpt struct { + values []*blkiodev.ThrottleDevice + validator ValidatorThrottleFctType +} + +// NewThrottledeviceOpt creates a new ThrottledeviceOpt +func NewThrottledeviceOpt(validator ValidatorThrottleFctType) ThrottledeviceOpt { + values := []*blkiodev.ThrottleDevice{} + return ThrottledeviceOpt{ + values: values, + validator: validator, + } +} + +// Set validates a ThrottleDevice and sets its name as a key in ThrottledeviceOpt +func (opt *ThrottledeviceOpt) Set(val string) error { + var value *blkiodev.ThrottleDevice + if opt.validator != nil { + v, err := opt.validator(val) + if err != nil { + return err + } + value = v + } + (opt.values) = append((opt.values), value) + return nil +} + +// String returns ThrottledeviceOpt values as a string. +func (opt *ThrottledeviceOpt) String() string { + var out []string + for _, v := range opt.values { + out = append(out, v.String()) + } + + return fmt.Sprintf("%v", out) +} + +// GetList returns a slice of pointers to ThrottleDevices. +func (opt *ThrottledeviceOpt) GetList() []*blkiodev.ThrottleDevice { + var throttledevice []*blkiodev.ThrottleDevice + throttledevice = append(throttledevice, opt.values...) + + return throttledevice +} + +// Type returns the option type +func (opt *ThrottledeviceOpt) Type() string { + return "throttled-device" +} diff --git a/opts/ulimit.go b/opts/ulimit.go new file mode 100644 index 000000000000..5adfe308519c --- /dev/null +++ b/opts/ulimit.go @@ -0,0 +1,57 @@ +package opts + +import ( + "fmt" + + "github.com/docker/go-units" +) + +// UlimitOpt defines a map of Ulimits +type UlimitOpt struct { + values *map[string]*units.Ulimit +} + +// NewUlimitOpt creates a new UlimitOpt +func NewUlimitOpt(ref *map[string]*units.Ulimit) *UlimitOpt { + if ref == nil { + ref = &map[string]*units.Ulimit{} + } + return &UlimitOpt{ref} +} + +// Set validates a Ulimit and sets its name as a key in UlimitOpt +func (o *UlimitOpt) Set(val string) error { + l, err := units.ParseUlimit(val) + if err != nil { + return err + } + + (*o.values)[l.Name] = l + + return nil +} + +// String returns Ulimit values as a string. +func (o *UlimitOpt) String() string { + var out []string + for _, v := range *o.values { + out = append(out, v.String()) + } + + return fmt.Sprintf("%v", out) +} + +// GetList returns a slice of pointers to Ulimits. +func (o *UlimitOpt) GetList() []*units.Ulimit { + var ulimits []*units.Ulimit + for _, v := range *o.values { + ulimits = append(ulimits, v) + } + + return ulimits +} + +// Type returns the option type +func (o *UlimitOpt) Type() string { + return "ulimit" +} diff --git a/opts/ulimit_test.go b/opts/ulimit_test.go new file mode 100644 index 000000000000..0aa3facdfb4c --- /dev/null +++ b/opts/ulimit_test.go @@ -0,0 +1,42 @@ +package opts + +import ( + "testing" + + "github.com/docker/go-units" +) + +func TestUlimitOpt(t *testing.T) { + ulimitMap := map[string]*units.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) + } +} diff --git a/opts/weightdevice.go b/opts/weightdevice.go new file mode 100644 index 000000000000..2a5da6da0876 --- /dev/null +++ b/opts/weightdevice.go @@ -0,0 +1,89 @@ +package opts + +import ( + "fmt" + "strconv" + "strings" + + "github.com/docker/docker/api/types/blkiodev" +) + +// ValidatorWeightFctType defines a validator function that returns a validated struct and/or an error. +type ValidatorWeightFctType func(val string) (*blkiodev.WeightDevice, error) + +// ValidateWeightDevice validates that the specified string has a valid device-weight format. +func ValidateWeightDevice(val string) (*blkiodev.WeightDevice, error) { + split := strings.SplitN(val, ":", 2) + if len(split) != 2 { + return nil, fmt.Errorf("bad format: %s", val) + } + if !strings.HasPrefix(split[0], "/dev/") { + return nil, fmt.Errorf("bad format for device path: %s", val) + } + weight, err := strconv.ParseUint(split[1], 10, 0) + if err != nil { + return nil, fmt.Errorf("invalid weight for device: %s", val) + } + if weight > 0 && (weight < 10 || weight > 1000) { + return nil, fmt.Errorf("invalid weight for device: %s", val) + } + + return &blkiodev.WeightDevice{ + Path: split[0], + Weight: uint16(weight), + }, nil +} + +// WeightdeviceOpt defines a map of WeightDevices +type WeightdeviceOpt struct { + values []*blkiodev.WeightDevice + validator ValidatorWeightFctType +} + +// NewWeightdeviceOpt creates a new WeightdeviceOpt +func NewWeightdeviceOpt(validator ValidatorWeightFctType) WeightdeviceOpt { + values := []*blkiodev.WeightDevice{} + return WeightdeviceOpt{ + values: values, + validator: validator, + } +} + +// Set validates a WeightDevice and sets its name as a key in WeightdeviceOpt +func (opt *WeightdeviceOpt) Set(val string) error { + var value *blkiodev.WeightDevice + if opt.validator != nil { + v, err := opt.validator(val) + if err != nil { + return err + } + value = v + } + (opt.values) = append((opt.values), value) + return nil +} + +// String returns WeightdeviceOpt values as a string. +func (opt *WeightdeviceOpt) String() string { + var out []string + for _, v := range opt.values { + out = append(out, v.String()) + } + + return fmt.Sprintf("%v", out) +} + +// GetList returns a slice of pointers to WeightDevices. +func (opt *WeightdeviceOpt) GetList() []*blkiodev.WeightDevice { + var weightdevice []*blkiodev.WeightDevice + for _, v := range opt.values { + weightdevice = append(weightdevice, v) + } + + return weightdevice +} + +// Type returns the option type +func (opt *WeightdeviceOpt) Type() string { + return "weighted-device" +} From ad345939d15c5176937404f4a7ac58928320cada Mon Sep 17 00:00:00 2001 From: yuexiao-wang Date: Mon, 26 Dec 2016 22:27:56 +0800 Subject: [PATCH 106/123] Update docker daemon to dockerd Signed-off-by: yuexiao-wang --- opts/hosts.go | 2 +- opts/opts_unix.go | 2 +- opts/opts_windows.go | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/opts/hosts.go b/opts/hosts.go index 7a948fb55ca1..594cccf2fbe6 100644 --- a/opts/hosts.go +++ b/opts/hosts.go @@ -9,7 +9,7 @@ import ( ) var ( - // DefaultHTTPPort Default HTTP Port used if only the protocol is provided to -H flag e.g. docker daemon -H tcp:// + // DefaultHTTPPort Default HTTP Port used if only the protocol is provided to -H flag e.g. dockerd -H tcp:// // These are the IANA registered port numbers for use with Docker // see http://www.iana.org/assignments/service-names-port-numbers/service-names-port-numbers.xhtml?search=docker DefaultHTTPPort = 2375 // Default HTTP Port diff --git a/opts/opts_unix.go b/opts/opts_unix.go index f1ce844a8f60..2766a43a0886 100644 --- a/opts/opts_unix.go +++ b/opts/opts_unix.go @@ -2,5 +2,5 @@ package opts -// DefaultHTTPHost Default HTTP Host used if only port is provided to -H flag e.g. docker daemon -H tcp://:8080 +// DefaultHTTPHost Default HTTP Host used if only port is provided to -H flag e.g. dockerd -H tcp://:8080 const DefaultHTTPHost = "localhost" diff --git a/opts/opts_windows.go b/opts/opts_windows.go index ebe40c969c92..98b7251a9e46 100644 --- a/opts/opts_windows.go +++ b/opts/opts_windows.go @@ -52,5 +52,5 @@ package opts // to the delay if a user were to do 'docker run -H=tcp://localhost:2375...' // explicitly. -// DefaultHTTPHost Default HTTP Host used if only port is provided to -H flag e.g. docker daemon -H tcp://:8080 +// DefaultHTTPHost Default HTTP Host used if only port is provided to -H flag e.g. dockerd -H tcp://:8080 const DefaultHTTPHost = "127.0.0.1" From ab926266194cf2c0c2e3f96aeee22e68bc7929df Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Fri, 30 Dec 2016 17:35:46 +0100 Subject: [PATCH 107/123] Improve usage output for docker run Commit a77f2450c70312f8c26877a18bfe2baa44d4abb9 switched `docker run` to use the `pflags` package. Due to this change, the usage output for the `--blkio-weight-device` and `--device-*` flags changed and now showed `weighted-device`, and `throttled-device` as value type. As a result, the output of `docker run --help` became a lot wider. This patch changes the output to show `list` instead, which is consistent with other options that allow to be set multiple times. Output before this change; Usage: docker run [OPTIONS] IMAGE [COMMAND] [ARG...] Run a command in a new container Options: --blkio-weight uint16 Block IO (relative weight), between 10 and 1000, or 0 to disable (default 0) --blkio-weight-device weighted-device Block IO weight (relative device weight) (default []) --device list Add a host device to the container (default []) --device-read-bps throttled-device Limit read rate (bytes per second) from a device (default []) --device-read-iops throttled-device Limit read rate (IO per second) from a device (default []) --device-write-bps throttled-device Limit write rate (bytes per second) to a device (default []) --device-write-iops throttled-device Limit write rate (IO per second) to a device (default []) -w, --workdir string Working directory inside the container Output after this change; Usage: docker run [OPTIONS] IMAGE [COMMAND] [ARG...] Run a command in a new container Options: --blkio-weight uint16 Block IO (relative weight), between 10 and 1000, or 0 to disable (default 0) --blkio-weight-device list Block IO weight (relative device weight) (default []) --device list Add a host device to the container (default []) --device-read-bps list Limit read rate (bytes per second) from a device (default []) --device-read-iops list Limit read rate (IO per second) from a device (default []) --device-write-bps list Limit write rate (bytes per second) to a device (default []) --device-write-iops list Limit write rate (IO per second) to a device (default []) -w, --workdir string Working directory inside the container Signed-off-by: Sebastiaan van Stijn --- opts/throttledevice.go | 2 +- opts/weightdevice.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/opts/throttledevice.go b/opts/throttledevice.go index 5024324298f9..65dd3ebf68c2 100644 --- a/opts/throttledevice.go +++ b/opts/throttledevice.go @@ -107,5 +107,5 @@ func (opt *ThrottledeviceOpt) GetList() []*blkiodev.ThrottleDevice { // Type returns the option type func (opt *ThrottledeviceOpt) Type() string { - return "throttled-device" + return "list" } diff --git a/opts/weightdevice.go b/opts/weightdevice.go index 2a5da6da0876..7e3d064f2720 100644 --- a/opts/weightdevice.go +++ b/opts/weightdevice.go @@ -85,5 +85,5 @@ func (opt *WeightdeviceOpt) GetList() []*blkiodev.WeightDevice { // Type returns the option type func (opt *WeightdeviceOpt) Type() string { - return "weighted-device" + return "list" } From 8eeed60a68c6837b13ebd10747220b8b902cdf42 Mon Sep 17 00:00:00 2001 From: Daniel Nephin Date: Thu, 29 Dec 2016 17:28:28 -0500 Subject: [PATCH 108/123] Add quoted string flag Value. Signed-off-by: Daniel Nephin --- opts/quotedstring.go | 30 ++++++++++++++++++++++++++++++ opts/quotedstring_test.go | 24 ++++++++++++++++++++++++ 2 files changed, 54 insertions(+) create mode 100644 opts/quotedstring.go create mode 100644 opts/quotedstring_test.go diff --git a/opts/quotedstring.go b/opts/quotedstring.go new file mode 100644 index 000000000000..8ddeee80856b --- /dev/null +++ b/opts/quotedstring.go @@ -0,0 +1,30 @@ +package opts + +// QuotedString is a string that may have extra quotes around the value. The +// quotes are stripped from the value. +type QuotedString string + +// Set sets a new value +func (s *QuotedString) Set(val string) error { + *s = QuotedString(trimQuotes(val)) + return nil +} + +// Type returns the type of the value +func (s *QuotedString) Type() string { + return "string" +} + +func (s *QuotedString) String() string { + return string(*s) +} + +func trimQuotes(value string) string { + lastIndex := len(value) - 1 + for _, char := range []byte{'\'', '"'} { + if value[0] == char && value[lastIndex] == char { + return value[1:lastIndex] + } + } + return value +} diff --git a/opts/quotedstring_test.go b/opts/quotedstring_test.go new file mode 100644 index 000000000000..a508b9d210ab --- /dev/null +++ b/opts/quotedstring_test.go @@ -0,0 +1,24 @@ +package opts + +import ( + "github.com/docker/docker/pkg/testutil/assert" + "testing" +) + +func TestQuotedStringSetWithQuotes(t *testing.T) { + qs := QuotedString("") + assert.NilError(t, qs.Set("\"something\"")) + assert.Equal(t, qs.String(), "something") +} + +func TestQuotedStringSetWithMismatchedQuotes(t *testing.T) { + qs := QuotedString("") + assert.NilError(t, qs.Set("\"something'")) + assert.Equal(t, qs.String(), "\"something'") +} + +func TestQuotedStringSetWithNoQuotes(t *testing.T) { + qs := QuotedString("") + assert.NilError(t, qs.Set("something")) + assert.Equal(t, qs.String(), "something") +} From 52e9a69df9b2a441b5699b78fe80fab3d8b45109 Mon Sep 17 00:00:00 2001 From: Daniel Nephin Date: Tue, 3 Jan 2017 15:58:41 -0500 Subject: [PATCH 109/123] Trim quotes from TLS flags. Signed-off-by: Daniel Nephin --- opts/quotedstring.go | 13 ++++++++++--- opts/quotedstring_test.go | 10 +++++++--- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/opts/quotedstring.go b/opts/quotedstring.go index 8ddeee80856b..fb1e5374bc3a 100644 --- a/opts/quotedstring.go +++ b/opts/quotedstring.go @@ -2,11 +2,13 @@ package opts // QuotedString is a string that may have extra quotes around the value. The // quotes are stripped from the value. -type QuotedString string +type QuotedString struct { + value *string +} // Set sets a new value func (s *QuotedString) Set(val string) error { - *s = QuotedString(trimQuotes(val)) + *s.value = trimQuotes(val) return nil } @@ -16,7 +18,7 @@ func (s *QuotedString) Type() string { } func (s *QuotedString) String() string { - return string(*s) + return string(*s.value) } func trimQuotes(value string) string { @@ -28,3 +30,8 @@ func trimQuotes(value string) string { } return value } + +// NewQuotedString returns a new quoted string option +func NewQuotedString(value *string) *QuotedString { + return &QuotedString{value: value} +} diff --git a/opts/quotedstring_test.go b/opts/quotedstring_test.go index a508b9d210ab..0ebf04bbe044 100644 --- a/opts/quotedstring_test.go +++ b/opts/quotedstring_test.go @@ -6,19 +6,23 @@ import ( ) func TestQuotedStringSetWithQuotes(t *testing.T) { - qs := QuotedString("") + value := "" + qs := NewQuotedString(&value) assert.NilError(t, qs.Set("\"something\"")) assert.Equal(t, qs.String(), "something") + assert.Equal(t, value, "something") } func TestQuotedStringSetWithMismatchedQuotes(t *testing.T) { - qs := QuotedString("") + value := "" + qs := NewQuotedString(&value) assert.NilError(t, qs.Set("\"something'")) assert.Equal(t, qs.String(), "\"something'") } func TestQuotedStringSetWithNoQuotes(t *testing.T) { - qs := QuotedString("") + value := "" + qs := NewQuotedString(&value) assert.NilError(t, qs.Set("something")) assert.Equal(t, qs.String(), "something") } From 438279688cb33909d905116e16fd56a437896fb7 Mon Sep 17 00:00:00 2001 From: Tony Abboud Date: Thu, 12 Jan 2017 12:01:29 -0500 Subject: [PATCH 110/123] Add error checking for hostPort range This fix catches the case where there is a single container port and a dynamic host port and will fail out gracefully Example docker-compose.yml snippet: port: ports: - "8091-8093:8091" - "80:8080" Signed-off-by: Tony Abboud --- opts/port.go | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/opts/port.go b/opts/port.go index 020a5d1e1c45..531908fb2425 100644 --- a/opts/port.go +++ b/opts/port.go @@ -98,7 +98,11 @@ func (p *PortOpt) Set(value string) error { ports, portBindings, _ := nat.ParsePortSpecs([]string{value}) for port := range ports { - portConfigs = append(portConfigs, ConvertPortToPortConfig(port, portBindings)...) + portConfig, err := ConvertPortToPortConfig(port, portBindings) + if err != nil { + return err + } + portConfigs = append(portConfigs, portConfig...) } p.ports = append(p.ports, portConfigs...) } @@ -129,11 +133,14 @@ func (p *PortOpt) Value() []swarm.PortConfig { func ConvertPortToPortConfig( port nat.Port, portBindings map[nat.Port][]nat.PortBinding, -) []swarm.PortConfig { +) ([]swarm.PortConfig, error) { ports := []swarm.PortConfig{} for _, binding := range portBindings[port] { - hostPort, _ := strconv.ParseUint(binding.HostPort, 10, 16) + hostPort, err := strconv.ParseUint(binding.HostPort, 10, 16) + if err != nil && binding.HostPort != "" { + return nil, fmt.Errorf("invalid hostport binding (%s) for port (%s)", binding.HostPort, port.Port()) + } ports = append(ports, swarm.PortConfig{ //TODO Name: ? Protocol: swarm.PortConfigProtocol(strings.ToLower(port.Proto())), @@ -142,5 +149,5 @@ func ConvertPortToPortConfig( PublishMode: swarm.PortConfigPublishModeIngress, }) } - return ports + return ports, nil } From e7020eefd0ed9cb3b79964b784ab8d2753b59f6f Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Tue, 24 Jan 2017 15:41:45 +0100 Subject: [PATCH 111/123] Add "src" alias for `--secret` This patch adds a "src" alias for `--secret` to be consistent with `--mount`. Signed-off-by: Sebastiaan van Stijn --- opts/secret.go | 2 +- opts/secret_test.go | 12 ++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/opts/secret.go b/opts/secret.go index b77a33f685a4..1fefcf843496 100644 --- a/opts/secret.go +++ b/opts/secret.go @@ -50,7 +50,7 @@ func (o *SecretOpt) Set(value string) error { value := parts[1] switch key { - case "source": + case "source", "src": options.Source = value case "target": tDir, _ := filepath.Split(value) diff --git a/opts/secret_test.go b/opts/secret_test.go index ce4418a0bcaa..d978c86e2274 100644 --- a/opts/secret_test.go +++ b/opts/secret_test.go @@ -35,6 +35,18 @@ func TestSecretOptionsSourceTarget(t *testing.T) { assert.Equal(t, req.Target, "testing") } +func TestSecretOptionsShorthand(t *testing.T) { + var opt SecretOpt + + testCase := "src=foo,target=testing" + assert.NilError(t, opt.Set(testCase)) + + reqs := opt.Value() + assert.Equal(t, len(reqs), 1) + req := reqs[0] + assert.Equal(t, req.Source, "foo") +} + func TestSecretOptionsCustomUidGid(t *testing.T) { var opt SecretOpt From 5a9a1569b9fc32495013b4dc3b787277214874f2 Mon Sep 17 00:00:00 2001 From: Yong Tang Date: Sun, 25 Dec 2016 01:11:12 -0800 Subject: [PATCH 112/123] Add daemon option --default-shm-size This fix fixes issue raised in 29492 where it was not possible to specify a default `--default-shm-size` in daemon configuration for each `docker run``. The flag `--default-shm-size` which is reloadable, has been added to the daemon configuation. Related docs has been updated. This fix fixes 29492. Signed-off-by: Yong Tang --- opts/opts.go | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/opts/opts.go b/opts/opts.go index c0f9cebf6024..c7d6c291bbe8 100644 --- a/opts/opts.go +++ b/opts/opts.go @@ -9,6 +9,7 @@ import ( "strings" "github.com/docker/docker/api/types/filters" + units "github.com/docker/go-units" ) var ( @@ -402,3 +403,38 @@ func ValidateLink(val string) (string, error) { _, _, err := ParseLink(val) return val, err } + +// MemBytes is a type for human readable memory bytes (like 128M, 2g, etc) +type MemBytes int64 + +// String returns the string format of the human readable memory bytes +func (m *MemBytes) String() string { + return units.BytesSize(float64(m.Value())) +} + +// Set sets the value of the MemBytes by passing a string +func (m *MemBytes) Set(value string) error { + val, err := units.RAMInBytes(value) + *m = MemBytes(val) + return err +} + +// Type returns the type +func (m *MemBytes) Type() string { + return "bytes" +} + +// Value returns the value in int64 +func (m *MemBytes) Value() int64 { + return int64(*m) +} + +// UnmarshalJSON is the customized unmarshaler for MemBytes +func (m *MemBytes) UnmarshalJSON(s []byte) error { + if len(s) <= 2 || s[0] != '"' || s[len(s)-1] != '"' { + return fmt.Errorf("invalid size: %q", s) + } + val, err := units.RAMInBytes(string(s[1 : len(s)-1])) + *m = MemBytes(val) + return err +} From 629abab4c0311feb7db023ffda4a3c7d99c1f8fc Mon Sep 17 00:00:00 2001 From: Yong Tang Date: Wed, 28 Dec 2016 14:44:07 -0800 Subject: [PATCH 113/123] Update opts.MemBytes to disable default, and move `docker run/create/build` to use opts.MemBytes This fix made several updates: 1. Update opts.MemBytes so that default value will not show up. The reason is that in case a default value is decided by daemon, instead of client, we actually want to not show default value. 2. Move `docker run/create/build` to use opts.MemBytes for `--shm-size` This is to bring consistency between daemon and docker run 3. docs updates. Signed-off-by: Yong Tang --- opts/opts.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/opts/opts.go b/opts/opts.go index c7d6c291bbe8..3ae0fdb6b973 100644 --- a/opts/opts.go +++ b/opts/opts.go @@ -409,7 +409,13 @@ type MemBytes int64 // String returns the string format of the human readable memory bytes func (m *MemBytes) String() string { - return units.BytesSize(float64(m.Value())) + // NOTE: In spf13/pflag/flag.go, "0" is considered as "zero value" while "0 B" is not. + // We return "0" in case value is 0 here so that the default value is hidden. + // (Sometimes "default 0 B" is actually misleading) + if m.Value() != 0 { + return units.BytesSize(float64(m.Value())) + } + return "0" } // Set sets the value of the MemBytes by passing a string From 7fa9161585c348f0e6432ebd66b423e393f79ad5 Mon Sep 17 00:00:00 2001 From: Vincent Demeester Date: Mon, 6 Feb 2017 14:16:03 +0100 Subject: [PATCH 114/123] Make sure we validate simple syntax on service commands We ignored errors for simple syntax in `PortOpt` (missed that in the previous migration of this code). This make sure we don't ignore `nat.Parse` errors. Test has been migrate too (errors are not exactly the same as before though -_-) Signed-off-by: Vincent Demeester --- opts/port.go | 15 ++++++++++++--- opts/port_test.go | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 3 deletions(-) diff --git a/opts/port.go b/opts/port.go index 531908fb2425..152683c98142 100644 --- a/opts/port.go +++ b/opts/port.go @@ -94,11 +94,20 @@ func (p *PortOpt) Set(value string) error { } else { // short syntax portConfigs := []swarm.PortConfig{} - // We can ignore errors because the format was already validated by ValidatePort - ports, portBindings, _ := nat.ParsePortSpecs([]string{value}) + ports, portBindingMap, err := nat.ParsePortSpecs([]string{value}) + if err != nil { + return err + } + for _, portBindings := range portBindingMap { + for _, portBinding := range portBindings { + if portBinding.HostIP != "" { + return fmt.Errorf("HostIP is not supported.") + } + } + } for port := range ports { - portConfig, err := ConvertPortToPortConfig(port, portBindings) + portConfig, err := ConvertPortToPortConfig(port, portBindingMap) if err != nil { return err } diff --git a/opts/port_test.go b/opts/port_test.go index 67bcf8f1d931..a483d269aad5 100644 --- a/opts/port_test.go +++ b/opts/port_test.go @@ -245,6 +245,42 @@ func TestPortOptInvalidComplexSyntax(t *testing.T) { } } +func TestPortOptInvalidSimpleSyntax(t *testing.T) { + testCases := []struct { + value string + expectedError string + }{ + { + value: "9999999", + expectedError: "Invalid containerPort: 9999999", + }, + { + value: "80/xyz", + expectedError: "Invalid proto: xyz", + }, + { + value: "tcp", + expectedError: "Invalid containerPort: tcp", + }, + { + value: "udp", + expectedError: "Invalid containerPort: udp", + }, + { + value: "", + expectedError: "No port specified", + }, + { + value: "1.1.1.1:80:80", + expectedError: "HostIP is not supported", + }, + } + for _, tc := range testCases { + var port PortOpt + assert.Error(t, port.Set(tc.value), tc.expectedError) + } +} + func assertContains(t *testing.T, portConfigs []swarm.PortConfig, expected swarm.PortConfig) { var contains = false for _, portConfig := range portConfigs { From ce42bb22a3efa64f1264016b98dab035d863eb56 Mon Sep 17 00:00:00 2001 From: David Sheets Date: Tue, 7 Feb 2017 12:17:21 +0000 Subject: [PATCH 115/123] Add 'consistent', 'cached', and 'delegated' mode flags This adds 'consistency' mode flags to the mount command line argument. Initially, the valid 'consistency' flags are 'consistent', 'cached', 'delegated', and 'default'. Signed-off-by: David Sheets Signed-off-by: Jeremy Yallop --- opts/mount.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/opts/mount.go b/opts/mount.go index ce6383ddca37..97895a78445d 100644 --- a/opts/mount.go +++ b/opts/mount.go @@ -95,6 +95,8 @@ func (m *MountOpt) Set(value string) error { if err != nil { return fmt.Errorf("invalid value for %s: %s", key, value) } + case "consistency": + mount.Consistency = mounttypes.Consistency(strings.ToLower(value)) case "bind-propagation": bindOptions().Propagation = mounttypes.Propagation(strings.ToLower(value)) case "volume-nocopy": From 5a39df474b3b29cc1f2f54b6bdfc11c1b5a69be7 Mon Sep 17 00:00:00 2001 From: Daniel Nephin Date: Thu, 19 Jan 2017 16:48:30 -0500 Subject: [PATCH 116/123] Add expanded mount syntax to Compose schema and types. Signed-off-by: Daniel Nephin --- opts/mount.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opts/mount.go b/opts/mount.go index 97895a78445d..d4ccf838d90d 100644 --- a/opts/mount.go +++ b/opts/mount.go @@ -102,7 +102,7 @@ func (m *MountOpt) Set(value string) error { case "volume-nocopy": volumeOptions().NoCopy, err = strconv.ParseBool(value) if err != nil { - return fmt.Errorf("invalid value for populate: %s", value) + return fmt.Errorf("invalid value for volume-nocopy: %s", value) } case "volume-label": setValueOnMap(volumeOptions().Labels, value) From 2a4ce79981fc1b0691b74838af4d0707645d8c60 Mon Sep 17 00:00:00 2001 From: Daniel Nephin Date: Mon, 6 Mar 2017 16:01:04 -0500 Subject: [PATCH 117/123] Use opts.MemBytes for flags. Signed-off-by: Daniel Nephin --- opts/opts.go | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/opts/opts.go b/opts/opts.go index 3ae0fdb6b973..8c82960c20d0 100644 --- a/opts/opts.go +++ b/opts/opts.go @@ -444,3 +444,39 @@ func (m *MemBytes) UnmarshalJSON(s []byte) error { *m = MemBytes(val) return err } + +// MemSwapBytes is a type for human readable memory bytes (like 128M, 2g, etc). +// It differs from MemBytes in that -1 is valid and the default. +type MemSwapBytes int64 + +// Set sets the value of the MemSwapBytes by passing a string +func (m *MemSwapBytes) Set(value string) error { + if value == "-1" { + *m = MemSwapBytes(-1) + return nil + } + val, err := units.RAMInBytes(value) + *m = MemSwapBytes(val) + return err +} + +// Type returns the type +func (m *MemSwapBytes) Type() string { + return "bytes" +} + +// Value returns the value in int64 +func (m *MemSwapBytes) Value() int64 { + return int64(*m) +} + +func (m *MemSwapBytes) String() string { + b := MemBytes(*m) + return b.String() +} + +// UnmarshalJSON is the customized unmarshaler for MemSwapBytes +func (m *MemSwapBytes) UnmarshalJSON(s []byte) error { + b := MemBytes(*m) + return b.UnmarshalJSON(s) +} From 4cde08da8ef81d48509c6a25fa59c55a93eeb4e1 Mon Sep 17 00:00:00 2001 From: Aaron Lehmann Date: Thu, 16 Mar 2017 10:54:18 -0700 Subject: [PATCH 118/123] api: Remove SecretRequestOption type This type is only used by CLI code. It duplicates SecretReference in the types/swarm package. Change the CLI code to use that type instead. Signed-off-by: Aaron Lehmann --- opts/secret.go | 42 +++++++++++++++++++----------------------- opts/secret_test.go | 32 ++++++++++++++++---------------- 2 files changed, 35 insertions(+), 39 deletions(-) diff --git a/opts/secret.go b/opts/secret.go index 1fefcf843496..56ed29eb5b56 100644 --- a/opts/secret.go +++ b/opts/secret.go @@ -8,12 +8,12 @@ import ( "strconv" "strings" - "github.com/docker/docker/api/types" + swarmtypes "github.com/docker/docker/api/types/swarm" ) // SecretOpt is a Value type for parsing secrets type SecretOpt struct { - values []*types.SecretRequestOption + values []*swarmtypes.SecretReference } // Set a new secret value @@ -24,18 +24,18 @@ func (o *SecretOpt) Set(value string) error { return err } - options := &types.SecretRequestOption{ - Source: "", - Target: "", - UID: "0", - GID: "0", - Mode: 0444, + options := &swarmtypes.SecretReference{ + File: &swarmtypes.SecretReferenceFileTarget{ + UID: "0", + GID: "0", + Mode: 0444, + }, } // support a simple syntax of --secret foo if len(fields) == 1 { - options.Source = fields[0] - options.Target = fields[0] + options.File.Name = fields[0] + options.SecretName = fields[0] o.values = append(o.values, options) return nil } @@ -51,34 +51,30 @@ func (o *SecretOpt) Set(value string) error { value := parts[1] switch key { case "source", "src": - options.Source = value + options.SecretName = value case "target": tDir, _ := filepath.Split(value) if tDir != "" { return fmt.Errorf("target must not be a path") } - options.Target = value + options.File.Name = value case "uid": - options.UID = value + options.File.UID = value case "gid": - options.GID = value + options.File.GID = value case "mode": m, err := strconv.ParseUint(value, 0, 32) if err != nil { return fmt.Errorf("invalid mode specified: %v", err) } - options.Mode = os.FileMode(m) + options.File.Mode = os.FileMode(m) default: - if len(fields) == 1 && value == "" { - - } else { - return fmt.Errorf("invalid field in secret request: %s", key) - } + return fmt.Errorf("invalid field in secret request: %s", key) } } - if options.Source == "" { + if options.SecretName == "" { return fmt.Errorf("source is required") } @@ -95,13 +91,13 @@ func (o *SecretOpt) Type() string { func (o *SecretOpt) String() string { secrets := []string{} for _, secret := range o.values { - repr := fmt.Sprintf("%s -> %s", secret.Source, secret.Target) + repr := fmt.Sprintf("%s -> %s", secret.SecretName, secret.File.Name) secrets = append(secrets, repr) } return strings.Join(secrets, ", ") } // Value returns the secret requests -func (o *SecretOpt) Value() []*types.SecretRequestOption { +func (o *SecretOpt) Value() []*swarmtypes.SecretReference { return o.values } diff --git a/opts/secret_test.go b/opts/secret_test.go index d978c86e2274..5654c79f1f5a 100644 --- a/opts/secret_test.go +++ b/opts/secret_test.go @@ -16,10 +16,10 @@ func TestSecretOptionsSimple(t *testing.T) { reqs := opt.Value() assert.Equal(t, len(reqs), 1) req := reqs[0] - assert.Equal(t, req.Source, "app-secret") - assert.Equal(t, req.Target, "app-secret") - assert.Equal(t, req.UID, "0") - assert.Equal(t, req.GID, "0") + assert.Equal(t, req.SecretName, "app-secret") + assert.Equal(t, req.File.Name, "app-secret") + assert.Equal(t, req.File.UID, "0") + assert.Equal(t, req.File.GID, "0") } func TestSecretOptionsSourceTarget(t *testing.T) { @@ -31,8 +31,8 @@ func TestSecretOptionsSourceTarget(t *testing.T) { reqs := opt.Value() assert.Equal(t, len(reqs), 1) req := reqs[0] - assert.Equal(t, req.Source, "foo") - assert.Equal(t, req.Target, "testing") + assert.Equal(t, req.SecretName, "foo") + assert.Equal(t, req.File.Name, "testing") } func TestSecretOptionsShorthand(t *testing.T) { @@ -44,7 +44,7 @@ func TestSecretOptionsShorthand(t *testing.T) { reqs := opt.Value() assert.Equal(t, len(reqs), 1) req := reqs[0] - assert.Equal(t, req.Source, "foo") + assert.Equal(t, req.SecretName, "foo") } func TestSecretOptionsCustomUidGid(t *testing.T) { @@ -56,10 +56,10 @@ func TestSecretOptionsCustomUidGid(t *testing.T) { reqs := opt.Value() assert.Equal(t, len(reqs), 1) req := reqs[0] - assert.Equal(t, req.Source, "foo") - assert.Equal(t, req.Target, "testing") - assert.Equal(t, req.UID, "1000") - assert.Equal(t, req.GID, "1001") + assert.Equal(t, req.SecretName, "foo") + assert.Equal(t, req.File.Name, "testing") + assert.Equal(t, req.File.UID, "1000") + assert.Equal(t, req.File.GID, "1001") } func TestSecretOptionsCustomMode(t *testing.T) { @@ -71,9 +71,9 @@ func TestSecretOptionsCustomMode(t *testing.T) { reqs := opt.Value() assert.Equal(t, len(reqs), 1) req := reqs[0] - assert.Equal(t, req.Source, "foo") - assert.Equal(t, req.Target, "testing") - assert.Equal(t, req.UID, "1000") - assert.Equal(t, req.GID, "1001") - assert.Equal(t, req.Mode, os.FileMode(0444)) + assert.Equal(t, req.SecretName, "foo") + assert.Equal(t, req.File.Name, "testing") + assert.Equal(t, req.File.UID, "1000") + assert.Equal(t, req.File.GID, "1001") + assert.Equal(t, req.File.Mode, os.FileMode(0444)) } From d160d9897008135104a82b6d143739844d7c35bb Mon Sep 17 00:00:00 2001 From: Aaron Lehmann Date: Thu, 30 Mar 2017 18:35:04 -0700 Subject: [PATCH 119/123] Make the CLI show defaults from the swarmkit defaults package If no fields related to an update config or restart policy are specified, these structs should not be created as part of the service, to avoid hardcoding the current defaults. Signed-off-by: Aaron Lehmann --- opts/opts.go | 8 +++++++- opts/opts_test.go | 12 ++++++------ 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/opts/opts.go b/opts/opts.go index 8c82960c20d0..f76f30805173 100644 --- a/opts/opts.go +++ b/opts/opts.go @@ -38,7 +38,10 @@ func NewListOptsRef(values *[]string, validator ValidatorFctType) *ListOpts { } func (opts *ListOpts) String() string { - return fmt.Sprintf("%v", []string((*opts.values))) + if len(*opts.values) == 0 { + return "" + } + return fmt.Sprintf("%v", *opts.values) } // Set validates if needed the input value and adds it to the @@ -343,6 +346,9 @@ type NanoCPUs int64 // String returns the string format of the number func (c *NanoCPUs) String() string { + if *c == 0 { + return "" + } return big.NewRat(c.Value(), 1e9).FloatString(3) } diff --git a/opts/opts_test.go b/opts/opts_test.go index e137127156c1..c1e7735b585f 100644 --- a/opts/opts_test.go +++ b/opts/opts_test.go @@ -93,12 +93,12 @@ 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()) + if o.String() != "" { + t.Errorf(`%s != ""`, o.String()) } o.Set("foo=bar") - if o.String() != "[]" { - t.Errorf("%s != []", o.String()) + if o.String() != "" { + t.Errorf(`%s != ""`, o.String()) } o.Set("max-file=2") if o.Len() != 1 { @@ -111,8 +111,8 @@ func TestListOptsWithValidator(t *testing.T) { t.Error("o.Get(\"baz\") == true") } o.Delete("max-file=2") - if o.String() != "[]" { - t.Errorf("%s != []", o.String()) + if o.String() != "" { + t.Errorf(`%s != ""`, o.String()) } } From eb366ae039f4de2b0e4e3add55bd4186b5544b88 Mon Sep 17 00:00:00 2001 From: Aaron Lehmann Date: Thu, 13 Apr 2017 15:45:37 -0700 Subject: [PATCH 120/123] Remove pkg/testutil/assert in favor of testify I noticed that we're using a homegrown package for assertions. The functions are extremely similar to testify, but with enough slight differences to be confusing (for example, Equal takes its arguments in a different order). We already vendor testify, and it's used in a few places by tests. I also found some problems with pkg/testutil/assert. For example, the NotNil function seems to be broken. It checks the argument against "nil", which only works for an interface. If you pass in a nil map or slice, the equality check will fail. In the interest of avoiding NIH, I'm proposing replacing pkg/testutil/assert with testify. The test code looks almost the same, but we avoid the confusion of having two similar but slightly different assertion packages, and having to maintain our own package instead of using a commonly-used one. In the process, I found a few places where the tests should halt if an assertion fails, so I've made those cases (that I noticed) use "require" instead of "assert", and I've vendored the "require" package from testify alongside the already-present "assert" package. Signed-off-by: Aaron Lehmann --- opts/mount_test.go | 100 +++++++++++++++++++------------------- opts/port_test.go | 19 ++++---- opts/quotedstring_test.go | 17 ++++--- opts/secret_test.go | 55 +++++++++++---------- 4 files changed, 98 insertions(+), 93 deletions(-) diff --git a/opts/mount_test.go b/opts/mount_test.go index 59606c38e2c3..72aaa6258e03 100644 --- a/opts/mount_test.go +++ b/opts/mount_test.go @@ -5,7 +5,9 @@ import ( "testing" mounttypes "github.com/docker/docker/api/types/mount" - "github.com/docker/docker/pkg/testutil/assert" + "github.com/docker/docker/pkg/testutil" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestMountOptString(t *testing.T) { @@ -24,7 +26,7 @@ func TestMountOptString(t *testing.T) { }, } expected := "bind /home/path /target, volume foo /target/foo" - assert.Equal(t, mount.String(), expected) + assert.Equal(t, expected, mount.String()) } func TestMountOptSetBindNoErrorBind(t *testing.T) { @@ -37,15 +39,15 @@ func TestMountOptSetBindNoErrorBind(t *testing.T) { } { var mount MountOpt - assert.NilError(t, mount.Set(testcase)) + assert.NoError(t, mount.Set(testcase)) mounts := mount.Value() - assert.Equal(t, len(mounts), 1) - assert.Equal(t, mounts[0], mounttypes.Mount{ + require.Len(t, mounts, 1) + assert.Equal(t, mounttypes.Mount{ Type: mounttypes.TypeBind, Source: "/source", Target: "/target", - }) + }, mounts[0]) } } @@ -59,15 +61,15 @@ func TestMountOptSetVolumeNoError(t *testing.T) { } { var mount MountOpt - assert.NilError(t, mount.Set(testcase)) + assert.NoError(t, mount.Set(testcase)) mounts := mount.Value() - assert.Equal(t, len(mounts), 1) - assert.Equal(t, mounts[0], mounttypes.Mount{ + require.Len(t, mounts, 1) + assert.Equal(t, mounttypes.Mount{ Type: mounttypes.TypeVolume, Source: "/source", Target: "/target", - }) + }, mounts[0]) } } @@ -75,82 +77,82 @@ func TestMountOptSetVolumeNoError(t *testing.T) { // volume mount. func TestMountOptDefaultType(t *testing.T) { var mount MountOpt - assert.NilError(t, mount.Set("target=/target,source=/foo")) - assert.Equal(t, mount.values[0].Type, mounttypes.TypeVolume) + assert.NoError(t, mount.Set("target=/target,source=/foo")) + assert.Equal(t, mounttypes.TypeVolume, mount.values[0].Type) } func TestMountOptSetErrorNoTarget(t *testing.T) { var mount MountOpt - assert.Error(t, mount.Set("type=volume,source=/foo"), "target is required") + assert.EqualError(t, mount.Set("type=volume,source=/foo"), "target is required") } func TestMountOptSetErrorInvalidKey(t *testing.T) { var mount MountOpt - assert.Error(t, mount.Set("type=volume,bogus=foo"), "unexpected key 'bogus'") + assert.EqualError(t, mount.Set("type=volume,bogus=foo"), "unexpected key 'bogus' in 'bogus=foo'") } func TestMountOptSetErrorInvalidField(t *testing.T) { var mount MountOpt - assert.Error(t, mount.Set("type=volume,bogus"), "invalid field 'bogus'") + assert.EqualError(t, mount.Set("type=volume,bogus"), "invalid field 'bogus' must be a key=value pair") } func TestMountOptSetErrorInvalidReadOnly(t *testing.T) { var mount MountOpt - assert.Error(t, mount.Set("type=volume,readonly=no"), "invalid value for readonly: no") - assert.Error(t, mount.Set("type=volume,readonly=invalid"), "invalid value for readonly: invalid") + assert.EqualError(t, mount.Set("type=volume,readonly=no"), "invalid value for readonly: no") + assert.EqualError(t, mount.Set("type=volume,readonly=invalid"), "invalid value for readonly: invalid") } func TestMountOptDefaultEnableReadOnly(t *testing.T) { var m MountOpt - assert.NilError(t, m.Set("type=bind,target=/foo,source=/foo")) - assert.Equal(t, m.values[0].ReadOnly, false) + assert.NoError(t, m.Set("type=bind,target=/foo,source=/foo")) + assert.False(t, m.values[0].ReadOnly) m = MountOpt{} - assert.NilError(t, m.Set("type=bind,target=/foo,source=/foo,readonly")) - assert.Equal(t, m.values[0].ReadOnly, true) + assert.NoError(t, m.Set("type=bind,target=/foo,source=/foo,readonly")) + assert.True(t, m.values[0].ReadOnly) m = MountOpt{} - assert.NilError(t, m.Set("type=bind,target=/foo,source=/foo,readonly=1")) - assert.Equal(t, m.values[0].ReadOnly, true) + assert.NoError(t, m.Set("type=bind,target=/foo,source=/foo,readonly=1")) + assert.True(t, m.values[0].ReadOnly) m = MountOpt{} - assert.NilError(t, m.Set("type=bind,target=/foo,source=/foo,readonly=true")) - assert.Equal(t, m.values[0].ReadOnly, true) + assert.NoError(t, m.Set("type=bind,target=/foo,source=/foo,readonly=true")) + assert.True(t, m.values[0].ReadOnly) m = MountOpt{} - assert.NilError(t, m.Set("type=bind,target=/foo,source=/foo,readonly=0")) - assert.Equal(t, m.values[0].ReadOnly, false) + assert.NoError(t, m.Set("type=bind,target=/foo,source=/foo,readonly=0")) + assert.False(t, m.values[0].ReadOnly) } func TestMountOptVolumeNoCopy(t *testing.T) { var m MountOpt - assert.NilError(t, m.Set("type=volume,target=/foo,volume-nocopy")) - assert.Equal(t, m.values[0].Source, "") + assert.NoError(t, m.Set("type=volume,target=/foo,volume-nocopy")) + assert.Equal(t, "", m.values[0].Source) m = MountOpt{} - assert.NilError(t, m.Set("type=volume,target=/foo,source=foo")) - assert.Equal(t, m.values[0].VolumeOptions == nil, true) + assert.NoError(t, m.Set("type=volume,target=/foo,source=foo")) + assert.True(t, m.values[0].VolumeOptions == nil) m = MountOpt{} - assert.NilError(t, m.Set("type=volume,target=/foo,source=foo,volume-nocopy=true")) - assert.Equal(t, m.values[0].VolumeOptions != nil, true) - assert.Equal(t, m.values[0].VolumeOptions.NoCopy, true) + assert.NoError(t, m.Set("type=volume,target=/foo,source=foo,volume-nocopy=true")) + assert.True(t, m.values[0].VolumeOptions != nil) + assert.True(t, m.values[0].VolumeOptions.NoCopy) m = MountOpt{} - assert.NilError(t, m.Set("type=volume,target=/foo,source=foo,volume-nocopy")) - assert.Equal(t, m.values[0].VolumeOptions != nil, true) - assert.Equal(t, m.values[0].VolumeOptions.NoCopy, true) + assert.NoError(t, m.Set("type=volume,target=/foo,source=foo,volume-nocopy")) + assert.True(t, m.values[0].VolumeOptions != nil) + assert.True(t, m.values[0].VolumeOptions.NoCopy) m = MountOpt{} - assert.NilError(t, m.Set("type=volume,target=/foo,source=foo,volume-nocopy=1")) - assert.Equal(t, m.values[0].VolumeOptions != nil, true) - assert.Equal(t, m.values[0].VolumeOptions.NoCopy, true) + assert.NoError(t, m.Set("type=volume,target=/foo,source=foo,volume-nocopy=1")) + assert.True(t, m.values[0].VolumeOptions != nil) + assert.True(t, m.values[0].VolumeOptions.NoCopy) } func TestMountOptTypeConflict(t *testing.T) { var m MountOpt - assert.Error(t, m.Set("type=bind,target=/foo,source=/foo,volume-nocopy=true"), "cannot mix") - assert.Error(t, m.Set("type=volume,target=/foo,source=/foo,bind-propagation=rprivate"), "cannot mix") + testutil.ErrorContains(t, m.Set("type=bind,target=/foo,source=/foo,volume-nocopy=true"), "cannot mix") + testutil.ErrorContains(t, m.Set("type=volume,target=/foo,source=/foo,bind-propagation=rprivate"), "cannot mix") } func TestMountOptSetTmpfsNoError(t *testing.T) { @@ -161,24 +163,24 @@ func TestMountOptSetTmpfsNoError(t *testing.T) { } { var mount MountOpt - assert.NilError(t, mount.Set(testcase)) + assert.NoError(t, mount.Set(testcase)) mounts := mount.Value() - assert.Equal(t, len(mounts), 1) - assert.DeepEqual(t, mounts[0], mounttypes.Mount{ + require.Len(t, mounts, 1) + assert.Equal(t, mounttypes.Mount{ Type: mounttypes.TypeTmpfs, Target: "/target", TmpfsOptions: &mounttypes.TmpfsOptions{ SizeBytes: 1024 * 1024, // not 1000 * 1000 Mode: os.FileMode(0700), }, - }) + }, mounts[0]) } } func TestMountOptSetTmpfsError(t *testing.T) { var m MountOpt - assert.Error(t, m.Set("type=tmpfs,target=/foo,tmpfs-size=foo"), "invalid value for tmpfs-size") - assert.Error(t, m.Set("type=tmpfs,target=/foo,tmpfs-mode=foo"), "invalid value for tmpfs-mode") - assert.Error(t, m.Set("type=tmpfs"), "target is required") + testutil.ErrorContains(t, m.Set("type=tmpfs,target=/foo,tmpfs-size=foo"), "invalid value for tmpfs-size") + testutil.ErrorContains(t, m.Set("type=tmpfs,target=/foo,tmpfs-mode=foo"), "invalid value for tmpfs-mode") + testutil.ErrorContains(t, m.Set("type=tmpfs"), "target is required") } diff --git a/opts/port_test.go b/opts/port_test.go index a483d269aad5..9315b7218c69 100644 --- a/opts/port_test.go +++ b/opts/port_test.go @@ -4,7 +4,8 @@ import ( "testing" "github.com/docker/docker/api/types/swarm" - "github.com/docker/docker/pkg/testutil/assert" + "github.com/docker/docker/pkg/testutil" + "github.com/stretchr/testify/assert" ) func TestPortOptValidSimpleSyntax(t *testing.T) { @@ -98,8 +99,8 @@ func TestPortOptValidSimpleSyntax(t *testing.T) { } for _, tc := range testCases { var port PortOpt - assert.NilError(t, port.Set(tc.value)) - assert.Equal(t, len(port.Value()), len(tc.expected)) + assert.NoError(t, port.Set(tc.value)) + assert.Len(t, port.Value(), len(tc.expected)) for _, expectedPortConfig := range tc.expected { assertContains(t, port.Value(), expectedPortConfig) } @@ -189,8 +190,8 @@ func TestPortOptValidComplexSyntax(t *testing.T) { } for _, tc := range testCases { var port PortOpt - assert.NilError(t, port.Set(tc.value)) - assert.Equal(t, len(port.Value()), len(tc.expected)) + assert.NoError(t, port.Set(tc.value)) + assert.Len(t, port.Value(), len(tc.expected)) for _, expectedPortConfig := range tc.expected { assertContains(t, port.Value(), expectedPortConfig) } @@ -241,7 +242,7 @@ func TestPortOptInvalidComplexSyntax(t *testing.T) { } for _, tc := range testCases { var port PortOpt - assert.Error(t, port.Set(tc.value), tc.expectedError) + testutil.ErrorContains(t, port.Set(tc.value), tc.expectedError) } } @@ -268,16 +269,16 @@ func TestPortOptInvalidSimpleSyntax(t *testing.T) { }, { value: "", - expectedError: "No port specified", + expectedError: "No port specified: ", }, { value: "1.1.1.1:80:80", - expectedError: "HostIP is not supported", + expectedError: "HostIP is not supported.", }, } for _, tc := range testCases { var port PortOpt - assert.Error(t, port.Set(tc.value), tc.expectedError) + assert.EqualError(t, port.Set(tc.value), tc.expectedError) } } diff --git a/opts/quotedstring_test.go b/opts/quotedstring_test.go index 0ebf04bbe044..54dcbc19bc7a 100644 --- a/opts/quotedstring_test.go +++ b/opts/quotedstring_test.go @@ -1,28 +1,29 @@ package opts import ( - "github.com/docker/docker/pkg/testutil/assert" "testing" + + "github.com/stretchr/testify/assert" ) func TestQuotedStringSetWithQuotes(t *testing.T) { value := "" qs := NewQuotedString(&value) - assert.NilError(t, qs.Set("\"something\"")) - assert.Equal(t, qs.String(), "something") - assert.Equal(t, value, "something") + assert.NoError(t, qs.Set(`"something"`)) + assert.Equal(t, "something", qs.String()) + assert.Equal(t, "something", value) } func TestQuotedStringSetWithMismatchedQuotes(t *testing.T) { value := "" qs := NewQuotedString(&value) - assert.NilError(t, qs.Set("\"something'")) - assert.Equal(t, qs.String(), "\"something'") + assert.NoError(t, qs.Set(`"something'`)) + assert.Equal(t, `"something'`, qs.String()) } func TestQuotedStringSetWithNoQuotes(t *testing.T) { value := "" qs := NewQuotedString(&value) - assert.NilError(t, qs.Set("something")) - assert.Equal(t, qs.String(), "something") + assert.NoError(t, qs.Set("something")) + assert.Equal(t, "something", qs.String()) } diff --git a/opts/secret_test.go b/opts/secret_test.go index 5654c79f1f5a..ad0005e4ad75 100644 --- a/opts/secret_test.go +++ b/opts/secret_test.go @@ -4,76 +4,77 @@ import ( "os" "testing" - "github.com/docker/docker/pkg/testutil/assert" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestSecretOptionsSimple(t *testing.T) { var opt SecretOpt testCase := "app-secret" - assert.NilError(t, opt.Set(testCase)) + assert.NoError(t, opt.Set(testCase)) reqs := opt.Value() - assert.Equal(t, len(reqs), 1) + require.Len(t, reqs, 1) req := reqs[0] - assert.Equal(t, req.SecretName, "app-secret") - assert.Equal(t, req.File.Name, "app-secret") - assert.Equal(t, req.File.UID, "0") - assert.Equal(t, req.File.GID, "0") + assert.Equal(t, "app-secret", req.SecretName) + assert.Equal(t, "app-secret", req.File.Name) + assert.Equal(t, "0", req.File.UID) + assert.Equal(t, "0", req.File.GID) } func TestSecretOptionsSourceTarget(t *testing.T) { var opt SecretOpt testCase := "source=foo,target=testing" - assert.NilError(t, opt.Set(testCase)) + assert.NoError(t, opt.Set(testCase)) reqs := opt.Value() - assert.Equal(t, len(reqs), 1) + require.Len(t, reqs, 1) req := reqs[0] - assert.Equal(t, req.SecretName, "foo") - assert.Equal(t, req.File.Name, "testing") + assert.Equal(t, "foo", req.SecretName) + assert.Equal(t, "testing", req.File.Name) } func TestSecretOptionsShorthand(t *testing.T) { var opt SecretOpt testCase := "src=foo,target=testing" - assert.NilError(t, opt.Set(testCase)) + assert.NoError(t, opt.Set(testCase)) reqs := opt.Value() - assert.Equal(t, len(reqs), 1) + require.Len(t, reqs, 1) req := reqs[0] - assert.Equal(t, req.SecretName, "foo") + assert.Equal(t, "foo", req.SecretName) } func TestSecretOptionsCustomUidGid(t *testing.T) { var opt SecretOpt testCase := "source=foo,target=testing,uid=1000,gid=1001" - assert.NilError(t, opt.Set(testCase)) + assert.NoError(t, opt.Set(testCase)) reqs := opt.Value() - assert.Equal(t, len(reqs), 1) + require.Len(t, reqs, 1) req := reqs[0] - assert.Equal(t, req.SecretName, "foo") - assert.Equal(t, req.File.Name, "testing") - assert.Equal(t, req.File.UID, "1000") - assert.Equal(t, req.File.GID, "1001") + assert.Equal(t, "foo", req.SecretName) + assert.Equal(t, "testing", req.File.Name) + assert.Equal(t, "1000", req.File.UID) + assert.Equal(t, "1001", req.File.GID) } func TestSecretOptionsCustomMode(t *testing.T) { var opt SecretOpt testCase := "source=foo,target=testing,uid=1000,gid=1001,mode=0444" - assert.NilError(t, opt.Set(testCase)) + assert.NoError(t, opt.Set(testCase)) reqs := opt.Value() - assert.Equal(t, len(reqs), 1) + require.Len(t, reqs, 1) req := reqs[0] - assert.Equal(t, req.SecretName, "foo") - assert.Equal(t, req.File.Name, "testing") - assert.Equal(t, req.File.UID, "1000") - assert.Equal(t, req.File.GID, "1001") - assert.Equal(t, req.File.Mode, os.FileMode(0444)) + assert.Equal(t, "foo", req.SecretName) + assert.Equal(t, "testing", req.File.Name) + assert.Equal(t, "1000", req.File.UID) + assert.Equal(t, "1001", req.File.GID) + assert.Equal(t, os.FileMode(0444), req.File.Mode) } From 08097edc7849e1543d723a3c382536909b58aa4f Mon Sep 17 00:00:00 2001 From: Evan Hazlett Date: Tue, 11 Apr 2017 13:34:19 -0400 Subject: [PATCH 121/123] support custom paths for secrets This adds support to specify custom container paths for secrets. Signed-off-by: Evan Hazlett --- opts/secret.go | 5 ----- 1 file changed, 5 deletions(-) diff --git a/opts/secret.go b/opts/secret.go index 56ed29eb5b56..a1fde54d9181 100644 --- a/opts/secret.go +++ b/opts/secret.go @@ -4,7 +4,6 @@ import ( "encoding/csv" "fmt" "os" - "path/filepath" "strconv" "strings" @@ -53,10 +52,6 @@ func (o *SecretOpt) Set(value string) error { case "source", "src": options.SecretName = value case "target": - tDir, _ := filepath.Split(value) - if tDir != "" { - return fmt.Errorf("target must not be a path") - } options.File.Name = value case "uid": options.File.UID = value From 560dc7660f55de69da1ed22181384cd6cb765af8 Mon Sep 17 00:00:00 2001 From: Aaron Lehmann Date: Thu, 16 Mar 2017 10:39:18 -0700 Subject: [PATCH 122/123] Update CLI docs and add opts/config.go Signed-off-by: Aaron Lehmann --- opts/config.go | 98 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 98 insertions(+) create mode 100644 opts/config.go diff --git a/opts/config.go b/opts/config.go new file mode 100644 index 000000000000..82fd2bce4e22 --- /dev/null +++ b/opts/config.go @@ -0,0 +1,98 @@ +package opts + +import ( + "encoding/csv" + "fmt" + "os" + "strconv" + "strings" + + swarmtypes "github.com/docker/docker/api/types/swarm" +) + +// ConfigOpt is a Value type for parsing configs +type ConfigOpt struct { + values []*swarmtypes.ConfigReference +} + +// Set a new config value +func (o *ConfigOpt) Set(value string) error { + csvReader := csv.NewReader(strings.NewReader(value)) + fields, err := csvReader.Read() + if err != nil { + return err + } + + options := &swarmtypes.ConfigReference{ + File: &swarmtypes.ConfigReferenceFileTarget{ + UID: "0", + GID: "0", + Mode: 0444, + }, + } + + // support a simple syntax of --config foo + if len(fields) == 1 { + options.File.Name = fields[0] + options.ConfigName = fields[0] + o.values = append(o.values, options) + return nil + } + + for _, field := range fields { + parts := strings.SplitN(field, "=", 2) + key := strings.ToLower(parts[0]) + + if len(parts) != 2 { + return fmt.Errorf("invalid field '%s' must be a key=value pair", field) + } + + value := parts[1] + switch key { + case "source", "src": + options.ConfigName = value + case "target": + options.File.Name = value + case "uid": + options.File.UID = value + case "gid": + options.File.GID = value + case "mode": + m, err := strconv.ParseUint(value, 0, 32) + if err != nil { + return fmt.Errorf("invalid mode specified: %v", err) + } + + options.File.Mode = os.FileMode(m) + default: + return fmt.Errorf("invalid field in config request: %s", key) + } + } + + if options.ConfigName == "" { + return fmt.Errorf("source is required") + } + + o.values = append(o.values, options) + return nil +} + +// Type returns the type of this option +func (o *ConfigOpt) Type() string { + return "config" +} + +// String returns a string repr of this option +func (o *ConfigOpt) String() string { + configs := []string{} + for _, config := range o.values { + repr := fmt.Sprintf("%s -> %s", config.ConfigName, config.File.Name) + configs = append(configs, repr) + } + return strings.Join(configs, ", ") +} + +// Value returns the config requests +func (o *ConfigOpt) Value() []*swarmtypes.ConfigReference { + return o.values +} From d7f6563efc059dcc59b21f30f68e9e8282481af9 Mon Sep 17 00:00:00 2001 From: Vincent Demeester Date: Mon, 15 May 2017 14:45:19 +0200 Subject: [PATCH 123/123] Update cli imports to using local package Also, rename a bunch of variable to not *shadow* the `opts` package name. Signed-off-by: Vincent Demeester --- cli/command/cli.go | 2 +- cli/command/config/create.go | 2 +- cli/command/config/ls.go | 22 ++++----- cli/command/container/commit.go | 40 ++++++++--------- cli/command/container/exec.go | 32 +++++++------- cli/command/container/list.go | 34 +++++++------- cli/command/container/opts.go | 2 +- cli/command/container/prune.go | 16 +++---- cli/command/container/ps_test.go | 2 +- cli/command/container/update.go | 76 ++++++++++++++++---------------- cli/command/image/build.go | 2 +- cli/command/image/import.go | 37 ++++++++-------- cli/command/image/list.go | 47 ++++++++++---------- cli/command/image/prune.go | 22 ++++----- cli/command/network/connect.go | 37 ++++++++-------- cli/command/network/create.go | 61 +++++++++++++------------ cli/command/network/list.go | 31 +++++++------ cli/command/network/prune.go | 19 ++++---- cli/command/node/list.go | 27 ++++++------ cli/command/node/opts.go | 2 +- cli/command/node/ps.go | 32 +++++++------- cli/command/node/update.go | 10 ++--- cli/command/plugin/list.go | 26 +++++------ cli/command/prune/prune.go | 2 +- cli/command/registry/search.go | 37 ++++++++-------- cli/command/secret/create.go | 12 ++--- cli/command/secret/ls.go | 22 ++++----- cli/command/service/list.go | 24 +++++----- cli/command/service/opts.go | 2 +- cli/command/service/opts_test.go | 2 +- cli/command/service/ps.go | 35 +++++++-------- cli/command/service/update.go | 44 +++++++++--------- cli/command/stack/common.go | 5 +-- cli/command/stack/ps.go | 33 +++++++------- cli/command/stack/services.go | 31 +++++++------ cli/command/swarm/opts.go | 2 +- cli/command/system/events.go | 31 +++++++------ cli/command/system/prune.go | 12 ++--- cli/command/volume/create.go | 28 ++++++------ cli/command/volume/list.go | 22 ++++----- cli/command/volume/prune.go | 16 +++---- cli/compose/convert/service.go | 2 +- cli/compose/loader/loader.go | 2 +- cli/flags/common.go | 2 +- opts/mount.go | 1 + opts/opts_test.go | 1 + opts/port.go | 3 +- opts/port_test.go | 2 +- 48 files changed, 472 insertions(+), 482 deletions(-) diff --git a/cli/command/cli.go b/cli/command/cli.go index 0c88beff5db1..f958a58a1525 100644 --- a/cli/command/cli.go +++ b/cli/command/cli.go @@ -12,11 +12,11 @@ import ( "github.com/docker/cli/cli/config/configfile" "github.com/docker/cli/cli/config/credentials" cliflags "github.com/docker/cli/cli/flags" + dopts "github.com/docker/cli/opts" "github.com/docker/docker/api" "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/versions" "github.com/docker/docker/client" - dopts "github.com/docker/docker/opts" "github.com/docker/go-connections/sockets" "github.com/docker/go-connections/tlsconfig" "github.com/docker/notary/passphrase" diff --git a/cli/command/config/create.go b/cli/command/config/create.go index fed2f207bf32..3f0a7db05ef4 100644 --- a/cli/command/config/create.go +++ b/cli/command/config/create.go @@ -7,8 +7,8 @@ import ( "github.com/docker/cli/cli" "github.com/docker/cli/cli/command" + "github.com/docker/cli/opts" "github.com/docker/docker/api/types/swarm" - "github.com/docker/docker/opts" "github.com/docker/docker/pkg/system" runconfigopts "github.com/docker/docker/runconfig/opts" "github.com/pkg/errors" diff --git a/cli/command/config/ls.go b/cli/command/config/ls.go index e3ca82ad5077..f01e3b91544e 100644 --- a/cli/command/config/ls.go +++ b/cli/command/config/ls.go @@ -4,8 +4,8 @@ import ( "github.com/docker/cli/cli" "github.com/docker/cli/cli/command" "github.com/docker/cli/cli/command/formatter" + "github.com/docker/cli/opts" "github.com/docker/docker/api/types" - "github.com/docker/docker/opts" "github.com/spf13/cobra" "golang.org/x/net/context" ) @@ -17,7 +17,7 @@ type listOptions struct { } func newConfigListCommand(dockerCli command.Cli) *cobra.Command { - opts := listOptions{filter: opts.NewFilterOpt()} + listOpts := listOptions{filter: opts.NewFilterOpt()} cmd := &cobra.Command{ Use: "ls [OPTIONS]", @@ -25,30 +25,30 @@ func newConfigListCommand(dockerCli command.Cli) *cobra.Command { Short: "List configs", Args: cli.NoArgs, RunE: func(cmd *cobra.Command, args []string) error { - return runConfigList(dockerCli, opts) + return runConfigList(dockerCli, listOpts) }, } flags := cmd.Flags() - flags.BoolVarP(&opts.quiet, "quiet", "q", false, "Only display IDs") - flags.StringVarP(&opts.format, "format", "", "", "Pretty-print configs using a Go template") - flags.VarP(&opts.filter, "filter", "f", "Filter output based on conditions provided") + flags.BoolVarP(&listOpts.quiet, "quiet", "q", false, "Only display IDs") + flags.StringVarP(&listOpts.format, "format", "", "", "Pretty-print configs using a Go template") + flags.VarP(&listOpts.filter, "filter", "f", "Filter output based on conditions provided") return cmd } -func runConfigList(dockerCli command.Cli, opts listOptions) error { +func runConfigList(dockerCli command.Cli, options listOptions) error { client := dockerCli.Client() ctx := context.Background() - configs, err := client.ConfigList(ctx, types.ConfigListOptions{Filters: opts.filter.Value()}) + configs, err := client.ConfigList(ctx, types.ConfigListOptions{Filters: options.filter.Value()}) if err != nil { return err } - format := opts.format + format := options.format if len(format) == 0 { - if len(dockerCli.ConfigFile().ConfigFormat) > 0 && !opts.quiet { + if len(dockerCli.ConfigFile().ConfigFormat) > 0 && !options.quiet { format = dockerCli.ConfigFile().ConfigFormat } else { format = formatter.TableFormatKey @@ -57,7 +57,7 @@ func runConfigList(dockerCli command.Cli, opts listOptions) error { configCtx := formatter.Context{ Output: dockerCli.Out(), - Format: formatter.NewConfigFormat(format, opts.quiet), + Format: formatter.NewConfigFormat(format, options.quiet), } return formatter.ConfigWrite(configCtx, configs) } diff --git a/cli/command/container/commit.go b/cli/command/container/commit.go index 70b79dcc22f4..689918e0830b 100644 --- a/cli/command/container/commit.go +++ b/cli/command/container/commit.go @@ -5,8 +5,8 @@ import ( "github.com/docker/cli/cli" "github.com/docker/cli/cli/command" + "github.com/docker/cli/opts" "github.com/docker/docker/api/types" - dockeropts "github.com/docker/docker/opts" "github.com/spf13/cobra" "golang.org/x/net/context" ) @@ -18,54 +18,54 @@ type commitOptions struct { pause bool comment string author string - changes dockeropts.ListOpts + changes opts.ListOpts } // NewCommitCommand creates a new cobra.Command for `docker commit` func NewCommitCommand(dockerCli *command.DockerCli) *cobra.Command { - var opts commitOptions + var options commitOptions cmd := &cobra.Command{ Use: "commit [OPTIONS] CONTAINER [REPOSITORY[:TAG]]", Short: "Create a new image from a container's changes", Args: cli.RequiresRangeArgs(1, 2), RunE: func(cmd *cobra.Command, args []string) error { - opts.container = args[0] + options.container = args[0] if len(args) > 1 { - opts.reference = args[1] + options.reference = args[1] } - return runCommit(dockerCli, &opts) + return runCommit(dockerCli, &options) }, } flags := cmd.Flags() flags.SetInterspersed(false) - flags.BoolVarP(&opts.pause, "pause", "p", true, "Pause container during commit") - flags.StringVarP(&opts.comment, "message", "m", "", "Commit message") - flags.StringVarP(&opts.author, "author", "a", "", "Author (e.g., \"John Hannibal Smith \")") + flags.BoolVarP(&options.pause, "pause", "p", true, "Pause container during commit") + flags.StringVarP(&options.comment, "message", "m", "", "Commit message") + flags.StringVarP(&options.author, "author", "a", "", "Author (e.g., \"John Hannibal Smith \")") - opts.changes = dockeropts.NewListOpts(nil) - flags.VarP(&opts.changes, "change", "c", "Apply Dockerfile instruction to the created image") + options.changes = opts.NewListOpts(nil) + flags.VarP(&options.changes, "change", "c", "Apply Dockerfile instruction to the created image") return cmd } -func runCommit(dockerCli *command.DockerCli, opts *commitOptions) error { +func runCommit(dockerCli *command.DockerCli, options *commitOptions) error { ctx := context.Background() - name := opts.container - reference := opts.reference + name := options.container + reference := options.reference - options := types.ContainerCommitOptions{ + commitOptions := types.ContainerCommitOptions{ Reference: reference, - Comment: opts.comment, - Author: opts.author, - Changes: opts.changes.GetAll(), - Pause: opts.pause, + Comment: options.comment, + Author: options.author, + Changes: options.changes.GetAll(), + Pause: options.pause, } - response, err := dockerCli.Client().ContainerCommit(ctx, name, options) + response, err := dockerCli.Client().ContainerCommit(ctx, name, commitOptions) if err != nil { return err } diff --git a/cli/command/container/exec.go b/cli/command/container/exec.go index df8a8a669652..f11b9a7ccc3a 100644 --- a/cli/command/container/exec.go +++ b/cli/command/container/exec.go @@ -7,9 +7,9 @@ import ( "github.com/Sirupsen/logrus" "github.com/docker/cli/cli" "github.com/docker/cli/cli/command" + "github.com/docker/cli/opts" "github.com/docker/docker/api/types" apiclient "github.com/docker/docker/client" - options "github.com/docker/docker/opts" "github.com/docker/docker/pkg/promise" "github.com/spf13/cobra" "golang.org/x/net/context" @@ -22,19 +22,19 @@ type execOptions struct { detach bool user string privileged bool - env *options.ListOpts + env *opts.ListOpts } func newExecOptions() *execOptions { var values []string return &execOptions{ - env: options.NewListOptsRef(&values, options.ValidateEnv), + env: opts.NewListOptsRef(&values, opts.ValidateEnv), } } // NewExecCommand creates a new cobra.Command for `docker exec` func NewExecCommand(dockerCli *command.DockerCli) *cobra.Command { - opts := newExecOptions() + options := newExecOptions() cmd := &cobra.Command{ Use: "exec [OPTIONS] CONTAINER COMMAND [ARG...]", @@ -43,35 +43,35 @@ func NewExecCommand(dockerCli *command.DockerCli) *cobra.Command { RunE: func(cmd *cobra.Command, args []string) error { container := args[0] execCmd := args[1:] - return runExec(dockerCli, opts, container, execCmd) + return runExec(dockerCli, options, container, execCmd) }, } flags := cmd.Flags() flags.SetInterspersed(false) - flags.StringVarP(&opts.detachKeys, "detach-keys", "", "", "Override the key sequence for detaching a container") - flags.BoolVarP(&opts.interactive, "interactive", "i", false, "Keep STDIN open even if not attached") - flags.BoolVarP(&opts.tty, "tty", "t", false, "Allocate a pseudo-TTY") - flags.BoolVarP(&opts.detach, "detach", "d", false, "Detached mode: run command in the background") - flags.StringVarP(&opts.user, "user", "u", "", "Username or UID (format: [:])") - flags.BoolVarP(&opts.privileged, "privileged", "", false, "Give extended privileges to the command") - flags.VarP(opts.env, "env", "e", "Set environment variables") + flags.StringVarP(&options.detachKeys, "detach-keys", "", "", "Override the key sequence for detaching a container") + flags.BoolVarP(&options.interactive, "interactive", "i", false, "Keep STDIN open even if not attached") + flags.BoolVarP(&options.tty, "tty", "t", false, "Allocate a pseudo-TTY") + flags.BoolVarP(&options.detach, "detach", "d", false, "Detached mode: run command in the background") + flags.StringVarP(&options.user, "user", "u", "", "Username or UID (format: [:])") + flags.BoolVarP(&options.privileged, "privileged", "", false, "Give extended privileges to the command") + flags.VarP(options.env, "env", "e", "Set environment variables") flags.SetAnnotation("env", "version", []string{"1.25"}) return cmd } // nolint: gocyclo -func runExec(dockerCli *command.DockerCli, opts *execOptions, container string, execCmd []string) error { - execConfig, err := parseExec(opts, execCmd) +func runExec(dockerCli *command.DockerCli, options *execOptions, container string, execCmd []string) error { + execConfig, err := parseExec(options, execCmd) // just in case the ParseExec does not exit if container == "" || err != nil { return cli.StatusError{StatusCode: 1} } - if opts.detachKeys != "" { - dockerCli.ConfigFile().DetachKeys = opts.detachKeys + if options.detachKeys != "" { + dockerCli.ConfigFile().DetachKeys = options.detachKeys } // Send client escape keys diff --git a/cli/command/container/list.go b/cli/command/container/list.go index f3752bce6185..844ef575198f 100644 --- a/cli/command/container/list.go +++ b/cli/command/container/list.go @@ -6,8 +6,8 @@ import ( "github.com/docker/cli/cli" "github.com/docker/cli/cli/command" "github.com/docker/cli/cli/command/formatter" + "github.com/docker/cli/opts" "github.com/docker/docker/api/types" - "github.com/docker/docker/opts" "github.com/docker/docker/pkg/templates" "github.com/spf13/cobra" "golang.org/x/net/context" @@ -26,27 +26,27 @@ type psOptions struct { // NewPsCommand creates a new cobra.Command for `docker ps` func NewPsCommand(dockerCli *command.DockerCli) *cobra.Command { - opts := psOptions{filter: opts.NewFilterOpt()} + options := psOptions{filter: opts.NewFilterOpt()} cmd := &cobra.Command{ Use: "ps [OPTIONS]", Short: "List containers", Args: cli.NoArgs, RunE: func(cmd *cobra.Command, args []string) error { - return runPs(dockerCli, &opts) + return runPs(dockerCli, &options) }, } flags := cmd.Flags() - flags.BoolVarP(&opts.quiet, "quiet", "q", false, "Only display numeric IDs") - flags.BoolVarP(&opts.size, "size", "s", false, "Display total file sizes") - flags.BoolVarP(&opts.all, "all", "a", false, "Show all containers (default shows just running)") - flags.BoolVar(&opts.noTrunc, "no-trunc", false, "Don't truncate output") - flags.BoolVarP(&opts.nLatest, "latest", "l", false, "Show the latest created container (includes all states)") - flags.IntVarP(&opts.last, "last", "n", -1, "Show n last created containers (includes all states)") - flags.StringVarP(&opts.format, "format", "", "", "Pretty-print containers using a Go template") - flags.VarP(&opts.filter, "filter", "f", "Filter output based on conditions provided") + flags.BoolVarP(&options.quiet, "quiet", "q", false, "Only display numeric IDs") + flags.BoolVarP(&options.size, "size", "s", false, "Display total file sizes") + flags.BoolVarP(&options.all, "all", "a", false, "Show all containers (default shows just running)") + flags.BoolVar(&options.noTrunc, "no-trunc", false, "Don't truncate output") + flags.BoolVarP(&options.nLatest, "latest", "l", false, "Show the latest created container (includes all states)") + flags.IntVarP(&options.last, "last", "n", -1, "Show n last created containers (includes all states)") + flags.StringVarP(&options.format, "format", "", "", "Pretty-print containers using a Go template") + flags.VarP(&options.filter, "filter", "f", "Filter output based on conditions provided") return cmd } @@ -109,10 +109,10 @@ func buildContainerListOptions(opts *psOptions) (*types.ContainerListOptions, er return options, nil } -func runPs(dockerCli *command.DockerCli, opts *psOptions) error { +func runPs(dockerCli *command.DockerCli, options *psOptions) error { ctx := context.Background() - listOptions, err := buildContainerListOptions(opts) + listOptions, err := buildContainerListOptions(options) if err != nil { return err } @@ -122,9 +122,9 @@ func runPs(dockerCli *command.DockerCli, opts *psOptions) error { return err } - format := opts.format + format := options.format if len(format) == 0 { - if len(dockerCli.ConfigFile().PsFormat) > 0 && !opts.quiet { + if len(dockerCli.ConfigFile().PsFormat) > 0 && !options.quiet { format = dockerCli.ConfigFile().PsFormat } else { format = formatter.TableFormatKey @@ -133,8 +133,8 @@ func runPs(dockerCli *command.DockerCli, opts *psOptions) error { containerCtx := formatter.Context{ Output: dockerCli.Out(), - Format: formatter.NewContainerFormat(format, opts.quiet, listOptions.Size), - Trunc: !opts.noTrunc, + Format: formatter.NewContainerFormat(format, options.quiet, listOptions.Size), + Trunc: !options.noTrunc, } return formatter.ContainerWrite(containerCtx, containers) } diff --git a/cli/command/container/opts.go b/cli/command/container/opts.go index 5c8e2c53c469..b636733815cb 100644 --- a/cli/command/container/opts.go +++ b/cli/command/container/opts.go @@ -12,10 +12,10 @@ import ( "time" "github.com/Sirupsen/logrus" + "github.com/docker/cli/opts" "github.com/docker/docker/api/types/container" networktypes "github.com/docker/docker/api/types/network" "github.com/docker/docker/api/types/strslice" - "github.com/docker/docker/opts" "github.com/docker/docker/pkg/signal" runconfigopts "github.com/docker/docker/runconfig/opts" "github.com/docker/go-connections/nat" diff --git a/cli/command/container/prune.go b/cli/command/container/prune.go index 676a6a8f3e49..38ac64729aa6 100644 --- a/cli/command/container/prune.go +++ b/cli/command/container/prune.go @@ -5,7 +5,7 @@ import ( "github.com/docker/cli/cli" "github.com/docker/cli/cli/command" - "github.com/docker/docker/opts" + "github.com/docker/cli/opts" units "github.com/docker/go-units" "github.com/spf13/cobra" "golang.org/x/net/context" @@ -18,14 +18,14 @@ type pruneOptions struct { // NewPruneCommand returns a new cobra prune command for containers func NewPruneCommand(dockerCli command.Cli) *cobra.Command { - opts := pruneOptions{filter: opts.NewFilterOpt()} + options := pruneOptions{filter: opts.NewFilterOpt()} cmd := &cobra.Command{ Use: "prune [OPTIONS]", Short: "Remove all stopped containers", Args: cli.NoArgs, RunE: func(cmd *cobra.Command, args []string) error { - spaceReclaimed, output, err := runPrune(dockerCli, opts) + spaceReclaimed, output, err := runPrune(dockerCli, options) if err != nil { return err } @@ -39,8 +39,8 @@ func NewPruneCommand(dockerCli command.Cli) *cobra.Command { } flags := cmd.Flags() - flags.BoolVarP(&opts.force, "force", "f", false, "Do not prompt for confirmation") - flags.Var(&opts.filter, "filter", "Provide filter values (e.g. 'until=')") + flags.BoolVarP(&options.force, "force", "f", false, "Do not prompt for confirmation") + flags.Var(&options.filter, "filter", "Provide filter values (e.g. 'until=')") return cmd } @@ -48,10 +48,10 @@ func NewPruneCommand(dockerCli command.Cli) *cobra.Command { const warning = `WARNING! This will remove all stopped containers. Are you sure you want to continue?` -func runPrune(dockerCli command.Cli, opts pruneOptions) (spaceReclaimed uint64, output string, err error) { - pruneFilters := command.PruneFilters(dockerCli, opts.filter.Value()) +func runPrune(dockerCli command.Cli, options pruneOptions) (spaceReclaimed uint64, output string, err error) { + pruneFilters := command.PruneFilters(dockerCli, options.filter.Value()) - if !opts.force && !command.PromptForConfirmation(dockerCli.In(), dockerCli.Out(), warning) { + if !options.force && !command.PromptForConfirmation(dockerCli.In(), dockerCli.Out(), warning) { return } diff --git a/cli/command/container/ps_test.go b/cli/command/container/ps_test.go index 47665b0e2ca6..d5a51d92eb81 100644 --- a/cli/command/container/ps_test.go +++ b/cli/command/container/ps_test.go @@ -3,7 +3,7 @@ package container import ( "testing" - "github.com/docker/docker/opts" + "github.com/docker/cli/opts" "github.com/stretchr/testify/assert" ) diff --git a/cli/command/container/update.go b/cli/command/container/update.go index 3743ad5c5966..c13c7e6ab148 100644 --- a/cli/command/container/update.go +++ b/cli/command/container/update.go @@ -6,8 +6,8 @@ import ( "github.com/docker/cli/cli" "github.com/docker/cli/cli/command" + "github.com/docker/cli/opts" containertypes "github.com/docker/docker/api/types/container" - "github.com/docker/docker/opts" runconfigopts "github.com/docker/docker/runconfig/opts" "github.com/pkg/errors" "github.com/spf13/cobra" @@ -37,71 +37,71 @@ type updateOptions struct { // NewUpdateCommand creates a new cobra.Command for `docker update` func NewUpdateCommand(dockerCli *command.DockerCli) *cobra.Command { - var opts updateOptions + var options updateOptions cmd := &cobra.Command{ Use: "update [OPTIONS] CONTAINER [CONTAINER...]", Short: "Update configuration of one or more containers", Args: cli.RequiresMinArgs(1), RunE: func(cmd *cobra.Command, args []string) error { - opts.containers = args - opts.nFlag = cmd.Flags().NFlag() - return runUpdate(dockerCli, &opts) + options.containers = args + options.nFlag = cmd.Flags().NFlag() + return runUpdate(dockerCli, &options) }, } flags := cmd.Flags() - flags.Uint16Var(&opts.blkioWeight, "blkio-weight", 0, "Block IO (relative weight), between 10 and 1000, or 0 to disable (default 0)") - flags.Int64Var(&opts.cpuPeriod, "cpu-period", 0, "Limit CPU CFS (Completely Fair Scheduler) period") - flags.Int64Var(&opts.cpuQuota, "cpu-quota", 0, "Limit CPU CFS (Completely Fair Scheduler) quota") - flags.Int64Var(&opts.cpuRealtimePeriod, "cpu-rt-period", 0, "Limit the CPU real-time period in microseconds") + flags.Uint16Var(&options.blkioWeight, "blkio-weight", 0, "Block IO (relative weight), between 10 and 1000, or 0 to disable (default 0)") + flags.Int64Var(&options.cpuPeriod, "cpu-period", 0, "Limit CPU CFS (Completely Fair Scheduler) period") + flags.Int64Var(&options.cpuQuota, "cpu-quota", 0, "Limit CPU CFS (Completely Fair Scheduler) quota") + flags.Int64Var(&options.cpuRealtimePeriod, "cpu-rt-period", 0, "Limit the CPU real-time period in microseconds") flags.SetAnnotation("cpu-rt-period", "version", []string{"1.25"}) - flags.Int64Var(&opts.cpuRealtimeRuntime, "cpu-rt-runtime", 0, "Limit the CPU real-time runtime in microseconds") + flags.Int64Var(&options.cpuRealtimeRuntime, "cpu-rt-runtime", 0, "Limit the CPU real-time runtime in microseconds") flags.SetAnnotation("cpu-rt-runtime", "version", []string{"1.25"}) - flags.StringVar(&opts.cpusetCpus, "cpuset-cpus", "", "CPUs in which to allow execution (0-3, 0,1)") - flags.StringVar(&opts.cpusetMems, "cpuset-mems", "", "MEMs in which to allow execution (0-3, 0,1)") - flags.Int64VarP(&opts.cpuShares, "cpu-shares", "c", 0, "CPU shares (relative weight)") - flags.VarP(&opts.memory, "memory", "m", "Memory limit") - flags.Var(&opts.memoryReservation, "memory-reservation", "Memory soft limit") - flags.Var(&opts.memorySwap, "memory-swap", "Swap limit equal to memory plus swap: '-1' to enable unlimited swap") - flags.Var(&opts.kernelMemory, "kernel-memory", "Kernel memory limit") - flags.StringVar(&opts.restartPolicy, "restart", "", "Restart policy to apply when a container exits") - - flags.Var(&opts.cpus, "cpus", "Number of CPUs") + flags.StringVar(&options.cpusetCpus, "cpuset-cpus", "", "CPUs in which to allow execution (0-3, 0,1)") + flags.StringVar(&options.cpusetMems, "cpuset-mems", "", "MEMs in which to allow execution (0-3, 0,1)") + flags.Int64VarP(&options.cpuShares, "cpu-shares", "c", 0, "CPU shares (relative weight)") + flags.VarP(&options.memory, "memory", "m", "Memory limit") + flags.Var(&options.memoryReservation, "memory-reservation", "Memory soft limit") + flags.Var(&options.memorySwap, "memory-swap", "Swap limit equal to memory plus swap: '-1' to enable unlimited swap") + flags.Var(&options.kernelMemory, "kernel-memory", "Kernel memory limit") + flags.StringVar(&options.restartPolicy, "restart", "", "Restart policy to apply when a container exits") + + flags.Var(&options.cpus, "cpus", "Number of CPUs") flags.SetAnnotation("cpus", "version", []string{"1.29"}) return cmd } -func runUpdate(dockerCli *command.DockerCli, opts *updateOptions) error { +func runUpdate(dockerCli *command.DockerCli, options *updateOptions) error { var err error - if opts.nFlag == 0 { + if options.nFlag == 0 { return errors.New("you must provide one or more flags when using this command") } var restartPolicy containertypes.RestartPolicy - if opts.restartPolicy != "" { - restartPolicy, err = runconfigopts.ParseRestartPolicy(opts.restartPolicy) + if options.restartPolicy != "" { + restartPolicy, err = runconfigopts.ParseRestartPolicy(options.restartPolicy) if err != nil { return err } } resources := containertypes.Resources{ - BlkioWeight: opts.blkioWeight, - CpusetCpus: opts.cpusetCpus, - CpusetMems: opts.cpusetMems, - CPUShares: opts.cpuShares, - Memory: opts.memory.Value(), - MemoryReservation: opts.memoryReservation.Value(), - MemorySwap: opts.memorySwap.Value(), - KernelMemory: opts.kernelMemory.Value(), - CPUPeriod: opts.cpuPeriod, - CPUQuota: opts.cpuQuota, - CPURealtimePeriod: opts.cpuRealtimePeriod, - CPURealtimeRuntime: opts.cpuRealtimeRuntime, - NanoCPUs: opts.cpus.Value(), + BlkioWeight: options.blkioWeight, + CpusetCpus: options.cpusetCpus, + CpusetMems: options.cpusetMems, + CPUShares: options.cpuShares, + Memory: options.memory.Value(), + MemoryReservation: options.memoryReservation.Value(), + MemorySwap: options.memorySwap.Value(), + KernelMemory: options.kernelMemory.Value(), + CPUPeriod: options.cpuPeriod, + CPUQuota: options.cpuQuota, + CPURealtimePeriod: options.cpuRealtimePeriod, + CPURealtimeRuntime: options.cpuRealtimeRuntime, + NanoCPUs: options.cpus.Value(), } updateConfig := containertypes.UpdateConfig{ @@ -115,7 +115,7 @@ func runUpdate(dockerCli *command.DockerCli, opts *updateOptions) error { warns []string errs []string ) - for _, container := range opts.containers { + for _, container := range options.containers { r, err := dockerCli.Client().ContainerUpdate(ctx, container, updateConfig) if err != nil { errs = append(errs, err.Error()) diff --git a/cli/command/image/build.go b/cli/command/image/build.go index cfb4fb882928..da28e898e9ae 100644 --- a/cli/command/image/build.go +++ b/cli/command/image/build.go @@ -15,11 +15,11 @@ import ( "github.com/docker/cli/cli" "github.com/docker/cli/cli/command" "github.com/docker/cli/cli/command/image/build" + "github.com/docker/cli/opts" "github.com/docker/distribution/reference" "github.com/docker/docker/api" "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/container" - "github.com/docker/docker/opts" "github.com/docker/docker/pkg/archive" "github.com/docker/docker/pkg/jsonmessage" "github.com/docker/docker/pkg/progress" diff --git a/cli/command/image/import.go b/cli/command/image/import.go index 474812227339..1f7189a95b0a 100644 --- a/cli/command/image/import.go +++ b/cli/command/image/import.go @@ -4,15 +4,14 @@ import ( "io" "os" - "golang.org/x/net/context" - "github.com/docker/cli/cli" "github.com/docker/cli/cli/command" + dockeropts "github.com/docker/cli/opts" "github.com/docker/docker/api/types" - dockeropts "github.com/docker/docker/opts" "github.com/docker/docker/pkg/jsonmessage" "github.com/docker/docker/pkg/urlutil" "github.com/spf13/cobra" + "golang.org/x/net/context" ) type importOptions struct { @@ -24,41 +23,41 @@ type importOptions struct { // NewImportCommand creates a new `docker import` command func NewImportCommand(dockerCli command.Cli) *cobra.Command { - var opts importOptions + var options importOptions cmd := &cobra.Command{ Use: "import [OPTIONS] file|URL|- [REPOSITORY[:TAG]]", Short: "Import the contents from a tarball to create a filesystem image", Args: cli.RequiresMinArgs(1), RunE: func(cmd *cobra.Command, args []string) error { - opts.source = args[0] + options.source = args[0] if len(args) > 1 { - opts.reference = args[1] + options.reference = args[1] } - return runImport(dockerCli, opts) + return runImport(dockerCli, options) }, } flags := cmd.Flags() - opts.changes = dockeropts.NewListOpts(nil) - flags.VarP(&opts.changes, "change", "c", "Apply Dockerfile instruction to the created image") - flags.StringVarP(&opts.message, "message", "m", "", "Set commit message for imported image") + options.changes = dockeropts.NewListOpts(nil) + flags.VarP(&options.changes, "change", "c", "Apply Dockerfile instruction to the created image") + flags.StringVarP(&options.message, "message", "m", "", "Set commit message for imported image") return cmd } -func runImport(dockerCli command.Cli, opts importOptions) error { +func runImport(dockerCli command.Cli, options importOptions) error { var ( in io.Reader - srcName = opts.source + srcName = options.source ) - if opts.source == "-" { + if options.source == "-" { in = dockerCli.In() - } else if !urlutil.IsURL(opts.source) { + } else if !urlutil.IsURL(options.source) { srcName = "-" - file, err := os.Open(opts.source) + file, err := os.Open(options.source) if err != nil { return err } @@ -71,14 +70,14 @@ func runImport(dockerCli command.Cli, opts importOptions) error { SourceName: srcName, } - options := types.ImageImportOptions{ - Message: opts.message, - Changes: opts.changes.GetAll(), + importOptions := types.ImageImportOptions{ + Message: options.message, + Changes: options.changes.GetAll(), } clnt := dockerCli.Client() - responseBody, err := clnt.ImageImport(context.Background(), source, opts.reference, options) + responseBody, err := clnt.ImageImport(context.Background(), source, options.reference, importOptions) if err != nil { return err } diff --git a/cli/command/image/list.go b/cli/command/image/list.go index 93edaa91e24e..6dada82525e8 100644 --- a/cli/command/image/list.go +++ b/cli/command/image/list.go @@ -1,14 +1,13 @@ package image import ( - "golang.org/x/net/context" - "github.com/docker/cli/cli" "github.com/docker/cli/cli/command" "github.com/docker/cli/cli/command/formatter" + "github.com/docker/cli/opts" "github.com/docker/docker/api/types" - "github.com/docker/docker/opts" "github.com/spf13/cobra" + "golang.org/x/net/context" ) type imagesOptions struct { @@ -24,7 +23,7 @@ type imagesOptions struct { // NewImagesCommand creates a new `docker images` command func NewImagesCommand(dockerCli command.Cli) *cobra.Command { - opts := imagesOptions{filter: opts.NewFilterOpt()} + options := imagesOptions{filter: opts.NewFilterOpt()} cmd := &cobra.Command{ Use: "images [OPTIONS] [REPOSITORY[:TAG]]", @@ -32,20 +31,20 @@ func NewImagesCommand(dockerCli command.Cli) *cobra.Command { Args: cli.RequiresMaxArgs(1), RunE: func(cmd *cobra.Command, args []string) error { if len(args) > 0 { - opts.matchName = args[0] + options.matchName = args[0] } - return runImages(dockerCli, opts) + return runImages(dockerCli, options) }, } flags := cmd.Flags() - flags.BoolVarP(&opts.quiet, "quiet", "q", false, "Only show numeric IDs") - flags.BoolVarP(&opts.all, "all", "a", false, "Show all images (default hides intermediate images)") - flags.BoolVar(&opts.noTrunc, "no-trunc", false, "Don't truncate output") - flags.BoolVar(&opts.showDigests, "digests", false, "Show digests") - flags.StringVar(&opts.format, "format", "", "Pretty-print images using a Go template") - flags.VarP(&opts.filter, "filter", "f", "Filter output based on conditions provided") + flags.BoolVarP(&options.quiet, "quiet", "q", false, "Only show numeric IDs") + flags.BoolVarP(&options.all, "all", "a", false, "Show all images (default hides intermediate images)") + flags.BoolVar(&options.noTrunc, "no-trunc", false, "Don't truncate output") + flags.BoolVar(&options.showDigests, "digests", false, "Show digests") + flags.StringVar(&options.format, "format", "", "Pretty-print images using a Go template") + flags.VarP(&options.filter, "filter", "f", "Filter output based on conditions provided") return cmd } @@ -57,27 +56,27 @@ func newListCommand(dockerCli command.Cli) *cobra.Command { return &cmd } -func runImages(dockerCli command.Cli, opts imagesOptions) error { +func runImages(dockerCli command.Cli, options imagesOptions) error { ctx := context.Background() - filters := opts.filter.Value() - if opts.matchName != "" { - filters.Add("reference", opts.matchName) + filters := options.filter.Value() + if options.matchName != "" { + filters.Add("reference", options.matchName) } - options := types.ImageListOptions{ - All: opts.all, + listOptions := types.ImageListOptions{ + All: options.all, Filters: filters, } - images, err := dockerCli.Client().ImageList(ctx, options) + images, err := dockerCli.Client().ImageList(ctx, listOptions) if err != nil { return err } - format := opts.format + format := options.format if len(format) == 0 { - if len(dockerCli.ConfigFile().ImagesFormat) > 0 && !opts.quiet { + if len(dockerCli.ConfigFile().ImagesFormat) > 0 && !options.quiet { format = dockerCli.ConfigFile().ImagesFormat } else { format = formatter.TableFormatKey @@ -87,10 +86,10 @@ func runImages(dockerCli command.Cli, opts imagesOptions) error { imageCtx := formatter.ImageContext{ Context: formatter.Context{ Output: dockerCli.Out(), - Format: formatter.NewImageFormat(format, opts.quiet, opts.showDigests), - Trunc: !opts.noTrunc, + Format: formatter.NewImageFormat(format, options.quiet, options.showDigests), + Trunc: !options.noTrunc, }, - Digest: opts.showDigests, + Digest: options.showDigests, } return formatter.ImageWrite(imageCtx, images) } diff --git a/cli/command/image/prune.go b/cli/command/image/prune.go index 3d67b0939e4f..8e521b61b0d6 100644 --- a/cli/command/image/prune.go +++ b/cli/command/image/prune.go @@ -7,7 +7,7 @@ import ( "github.com/docker/cli/cli" "github.com/docker/cli/cli/command" - "github.com/docker/docker/opts" + "github.com/docker/cli/opts" units "github.com/docker/go-units" "github.com/spf13/cobra" ) @@ -20,14 +20,14 @@ type pruneOptions struct { // NewPruneCommand returns a new cobra prune command for images func NewPruneCommand(dockerCli command.Cli) *cobra.Command { - opts := pruneOptions{filter: opts.NewFilterOpt()} + options := pruneOptions{filter: opts.NewFilterOpt()} cmd := &cobra.Command{ Use: "prune [OPTIONS]", Short: "Remove unused images", Args: cli.NoArgs, RunE: func(cmd *cobra.Command, args []string) error { - spaceReclaimed, output, err := runPrune(dockerCli, opts) + spaceReclaimed, output, err := runPrune(dockerCli, options) if err != nil { return err } @@ -41,9 +41,9 @@ func NewPruneCommand(dockerCli command.Cli) *cobra.Command { } flags := cmd.Flags() - flags.BoolVarP(&opts.force, "force", "f", false, "Do not prompt for confirmation") - flags.BoolVarP(&opts.all, "all", "a", false, "Remove all unused images, not just dangling ones") - flags.Var(&opts.filter, "filter", "Provide filter values (e.g. 'until=')") + flags.BoolVarP(&options.force, "force", "f", false, "Do not prompt for confirmation") + flags.BoolVarP(&options.all, "all", "a", false, "Remove all unused images, not just dangling ones") + flags.Var(&options.filter, "filter", "Provide filter values (e.g. 'until=')") return cmd } @@ -55,16 +55,16 @@ Are you sure you want to continue?` Are you sure you want to continue?` ) -func runPrune(dockerCli command.Cli, opts pruneOptions) (spaceReclaimed uint64, output string, err error) { - pruneFilters := opts.filter.Value() - pruneFilters.Add("dangling", fmt.Sprintf("%v", !opts.all)) +func runPrune(dockerCli command.Cli, options pruneOptions) (spaceReclaimed uint64, output string, err error) { + pruneFilters := options.filter.Value() + pruneFilters.Add("dangling", fmt.Sprintf("%v", !options.all)) pruneFilters = command.PruneFilters(dockerCli, pruneFilters) warning := danglingWarning - if opts.all { + if options.all { warning = allImageWarning } - if !opts.force && !command.PromptForConfirmation(dockerCli.In(), dockerCli.Out(), warning) { + if !options.force && !command.PromptForConfirmation(dockerCli.In(), dockerCli.Out(), warning) { return } diff --git a/cli/command/network/connect.go b/cli/command/network/connect.go index 813a8e35361a..19bd3c56aa06 100644 --- a/cli/command/network/connect.go +++ b/cli/command/network/connect.go @@ -1,13 +1,12 @@ package network import ( - "golang.org/x/net/context" - "github.com/docker/cli/cli" "github.com/docker/cli/cli/command" + "github.com/docker/cli/opts" "github.com/docker/docker/api/types/network" - "github.com/docker/docker/opts" "github.com/spf13/cobra" + "golang.org/x/net/context" ) type connectOptions struct { @@ -21,7 +20,7 @@ type connectOptions struct { } func newConnectCommand(dockerCli *command.DockerCli) *cobra.Command { - opts := connectOptions{ + options := connectOptions{ links: opts.NewListOpts(opts.ValidateLink), } @@ -30,34 +29,34 @@ func newConnectCommand(dockerCli *command.DockerCli) *cobra.Command { Short: "Connect a container to a network", Args: cli.ExactArgs(2), RunE: func(cmd *cobra.Command, args []string) error { - opts.network = args[0] - opts.container = args[1] - return runConnect(dockerCli, opts) + options.network = args[0] + options.container = args[1] + return runConnect(dockerCli, options) }, } flags := cmd.Flags() - flags.StringVar(&opts.ipaddress, "ip", "", "IPv4 address (e.g., 172.30.100.104)") - flags.StringVar(&opts.ipv6address, "ip6", "", "IPv6 address (e.g., 2001:db8::33)") - flags.Var(&opts.links, "link", "Add link to another container") - flags.StringSliceVar(&opts.aliases, "alias", []string{}, "Add network-scoped alias for the container") - flags.StringSliceVar(&opts.linklocalips, "link-local-ip", []string{}, "Add a link-local address for the container") + flags.StringVar(&options.ipaddress, "ip", "", "IPv4 address (e.g., 172.30.100.104)") + flags.StringVar(&options.ipv6address, "ip6", "", "IPv6 address (e.g., 2001:db8::33)") + flags.Var(&options.links, "link", "Add link to another container") + flags.StringSliceVar(&options.aliases, "alias", []string{}, "Add network-scoped alias for the container") + flags.StringSliceVar(&options.linklocalips, "link-local-ip", []string{}, "Add a link-local address for the container") return cmd } -func runConnect(dockerCli *command.DockerCli, opts connectOptions) error { +func runConnect(dockerCli *command.DockerCli, options connectOptions) error { client := dockerCli.Client() epConfig := &network.EndpointSettings{ IPAMConfig: &network.EndpointIPAMConfig{ - IPv4Address: opts.ipaddress, - IPv6Address: opts.ipv6address, - LinkLocalIPs: opts.linklocalips, + IPv4Address: options.ipaddress, + IPv6Address: options.ipv6address, + LinkLocalIPs: options.linklocalips, }, - Links: opts.links.GetAll(), - Aliases: opts.aliases, + Links: options.links.GetAll(), + Aliases: options.aliases, } - return client.NetworkConnect(context.Background(), opts.network, opts.container, epConfig) + return client.NetworkConnect(context.Background(), options.network, options.container, epConfig) } diff --git a/cli/command/network/create.go b/cli/command/network/create.go index bd8721ce5741..d5da8fc34c89 100644 --- a/cli/command/network/create.go +++ b/cli/command/network/create.go @@ -5,16 +5,15 @@ import ( "net" "strings" - "golang.org/x/net/context" - "github.com/docker/cli/cli" "github.com/docker/cli/cli/command" + "github.com/docker/cli/opts" "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/network" - "github.com/docker/docker/opts" runconfigopts "github.com/docker/docker/runconfig/opts" "github.com/pkg/errors" "github.com/spf13/cobra" + "golang.org/x/net/context" ) type createOptions struct { @@ -36,7 +35,7 @@ type createOptions struct { } func newCreateCommand(dockerCli *command.DockerCli) *cobra.Command { - opts := createOptions{ + options := createOptions{ driverOpts: *opts.NewMapOpts(nil, nil), labels: opts.NewListOpts(opts.ValidateEnv), ipamAux: *opts.NewMapOpts(nil, nil), @@ -48,59 +47,59 @@ func newCreateCommand(dockerCli *command.DockerCli) *cobra.Command { Short: "Create a network", Args: cli.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { - opts.name = args[0] - return runCreate(dockerCli, opts) + options.name = args[0] + return runCreate(dockerCli, options) }, } flags := cmd.Flags() - flags.StringVarP(&opts.driver, "driver", "d", "bridge", "Driver to manage the Network") - flags.VarP(&opts.driverOpts, "opt", "o", "Set driver specific options") - flags.Var(&opts.labels, "label", "Set metadata on a network") - flags.BoolVar(&opts.internal, "internal", false, "Restrict external access to the network") - flags.BoolVar(&opts.ipv6, "ipv6", false, "Enable IPv6 networking") - flags.BoolVar(&opts.attachable, "attachable", false, "Enable manual container attachment") + flags.StringVarP(&options.driver, "driver", "d", "bridge", "Driver to manage the Network") + flags.VarP(&options.driverOpts, "opt", "o", "Set driver specific options") + flags.Var(&options.labels, "label", "Set metadata on a network") + flags.BoolVar(&options.internal, "internal", false, "Restrict external access to the network") + flags.BoolVar(&options.ipv6, "ipv6", false, "Enable IPv6 networking") + flags.BoolVar(&options.attachable, "attachable", false, "Enable manual container attachment") flags.SetAnnotation("attachable", "version", []string{"1.25"}) - flags.BoolVar(&opts.ingress, "ingress", false, "Create swarm routing-mesh network") + flags.BoolVar(&options.ingress, "ingress", false, "Create swarm routing-mesh network") flags.SetAnnotation("ingress", "version", []string{"1.29"}) - flags.StringVar(&opts.ipamDriver, "ipam-driver", "default", "IP Address Management Driver") - flags.StringSliceVar(&opts.ipamSubnet, "subnet", []string{}, "Subnet in CIDR format that represents a network segment") - flags.StringSliceVar(&opts.ipamIPRange, "ip-range", []string{}, "Allocate container ip from a sub-range") - flags.StringSliceVar(&opts.ipamGateway, "gateway", []string{}, "IPv4 or IPv6 Gateway for the master subnet") + flags.StringVar(&options.ipamDriver, "ipam-driver", "default", "IP Address Management Driver") + flags.StringSliceVar(&options.ipamSubnet, "subnet", []string{}, "Subnet in CIDR format that represents a network segment") + flags.StringSliceVar(&options.ipamIPRange, "ip-range", []string{}, "Allocate container ip from a sub-range") + flags.StringSliceVar(&options.ipamGateway, "gateway", []string{}, "IPv4 or IPv6 Gateway for the master subnet") - flags.Var(&opts.ipamAux, "aux-address", "Auxiliary IPv4 or IPv6 addresses used by Network driver") - flags.Var(&opts.ipamOpt, "ipam-opt", "Set IPAM driver specific options") + flags.Var(&options.ipamAux, "aux-address", "Auxiliary IPv4 or IPv6 addresses used by Network driver") + flags.Var(&options.ipamOpt, "ipam-opt", "Set IPAM driver specific options") return cmd } -func runCreate(dockerCli *command.DockerCli, opts createOptions) error { +func runCreate(dockerCli *command.DockerCli, options createOptions) error { client := dockerCli.Client() - ipamCfg, err := consolidateIpam(opts.ipamSubnet, opts.ipamIPRange, opts.ipamGateway, opts.ipamAux.GetAll()) + ipamCfg, err := consolidateIpam(options.ipamSubnet, options.ipamIPRange, options.ipamGateway, options.ipamAux.GetAll()) if err != nil { return err } // Construct network create request body nc := types.NetworkCreate{ - Driver: opts.driver, - Options: opts.driverOpts.GetAll(), + Driver: options.driver, + Options: options.driverOpts.GetAll(), IPAM: &network.IPAM{ - Driver: opts.ipamDriver, + Driver: options.ipamDriver, Config: ipamCfg, - Options: opts.ipamOpt.GetAll(), + Options: options.ipamOpt.GetAll(), }, CheckDuplicate: true, - Internal: opts.internal, - EnableIPv6: opts.ipv6, - Attachable: opts.attachable, - Ingress: opts.ingress, - Labels: runconfigopts.ConvertKVStringsToMap(opts.labels.GetAll()), + Internal: options.internal, + EnableIPv6: options.ipv6, + Attachable: options.attachable, + Ingress: options.ingress, + Labels: runconfigopts.ConvertKVStringsToMap(options.labels.GetAll()), } - resp, err := client.NetworkCreate(context.Background(), opts.name, nc) + resp, err := client.NetworkCreate(context.Background(), options.name, nc) if err != nil { return err } diff --git a/cli/command/network/list.go b/cli/command/network/list.go index a35f32f1477f..79a860f9b3f4 100644 --- a/cli/command/network/list.go +++ b/cli/command/network/list.go @@ -3,14 +3,13 @@ package network import ( "sort" - "golang.org/x/net/context" - "github.com/docker/cli/cli" "github.com/docker/cli/cli/command" "github.com/docker/cli/cli/command/formatter" + "github.com/docker/cli/opts" "github.com/docker/docker/api/types" - "github.com/docker/docker/opts" "github.com/spf13/cobra" + "golang.org/x/net/context" ) type byNetworkName []types.NetworkResource @@ -27,7 +26,7 @@ type listOptions struct { } func newListCommand(dockerCli *command.DockerCli) *cobra.Command { - opts := listOptions{filter: opts.NewFilterOpt()} + options := listOptions{filter: opts.NewFilterOpt()} cmd := &cobra.Command{ Use: "ls [OPTIONS]", @@ -35,30 +34,30 @@ func newListCommand(dockerCli *command.DockerCli) *cobra.Command { Short: "List networks", Args: cli.NoArgs, RunE: func(cmd *cobra.Command, args []string) error { - return runList(dockerCli, opts) + return runList(dockerCli, options) }, } flags := cmd.Flags() - flags.BoolVarP(&opts.quiet, "quiet", "q", false, "Only display network IDs") - flags.BoolVar(&opts.noTrunc, "no-trunc", false, "Do not truncate the output") - flags.StringVar(&opts.format, "format", "", "Pretty-print networks using a Go template") - flags.VarP(&opts.filter, "filter", "f", "Provide filter values (e.g. 'driver=bridge')") + flags.BoolVarP(&options.quiet, "quiet", "q", false, "Only display network IDs") + flags.BoolVar(&options.noTrunc, "no-trunc", false, "Do not truncate the output") + flags.StringVar(&options.format, "format", "", "Pretty-print networks using a Go template") + flags.VarP(&options.filter, "filter", "f", "Provide filter values (e.g. 'driver=bridge')") return cmd } -func runList(dockerCli *command.DockerCli, opts listOptions) error { +func runList(dockerCli *command.DockerCli, options listOptions) error { client := dockerCli.Client() - options := types.NetworkListOptions{Filters: opts.filter.Value()} - networkResources, err := client.NetworkList(context.Background(), options) + listOptions := types.NetworkListOptions{Filters: options.filter.Value()} + networkResources, err := client.NetworkList(context.Background(), listOptions) if err != nil { return err } - format := opts.format + format := options.format if len(format) == 0 { - if len(dockerCli.ConfigFile().NetworksFormat) > 0 && !opts.quiet { + if len(dockerCli.ConfigFile().NetworksFormat) > 0 && !options.quiet { format = dockerCli.ConfigFile().NetworksFormat } else { format = formatter.TableFormatKey @@ -69,8 +68,8 @@ func runList(dockerCli *command.DockerCli, opts listOptions) error { networksCtx := formatter.Context{ Output: dockerCli.Out(), - Format: formatter.NewNetworkFormat(format, opts.quiet), - Trunc: !opts.noTrunc, + Format: formatter.NewNetworkFormat(format, options.quiet), + Trunc: !options.noTrunc, } return formatter.NetworkWrite(networksCtx, networkResources) } diff --git a/cli/command/network/prune.go b/cli/command/network/prune.go index dde573eeabf3..b31db99da75d 100644 --- a/cli/command/network/prune.go +++ b/cli/command/network/prune.go @@ -3,12 +3,11 @@ package network import ( "fmt" - "golang.org/x/net/context" - "github.com/docker/cli/cli" "github.com/docker/cli/cli/command" - "github.com/docker/docker/opts" + "github.com/docker/cli/opts" "github.com/spf13/cobra" + "golang.org/x/net/context" ) type pruneOptions struct { @@ -18,14 +17,14 @@ type pruneOptions struct { // NewPruneCommand returns a new cobra prune command for networks func NewPruneCommand(dockerCli command.Cli) *cobra.Command { - opts := pruneOptions{filter: opts.NewFilterOpt()} + options := pruneOptions{filter: opts.NewFilterOpt()} cmd := &cobra.Command{ Use: "prune [OPTIONS]", Short: "Remove all unused networks", Args: cli.NoArgs, RunE: func(cmd *cobra.Command, args []string) error { - output, err := runPrune(dockerCli, opts) + output, err := runPrune(dockerCli, options) if err != nil { return err } @@ -38,8 +37,8 @@ func NewPruneCommand(dockerCli command.Cli) *cobra.Command { } flags := cmd.Flags() - flags.BoolVarP(&opts.force, "force", "f", false, "Do not prompt for confirmation") - flags.Var(&opts.filter, "filter", "Provide filter values (e.g. 'until=')") + flags.BoolVarP(&options.force, "force", "f", false, "Do not prompt for confirmation") + flags.Var(&options.filter, "filter", "Provide filter values (e.g. 'until=')") return cmd } @@ -47,10 +46,10 @@ func NewPruneCommand(dockerCli command.Cli) *cobra.Command { const warning = `WARNING! This will remove all networks not used by at least one container. Are you sure you want to continue?` -func runPrune(dockerCli command.Cli, opts pruneOptions) (output string, err error) { - pruneFilters := command.PruneFilters(dockerCli, opts.filter.Value()) +func runPrune(dockerCli command.Cli, options pruneOptions) (output string, err error) { + pruneFilters := command.PruneFilters(dockerCli, options.filter.Value()) - if !opts.force && !command.PromptForConfirmation(dockerCli.In(), dockerCli.Out(), warning) { + if !options.force && !command.PromptForConfirmation(dockerCli.In(), dockerCli.Out(), warning) { return } diff --git a/cli/command/node/list.go b/cli/command/node/list.go index e2912b186a98..0c3d7e1abc1a 100644 --- a/cli/command/node/list.go +++ b/cli/command/node/list.go @@ -1,14 +1,13 @@ package node import ( - "golang.org/x/net/context" - "github.com/docker/cli/cli" "github.com/docker/cli/cli/command" "github.com/docker/cli/cli/command/formatter" + "github.com/docker/cli/opts" "github.com/docker/docker/api/types" - "github.com/docker/docker/opts" "github.com/spf13/cobra" + "golang.org/x/net/context" ) type listOptions struct { @@ -18,7 +17,7 @@ type listOptions struct { } func newListCommand(dockerCli command.Cli) *cobra.Command { - opts := listOptions{filter: opts.NewFilterOpt()} + options := listOptions{filter: opts.NewFilterOpt()} cmd := &cobra.Command{ Use: "ls [OPTIONS]", @@ -26,30 +25,30 @@ func newListCommand(dockerCli command.Cli) *cobra.Command { Short: "List nodes in the swarm", Args: cli.NoArgs, RunE: func(cmd *cobra.Command, args []string) error { - return runList(dockerCli, opts) + return runList(dockerCli, options) }, } flags := cmd.Flags() - flags.BoolVarP(&opts.quiet, "quiet", "q", false, "Only display IDs") - flags.StringVar(&opts.format, "format", "", "Pretty-print nodes using a Go template") - flags.VarP(&opts.filter, "filter", "f", "Filter output based on conditions provided") + flags.BoolVarP(&options.quiet, "quiet", "q", false, "Only display IDs") + flags.StringVar(&options.format, "format", "", "Pretty-print nodes using a Go template") + flags.VarP(&options.filter, "filter", "f", "Filter output based on conditions provided") return cmd } -func runList(dockerCli command.Cli, opts listOptions) error { +func runList(dockerCli command.Cli, options listOptions) error { client := dockerCli.Client() ctx := context.Background() nodes, err := client.NodeList( ctx, - types.NodeListOptions{Filters: opts.filter.Value()}) + types.NodeListOptions{Filters: options.filter.Value()}) if err != nil { return err } info := types.Info{} - if len(nodes) > 0 && !opts.quiet { + if len(nodes) > 0 && !options.quiet { // only non-empty nodes and not quiet, should we call /info api info, err = client.Info(ctx) if err != nil { @@ -57,17 +56,17 @@ func runList(dockerCli command.Cli, opts listOptions) error { } } - format := opts.format + format := options.format if len(format) == 0 { format = formatter.TableFormatKey - if len(dockerCli.ConfigFile().NodesFormat) > 0 && !opts.quiet { + if len(dockerCli.ConfigFile().NodesFormat) > 0 && !options.quiet { format = dockerCli.ConfigFile().NodesFormat } } nodesCtx := formatter.Context{ Output: dockerCli.Out(), - Format: formatter.NewNodeFormat(format, opts.quiet), + Format: formatter.NewNodeFormat(format, options.quiet), } return formatter.NodeWrite(nodesCtx, nodes, info) } diff --git a/cli/command/node/opts.go b/cli/command/node/opts.go index 0ad365f0c6f8..484ab5edaf49 100644 --- a/cli/command/node/opts.go +++ b/cli/command/node/opts.go @@ -1,7 +1,7 @@ package node import ( - "github.com/docker/docker/opts" + "github.com/docker/cli/opts" ) type nodeOptions struct { diff --git a/cli/command/node/ps.go b/cli/command/node/ps.go index 37346ce86292..6a586a3bcc62 100644 --- a/cli/command/node/ps.go +++ b/cli/command/node/ps.go @@ -8,9 +8,9 @@ import ( "github.com/docker/cli/cli/command/formatter" "github.com/docker/cli/cli/command/idresolver" "github.com/docker/cli/cli/command/task" + "github.com/docker/cli/opts" "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/swarm" - "github.com/docker/docker/opts" "github.com/pkg/errors" "github.com/spf13/cobra" "golang.org/x/net/context" @@ -26,33 +26,33 @@ type psOptions struct { } func newPsCommand(dockerCli command.Cli) *cobra.Command { - opts := psOptions{filter: opts.NewFilterOpt()} + options := psOptions{filter: opts.NewFilterOpt()} cmd := &cobra.Command{ Use: "ps [OPTIONS] [NODE...]", Short: "List tasks running on one or more nodes, defaults to current node", Args: cli.RequiresMinArgs(0), RunE: func(cmd *cobra.Command, args []string) error { - opts.nodeIDs = []string{"self"} + options.nodeIDs = []string{"self"} if len(args) != 0 { - opts.nodeIDs = args + options.nodeIDs = args } - return runPs(dockerCli, opts) + return runPs(dockerCli, options) }, } flags := cmd.Flags() - flags.BoolVar(&opts.noTrunc, "no-trunc", false, "Do not truncate output") - flags.BoolVar(&opts.noResolve, "no-resolve", false, "Do not map IDs to Names") - flags.VarP(&opts.filter, "filter", "f", "Filter output based on conditions provided") - flags.StringVar(&opts.format, "format", "", "Pretty-print tasks using a Go template") - flags.BoolVarP(&opts.quiet, "quiet", "q", false, "Only display task IDs") + flags.BoolVar(&options.noTrunc, "no-trunc", false, "Do not truncate output") + flags.BoolVar(&options.noResolve, "no-resolve", false, "Do not map IDs to Names") + flags.VarP(&options.filter, "filter", "f", "Filter output based on conditions provided") + flags.StringVar(&options.format, "format", "", "Pretty-print tasks using a Go template") + flags.BoolVarP(&options.quiet, "quiet", "q", false, "Only display task IDs") return cmd } -func runPs(dockerCli command.Cli, opts psOptions) error { +func runPs(dockerCli command.Cli, options psOptions) error { client := dockerCli.Client() ctx := context.Background() @@ -61,7 +61,7 @@ func runPs(dockerCli command.Cli, opts psOptions) error { tasks []swarm.Task ) - for _, nodeID := range opts.nodeIDs { + for _, nodeID := range options.nodeIDs { nodeRef, err := Reference(ctx, client, nodeID) if err != nil { errs = append(errs, err.Error()) @@ -74,7 +74,7 @@ func runPs(dockerCli command.Cli, opts psOptions) error { continue } - filter := opts.filter.Value() + filter := options.filter.Value() filter.Add("node", node.ID) nodeTasks, err := client.TaskList(ctx, types.TaskListOptions{Filters: filter}) @@ -86,9 +86,9 @@ func runPs(dockerCli command.Cli, opts psOptions) error { tasks = append(tasks, nodeTasks...) } - format := opts.format + format := options.format if len(format) == 0 { - if dockerCli.ConfigFile() != nil && len(dockerCli.ConfigFile().TasksFormat) > 0 && !opts.quiet { + if dockerCli.ConfigFile() != nil && len(dockerCli.ConfigFile().TasksFormat) > 0 && !options.quiet { format = dockerCli.ConfigFile().TasksFormat } else { format = formatter.TableFormatKey @@ -96,7 +96,7 @@ func runPs(dockerCli command.Cli, opts psOptions) error { } if len(errs) == 0 || len(tasks) != 0 { - if err := task.Print(ctx, dockerCli, tasks, idresolver.New(client, opts.noResolve), !opts.noTrunc, opts.quiet, format); err != nil { + if err := task.Print(ctx, dockerCli, tasks, idresolver.New(client, options.noResolve), !options.noTrunc, options.quiet, format); err != nil { errs = append(errs, err.Error()) } } diff --git a/cli/command/node/update.go b/cli/command/node/update.go index 0503044da545..4ba5f6d38b48 100644 --- a/cli/command/node/update.go +++ b/cli/command/node/update.go @@ -5,8 +5,8 @@ import ( "github.com/docker/cli/cli" "github.com/docker/cli/cli/command" + "github.com/docker/cli/opts" "github.com/docker/docker/api/types/swarm" - "github.com/docker/docker/opts" runconfigopts "github.com/docker/docker/runconfig/opts" "github.com/pkg/errors" "github.com/spf13/cobra" @@ -19,7 +19,7 @@ var ( ) func newUpdateCommand(dockerCli command.Cli) *cobra.Command { - nodeOpts := newNodeOptions() + options := newNodeOptions() cmd := &cobra.Command{ Use: "update [OPTIONS] NODE", @@ -31,9 +31,9 @@ func newUpdateCommand(dockerCli command.Cli) *cobra.Command { } flags := cmd.Flags() - flags.StringVar(&nodeOpts.role, flagRole, "", `Role of the node ("worker"|"manager")`) - flags.StringVar(&nodeOpts.availability, flagAvailability, "", `Availability of the node ("active"|"pause"|"drain")`) - flags.Var(&nodeOpts.annotations.labels, flagLabelAdd, "Add or update a node label (key=value)") + flags.StringVar(&options.role, flagRole, "", `Role of the node ("worker"|"manager")`) + flags.StringVar(&options.availability, flagAvailability, "", `Availability of the node ("active"|"pause"|"drain")`) + flags.Var(&options.annotations.labels, flagLabelAdd, "Add or update a node label (key=value)") labelKeys := opts.NewListOpts(nil) flags.Var(&labelKeys, flagLabelRemove, "Remove a node label if exists") return cmd diff --git a/cli/command/plugin/list.go b/cli/command/plugin/list.go index 5e9c69d214ed..c4bbeb5b46c6 100644 --- a/cli/command/plugin/list.go +++ b/cli/command/plugin/list.go @@ -4,7 +4,7 @@ import ( "github.com/docker/cli/cli" "github.com/docker/cli/cli/command" "github.com/docker/cli/cli/command/formatter" - "github.com/docker/docker/opts" + "github.com/docker/cli/opts" "github.com/spf13/cobra" "golang.org/x/net/context" ) @@ -17,7 +17,7 @@ type listOptions struct { } func newListCommand(dockerCli *command.DockerCli) *cobra.Command { - opts := listOptions{filter: opts.NewFilterOpt()} + options := listOptions{filter: opts.NewFilterOpt()} cmd := &cobra.Command{ Use: "ls [OPTIONS]", @@ -25,29 +25,29 @@ func newListCommand(dockerCli *command.DockerCli) *cobra.Command { Aliases: []string{"list"}, Args: cli.NoArgs, RunE: func(cmd *cobra.Command, args []string) error { - return runList(dockerCli, opts) + return runList(dockerCli, options) }, } flags := cmd.Flags() - flags.BoolVarP(&opts.quiet, "quiet", "q", false, "Only display plugin IDs") - flags.BoolVar(&opts.noTrunc, "no-trunc", false, "Don't truncate output") - flags.StringVar(&opts.format, "format", "", "Pretty-print plugins using a Go template") - flags.VarP(&opts.filter, "filter", "f", "Provide filter values (e.g. 'enabled=true')") + flags.BoolVarP(&options.quiet, "quiet", "q", false, "Only display plugin IDs") + flags.BoolVar(&options.noTrunc, "no-trunc", false, "Don't truncate output") + flags.StringVar(&options.format, "format", "", "Pretty-print plugins using a Go template") + flags.VarP(&options.filter, "filter", "f", "Provide filter values (e.g. 'enabled=true')") return cmd } -func runList(dockerCli *command.DockerCli, opts listOptions) error { - plugins, err := dockerCli.Client().PluginList(context.Background(), opts.filter.Value()) +func runList(dockerCli *command.DockerCli, options listOptions) error { + plugins, err := dockerCli.Client().PluginList(context.Background(), options.filter.Value()) if err != nil { return err } - format := opts.format + format := options.format if len(format) == 0 { - if len(dockerCli.ConfigFile().PluginsFormat) > 0 && !opts.quiet { + if len(dockerCli.ConfigFile().PluginsFormat) > 0 && !options.quiet { format = dockerCli.ConfigFile().PluginsFormat } else { format = formatter.TableFormatKey @@ -56,8 +56,8 @@ func runList(dockerCli *command.DockerCli, opts listOptions) error { pluginsCtx := formatter.Context{ Output: dockerCli.Out(), - Format: formatter.NewPluginFormat(format, opts.quiet), - Trunc: !opts.noTrunc, + Format: formatter.NewPluginFormat(format, options.quiet), + Trunc: !options.noTrunc, } return formatter.PluginWrite(pluginsCtx, plugins) } diff --git a/cli/command/prune/prune.go b/cli/command/prune/prune.go index b6cb16a5cb2b..e916a8283eec 100644 --- a/cli/command/prune/prune.go +++ b/cli/command/prune/prune.go @@ -6,7 +6,7 @@ import ( "github.com/docker/cli/cli/command/image" "github.com/docker/cli/cli/command/network" "github.com/docker/cli/cli/command/volume" - "github.com/docker/docker/opts" + "github.com/docker/cli/opts" "github.com/spf13/cobra" ) diff --git a/cli/command/registry/search.go b/cli/command/registry/search.go index fa0d75ab2c44..443c9a3fa8cc 100644 --- a/cli/command/registry/search.go +++ b/cli/command/registry/search.go @@ -6,16 +6,15 @@ import ( "strings" "text/tabwriter" - "golang.org/x/net/context" - "github.com/docker/cli/cli" "github.com/docker/cli/cli/command" + "github.com/docker/cli/opts" "github.com/docker/docker/api/types" registrytypes "github.com/docker/docker/api/types/registry" - "github.com/docker/docker/opts" "github.com/docker/docker/pkg/stringutils" "github.com/docker/docker/registry" "github.com/spf13/cobra" + "golang.org/x/net/context" ) type searchOptions struct { @@ -31,26 +30,26 @@ type searchOptions struct { // NewSearchCommand creates a new `docker search` command func NewSearchCommand(dockerCli command.Cli) *cobra.Command { - opts := searchOptions{filter: opts.NewFilterOpt()} + options := searchOptions{filter: opts.NewFilterOpt()} cmd := &cobra.Command{ Use: "search [OPTIONS] TERM", Short: "Search the Docker Hub for images", Args: cli.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { - opts.term = args[0] - return runSearch(dockerCli, opts) + options.term = args[0] + return runSearch(dockerCli, options) }, } flags := cmd.Flags() - flags.BoolVar(&opts.noTrunc, "no-trunc", false, "Don't truncate output") - flags.VarP(&opts.filter, "filter", "f", "Filter output based on conditions provided") - flags.IntVar(&opts.limit, "limit", registry.DefaultSearchLimit, "Max number of search results") + flags.BoolVar(&options.noTrunc, "no-trunc", false, "Don't truncate output") + flags.VarP(&options.filter, "filter", "f", "Filter output based on conditions provided") + flags.IntVar(&options.limit, "limit", registry.DefaultSearchLimit, "Max number of search results") - flags.BoolVar(&opts.automated, "automated", false, "Only show automated builds") - flags.UintVarP(&opts.stars, "stars", "s", 0, "Only displays with at least x stars") + flags.BoolVar(&options.automated, "automated", false, "Only show automated builds") + flags.UintVarP(&options.stars, "stars", "s", 0, "Only displays with at least x stars") flags.MarkDeprecated("automated", "use --filter=is-automated=true instead") flags.MarkDeprecated("stars", "use --filter=stars=3 instead") @@ -58,8 +57,8 @@ func NewSearchCommand(dockerCli command.Cli) *cobra.Command { return cmd } -func runSearch(dockerCli command.Cli, opts searchOptions) error { - indexInfo, err := registry.ParseSearchIndexInfo(opts.term) +func runSearch(dockerCli command.Cli, options searchOptions) error { + indexInfo, err := registry.ParseSearchIndexInfo(options.term) if err != nil { return err } @@ -74,16 +73,16 @@ func runSearch(dockerCli command.Cli, opts searchOptions) error { return err } - options := types.ImageSearchOptions{ + searchOptions := types.ImageSearchOptions{ RegistryAuth: encodedAuth, PrivilegeFunc: requestPrivilege, - Filters: opts.filter.Value(), - Limit: opts.limit, + Filters: options.filter.Value(), + Limit: options.limit, } clnt := dockerCli.Client() - unorderedResults, err := clnt.ImageSearch(ctx, opts.term, options) + unorderedResults, err := clnt.ImageSearch(ctx, options.term, searchOptions) if err != nil { return err } @@ -95,12 +94,12 @@ func runSearch(dockerCli command.Cli, opts searchOptions) error { fmt.Fprintf(w, "NAME\tDESCRIPTION\tSTARS\tOFFICIAL\tAUTOMATED\n") for _, res := range results { // --automated and -s, --stars are deprecated since Docker 1.12 - if (opts.automated && !res.IsAutomated) || (int(opts.stars) > res.StarCount) { + if (options.automated && !res.IsAutomated) || (int(options.stars) > res.StarCount) { continue } desc := strings.Replace(res.Description, "\n", " ", -1) desc = strings.Replace(desc, "\r", " ", -1) - if !opts.noTrunc { + if !options.noTrunc { desc = stringutils.Ellipsis(desc, 45) } fmt.Fprintf(w, "%s\t%s\t%d\t", res.Name, desc, res.StarCount) diff --git a/cli/command/secret/create.go b/cli/command/secret/create.go index b52c73eff506..e708f7c59192 100644 --- a/cli/command/secret/create.go +++ b/cli/command/secret/create.go @@ -7,8 +7,8 @@ import ( "github.com/docker/cli/cli" "github.com/docker/cli/cli/command" + "github.com/docker/cli/opts" "github.com/docker/docker/api/types/swarm" - "github.com/docker/docker/opts" "github.com/docker/docker/pkg/system" runconfigopts "github.com/docker/docker/runconfig/opts" "github.com/pkg/errors" @@ -23,7 +23,7 @@ type createOptions struct { } func newSecretCreateCommand(dockerCli command.Cli) *cobra.Command { - createOpts := createOptions{ + options := createOptions{ labels: opts.NewListOpts(opts.ValidateEnv), } @@ -32,13 +32,13 @@ func newSecretCreateCommand(dockerCli command.Cli) *cobra.Command { Short: "Create a secret from a file or STDIN as content", Args: cli.ExactArgs(2), RunE: func(cmd *cobra.Command, args []string) error { - createOpts.name = args[0] - createOpts.file = args[1] - return runSecretCreate(dockerCli, createOpts) + options.name = args[0] + options.file = args[1] + return runSecretCreate(dockerCli, options) }, } flags := cmd.Flags() - flags.VarP(&createOpts.labels, "label", "l", "Secret labels") + flags.VarP(&options.labels, "label", "l", "Secret labels") return cmd } diff --git a/cli/command/secret/ls.go b/cli/command/secret/ls.go index b63e06f50c07..35bc3da0f21d 100644 --- a/cli/command/secret/ls.go +++ b/cli/command/secret/ls.go @@ -4,8 +4,8 @@ import ( "github.com/docker/cli/cli" "github.com/docker/cli/cli/command" "github.com/docker/cli/cli/command/formatter" + "github.com/docker/cli/opts" "github.com/docker/docker/api/types" - "github.com/docker/docker/opts" "github.com/spf13/cobra" "golang.org/x/net/context" ) @@ -17,7 +17,7 @@ type listOptions struct { } func newSecretListCommand(dockerCli command.Cli) *cobra.Command { - opts := listOptions{filter: opts.NewFilterOpt()} + options := listOptions{filter: opts.NewFilterOpt()} cmd := &cobra.Command{ Use: "ls [OPTIONS]", @@ -25,29 +25,29 @@ func newSecretListCommand(dockerCli command.Cli) *cobra.Command { Short: "List secrets", Args: cli.NoArgs, RunE: func(cmd *cobra.Command, args []string) error { - return runSecretList(dockerCli, opts) + return runSecretList(dockerCli, options) }, } flags := cmd.Flags() - flags.BoolVarP(&opts.quiet, "quiet", "q", false, "Only display IDs") - flags.StringVarP(&opts.format, "format", "", "", "Pretty-print secrets using a Go template") - flags.VarP(&opts.filter, "filter", "f", "Filter output based on conditions provided") + flags.BoolVarP(&options.quiet, "quiet", "q", false, "Only display IDs") + flags.StringVarP(&options.format, "format", "", "", "Pretty-print secrets using a Go template") + flags.VarP(&options.filter, "filter", "f", "Filter output based on conditions provided") return cmd } -func runSecretList(dockerCli command.Cli, opts listOptions) error { +func runSecretList(dockerCli command.Cli, options listOptions) error { client := dockerCli.Client() ctx := context.Background() - secrets, err := client.SecretList(ctx, types.SecretListOptions{Filters: opts.filter.Value()}) + secrets, err := client.SecretList(ctx, types.SecretListOptions{Filters: options.filter.Value()}) if err != nil { return err } - format := opts.format + format := options.format if len(format) == 0 { - if len(dockerCli.ConfigFile().SecretFormat) > 0 && !opts.quiet { + if len(dockerCli.ConfigFile().SecretFormat) > 0 && !options.quiet { format = dockerCli.ConfigFile().SecretFormat } else { format = formatter.TableFormatKey @@ -55,7 +55,7 @@ func runSecretList(dockerCli command.Cli, opts listOptions) error { } secretCtx := formatter.Context{ Output: dockerCli.Out(), - Format: formatter.NewSecretFormat(format, opts.quiet), + Format: formatter.NewSecretFormat(format, options.quiet), } return formatter.SecretWrite(secretCtx, secrets) } diff --git a/cli/command/service/list.go b/cli/command/service/list.go index 338ac9164bd5..5b359d307a96 100644 --- a/cli/command/service/list.go +++ b/cli/command/service/list.go @@ -6,10 +6,10 @@ import ( "github.com/docker/cli/cli" "github.com/docker/cli/cli/command" "github.com/docker/cli/cli/command/formatter" + "github.com/docker/cli/opts" "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/filters" "github.com/docker/docker/api/types/swarm" - "github.com/docker/docker/opts" "github.com/spf13/cobra" "golang.org/x/net/context" ) @@ -21,7 +21,7 @@ type listOptions struct { } func newListCommand(dockerCli *command.DockerCli) *cobra.Command { - opts := listOptions{filter: opts.NewFilterOpt()} + options := listOptions{filter: opts.NewFilterOpt()} cmd := &cobra.Command{ Use: "ls [OPTIONS]", @@ -29,30 +29,30 @@ func newListCommand(dockerCli *command.DockerCli) *cobra.Command { Short: "List services", Args: cli.NoArgs, RunE: func(cmd *cobra.Command, args []string) error { - return runList(dockerCli, opts) + return runList(dockerCli, options) }, } flags := cmd.Flags() - flags.BoolVarP(&opts.quiet, "quiet", "q", false, "Only display IDs") - flags.StringVar(&opts.format, "format", "", "Pretty-print services using a Go template") - flags.VarP(&opts.filter, "filter", "f", "Filter output based on conditions provided") + flags.BoolVarP(&options.quiet, "quiet", "q", false, "Only display IDs") + flags.StringVar(&options.format, "format", "", "Pretty-print services using a Go template") + flags.VarP(&options.filter, "filter", "f", "Filter output based on conditions provided") return cmd } -func runList(dockerCli *command.DockerCli, opts listOptions) error { +func runList(dockerCli *command.DockerCli, options listOptions) error { ctx := context.Background() client := dockerCli.Client() - serviceFilters := opts.filter.Value() + serviceFilters := options.filter.Value() services, err := client.ServiceList(ctx, types.ServiceListOptions{Filters: serviceFilters}) if err != nil { return err } info := map[string]formatter.ServiceListInfo{} - if len(services) > 0 && !opts.quiet { + if len(services) > 0 && !options.quiet { // only non-empty services and not quiet, should we call TaskList and NodeList api taskFilter := filters.NewArgs() for _, service := range services { @@ -72,9 +72,9 @@ func runList(dockerCli *command.DockerCli, opts listOptions) error { info = GetServicesStatus(services, nodes, tasks) } - format := opts.format + format := options.format if len(format) == 0 { - if len(dockerCli.ConfigFile().ServicesFormat) > 0 && !opts.quiet { + if len(dockerCli.ConfigFile().ServicesFormat) > 0 && !options.quiet { format = dockerCli.ConfigFile().ServicesFormat } else { format = formatter.TableFormatKey @@ -83,7 +83,7 @@ func runList(dockerCli *command.DockerCli, opts listOptions) error { servicesCtx := formatter.Context{ Output: dockerCli.Out(), - Format: formatter.NewServiceListFormat(format, opts.quiet), + Format: formatter.NewServiceListFormat(format, options.quiet), } return formatter.ServiceListWrite(servicesCtx, services, info) } diff --git a/cli/command/service/opts.go b/cli/command/service/opts.go index d1742fea4146..e1d328c487bc 100644 --- a/cli/command/service/opts.go +++ b/cli/command/service/opts.go @@ -7,10 +7,10 @@ import ( "strings" "time" + "github.com/docker/cli/opts" "github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/swarm" "github.com/docker/docker/client" - "github.com/docker/docker/opts" runconfigopts "github.com/docker/docker/runconfig/opts" "github.com/docker/swarmkit/api" "github.com/docker/swarmkit/api/defaults" diff --git a/cli/command/service/opts_test.go b/cli/command/service/opts_test.go index 675fbe4b9949..8d814f75c8cb 100644 --- a/cli/command/service/opts_test.go +++ b/cli/command/service/opts_test.go @@ -4,8 +4,8 @@ import ( "testing" "time" + "github.com/docker/cli/opts" "github.com/docker/docker/api/types/container" - "github.com/docker/docker/opts" "github.com/stretchr/testify/assert" ) diff --git a/cli/command/service/ps.go b/cli/command/service/ps.go index 070f1db8ae98..87b29d2043a8 100644 --- a/cli/command/service/ps.go +++ b/cli/command/service/ps.go @@ -3,19 +3,18 @@ package service import ( "strings" - "golang.org/x/net/context" - "github.com/docker/cli/cli" "github.com/docker/cli/cli/command" "github.com/docker/cli/cli/command/formatter" "github.com/docker/cli/cli/command/idresolver" "github.com/docker/cli/cli/command/node" "github.com/docker/cli/cli/command/task" + "github.com/docker/cli/opts" "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/filters" - "github.com/docker/docker/opts" "github.com/pkg/errors" "github.com/spf13/cobra" + "golang.org/x/net/context" ) type psOptions struct { @@ -28,36 +27,36 @@ type psOptions struct { } func newPsCommand(dockerCli command.Cli) *cobra.Command { - opts := psOptions{filter: opts.NewFilterOpt()} + options := psOptions{filter: opts.NewFilterOpt()} cmd := &cobra.Command{ Use: "ps [OPTIONS] SERVICE [SERVICE...]", Short: "List the tasks of one or more services", Args: cli.RequiresMinArgs(1), RunE: func(cmd *cobra.Command, args []string) error { - opts.services = args - return runPS(dockerCli, opts) + options.services = args + return runPS(dockerCli, options) }, } flags := cmd.Flags() - flags.BoolVarP(&opts.quiet, "quiet", "q", false, "Only display task IDs") - flags.BoolVar(&opts.noTrunc, "no-trunc", false, "Do not truncate output") - flags.BoolVar(&opts.noResolve, "no-resolve", false, "Do not map IDs to Names") - flags.StringVar(&opts.format, "format", "", "Pretty-print tasks using a Go template") - flags.VarP(&opts.filter, "filter", "f", "Filter output based on conditions provided") + flags.BoolVarP(&options.quiet, "quiet", "q", false, "Only display task IDs") + flags.BoolVar(&options.noTrunc, "no-trunc", false, "Do not truncate output") + flags.BoolVar(&options.noResolve, "no-resolve", false, "Do not map IDs to Names") + flags.StringVar(&options.format, "format", "", "Pretty-print tasks using a Go template") + flags.VarP(&options.filter, "filter", "f", "Filter output based on conditions provided") return cmd } -func runPS(dockerCli command.Cli, opts psOptions) error { +func runPS(dockerCli command.Cli, options psOptions) error { client := dockerCli.Client() ctx := context.Background() - filter := opts.filter.Value() + filter := options.filter.Value() serviceIDFilter := filters.NewArgs() serviceNameFilter := filters.NewArgs() - for _, service := range opts.services { + for _, service := range options.services { serviceIDFilter.Add("id", service) serviceNameFilter.Add("name", service) } @@ -70,7 +69,7 @@ func runPS(dockerCli command.Cli, opts psOptions) error { return err } - for _, service := range opts.services { + for _, service := range options.services { serviceCount := 0 // Lookup by ID/Prefix for _, serviceEntry := range serviceByIDList { @@ -110,14 +109,14 @@ func runPS(dockerCli command.Cli, opts psOptions) error { return err } - format := opts.format + format := options.format if len(format) == 0 { - if len(dockerCli.ConfigFile().TasksFormat) > 0 && !opts.quiet { + if len(dockerCli.ConfigFile().TasksFormat) > 0 && !options.quiet { format = dockerCli.ConfigFile().TasksFormat } else { format = formatter.TableFormatKey } } - return task.Print(ctx, dockerCli, tasks, idresolver.New(client, opts.noResolve), !opts.noTrunc, opts.quiet, format) + return task.Print(ctx, dockerCli, tasks, idresolver.New(client, options.noResolve), !options.noTrunc, options.quiet, format) } diff --git a/cli/command/service/update.go b/cli/command/service/update.go index 6dba8c8baebe..ef5452fd3cb6 100644 --- a/cli/command/service/update.go +++ b/cli/command/service/update.go @@ -8,13 +8,13 @@ import ( "github.com/docker/cli/cli" "github.com/docker/cli/cli/command" + "github.com/docker/cli/opts" "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/container" mounttypes "github.com/docker/docker/api/types/mount" "github.com/docker/docker/api/types/swarm" "github.com/docker/docker/api/types/versions" "github.com/docker/docker/client" - "github.com/docker/docker/opts" runconfigopts "github.com/docker/docker/runconfig/opts" "github.com/docker/swarmkit/api/defaults" "github.com/pkg/errors" @@ -24,14 +24,14 @@ import ( ) func newUpdateCommand(dockerCli *command.DockerCli) *cobra.Command { - serviceOpts := newServiceOptions() + options := newServiceOptions() cmd := &cobra.Command{ Use: "update [OPTIONS] SERVICE", Short: "Update a service", Args: cli.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { - return runUpdate(dockerCli, cmd.Flags(), serviceOpts, args[0]) + return runUpdate(dockerCli, cmd.Flags(), options, args[0]) }, } @@ -42,7 +42,7 @@ func newUpdateCommand(dockerCli *command.DockerCli) *cobra.Command { flags.SetAnnotation("rollback", "version", []string{"1.25"}) flags.Bool("force", false, "Force update even if no changes require it") flags.SetAnnotation("force", "version", []string{"1.25"}) - addServiceFlags(flags, serviceOpts, nil) + addServiceFlags(flags, options, nil) flags.Var(newListOptsVar(), flagEnvRemove, "Remove an environment variable") flags.Var(newListOptsVar(), flagGroupRemove, "Remove a previously added supplementary user group from the container") @@ -61,39 +61,39 @@ func newUpdateCommand(dockerCli *command.DockerCli) *cobra.Command { flags.SetAnnotation(flagDNSSearchRemove, "version", []string{"1.25"}) flags.Var(newListOptsVar(), flagHostRemove, "Remove a custom host-to-IP mapping (host:ip)") flags.SetAnnotation(flagHostRemove, "version", []string{"1.25"}) - flags.Var(&serviceOpts.labels, flagLabelAdd, "Add or update a service label") - flags.Var(&serviceOpts.containerLabels, flagContainerLabelAdd, "Add or update a container label") - flags.Var(&serviceOpts.env, flagEnvAdd, "Add or update an environment variable") + flags.Var(&options.labels, flagLabelAdd, "Add or update a service label") + flags.Var(&options.containerLabels, flagContainerLabelAdd, "Add or update a container label") + flags.Var(&options.env, flagEnvAdd, "Add or update an environment variable") flags.Var(newListOptsVar(), flagSecretRemove, "Remove a secret") flags.SetAnnotation(flagSecretRemove, "version", []string{"1.25"}) - flags.Var(&serviceOpts.secrets, flagSecretAdd, "Add or update a secret on a service") + flags.Var(&options.secrets, flagSecretAdd, "Add or update a secret on a service") flags.SetAnnotation(flagSecretAdd, "version", []string{"1.25"}) flags.Var(newListOptsVar(), flagConfigRemove, "Remove a configuration file") flags.SetAnnotation(flagConfigRemove, "version", []string{"1.30"}) - flags.Var(&serviceOpts.configs, flagConfigAdd, "Add or update a config file on a service") + flags.Var(&options.configs, flagConfigAdd, "Add or update a config file on a service") flags.SetAnnotation(flagConfigAdd, "version", []string{"1.30"}) - flags.Var(&serviceOpts.mounts, flagMountAdd, "Add or update a mount on a service") - flags.Var(&serviceOpts.constraints, flagConstraintAdd, "Add or update a placement constraint") - flags.Var(&serviceOpts.placementPrefs, flagPlacementPrefAdd, "Add a placement preference") + flags.Var(&options.mounts, flagMountAdd, "Add or update a mount on a service") + flags.Var(&options.constraints, flagConstraintAdd, "Add or update a placement constraint") + flags.Var(&options.placementPrefs, flagPlacementPrefAdd, "Add a placement preference") flags.SetAnnotation(flagPlacementPrefAdd, "version", []string{"1.28"}) flags.Var(&placementPrefOpts{}, flagPlacementPrefRemove, "Remove a placement preference") flags.SetAnnotation(flagPlacementPrefRemove, "version", []string{"1.28"}) - flags.Var(&serviceOpts.networks, flagNetworkAdd, "Add a network") + flags.Var(&options.networks, flagNetworkAdd, "Add a network") flags.SetAnnotation(flagNetworkAdd, "version", []string{"1.29"}) flags.Var(newListOptsVar(), flagNetworkRemove, "Remove a network") flags.SetAnnotation(flagNetworkRemove, "version", []string{"1.29"}) - flags.Var(&serviceOpts.endpoint.publishPorts, flagPublishAdd, "Add or update a published port") - flags.Var(&serviceOpts.groups, flagGroupAdd, "Add an additional supplementary user group to the container") + flags.Var(&options.endpoint.publishPorts, flagPublishAdd, "Add or update a published port") + flags.Var(&options.groups, flagGroupAdd, "Add an additional supplementary user group to the container") flags.SetAnnotation(flagGroupAdd, "version", []string{"1.25"}) - flags.Var(&serviceOpts.dns, flagDNSAdd, "Add or update a custom DNS server") + flags.Var(&options.dns, flagDNSAdd, "Add or update a custom DNS server") flags.SetAnnotation(flagDNSAdd, "version", []string{"1.25"}) - flags.Var(&serviceOpts.dnsOption, flagDNSOptionAdd, "Add or update a DNS option") + flags.Var(&options.dnsOption, flagDNSOptionAdd, "Add or update a DNS option") flags.SetAnnotation(flagDNSOptionAdd, "version", []string{"1.25"}) - flags.Var(&serviceOpts.dnsSearch, flagDNSSearchAdd, "Add or update a custom DNS search domain") + flags.Var(&options.dnsSearch, flagDNSSearchAdd, "Add or update a custom DNS search domain") flags.SetAnnotation(flagDNSSearchAdd, "version", []string{"1.25"}) - flags.Var(&serviceOpts.hosts, flagHostAdd, "Add or update a custom host-to-IP mapping (host:ip)") + flags.Var(&options.hosts, flagHostAdd, "Add or update a custom host-to-IP mapping (host:ip)") flags.SetAnnotation(flagHostAdd, "version", []string{"1.25"}) return cmd @@ -104,7 +104,7 @@ func newListOptsVar() *opts.ListOpts { } // nolint: gocyclo -func runUpdate(dockerCli *command.DockerCli, flags *pflag.FlagSet, opts *serviceOptions, serviceID string) error { +func runUpdate(dockerCli *command.DockerCli, flags *pflag.FlagSet, options *serviceOptions, serviceID string) error { apiClient := dockerCli.Client() ctx := context.Background() @@ -214,7 +214,7 @@ func runUpdate(dockerCli *command.DockerCli, flags *pflag.FlagSet, opts *service fmt.Fprintf(dockerCli.Out(), "%s\n", serviceID) - if opts.detach { + if options.detach { if !flags.Changed("detach") { fmt.Fprintln(dockerCli.Err(), "Since --detach=false was not specified, tasks will be updated in the background.\n"+ "In a future release, --detach=false will become the default.") @@ -222,7 +222,7 @@ func runUpdate(dockerCli *command.DockerCli, flags *pflag.FlagSet, opts *service return nil } - return waitOnService(ctx, dockerCli, serviceID, opts) + return waitOnService(ctx, dockerCli, serviceID, options) } // nolint: gocyclo diff --git a/cli/command/stack/common.go b/cli/command/stack/common.go index 7b3cd3cca882..27d664bdc93c 100644 --- a/cli/command/stack/common.go +++ b/cli/command/stack/common.go @@ -1,14 +1,13 @@ package stack import ( - "golang.org/x/net/context" - "github.com/docker/cli/cli/compose/convert" + "github.com/docker/cli/opts" "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/filters" "github.com/docker/docker/api/types/swarm" "github.com/docker/docker/client" - "github.com/docker/docker/opts" + "golang.org/x/net/context" ) func getStackFilter(namespace string) filters.Args { diff --git a/cli/command/stack/ps.go b/cli/command/stack/ps.go index c5a73d7c9df0..ae9ed0f70a1c 100644 --- a/cli/command/stack/ps.go +++ b/cli/command/stack/ps.go @@ -3,16 +3,15 @@ package stack import ( "fmt" - "golang.org/x/net/context" - "github.com/docker/cli/cli" "github.com/docker/cli/cli/command" "github.com/docker/cli/cli/command/formatter" "github.com/docker/cli/cli/command/idresolver" "github.com/docker/cli/cli/command/task" + "github.com/docker/cli/opts" "github.com/docker/docker/api/types" - "github.com/docker/docker/opts" "github.com/spf13/cobra" + "golang.org/x/net/context" ) type psOptions struct { @@ -25,33 +24,33 @@ type psOptions struct { } func newPsCommand(dockerCli command.Cli) *cobra.Command { - opts := psOptions{filter: opts.NewFilterOpt()} + options := psOptions{filter: opts.NewFilterOpt()} cmd := &cobra.Command{ Use: "ps [OPTIONS] STACK", Short: "List the tasks in the stack", Args: cli.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { - opts.namespace = args[0] - return runPS(dockerCli, opts) + options.namespace = args[0] + return runPS(dockerCli, options) }, } flags := cmd.Flags() - flags.BoolVar(&opts.noTrunc, "no-trunc", false, "Do not truncate output") - flags.BoolVar(&opts.noResolve, "no-resolve", false, "Do not map IDs to Names") - flags.VarP(&opts.filter, "filter", "f", "Filter output based on conditions provided") - flags.BoolVarP(&opts.quiet, "quiet", "q", false, "Only display task IDs") - flags.StringVar(&opts.format, "format", "", "Pretty-print tasks using a Go template") + flags.BoolVar(&options.noTrunc, "no-trunc", false, "Do not truncate output") + flags.BoolVar(&options.noResolve, "no-resolve", false, "Do not map IDs to Names") + flags.VarP(&options.filter, "filter", "f", "Filter output based on conditions provided") + flags.BoolVarP(&options.quiet, "quiet", "q", false, "Only display task IDs") + flags.StringVar(&options.format, "format", "", "Pretty-print tasks using a Go template") return cmd } -func runPS(dockerCli command.Cli, opts psOptions) error { - namespace := opts.namespace +func runPS(dockerCli command.Cli, options psOptions) error { + namespace := options.namespace client := dockerCli.Client() ctx := context.Background() - filter := getStackFilterFromOpt(opts.namespace, opts.filter) + filter := getStackFilterFromOpt(options.namespace, options.filter) tasks, err := client.TaskList(ctx, types.TaskListOptions{Filters: filter}) if err != nil { @@ -63,14 +62,14 @@ func runPS(dockerCli command.Cli, opts psOptions) error { return nil } - format := opts.format + format := options.format if len(format) == 0 { - if len(dockerCli.ConfigFile().TasksFormat) > 0 && !opts.quiet { + if len(dockerCli.ConfigFile().TasksFormat) > 0 && !options.quiet { format = dockerCli.ConfigFile().TasksFormat } else { format = formatter.TableFormatKey } } - return task.Print(ctx, dockerCli, tasks, idresolver.New(client, opts.noResolve), !opts.noTrunc, opts.quiet, format) + return task.Print(ctx, dockerCli, tasks, idresolver.New(client, options.noResolve), !options.noTrunc, options.quiet, format) } diff --git a/cli/command/stack/services.go b/cli/command/stack/services.go index f42f448a6f78..459d7dd5a293 100644 --- a/cli/command/stack/services.go +++ b/cli/command/stack/services.go @@ -3,16 +3,15 @@ package stack import ( "fmt" - "golang.org/x/net/context" - "github.com/docker/cli/cli" "github.com/docker/cli/cli/command" "github.com/docker/cli/cli/command/formatter" "github.com/docker/cli/cli/command/service" + "github.com/docker/cli/opts" "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/filters" - "github.com/docker/docker/opts" "github.com/spf13/cobra" + "golang.org/x/net/context" ) type servicesOptions struct { @@ -23,30 +22,30 @@ type servicesOptions struct { } func newServicesCommand(dockerCli *command.DockerCli) *cobra.Command { - opts := servicesOptions{filter: opts.NewFilterOpt()} + options := servicesOptions{filter: opts.NewFilterOpt()} cmd := &cobra.Command{ Use: "services [OPTIONS] STACK", Short: "List the services in the stack", Args: cli.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { - opts.namespace = args[0] - return runServices(dockerCli, opts) + options.namespace = args[0] + return runServices(dockerCli, options) }, } flags := cmd.Flags() - flags.BoolVarP(&opts.quiet, "quiet", "q", false, "Only display IDs") - flags.StringVar(&opts.format, "format", "", "Pretty-print services using a Go template") - flags.VarP(&opts.filter, "filter", "f", "Filter output based on conditions provided") + flags.BoolVarP(&options.quiet, "quiet", "q", false, "Only display IDs") + flags.StringVar(&options.format, "format", "", "Pretty-print services using a Go template") + flags.VarP(&options.filter, "filter", "f", "Filter output based on conditions provided") return cmd } -func runServices(dockerCli *command.DockerCli, opts servicesOptions) error { +func runServices(dockerCli *command.DockerCli, options servicesOptions) error { ctx := context.Background() client := dockerCli.Client() - filter := getStackFilterFromOpt(opts.namespace, opts.filter) + filter := getStackFilterFromOpt(options.namespace, options.filter) services, err := client.ServiceList(ctx, types.ServiceListOptions{Filters: filter}) if err != nil { return err @@ -56,12 +55,12 @@ func runServices(dockerCli *command.DockerCli, opts servicesOptions) error { // if no services in this stack, print message and exit 0 if len(services) == 0 { - fmt.Fprintf(out, "Nothing found in stack: %s\n", opts.namespace) + fmt.Fprintf(out, "Nothing found in stack: %s\n", options.namespace) return nil } info := map[string]formatter.ServiceListInfo{} - if !opts.quiet { + if !options.quiet { taskFilter := filters.NewArgs() for _, service := range services { taskFilter.Add("service", service.ID) @@ -80,9 +79,9 @@ func runServices(dockerCli *command.DockerCli, opts servicesOptions) error { info = service.GetServicesStatus(services, nodes, tasks) } - format := opts.format + format := options.format if len(format) == 0 { - if len(dockerCli.ConfigFile().ServicesFormat) > 0 && !opts.quiet { + if len(dockerCli.ConfigFile().ServicesFormat) > 0 && !options.quiet { format = dockerCli.ConfigFile().ServicesFormat } else { format = formatter.TableFormatKey @@ -91,7 +90,7 @@ func runServices(dockerCli *command.DockerCli, opts servicesOptions) error { servicesCtx := formatter.Context{ Output: dockerCli.Out(), - Format: formatter.NewServiceListFormat(format, opts.quiet), + Format: formatter.NewServiceListFormat(format, options.quiet), } return formatter.ServiceListWrite(servicesCtx, services, info) } diff --git a/cli/command/swarm/opts.go b/cli/command/swarm/opts.go index d2097050af86..868605e6d02e 100644 --- a/cli/command/swarm/opts.go +++ b/cli/command/swarm/opts.go @@ -8,8 +8,8 @@ import ( "strings" "time" + "github.com/docker/cli/opts" "github.com/docker/docker/api/types/swarm" - "github.com/docker/docker/opts" "github.com/pkg/errors" "github.com/spf13/pflag" ) diff --git a/cli/command/system/events.go b/cli/command/system/events.go index 17378d1e4cb1..ba83d26fa340 100644 --- a/cli/command/system/events.go +++ b/cli/command/system/events.go @@ -9,16 +9,15 @@ import ( "text/template" "time" - "golang.org/x/net/context" - "github.com/docker/cli/cli" "github.com/docker/cli/cli/command" + "github.com/docker/cli/opts" "github.com/docker/docker/api/types" eventtypes "github.com/docker/docker/api/types/events" - "github.com/docker/docker/opts" "github.com/docker/docker/pkg/jsonlog" "github.com/docker/docker/pkg/templates" "github.com/spf13/cobra" + "golang.org/x/net/context" ) type eventsOptions struct { @@ -30,41 +29,41 @@ type eventsOptions struct { // NewEventsCommand creates a new cobra.Command for `docker events` func NewEventsCommand(dockerCli *command.DockerCli) *cobra.Command { - opts := eventsOptions{filter: opts.NewFilterOpt()} + options := eventsOptions{filter: opts.NewFilterOpt()} cmd := &cobra.Command{ Use: "events [OPTIONS]", Short: "Get real time events from the server", Args: cli.NoArgs, RunE: func(cmd *cobra.Command, args []string) error { - return runEvents(dockerCli, &opts) + return runEvents(dockerCli, &options) }, } flags := cmd.Flags() - flags.StringVar(&opts.since, "since", "", "Show all events created since timestamp") - flags.StringVar(&opts.until, "until", "", "Stream events until this timestamp") - flags.VarP(&opts.filter, "filter", "f", "Filter output based on conditions provided") - flags.StringVar(&opts.format, "format", "", "Format the output using the given Go template") + flags.StringVar(&options.since, "since", "", "Show all events created since timestamp") + flags.StringVar(&options.until, "until", "", "Stream events until this timestamp") + flags.VarP(&options.filter, "filter", "f", "Filter output based on conditions provided") + flags.StringVar(&options.format, "format", "", "Format the output using the given Go template") return cmd } -func runEvents(dockerCli *command.DockerCli, opts *eventsOptions) error { - tmpl, err := makeTemplate(opts.format) +func runEvents(dockerCli *command.DockerCli, options *eventsOptions) error { + tmpl, err := makeTemplate(options.format) if err != nil { return cli.StatusError{ StatusCode: 64, Status: "Error parsing format: " + err.Error()} } - options := types.EventsOptions{ - Since: opts.since, - Until: opts.until, - Filters: opts.filter.Value(), + eventOptions := types.EventsOptions{ + Since: options.since, + Until: options.until, + Filters: options.filter.Value(), } ctx, cancel := context.WithCancel(context.Background()) - events, errs := dockerCli.Client().Events(ctx, options) + events, errs := dockerCli.Client().Events(ctx, eventOptions) defer cancel() out := dockerCli.Out() diff --git a/cli/command/system/prune.go b/cli/command/system/prune.go index dcc5d722175a..7b77a3be6278 100644 --- a/cli/command/system/prune.go +++ b/cli/command/system/prune.go @@ -6,7 +6,7 @@ import ( "github.com/docker/cli/cli" "github.com/docker/cli/cli/command" "github.com/docker/cli/cli/command/prune" - "github.com/docker/docker/opts" + "github.com/docker/cli/opts" units "github.com/docker/go-units" "github.com/spf13/cobra" ) @@ -19,22 +19,22 @@ type pruneOptions struct { // NewPruneCommand creates a new cobra.Command for `docker prune` func NewPruneCommand(dockerCli command.Cli) *cobra.Command { - opts := pruneOptions{filter: opts.NewFilterOpt()} + options := pruneOptions{filter: opts.NewFilterOpt()} cmd := &cobra.Command{ Use: "prune [OPTIONS]", Short: "Remove unused data", Args: cli.NoArgs, RunE: func(cmd *cobra.Command, args []string) error { - return runPrune(dockerCli, opts) + return runPrune(dockerCli, options) }, Tags: map[string]string{"version": "1.25"}, } flags := cmd.Flags() - flags.BoolVarP(&opts.force, "force", "f", false, "Do not prompt for confirmation") - flags.BoolVarP(&opts.all, "all", "a", false, "Remove all unused images not just dangling ones") - flags.Var(&opts.filter, "filter", "Provide filter values (e.g. 'until=')") + flags.BoolVarP(&options.force, "force", "f", false, "Do not prompt for confirmation") + flags.BoolVarP(&options.all, "all", "a", false, "Remove all unused images not just dangling ones") + flags.Var(&options.filter, "filter", "Provide filter values (e.g. 'until=')") return cmd } diff --git a/cli/command/volume/create.go b/cli/command/volume/create.go index 3b756227c183..bf7f1a72b66c 100644 --- a/cli/command/volume/create.go +++ b/cli/command/volume/create.go @@ -5,8 +5,8 @@ import ( "github.com/docker/cli/cli" "github.com/docker/cli/cli/command" + "github.com/docker/cli/opts" volumetypes "github.com/docker/docker/api/types/volume" - "github.com/docker/docker/opts" runconfigopts "github.com/docker/docker/runconfig/opts" "github.com/pkg/errors" "github.com/spf13/cobra" @@ -21,7 +21,7 @@ type createOptions struct { } func newCreateCommand(dockerCli command.Cli) *cobra.Command { - opts := createOptions{ + options := createOptions{ driverOpts: *opts.NewMapOpts(nil, nil), labels: opts.NewListOpts(opts.ValidateEnv), } @@ -32,32 +32,32 @@ func newCreateCommand(dockerCli command.Cli) *cobra.Command { Args: cli.RequiresMaxArgs(1), RunE: func(cmd *cobra.Command, args []string) error { if len(args) == 1 { - if opts.name != "" { + if options.name != "" { return errors.Errorf("Conflicting options: either specify --name or provide positional arg, not both\n") } - opts.name = args[0] + options.name = args[0] } - return runCreate(dockerCli, opts) + return runCreate(dockerCli, options) }, } flags := cmd.Flags() - flags.StringVarP(&opts.driver, "driver", "d", "local", "Specify volume driver name") - flags.StringVar(&opts.name, "name", "", "Specify volume name") + flags.StringVarP(&options.driver, "driver", "d", "local", "Specify volume driver name") + flags.StringVar(&options.name, "name", "", "Specify volume name") flags.Lookup("name").Hidden = true - flags.VarP(&opts.driverOpts, "opt", "o", "Set driver specific options") - flags.Var(&opts.labels, "label", "Set metadata for a volume") + flags.VarP(&options.driverOpts, "opt", "o", "Set driver specific options") + flags.Var(&options.labels, "label", "Set metadata for a volume") return cmd } -func runCreate(dockerCli command.Cli, opts createOptions) error { +func runCreate(dockerCli command.Cli, options createOptions) error { client := dockerCli.Client() volReq := volumetypes.VolumesCreateBody{ - Driver: opts.driver, - DriverOpts: opts.driverOpts.GetAll(), - Name: opts.name, - Labels: runconfigopts.ConvertKVStringsToMap(opts.labels.GetAll()), + Driver: options.driver, + DriverOpts: options.driverOpts.GetAll(), + Name: options.name, + Labels: runconfigopts.ConvertKVStringsToMap(options.labels.GetAll()), } vol, err := client.VolumeCreate(context.Background(), volReq) diff --git a/cli/command/volume/list.go b/cli/command/volume/list.go index 63dae8402daa..d9b5ef80e3da 100644 --- a/cli/command/volume/list.go +++ b/cli/command/volume/list.go @@ -6,8 +6,8 @@ import ( "github.com/docker/cli/cli" "github.com/docker/cli/cli/command" "github.com/docker/cli/cli/command/formatter" + "github.com/docker/cli/opts" "github.com/docker/docker/api/types" - "github.com/docker/docker/opts" "github.com/spf13/cobra" "golang.org/x/net/context" ) @@ -27,7 +27,7 @@ type listOptions struct { } func newListCommand(dockerCli command.Cli) *cobra.Command { - opts := listOptions{filter: opts.NewFilterOpt()} + options := listOptions{filter: opts.NewFilterOpt()} cmd := &cobra.Command{ Use: "ls [OPTIONS]", @@ -35,28 +35,28 @@ func newListCommand(dockerCli command.Cli) *cobra.Command { Short: "List volumes", Args: cli.NoArgs, RunE: func(cmd *cobra.Command, args []string) error { - return runList(dockerCli, opts) + return runList(dockerCli, options) }, } flags := cmd.Flags() - flags.BoolVarP(&opts.quiet, "quiet", "q", false, "Only display volume names") - flags.StringVar(&opts.format, "format", "", "Pretty-print volumes using a Go template") - flags.VarP(&opts.filter, "filter", "f", "Provide filter values (e.g. 'dangling=true')") + flags.BoolVarP(&options.quiet, "quiet", "q", false, "Only display volume names") + flags.StringVar(&options.format, "format", "", "Pretty-print volumes using a Go template") + flags.VarP(&options.filter, "filter", "f", "Provide filter values (e.g. 'dangling=true')") return cmd } -func runList(dockerCli command.Cli, opts listOptions) error { +func runList(dockerCli command.Cli, options listOptions) error { client := dockerCli.Client() - volumes, err := client.VolumeList(context.Background(), opts.filter.Value()) + volumes, err := client.VolumeList(context.Background(), options.filter.Value()) if err != nil { return err } - format := opts.format + format := options.format if len(format) == 0 { - if len(dockerCli.ConfigFile().VolumesFormat) > 0 && !opts.quiet { + if len(dockerCli.ConfigFile().VolumesFormat) > 0 && !options.quiet { format = dockerCli.ConfigFile().VolumesFormat } else { format = formatter.TableFormatKey @@ -67,7 +67,7 @@ func runList(dockerCli command.Cli, opts listOptions) error { volumeCtx := formatter.Context{ Output: dockerCli.Out(), - Format: formatter.NewVolumeFormat(format, opts.quiet), + Format: formatter.NewVolumeFormat(format, options.quiet), } return formatter.VolumeWrite(volumeCtx, volumes.Volumes) } diff --git a/cli/command/volume/prune.go b/cli/command/volume/prune.go index 77a81b9284ca..157f883b07a5 100644 --- a/cli/command/volume/prune.go +++ b/cli/command/volume/prune.go @@ -5,7 +5,7 @@ import ( "github.com/docker/cli/cli" "github.com/docker/cli/cli/command" - "github.com/docker/docker/opts" + "github.com/docker/cli/opts" units "github.com/docker/go-units" "github.com/spf13/cobra" "golang.org/x/net/context" @@ -18,14 +18,14 @@ type pruneOptions struct { // NewPruneCommand returns a new cobra prune command for volumes func NewPruneCommand(dockerCli command.Cli) *cobra.Command { - opts := pruneOptions{filter: opts.NewFilterOpt()} + options := pruneOptions{filter: opts.NewFilterOpt()} cmd := &cobra.Command{ Use: "prune [OPTIONS]", Short: "Remove all unused volumes", Args: cli.NoArgs, RunE: func(cmd *cobra.Command, args []string) error { - spaceReclaimed, output, err := runPrune(dockerCli, opts) + spaceReclaimed, output, err := runPrune(dockerCli, options) if err != nil { return err } @@ -39,8 +39,8 @@ func NewPruneCommand(dockerCli command.Cli) *cobra.Command { } flags := cmd.Flags() - flags.BoolVarP(&opts.force, "force", "f", false, "Do not prompt for confirmation") - flags.Var(&opts.filter, "filter", "Provide filter values (e.g. 'label=