Skip to content

Commit 8b725e1

Browse files
committed
Clean some stuff from runconfig that are cli only…
… 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 <[email protected]>
1 parent a58827b commit 8b725e1

11 files changed

Lines changed: 626 additions & 0 deletions

File tree

opts/env.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package opts
2+
3+
import (
4+
"fmt"
5+
"os"
6+
"runtime"
7+
"strings"
8+
)
9+
10+
// ValidateEnv validates an environment variable and returns it.
11+
// If no value is specified, it returns the current value using os.Getenv.
12+
//
13+
// As on ParseEnvFile and related to #16585, environment variable names
14+
// are not validate what so ever, it's up to application inside docker
15+
// to validate them or not.
16+
//
17+
// The only validation here is to check if name is empty, per #25099
18+
func ValidateEnv(val string) (string, error) {
19+
arr := strings.Split(val, "=")
20+
if arr[0] == "" {
21+
return "", fmt.Errorf("invalid environment variable: %s", val)
22+
}
23+
if len(arr) > 1 {
24+
return val, nil
25+
}
26+
if !doesEnvExist(val) {
27+
return val, nil
28+
}
29+
return fmt.Sprintf("%s=%s", val, os.Getenv(val)), nil
30+
}
31+
32+
func doesEnvExist(name string) bool {
33+
for _, entry := range os.Environ() {
34+
parts := strings.SplitN(entry, "=", 2)
35+
if runtime.GOOS == "windows" {
36+
// Environment variable are case-insensitive on Windows. PaTh, path and PATH are equivalent.
37+
if strings.EqualFold(parts[0], name) {
38+
return true
39+
}
40+
}
41+
if parts[0] == name {
42+
return true
43+
}
44+
}
45+
return false
46+
}

opts/env_test.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package opts
2+
3+
import (
4+
"fmt"
5+
"os"
6+
"runtime"
7+
"testing"
8+
)
9+
10+
func TestValidateEnv(t *testing.T) {
11+
valids := map[string]string{
12+
"a": "a",
13+
"something": "something",
14+
"_=a": "_=a",
15+
"env1=value1": "env1=value1",
16+
"_env1=value1": "_env1=value1",
17+
"env2=value2=value3": "env2=value2=value3",
18+
"env3=abc!qwe": "env3=abc!qwe",
19+
"env_4=value 4": "env_4=value 4",
20+
"PATH": fmt.Sprintf("PATH=%v", os.Getenv("PATH")),
21+
"PATH=something": "PATH=something",
22+
"asd!qwe": "asd!qwe",
23+
"1asd": "1asd",
24+
"123": "123",
25+
"some space": "some space",
26+
" some space before": " some space before",
27+
"some space after ": "some space after ",
28+
}
29+
// Environment variables are case in-sensitive on Windows
30+
if runtime.GOOS == "windows" {
31+
valids["PaTh"] = fmt.Sprintf("PaTh=%v", os.Getenv("PATH"))
32+
}
33+
for value, expected := range valids {
34+
actual, err := ValidateEnv(value)
35+
if err != nil {
36+
t.Fatal(err)
37+
}
38+
if actual != expected {
39+
t.Fatalf("Expected [%v], got [%v]", expected, actual)
40+
}
41+
}
42+
}

opts/hosts.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,3 +149,17 @@ func ParseTCPAddr(tryAddr string, defaultAddr string) (string, error) {
149149

150150
return fmt.Sprintf("tcp://%s%s", net.JoinHostPort(host, port), u.Path), nil
151151
}
152+
153+
// ValidateExtraHost validates that the specified string is a valid extrahost and returns it.
154+
// ExtraHost is in the form of name:ip where the ip has to be a valid ip (IPv4 or IPv6).
155+
func ValidateExtraHost(val string) (string, error) {
156+
// allow for IPv6 addresses in extra hosts by only splitting on first ":"
157+
arr := strings.SplitN(val, ":", 2)
158+
if len(arr) != 2 || len(arr[0]) == 0 {
159+
return "", fmt.Errorf("bad format for add-host: %q", val)
160+
}
161+
if _, err := ValidateIPAddress(arr[1]); err != nil {
162+
return "", fmt.Errorf("invalid IP address in add-host: %q", arr[1])
163+
}
164+
return val, nil
165+
}

opts/hosts_test.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package opts
22

33
import (
44
"fmt"
5+
"strings"
56
"testing"
67
)
78

