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 <vincent@sbr.pm>
master
Vincent Demeester 2016-12-23 20:09:12 +01:00
parent a58827b0c2
commit 8b725e10e7
11 changed files with 626 additions and 0 deletions

46
opts/env.go Normal file
View File

@ -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
}

42
opts/env_test.go Normal file
View File

@ -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)
}
}
}

View File

@ -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
}

View File

@ -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)
}
}
}
}

View File

@ -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
}

View File

@ -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)
}
}

79
opts/runtime.go Normal file
View File

@ -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"
}

111
opts/throttledevice.go Normal file
View File

@ -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 <device-path>:<number>[<unit>]. 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 <device-path>:<number>[<unit>]. 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 <device-path>:<number>. Number must be a positive integer", val)
}
if rate < 0 {
return nil, fmt.Errorf("invalid rate for device: %s. The correct format is <device-path>:<number>. 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"
}

57
opts/ulimit.go Normal file
View File

@ -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"
}

42
opts/ulimit_test.go Normal file
View File

@ -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)
}
}

89
opts/weightdevice.go Normal file
View File

@ -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"
}