@@ -146,3 +147,35 @@ func TestParseInvalidUnixAddrInvalid(t *testing.T) {
146147
t.Fatalf("Expected an %v, got %v", v, "unix:///var/run/docker.sock")
147148
}
148149
}
150+
151+
func TestValidateExtraHosts(t *testing.T) {
152+
valid := []string{
153+
`myhost:192.168.0.1`,
154+
`thathost:10.0.2.1`,
155+
`anipv6host:2003:ab34:e::1`,
156+
`ipv6local:::1`,
157+
}
158+
159+
invalid := map[string]string{
160+
`myhost:192.notanipaddress.1`: `invalid IP`,
161+
`thathost-nosemicolon10.0.0.1`: `bad format`,
162+
`anipv6host:::::1`: `invalid IP`,
163+
`ipv6local:::0::`: `invalid IP`,
164+
}
165+
166+
for _, extrahost := range valid {
167+
if _, err := ValidateExtraHost(extrahost); err != nil {
168+
t.Fatalf("ValidateExtraHost(`"+extrahost+"`) should succeed: error %v", err)
169+
}
170+
}
171+
172+
for extraHost, expectedError := range invalid {
173+
if _, err := ValidateExtraHost(extraHost); err == nil {
174+
t.Fatalf("ValidateExtraHost(`%q`) should have failed validation", extraHost)
175+
} else {
176+
if !strings.Contains(err.Error(), expectedError) {
177+
t.Fatalf("ValidateExtraHost(`%q`) error should contain %q", extraHost, expectedError)
178+
}
179+
}
180+
}
181+
}

opts/opts.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"fmt"
55
"math/big"
66
"net"
7+
"path"
78
"regexp"
89
"strings"
910

@@ -231,6 +232,15 @@ func ValidateIPAddress(val string) (string, error) {
231232
return "", fmt.Errorf("%s is not an ip address", val)
232233
}
233234

235+
// ValidateMACAddress validates a MAC address.
236+
func ValidateMACAddress(val string) (string, error) {
237+
_, err := net.ParseMAC(strings.TrimSpace(val))
238+
if err != nil {
239+
return "", err
240+
}
241+
return val, nil
242+
}
243+
234244
// ValidateDNSSearch validates domain for resolvconf search configuration.
235245
// A zero length domain is represented by a dot (.).
236246
func ValidateDNSSearch(val string) (string, error) {
@@ -364,3 +374,31 @@ func ParseCPUs(value string) (int64, error) {
364374
}
365375
return nano.Num().Int64(), nil
366376
}
377+
378+
// ParseLink parses and validates the specified string as a link format (name:alias)
379+
func ParseLink(val string) (string, string, error) {
380+
if val == "" {
381+
return "", "", fmt.Errorf("empty string specified for links")
382+
}
383+
arr := strings.Split(val, ":")
384+
if len(arr) > 2 {
385+
return "", "", fmt.Errorf("bad format for links: %s", val)
386+
}
387+
if len(arr) == 1 {
388+
return val, val, nil
389+
}
390+
// This is kept because we can actually get a HostConfig with links
391+
// from an already created container and the format is not `foo:bar`
392+
// but `/foo:/c1/bar`
393+
if strings.HasPrefix(arr[0], "/") {
394+
_, alias := path.Split(arr[1])
395+
return arr[0][1:], alias, nil
396+
}
397+
return arr[0], arr[1], nil
398+
}
399+
400+
// ValidateLink validates that the specified string has a valid link format (containerName:alias).
401+
func ValidateLink(val string) (string, error) {
402+
_, _, err := ParseLink(val)
403+
return val, err
404+
}

opts/opts_test.go

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,3 +230,78 @@ func TestNamedMapOpts(t *testing.T) {
230230
t.Errorf("expected map-size to be in the values, got %v", tmpMap)
231231
}
232232
}
233+
234+
func TestValidateMACAddress(t *testing.T) {
235+
if _, err := ValidateMACAddress(`92:d0:c6:0a:29:33`); err != nil {
236+
t.Fatalf("ValidateMACAddress(`92:d0:c6:0a:29:33`) got %s", err)
237+
}
238+
239+
if _, err := ValidateMACAddress(`92:d0:c6:0a:33`); err == nil {
240+
t.Fatalf("ValidateMACAddress(`92:d0:c6:0a:33`) succeeded; expected failure on invalid MAC")
241+
}
242+
243+
if _, err := ValidateMACAddress(`random invalid string`); err == nil {
244+
t.Fatalf("ValidateMACAddress(`random invalid string`) succeeded; expected failure on invalid MAC")
245+
}
246+
}
247+
248+
func TestValidateLink(t *testing.T) {
249+
valid := []string{
250+
"name",
251+
"dcdfbe62ecd0:alias",
252+
"7a67485460b7642516a4ad82ecefe7f57d0c4916f530561b71a50a3f9c4e33da",
253+
"angry_torvalds:linus",
254+
}
255+
invalid := map[string]string{
256+
"": "empty string specified for links",
257+
"too:much:of:it": "bad format for links: too:much:of:it",
258+
}
259+
260+
for _, link := range valid {
261+
if _, err := ValidateLink(link); err != nil {
262+
t.Fatalf("ValidateLink(`%q`) should succeed: error %q", link, err)
263+
}
264+
}
265+
266+
for link, expectedError := range invalid {
267+
if _, err := ValidateLink(link); err == nil {
268+
t.Fatalf("ValidateLink(`%q`) should have failed validation", link)
269+
} else {
270+
if !strings.Contains(err.Error(), expectedError) {
271+
t.Fatalf("ValidateLink(`%q`) error should contain %q", link, expectedError)
272+
}
273+
}
274+
}
275+
}
276+
277+
func TestParseLink(t *testing.T) {
278+
name, alias, err := ParseLink("name:alias")
279+
if err != nil {
280+
t.Fatalf("Expected not to error out on a valid name:alias format but got: %v", err)
281+
}
282+
if name != "name" {
283+
t.Fatalf("Link name should have been name, got %s instead", name)
284+
}
285+
if alias != "alias" {
286+
t.Fatalf("Link alias should have been alias, got %s instead", alias)
287+
}
288+
// short format definition
289+
name, alias, err = ParseLink("name")
290+
if err != nil {
291+
t.Fatalf("Expected not to error out on a valid name only format but got: %v", err)
292+
}
293+
if name != "name" {
294+
t.Fatalf("Link name should have been name, got %s instead", name)
295+
}
296+
if alias != "name" {
297+
t.Fatalf("Link alias should have been name, got %s instead", alias)
298+
}
299+
// empty string link definition is not allowed
300+
if _, _, err := ParseLink(""); err == nil || !strings.Contains(err.Error(), "empty string specified for links") {
301+
t.Fatalf("Expected error 'empty string specified for links' but got: %v", err)
302+
}
303+
// more than two colons are not allowed
304+
if _, _, err := ParseLink("link:alias:wrong"); err == nil || !strings.Contains(err.Error(), "bad format for links: link:alias:wrong") {
305+
t.Fatalf("Expected error 'bad format for links: link:alias:wrong' but got: %v", err)
306+
}
307+
}

opts/runtime.go

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
package opts
2+
3+
import (
4+
"fmt"
5+
"strings"
6+
7+
"github.com/docker/docker/api/types"
8+
)
9+
10+
// RuntimeOpt defines a map of Runtimes
11+
type RuntimeOpt struct {
12+
name string
13+
stockRuntimeName string
14+
values *map[string]types.Runtime
15+
}
16+
17+
// NewNamedRuntimeOpt creates a new RuntimeOpt
18+
func NewNamedRuntimeOpt(name string, ref *map[string]types.Runtime, stockRuntime string) *RuntimeOpt {
19+
if ref == nil {
20+
ref = &map[string]types.Runtime{}
21+
}
22+
return &RuntimeOpt{name: name, values: ref, stockRuntimeName: stockRuntime}
23+
}
24+
25+
// Name returns the name of the NamedListOpts in the configuration.
26+
func (o *RuntimeOpt) Name() string {
27+
return o.name
28+
}
29+
30+
// Set validates and updates the list of Runtimes
31+
func (o *RuntimeOpt) Set(val string) error {
32+
parts := strings.SplitN(val, "=", 2)
33+
if len(parts) != 2 {
34+
return fmt.Errorf("invalid runtime argument: %s", val)
35+
}
36+
37+
parts[0] = strings.TrimSpace(parts[0])
38+
parts[1] = strings.TrimSpace(parts[1])
39+
if parts[0] == "" || parts[1] == "" {
40+
return fmt.Errorf("invalid runtime argument: %s", val)
41+
}
42+
43+
parts[0] = strings.ToLower(parts[0])
44+
if parts[0] == o.stockRuntimeName {
45+
return fmt.Errorf("runtime name '%s' is reserved", o.stockRuntimeName)
46+
}
47+
48+
if _, ok := (*o.values)[parts[0]]; ok {
49+
return fmt.Errorf("runtime '%s' was already defined", parts[0])
50+
}
51+
52+
(*o.values)[parts[0]] = types.Runtime{Path: parts[1]}
53+
54+
return nil
55+
}
56+
57+
// String returns Runtime values as a string.
58+
func (o *RuntimeOpt) String() string {
59+
var out []string
60+
for k := range *o.values {
61+
out = append(out, k)
62+
}
63+
64+
return fmt.Sprintf("%v", out)
65+
}
66+
67+
// GetMap returns a map of Runtimes (name: path)
68+
func (o *RuntimeOpt) GetMap() map[string]types.Runtime {
69+
if o.values != nil {
70+
return *o.values
71+
}
72+
73+
return map[string]types.Runtime{}
74+
}
75+
76+
// Type returns the type of the option
77+
func (o *RuntimeOpt) Type() string {
78+
return "runtime"
79+
}

0 commit comments

Comments
 (0)