add better generate
Signed-off-by: Jess Frazelle <acidburn@microsoft.com>
This commit is contained in:
parent
3fc6abf56b
commit
cdd93563f5
5655 changed files with 1187011 additions and 392 deletions
98
vendor/github.com/docker/docker-ce/components/cli/opts/config.go
generated
vendored
Normal file
98
vendor/github.com/docker/docker-ce/components/cli/opts/config.go
generated
vendored
Normal file
|
@ -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
|
||||
}
|
64
vendor/github.com/docker/docker-ce/components/cli/opts/duration.go
generated
vendored
Normal file
64
vendor/github.com/docker/docker-ce/components/cli/opts/duration.go
generated
vendored
Normal file
|
@ -0,0 +1,64 @@
|
|||
package opts
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// PositiveDurationOpt is an option type for time.Duration that uses a pointer.
|
||||
// It behave similarly to DurationOpt but only allows positive duration values.
|
||||
type PositiveDurationOpt struct {
|
||||
DurationOpt
|
||||
}
|
||||
|
||||
// Set a new value on the option. Setting a negative duration value will cause
|
||||
// an error to be returned.
|
||||
func (d *PositiveDurationOpt) Set(s string) error {
|
||||
err := d.DurationOpt.Set(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if *d.DurationOpt.value < 0 {
|
||||
return errors.Errorf("duration cannot be negative")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DurationOpt is an option type for time.Duration that uses a pointer. This
|
||||
// allows us to get nil values outside, instead of defaulting to 0
|
||||
type DurationOpt struct {
|
||||
value *time.Duration
|
||||
}
|
||||
|
||||
// NewDurationOpt creates a DurationOpt with the specified duration
|
||||
func NewDurationOpt(value *time.Duration) *DurationOpt {
|
||||
return &DurationOpt{
|
||||
value: value,
|
||||
}
|
||||
}
|
||||
|
||||
// Set a new value on the option
|
||||
func (d *DurationOpt) Set(s string) error {
|
||||
v, err := time.ParseDuration(s)
|
||||
d.value = &v
|
||||
return err
|
||||
}
|
||||
|
||||
// Type returns the type of this option, which will be displayed in `--help` output
|
||||
func (d *DurationOpt) Type() string {
|
||||
return "duration"
|
||||
}
|
||||
|
||||
// String returns a string repr of this option
|
||||
func (d *DurationOpt) String() string {
|
||||
if d.value != nil {
|
||||
return d.value.String()
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// Value returns the time.Duration
|
||||
func (d *DurationOpt) Value() *time.Duration {
|
||||
return d.value
|
||||
}
|
30
vendor/github.com/docker/docker-ce/components/cli/opts/duration_test.go
generated
vendored
Normal file
30
vendor/github.com/docker/docker-ce/components/cli/opts/duration_test.go
generated
vendored
Normal file
|
@ -0,0 +1,30 @@
|
|||
package opts
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/gotestyourself/gotestyourself/assert"
|
||||
is "github.com/gotestyourself/gotestyourself/assert/cmp"
|
||||
)
|
||||
|
||||
func TestDurationOptString(t *testing.T) {
|
||||
dur := time.Duration(300 * 10e8)
|
||||
duration := DurationOpt{value: &dur}
|
||||
assert.Check(t, is.Equal("5m0s", duration.String()))
|
||||
}
|
||||
|
||||
func TestDurationOptSetAndValue(t *testing.T) {
|
||||
var duration DurationOpt
|
||||
assert.NilError(t, duration.Set("300s"))
|
||||
assert.Check(t, is.Equal(time.Duration(300*10e8), *duration.Value()))
|
||||
assert.NilError(t, duration.Set("-300s"))
|
||||
assert.Check(t, is.Equal(time.Duration(-300*10e8), *duration.Value()))
|
||||
}
|
||||
|
||||
func TestPositiveDurationOptSetAndValue(t *testing.T) {
|
||||
var duration PositiveDurationOpt
|
||||
assert.NilError(t, duration.Set("300s"))
|
||||
assert.Check(t, is.Equal(time.Duration(300*10e8), *duration.Value()))
|
||||
assert.Error(t, duration.Set("-300s"), "duration cannot be negative")
|
||||
}
|
46
vendor/github.com/docker/docker-ce/components/cli/opts/env.go
generated
vendored
Normal file
46
vendor/github.com/docker/docker-ce/components/cli/opts/env.go
generated
vendored
Normal 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
vendor/github.com/docker/docker-ce/components/cli/opts/env_test.go
generated
vendored
Normal file
42
vendor/github.com/docker/docker-ce/components/cli/opts/env_test.go
generated
vendored
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
22
vendor/github.com/docker/docker-ce/components/cli/opts/envfile.go
generated
vendored
Normal file
22
vendor/github.com/docker/docker-ce/components/cli/opts/envfile.go
generated
vendored
Normal file
|
@ -0,0 +1,22 @@
|
|||
package opts
|
||||
|
||||
import (
|
||||
"os"
|
||||
)
|
||||
|
||||
// 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) {
|
||||
return parseKeyValueFile(filename, os.Getenv)
|
||||
}
|
141
vendor/github.com/docker/docker-ce/components/cli/opts/envfile_test.go
generated
vendored
Normal file
141
vendor/github.com/docker/docker-ce/components/cli/opts/envfile_test.go
generated
vendored
Normal file
|
@ -0,0 +1,141 @@
|
|||
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 expectedLines")
|
||||
}
|
||||
}
|
||||
|
||||
// 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 an ErrBadKey, got nothing")
|
||||
}
|
||||
if _, ok := err.(ErrBadKey); !ok {
|
||||
t.Fatalf("Expected an ErrBadKey, 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 an ErrBadKey, got nothing")
|
||||
}
|
||||
if _, ok := err.(ErrBadKey); !ok {
|
||||
t.Fatalf("Expected an ErrBadKey, 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())
|
||||
}
|
||||
}
|
71
vendor/github.com/docker/docker-ce/components/cli/opts/file.go
generated
vendored
Normal file
71
vendor/github.com/docker/docker-ce/components/cli/opts/file.go
generated
vendored
Normal file
|
@ -0,0 +1,71 @@
|
|||
package opts
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
var whiteSpaces = " \t"
|
||||
|
||||
// ErrBadKey typed error for bad environment variable
|
||||
type ErrBadKey struct {
|
||||
msg string
|
||||
}
|
||||
|
||||
func (e ErrBadKey) Error() string {
|
||||
return fmt.Sprintf("poorly formatted environment: %s", e.msg)
|
||||
}
|
||||
|
||||
func parseKeyValueFile(filename string, emptyFn func(string) string) ([]string, error) {
|
||||
fh, err := os.Open(filename)
|
||||
if err != nil {
|
||||
return []string{}, err
|
||||
}
|
||||
defer fh.Close()
|
||||
|
||||
lines := []string{}
|
||||
scanner := bufio.NewScanner(fh)
|
||||
currentLine := 0
|
||||
utf8bom := []byte{0xEF, 0xBB, 0xBF}
|
||||
for scanner.Scan() {
|
||||
scannedBytes := scanner.Bytes()
|
||||
if !utf8.Valid(scannedBytes) {
|
||||
return []string{}, fmt.Errorf("env file %s contains invalid utf8 bytes at line %d: %v", filename, currentLine+1, scannedBytes)
|
||||
}
|
||||
// We trim UTF8 BOM
|
||||
if currentLine == 0 {
|
||||
scannedBytes = bytes.TrimPrefix(scannedBytes, utf8bom)
|
||||
}
|
||||
// trim the line from all leading whitespace first
|
||||
line := strings.TrimLeftFunc(string(scannedBytes), unicode.IsSpace)
|
||||
currentLine++
|
||||
// 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{}, ErrBadKey{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 {
|
||||
var value string
|
||||
if emptyFn != nil {
|
||||
value = emptyFn(line)
|
||||
}
|
||||
// if only a pass-through variable is given, clean it up.
|
||||
lines = append(lines, fmt.Sprintf("%s=%s", strings.TrimSpace(line), value))
|
||||
}
|
||||
}
|
||||
}
|
||||
return lines, scanner.Err()
|
||||
}
|
165
vendor/github.com/docker/docker-ce/components/cli/opts/hosts.go
generated
vendored
Normal file
165
vendor/github.com/docker/docker-ce/components/cli/opts/hosts.go
generated
vendored
Normal file
|
@ -0,0 +1,165 @@
|
|||
package opts
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
// 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
|
||||
// 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)
|
||||
// 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) {
|
||||
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
|
||||
return val, nil
|
||||
}
|
||||
|
||||
// ParseHost and set defaults for a Daemon host string
|
||||
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, this may return one of the global Default* strings defined in hosts.go.
|
||||
func parseDockerDaemonHost(addr string) (string, error) {
|
||||
addrParts := strings.SplitN(addr, "://", 2)
|
||||
if len(addrParts) == 1 && addrParts[0] != "" {
|
||||
addrParts = []string{"tcp", addrParts[0]}
|
||||
}
|
||||
|
||||
switch addrParts[0] {
|
||||
case "tcp":
|
||||
return ParseTCPAddr(addrParts[1], DefaultTCPHost)
|
||||
case "unix":
|
||||
return parseSimpleProtoAddr("unix", addrParts[1], DefaultUnixSocket)
|
||||
case "npipe":
|
||||
return parseSimpleProtoAddr("npipe", addrParts[1], DefaultNamedPipe)
|
||||
case "fd":
|
||||
return addr, nil
|
||||
default:
|
||||
return "", fmt.Errorf("Invalid bind address format: %s", addr)
|
||||
}
|
||||
}
|
||||
|
||||
// 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 %s: %s", proto, addr)
|
||||
}
|
||||
if addr == "" {
|
||||
addr = defaultAddr
|
||||
}
|
||||
return fmt.Sprintf("%s://%s", proto, 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 {
|
||||
// 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)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
181
vendor/github.com/docker/docker-ce/components/cli/opts/hosts_test.go
generated
vendored
Normal file
181
vendor/github.com/docker/docker-ce/components/cli/opts/hosts_test.go
generated
vendored
Normal file
|
@ -0,0 +1,181 @@
|
|||
package opts
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestParseHost(t *testing.T) {
|
||||
invalid := []string{
|
||||
"something with spaces",
|
||||
"://",
|
||||
"unknown://",
|
||||
"tcp://:port",
|
||||
"tcp://invalid:port",
|
||||
}
|
||||
|
||||
valid := map[string]string{
|
||||
"": DefaultHost,
|
||||
" ": DefaultHost,
|
||||
" ": DefaultHost,
|
||||
"fd://": "fd://",
|
||||
"fd://something": "fd://something",
|
||||
"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://" + DefaultUnixSocket,
|
||||
"unix://path/to/socket": "unix://path/to/socket",
|
||||
"npipe://": "npipe://" + DefaultNamedPipe,
|
||||
"npipe:////./pipe/foo": "npipe:////./pipe/foo",
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseDockerDaemonHost(t *testing.T) {
|
||||
invalids := map[string]string{
|
||||
|
||||
"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 proto, expected tcp: unix:///run/docker.sock",
|
||||
" tcp://:7777/path ": "Invalid bind address format: tcp://:7777/path ",
|
||||
"": "Invalid bind address format: ",
|
||||
}
|
||||
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": 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://" + DefaultUnixSocket,
|
||||
"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(invalidAddr); err == nil || err.Error() != expectedError {
|
||||
t.Errorf("tcp %v address expected error %q return, got %q and addr %v", invalidAddr, expectedError, err, addr)
|
||||
}
|
||||
}
|
||||
for validAddr, expectedAddr := range valids {
|
||||
if addr, err := parseDockerDaemonHost(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{
|
||||
"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 := 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 := 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 := 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")
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
8
vendor/github.com/docker/docker-ce/components/cli/opts/hosts_unix.go
generated
vendored
Normal file
8
vendor/github.com/docker/docker-ce/components/cli/opts/hosts_unix.go
generated
vendored
Normal file
|
@ -0,0 +1,8 @@
|
|||
// +build !windows
|
||||
|
||||
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)
|
6
vendor/github.com/docker/docker-ce/components/cli/opts/hosts_windows.go
generated
vendored
Normal file
6
vendor/github.com/docker/docker-ce/components/cli/opts/hosts_windows.go
generated
vendored
Normal file
|
@ -0,0 +1,6 @@
|
|||
// +build windows
|
||||
|
||||
package opts
|
||||
|
||||
// DefaultHost constant defines the default host string used by docker on Windows
|
||||
var DefaultHost = "npipe://" + DefaultNamedPipe
|
47
vendor/github.com/docker/docker-ce/components/cli/opts/ip.go
generated
vendored
Normal file
47
vendor/github.com/docker/docker-ce/components/cli/opts/ip.go
generated
vendored
Normal file
|
@ -0,0 +1,47 @@
|
|||
package opts
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
)
|
||||
|
||||
// IPOpt holds an IP. It is used to store values from CLI flags.
|
||||
type IPOpt struct {
|
||||
*net.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,
|
||||
}
|
||||
o.Set(defaultVal)
|
||||
return o
|
||||
}
|
||||
|
||||
// Set sets an IPv4 or IPv6 address from a given string. If the given
|
||||
// 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 {
|
||||
return fmt.Errorf("%s is not an ip address", val)
|
||||
}
|
||||
*o.IP = ip
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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 {
|
||||
return ""
|
||||
}
|
||||
return o.IP.String()
|
||||
}
|
||||
|
||||
// Type returns the type of the option
|
||||
func (o *IPOpt) Type() string {
|
||||
return "ip"
|
||||
}
|
54
vendor/github.com/docker/docker-ce/components/cli/opts/ip_test.go
generated
vendored
Normal file
54
vendor/github.com/docker/docker-ce/components/cli/opts/ip_test.go
generated
vendored
Normal file
|
@ -0,0 +1,54 @@
|
|||
package opts
|
||||
|
||||
import (
|
||||
"net"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestIpOptString(t *testing.T) {
|
||||
addresses := []string{"", "0.0.0.0"}
|
||||
var ip net.IP
|
||||
|
||||
for _, address := range addresses {
|
||||
stringAddress := NewIPOpt(&ip, address).String()
|
||||
if stringAddress != address {
|
||||
t.Fatalf("IpOpt string should be `%s`, not `%s`", address, stringAddress)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewIpOptInvalidDefaultVal(t *testing.T) {
|
||||
ip := net.IPv4(127, 0, 0, 1)
|
||||
defaultVal := "Not an ip"
|
||||
|
||||
ipOpt := NewIPOpt(&ip, defaultVal)
|
||||
|
||||
expected := "127.0.0.1"
|
||||
if ipOpt.String() != expected {
|
||||
t.Fatalf("Expected [%v], got [%v]", expected, ipOpt.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewIpOptValidDefaultVal(t *testing.T) {
|
||||
ip := net.IPv4(127, 0, 0, 1)
|
||||
defaultVal := "192.168.1.1"
|
||||
|
||||
ipOpt := NewIPOpt(&ip, defaultVal)
|
||||
|
||||
expected := "192.168.1.1"
|
||||
if ipOpt.String() != expected {
|
||||
t.Fatalf("Expected [%v], got [%v]", expected, ipOpt.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestIpOptSetInvalidVal(t *testing.T) {
|
||||
ip := net.IPv4(127, 0, 0, 1)
|
||||
ipOpt := &IPOpt{IP: &ip}
|
||||
|
||||
invalidIP := "invalid ip"
|
||||
expectedError := "invalid ip is not an ip address"
|
||||
err := ipOpt.Set(invalidIP)
|
||||
if err == nil || err.Error() != expectedError {
|
||||
t.Fatalf("Expected an Error with [%v], got [%v]", expectedError, err.Error())
|
||||
}
|
||||
}
|
174
vendor/github.com/docker/docker-ce/components/cli/opts/mount.go
generated
vendored
Normal file
174
vendor/github.com/docker/docker-ce/components/cli/opts/mount.go
generated
vendored
Normal file
|
@ -0,0 +1,174 @@
|
|||
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
|
||||
type MountOpt struct {
|
||||
values []mounttypes.Mount
|
||||
}
|
||||
|
||||
// Set a new mount value
|
||||
// nolint: gocyclo
|
||||
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
|
||||
}
|
||||
|
||||
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 {
|
||||
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 "consistency":
|
||||
mount.Consistency = mounttypes.Consistency(strings.ToLower(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 volume-nocopy: %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)
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
if mount.Type == "" {
|
||||
return fmt.Errorf("type is required")
|
||||
}
|
||||
|
||||
if mount.Target == "" {
|
||||
return fmt.Errorf("target is required")
|
||||
}
|
||||
|
||||
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.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)
|
||||
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
|
||||
}
|
185
vendor/github.com/docker/docker-ce/components/cli/opts/mount_test.go
generated
vendored
Normal file
185
vendor/github.com/docker/docker-ce/components/cli/opts/mount_test.go
generated
vendored
Normal file
|
@ -0,0 +1,185 @@
|
|||
package opts
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
mounttypes "github.com/docker/docker/api/types/mount"
|
||||
"github.com/gotestyourself/gotestyourself/assert"
|
||||
is "github.com/gotestyourself/gotestyourself/assert/cmp"
|
||||
)
|
||||
|
||||
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.Check(t, is.Equal(expected, mount.String()))
|
||||
}
|
||||
|
||||
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.Assert(t, is.Len(mounts, 1))
|
||||
assert.Check(t, is.DeepEqual(mounttypes.Mount{
|
||||
Type: mounttypes.TypeBind,
|
||||
Source: "/source",
|
||||
Target: "/target",
|
||||
}, mounts[0]))
|
||||
}
|
||||
}
|
||||
|
||||
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.Assert(t, is.Len(mounts, 1))
|
||||
assert.Check(t, is.DeepEqual(mounttypes.Mount{
|
||||
Type: mounttypes.TypeVolume,
|
||||
Source: "/source",
|
||||
Target: "/target",
|
||||
}, mounts[0]))
|
||||
}
|
||||
}
|
||||
|
||||
// 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.Check(t, is.Equal(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")
|
||||
}
|
||||
|
||||
func TestMountOptSetErrorInvalidKey(t *testing.T) {
|
||||
var mount MountOpt
|
||||
assert.Error(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' 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")
|
||||
}
|
||||
|
||||
func TestMountOptDefaultEnableReadOnly(t *testing.T) {
|
||||
var m MountOpt
|
||||
assert.NilError(t, m.Set("type=bind,target=/foo,source=/foo"))
|
||||
assert.Check(t, !m.values[0].ReadOnly)
|
||||
|
||||
m = MountOpt{}
|
||||
assert.NilError(t, m.Set("type=bind,target=/foo,source=/foo,readonly"))
|
||||
assert.Check(t, m.values[0].ReadOnly)
|
||||
|
||||
m = MountOpt{}
|
||||
assert.NilError(t, m.Set("type=bind,target=/foo,source=/foo,readonly=1"))
|
||||
assert.Check(t, m.values[0].ReadOnly)
|
||||
|
||||
m = MountOpt{}
|
||||
assert.NilError(t, m.Set("type=bind,target=/foo,source=/foo,readonly=true"))
|
||||
assert.Check(t, m.values[0].ReadOnly)
|
||||
|
||||
m = MountOpt{}
|
||||
assert.NilError(t, m.Set("type=bind,target=/foo,source=/foo,readonly=0"))
|
||||
assert.Check(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.Check(t, is.Equal("", m.values[0].Source))
|
||||
|
||||
m = MountOpt{}
|
||||
assert.NilError(t, m.Set("type=volume,target=/foo,source=foo"))
|
||||
assert.Check(t, m.values[0].VolumeOptions == nil)
|
||||
|
||||
m = MountOpt{}
|
||||
assert.NilError(t, m.Set("type=volume,target=/foo,source=foo,volume-nocopy=true"))
|
||||
assert.Check(t, m.values[0].VolumeOptions != nil)
|
||||
assert.Check(t, m.values[0].VolumeOptions.NoCopy)
|
||||
|
||||
m = MountOpt{}
|
||||
assert.NilError(t, m.Set("type=volume,target=/foo,source=foo,volume-nocopy"))
|
||||
assert.Check(t, m.values[0].VolumeOptions != nil)
|
||||
assert.Check(t, m.values[0].VolumeOptions.NoCopy)
|
||||
|
||||
m = MountOpt{}
|
||||
assert.NilError(t, m.Set("type=volume,target=/foo,source=foo,volume-nocopy=1"))
|
||||
assert.Check(t, m.values[0].VolumeOptions != nil)
|
||||
assert.Check(t, m.values[0].VolumeOptions.NoCopy)
|
||||
}
|
||||
|
||||
func TestMountOptTypeConflict(t *testing.T) {
|
||||
var m MountOpt
|
||||
assert.ErrorContains(t, m.Set("type=bind,target=/foo,source=/foo,volume-nocopy=true"), "cannot mix")
|
||||
assert.ErrorContains(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.Assert(t, is.Len(mounts, 1))
|
||||
assert.Check(t, is.DeepEqual(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.ErrorContains(t, m.Set("type=tmpfs,target=/foo,tmpfs-size=foo"), "invalid value for tmpfs-size")
|
||||
assert.ErrorContains(t, m.Set("type=tmpfs,target=/foo,tmpfs-mode=foo"), "invalid value for tmpfs-mode")
|
||||
assert.ErrorContains(t, m.Set("type=tmpfs"), "target is required")
|
||||
}
|
106
vendor/github.com/docker/docker-ce/components/cli/opts/network.go
generated
vendored
Normal file
106
vendor/github.com/docker/docker-ce/components/cli/opts/network.go
generated
vendored
Normal file
|
@ -0,0 +1,106 @@
|
|||
package opts
|
||||
|
||||
import (
|
||||
"encoding/csv"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
networkOptName = "name"
|
||||
networkOptAlias = "alias"
|
||||
driverOpt = "driver-opt"
|
||||
)
|
||||
|
||||
// NetworkAttachmentOpts represents the network options for endpoint creation
|
||||
type NetworkAttachmentOpts struct {
|
||||
Target string
|
||||
Aliases []string
|
||||
DriverOpts map[string]string
|
||||
}
|
||||
|
||||
// NetworkOpt represents a network config in swarm mode.
|
||||
type NetworkOpt struct {
|
||||
options []NetworkAttachmentOpts
|
||||
}
|
||||
|
||||
// Set networkopts value
|
||||
func (n *NetworkOpt) Set(value string) error {
|
||||
longSyntax, err := regexp.MatchString(`\w+=\w+(,\w+=\w+)*`, value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var netOpt NetworkAttachmentOpts
|
||||
if longSyntax {
|
||||
csvReader := csv.NewReader(strings.NewReader(value))
|
||||
fields, err := csvReader.Read()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
netOpt.Aliases = []string{}
|
||||
for _, field := range fields {
|
||||
parts := strings.SplitN(field, "=", 2)
|
||||
|
||||
if len(parts) < 2 {
|
||||
return fmt.Errorf("invalid field %s", field)
|
||||
}
|
||||
|
||||
key := strings.TrimSpace(strings.ToLower(parts[0]))
|
||||
value := strings.TrimSpace(strings.ToLower(parts[1]))
|
||||
|
||||
switch key {
|
||||
case networkOptName:
|
||||
netOpt.Target = value
|
||||
case networkOptAlias:
|
||||
netOpt.Aliases = append(netOpt.Aliases, value)
|
||||
case driverOpt:
|
||||
key, value, err = parseDriverOpt(value)
|
||||
if err == nil {
|
||||
if netOpt.DriverOpts == nil {
|
||||
netOpt.DriverOpts = make(map[string]string)
|
||||
}
|
||||
netOpt.DriverOpts[key] = value
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("invalid field key %s", key)
|
||||
}
|
||||
}
|
||||
if len(netOpt.Target) == 0 {
|
||||
return fmt.Errorf("network name/id is not specified")
|
||||
}
|
||||
} else {
|
||||
netOpt.Target = value
|
||||
}
|
||||
n.options = append(n.options, netOpt)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Type returns the type of this option
|
||||
func (n *NetworkOpt) Type() string {
|
||||
return "network"
|
||||
}
|
||||
|
||||
// Value returns the networkopts
|
||||
func (n *NetworkOpt) Value() []NetworkAttachmentOpts {
|
||||
return n.options
|
||||
}
|
||||
|
||||
// String returns the network opts as a string
|
||||
func (n *NetworkOpt) String() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func parseDriverOpt(driverOpt string) (string, string, error) {
|
||||
parts := strings.SplitN(driverOpt, "=", 2)
|
||||
if len(parts) != 2 {
|
||||
return "", "", fmt.Errorf("invalid key value pair format in driver options")
|
||||
}
|
||||
key := strings.TrimSpace(strings.ToLower(parts[0]))
|
||||
value := strings.TrimSpace(strings.ToLower(parts[1]))
|
||||
return key, value, nil
|
||||
}
|
100
vendor/github.com/docker/docker-ce/components/cli/opts/network_test.go
generated
vendored
Normal file
100
vendor/github.com/docker/docker-ce/components/cli/opts/network_test.go
generated
vendored
Normal file
|
@ -0,0 +1,100 @@
|
|||
package opts
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/gotestyourself/gotestyourself/assert"
|
||||
is "github.com/gotestyourself/gotestyourself/assert/cmp"
|
||||
)
|
||||
|
||||
func TestNetworkOptLegacySyntax(t *testing.T) {
|
||||
testCases := []struct {
|
||||
value string
|
||||
expected []NetworkAttachmentOpts
|
||||
}{
|
||||
{
|
||||
value: "docknet1",
|
||||
expected: []NetworkAttachmentOpts{
|
||||
{
|
||||
Target: "docknet1",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
var network NetworkOpt
|
||||
assert.NilError(t, network.Set(tc.value))
|
||||
assert.Check(t, is.DeepEqual(tc.expected, network.Value()))
|
||||
}
|
||||
}
|
||||
|
||||
func TestNetworkOptCompleteSyntax(t *testing.T) {
|
||||
testCases := []struct {
|
||||
value string
|
||||
expected []NetworkAttachmentOpts
|
||||
}{
|
||||
{
|
||||
value: "name=docknet1,alias=web,driver-opt=field1=value1",
|
||||
expected: []NetworkAttachmentOpts{
|
||||
{
|
||||
Target: "docknet1",
|
||||
Aliases: []string{"web"},
|
||||
DriverOpts: map[string]string{
|
||||
"field1": "value1",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
value: "name=docknet1,alias=web1,alias=web2,driver-opt=field1=value1,driver-opt=field2=value2",
|
||||
expected: []NetworkAttachmentOpts{
|
||||
{
|
||||
Target: "docknet1",
|
||||
Aliases: []string{"web1", "web2"},
|
||||
DriverOpts: map[string]string{
|
||||
"field1": "value1",
|
||||
"field2": "value2",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
value: "name=docknet1",
|
||||
expected: []NetworkAttachmentOpts{
|
||||
{
|
||||
Target: "docknet1",
|
||||
Aliases: []string{},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
var network NetworkOpt
|
||||
assert.NilError(t, network.Set(tc.value))
|
||||
assert.Check(t, is.DeepEqual(tc.expected, network.Value()))
|
||||
}
|
||||
}
|
||||
|
||||
func TestNetworkOptInvalidSyntax(t *testing.T) {
|
||||
testCases := []struct {
|
||||
value string
|
||||
expectedError string
|
||||
}{
|
||||
{
|
||||
value: "invalidField=docknet1",
|
||||
expectedError: "invalid field",
|
||||
},
|
||||
{
|
||||
value: "network=docknet1,invalid=web",
|
||||
expectedError: "invalid field",
|
||||
},
|
||||
{
|
||||
value: "driver-opt=field1=value1,driver-opt=field2=value2",
|
||||
expectedError: "network name/id is not specified",
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
var network NetworkOpt
|
||||
assert.ErrorContains(t, network.Set(tc.value), tc.expectedError)
|
||||
}
|
||||
}
|
488
vendor/github.com/docker/docker-ce/components/cli/opts/opts.go
generated
vendored
Normal file
488
vendor/github.com/docker/docker-ce/components/cli/opts/opts.go
generated
vendored
Normal file
|
@ -0,0 +1,488 @@
|
|||
package opts
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/big"
|
||||
"net"
|
||||
"path"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
units "github.com/docker/go-units"
|
||||
)
|
||||
|
||||
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*$`)
|
||||
)
|
||||
|
||||
// ListOpts holds a list of values and a validation function.
|
||||
type ListOpts struct {
|
||||
values *[]string
|
||||
validator ValidatorFctType
|
||||
}
|
||||
|
||||
// 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,
|
||||
validator: validator,
|
||||
}
|
||||
}
|
||||
|
||||
func (opts *ListOpts) String() string {
|
||||
if len(*opts.values) == 0 {
|
||||
return ""
|
||||
}
|
||||
return fmt.Sprintf("%v", *opts.values)
|
||||
}
|
||||
|
||||
// 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 {
|
||||
v, err := opts.validator(value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
value = v
|
||||
}
|
||||
(*opts.values) = append((*opts.values), value)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Delete removes the specified 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.
|
||||
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 of slice.
|
||||
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 {
|
||||
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))
|
||||
}
|
||||
|
||||
// Type returns a string name for this Option type
|
||||
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 {
|
||||
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
|
||||
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)
|
||||
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
|
||||
}
|
||||
|
||||
// GetAll returns the values of MapOpts as a map.
|
||||
func (opts *MapOpts) GetAll() map[string]string {
|
||||
return opts.values
|
||||
}
|
||||
|
||||
func (opts *MapOpts) String() string {
|
||||
return fmt.Sprintf("%v", 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 {
|
||||
values = make(map[string]string)
|
||||
}
|
||||
return &MapOpts{
|
||||
values: values,
|
||||
validator: validator,
|
||||
}
|
||||
}
|
||||
|
||||
// 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)
|
||||
|
||||
// ValidatorFctListType defines a validator function that returns a validated list of string and/or an error
|
||||
type ValidatorFctListType func(val string) ([]string, error)
|
||||
|
||||
// ValidateIPAddress validates an Ip address.
|
||||
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 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) {
|
||||
if val = strings.Trim(val, " "); val == "." {
|
||||
return val, nil
|
||||
}
|
||||
return validateDomain(val)
|
||||
}
|
||||
|
||||
func validateDomain(val string) (string, error) {
|
||||
if alphaRegexp.FindString(val) == "" {
|
||||
return "", fmt.Errorf("%s is not a valid domain", val)
|
||||
}
|
||||
ns := domainRegexp.FindSubmatch([]byte(val))
|
||||
if len(ns) > 0 && len(ns[1]) < 255 {
|
||||
return string(ns[1]), nil
|
||||
}
|
||||
return "", fmt.Errorf("%s is not a valid domain", val)
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
return val, nil
|
||||
}
|
||||
|
||||
// ValidateSysctl validates a 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)
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// 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 {
|
||||
if *c == 0 {
|
||||
return ""
|
||||
}
|
||||
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 {
|
||||
cpus, err := ParseCPUs(value)
|
||||
*c = NanoCPUs(cpus)
|
||||
return err
|
||||
}
|
||||
|
||||
// Type returns the type
|
||||
func (c *NanoCPUs) Type() string {
|
||||
return "decimal"
|
||||
}
|
||||
|
||||
// Value returns the value in int64
|
||||
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
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// 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 {
|
||||
// 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
|
||||
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
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
308
vendor/github.com/docker/docker-ce/components/cli/opts/opts_test.go
generated
vendored
Normal file
308
vendor/github.com/docker/docker-ce/components/cli/opts/opts_test.go
generated
vendored
Normal file
|
@ -0,0 +1,308 @@
|
|||
package opts
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
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(`::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(`random invalid string`); err == nil || ret != "" {
|
||||
t.Fatalf("ValidateIPAddress(`random invalid string`) got %s %s", ret, err)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestMapOpts(t *testing.T) {
|
||||
tmpMap := make(map[string]string)
|
||||
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())
|
||||
}
|
||||
|
||||
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.Error("validator is not being called")
|
||||
}
|
||||
}
|
||||
|
||||
func TestListOptsWithoutValidator(t *testing.T) {
|
||||
o := NewListOpts(nil)
|
||||
o.Set("foo")
|
||||
if o.String() != "[foo]" {
|
||||
t.Errorf("%s != [foo]", o.String())
|
||||
}
|
||||
o.Set("bar")
|
||||
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")
|
||||
}
|
||||
if o.Get("baz") {
|
||||
t.Error("o.Get(\"baz\") == true")
|
||||
}
|
||||
o.Delete("foo")
|
||||
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 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())
|
||||
}
|
||||
}
|
||||
|
||||
// nolint: lll
|
||||
func TestValidateDNSSearch(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`,
|
||||
`foo.bar.baz.this.should.fail.on.long.name.because.it.is.longer.thanisshouldbethis.should.fail.on.long.name.because.it.is.longer.thanisshouldbethis.should.fail.on.long.name.because.it.is.longer.thanisshouldbethis.should.fail.on.long.name.because.it.is.longer.thanisshouldbe`,
|
||||
}
|
||||
|
||||
for _, domain := range valid {
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 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])
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
6
vendor/github.com/docker/docker-ce/components/cli/opts/opts_unix.go
generated
vendored
Normal file
6
vendor/github.com/docker/docker-ce/components/cli/opts/opts_unix.go
generated
vendored
Normal file
|
@ -0,0 +1,6 @@
|
|||
// +build !windows
|
||||
|
||||
package opts
|
||||
|
||||
// DefaultHTTPHost Default HTTP Host used if only port is provided to -H flag e.g. dockerd -H tcp://:8080
|
||||
const DefaultHTTPHost = "localhost"
|
56
vendor/github.com/docker/docker-ce/components/cli/opts/opts_windows.go
generated
vendored
Normal file
56
vendor/github.com/docker/docker-ce/components/cli/opts/opts_windows.go
generated
vendored
Normal file
|
@ -0,0 +1,56 @@
|
|||
package opts
|
||||
|
||||
// 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 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
|
||||
// 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. 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 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 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. dockerd -H tcp://:8080
|
||||
const DefaultHTTPHost = "127.0.0.1"
|
99
vendor/github.com/docker/docker-ce/components/cli/opts/parse.go
generated
vendored
Normal file
99
vendor/github.com/docker/docker-ce/components/cli/opts/parse.go
generated
vendored
Normal file
|
@ -0,0 +1,99 @@
|
|||
package opts
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/docker/api/types/container"
|
||||
)
|
||||
|
||||
// ReadKVStrings reads a file of line terminated key=value pairs, and overrides any keys
|
||||
// present in the file with additional pairs specified in the override parameter
|
||||
func ReadKVStrings(files []string, override []string) ([]string, error) {
|
||||
return readKVStrings(files, override, nil)
|
||||
}
|
||||
|
||||
// ReadKVEnvStrings reads a file of line terminated key=value pairs, and overrides any keys
|
||||
// present in the file with additional pairs specified in the override parameter.
|
||||
// If a key has no value, it will get the value from the environment.
|
||||
func ReadKVEnvStrings(files []string, override []string) ([]string, error) {
|
||||
return readKVStrings(files, override, os.Getenv)
|
||||
}
|
||||
|
||||
func readKVStrings(files []string, override []string, emptyFn func(string) string) ([]string, error) {
|
||||
variables := []string{}
|
||||
for _, ef := range files {
|
||||
parsedVars, err := parseKeyValueFile(ef, emptyFn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
variables = append(variables, parsedVars...)
|
||||
}
|
||||
// parse the '-e' and '--env' after, to allow override
|
||||
variables = append(variables, override...)
|
||||
|
||||
return variables, nil
|
||||
}
|
||||
|
||||
// ConvertKVStringsToMap converts ["key=value"] to {"key":"value"}
|
||||
func ConvertKVStringsToMap(values []string) map[string]string {
|
||||
result := make(map[string]string, len(values))
|
||||
for _, value := range values {
|
||||
kv := strings.SplitN(value, "=", 2)
|
||||
if len(kv) == 1 {
|
||||
result[kv[0]] = ""
|
||||
} else {
|
||||
result[kv[0]] = kv[1]
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// ConvertKVStringsToMapWithNil converts ["key=value"] to {"key":"value"}
|
||||
// but set unset keys to nil - meaning the ones with no "=" in them.
|
||||
// We use this in cases where we need to distinguish between
|
||||
// FOO= and FOO
|
||||
// where the latter case just means FOO was mentioned but not given a value
|
||||
func ConvertKVStringsToMapWithNil(values []string) map[string]*string {
|
||||
result := make(map[string]*string, len(values))
|
||||
for _, value := range values {
|
||||
kv := strings.SplitN(value, "=", 2)
|
||||
if len(kv) == 1 {
|
||||
result[kv[0]] = nil
|
||||
} else {
|
||||
result[kv[0]] = &kv[1]
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// ParseRestartPolicy returns the parsed policy or an error indicating what is incorrect
|
||||
func ParseRestartPolicy(policy string) (container.RestartPolicy, error) {
|
||||
p := container.RestartPolicy{}
|
||||
|
||||
if policy == "" {
|
||||
return p, nil
|
||||
}
|
||||
|
||||
parts := strings.Split(policy, ":")
|
||||
|
||||
if len(parts) > 2 {
|
||||
return p, fmt.Errorf("invalid restart policy format")
|
||||
}
|
||||
if len(parts) == 2 {
|
||||
count, err := strconv.Atoi(parts[1])
|
||||
if err != nil {
|
||||
return p, fmt.Errorf("maximum retry count must be an integer")
|
||||
}
|
||||
|
||||
p.MaximumRetryCount = count
|
||||
}
|
||||
|
||||
p.Name = parts[0]
|
||||
|
||||
return p, nil
|
||||
}
|
163
vendor/github.com/docker/docker-ce/components/cli/opts/port.go
generated
vendored
Normal file
163
vendor/github.com/docker/docker-ce/components/cli/opts/port.go
generated
vendored
Normal file
|
@ -0,0 +1,163 @@
|
|||
package opts
|
||||
|
||||
import (
|
||||
"encoding/csv"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/docker/api/types/swarm"
|
||||
"github.com/docker/go-connections/nat"
|
||||
)
|
||||
|
||||
const (
|
||||
portOptTargetPort = "target"
|
||||
portOptPublishedPort = "published"
|
||||
portOptProtocol = "protocol"
|
||||
portOptMode = "mode"
|
||||
)
|
||||
|
||||
// PortOpt represents a port config in swarm mode.
|
||||
type PortOpt struct {
|
||||
ports []swarm.PortConfig
|
||||
}
|
||||
|
||||
// Set a new port value
|
||||
// nolint: gocyclo
|
||||
func (p *PortOpt) Set(value string) error {
|
||||
longSyntax, err := regexp.MatchString(`\w+=\w+(,\w+=\w+)*`, value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if longSyntax {
|
||||
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) && value != string(swarm.PortConfigProtocolSCTP) {
|
||||
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)
|
||||
}
|
||||
|
||||
if pConfig.PublishMode == "" {
|
||||
pConfig.PublishMode = swarm.PortConfigPublishModeIngress
|
||||
}
|
||||
|
||||
if pConfig.Protocol == "" {
|
||||
pConfig.Protocol = swarm.PortConfigProtocolTCP
|
||||
}
|
||||
|
||||
p.ports = append(p.ports, pConfig)
|
||||
} else {
|
||||
// short syntax
|
||||
portConfigs := []swarm.PortConfig{}
|
||||
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, portBindingMap)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
portConfigs = append(portConfigs, portConfig...)
|
||||
}
|
||||
p.ports = append(p.ports, portConfigs...)
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
// ConvertPortToPortConfig converts ports to the swarm type
|
||||
func ConvertPortToPortConfig(
|
||||
port nat.Port,
|
||||
portBindings map[nat.Port][]nat.PortBinding,
|
||||
) ([]swarm.PortConfig, error) {
|
||||
ports := []swarm.PortConfig{}
|
||||
|
||||
for _, binding := range portBindings[port] {
|
||||
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())),
|
||||
TargetPort: uint32(port.Int()),
|
||||
PublishedPort: uint32(hostPort),
|
||||
PublishMode: swarm.PortConfigPublishModeIngress,
|
||||
})
|
||||
}
|
||||
return ports, nil
|
||||
}
|
296
vendor/github.com/docker/docker-ce/components/cli/opts/port_test.go
generated
vendored
Normal file
296
vendor/github.com/docker/docker-ce/components/cli/opts/port_test.go
generated
vendored
Normal file
|
@ -0,0 +1,296 @@
|
|||
package opts
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/docker/docker/api/types/swarm"
|
||||
"github.com/gotestyourself/gotestyourself/assert"
|
||||
is "github.com/gotestyourself/gotestyourself/assert/cmp"
|
||||
)
|
||||
|
||||
func TestPortOptValidSimpleSyntax(t *testing.T) {
|
||||
testCases := []struct {
|
||||
value string
|
||||
expected []swarm.PortConfig
|
||||
}{
|
||||
{
|
||||
value: "80",
|
||||
expected: []swarm.PortConfig{
|
||||
{
|
||||
Protocol: "tcp",
|
||||
TargetPort: 80,
|
||||
PublishMode: swarm.PortConfigPublishModeIngress,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
value: "80:8080",
|
||||
expected: []swarm.PortConfig{
|
||||
{
|
||||
Protocol: "tcp",
|
||||
TargetPort: 8080,
|
||||
PublishedPort: 80,
|
||||
PublishMode: swarm.PortConfigPublishModeIngress,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
value: "8080:80/tcp",
|
||||
expected: []swarm.PortConfig{
|
||||
{
|
||||
Protocol: "tcp",
|
||||
TargetPort: 80,
|
||||
PublishedPort: 8080,
|
||||
PublishMode: swarm.PortConfigPublishModeIngress,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
value: "80:8080/udp",
|
||||
expected: []swarm.PortConfig{
|
||||
{
|
||||
Protocol: "udp",
|
||||
TargetPort: 8080,
|
||||
PublishedPort: 80,
|
||||
PublishMode: swarm.PortConfigPublishModeIngress,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
value: "80-81:8080-8081/tcp",
|
||||
expected: []swarm.PortConfig{
|
||||
{
|
||||
Protocol: "tcp",
|
||||
TargetPort: 8080,
|
||||
PublishedPort: 80,
|
||||
PublishMode: swarm.PortConfigPublishModeIngress,
|
||||
},
|
||||
{
|
||||
Protocol: "tcp",
|
||||
TargetPort: 8081,
|
||||
PublishedPort: 81,
|
||||
PublishMode: swarm.PortConfigPublishModeIngress,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
value: "80-82:8080-8082/udp",
|
||||
expected: []swarm.PortConfig{
|
||||
{
|
||||
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,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
var port PortOpt
|
||||
assert.NilError(t, port.Set(tc.value))
|
||||
assert.Check(t, is.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,
|
||||
Protocol: "tcp",
|
||||
PublishMode: swarm.PortConfigPublishModeIngress,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
value: "target=80,protocol=tcp",
|
||||
expected: []swarm.PortConfig{
|
||||
{
|
||||
Protocol: "tcp",
|
||||
TargetPort: 80,
|
||||
PublishMode: swarm.PortConfigPublishModeIngress,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
value: "target=80,published=8080,protocol=tcp",
|
||||
expected: []swarm.PortConfig{
|
||||
{
|
||||
Protocol: "tcp",
|
||||
TargetPort: 80,
|
||||
PublishedPort: 8080,
|
||||
PublishMode: swarm.PortConfigPublishModeIngress,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
value: "published=80,target=8080,protocol=tcp",
|
||||
expected: []swarm.PortConfig{
|
||||
{
|
||||
Protocol: "tcp",
|
||||
TargetPort: 8080,
|
||||
PublishedPort: 80,
|
||||
PublishMode: swarm.PortConfigPublishModeIngress,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
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",
|
||||
Protocol: "tcp",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
value: "target=80,published=8080,mode=ingress",
|
||||
expected: []swarm.PortConfig{
|
||||
{
|
||||
TargetPort: 80,
|
||||
PublishedPort: 8080,
|
||||
PublishMode: "ingress",
|
||||
Protocol: "tcp",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
var port PortOpt
|
||||
assert.NilError(t, port.Set(tc.value))
|
||||
assert.Check(t, is.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.ErrorContains(t, port.Set(tc.value), tc.expectedError)
|
||||
}
|
||||
}
|
||||
|
||||
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: <empty>",
|
||||
},
|
||||
{
|
||||
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 {
|
||||
if portConfig == expected {
|
||||
contains = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !contains {
|
||||
t.Errorf("expected %v to contain %v, did not", portConfigs, expected)
|
||||
}
|
||||
}
|
37
vendor/github.com/docker/docker-ce/components/cli/opts/quotedstring.go
generated
vendored
Normal file
37
vendor/github.com/docker/docker-ce/components/cli/opts/quotedstring.go
generated
vendored
Normal file
|
@ -0,0 +1,37 @@
|
|||
package opts
|
||||
|
||||
// QuotedString is a string that may have extra quotes around the value. The
|
||||
// quotes are stripped from the value.
|
||||
type QuotedString struct {
|
||||
value *string
|
||||
}
|
||||
|
||||
// Set sets a new value
|
||||
func (s *QuotedString) Set(val string) error {
|
||||
*s.value = trimQuotes(val)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Type returns the type of the value
|
||||
func (s *QuotedString) Type() string {
|
||||
return "string"
|
||||
}
|
||||
|
||||
func (s *QuotedString) String() string {
|
||||
return *s.value
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// NewQuotedString returns a new quoted string option
|
||||
func NewQuotedString(value *string) *QuotedString {
|
||||
return &QuotedString{value: value}
|
||||
}
|
30
vendor/github.com/docker/docker-ce/components/cli/opts/quotedstring_test.go
generated
vendored
Normal file
30
vendor/github.com/docker/docker-ce/components/cli/opts/quotedstring_test.go
generated
vendored
Normal file
|
@ -0,0 +1,30 @@
|
|||
package opts
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/gotestyourself/gotestyourself/assert"
|
||||
is "github.com/gotestyourself/gotestyourself/assert/cmp"
|
||||
)
|
||||
|
||||
func TestQuotedStringSetWithQuotes(t *testing.T) {
|
||||
value := ""
|
||||
qs := NewQuotedString(&value)
|
||||
assert.NilError(t, qs.Set(`"something"`))
|
||||
assert.Check(t, is.Equal("something", qs.String()))
|
||||
assert.Check(t, is.Equal("something", value))
|
||||
}
|
||||
|
||||
func TestQuotedStringSetWithMismatchedQuotes(t *testing.T) {
|
||||
value := ""
|
||||
qs := NewQuotedString(&value)
|
||||
assert.NilError(t, qs.Set(`"something'`))
|
||||
assert.Check(t, is.Equal(`"something'`, qs.String()))
|
||||
}
|
||||
|
||||
func TestQuotedStringSetWithNoQuotes(t *testing.T) {
|
||||
value := ""
|
||||
qs := NewQuotedString(&value)
|
||||
assert.NilError(t, qs.Set("something"))
|
||||
assert.Check(t, is.Equal("something", qs.String()))
|
||||
}
|
79
vendor/github.com/docker/docker-ce/components/cli/opts/runtime.go
generated
vendored
Normal file
79
vendor/github.com/docker/docker-ce/components/cli/opts/runtime.go
generated
vendored
Normal 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"
|
||||
}
|
98
vendor/github.com/docker/docker-ce/components/cli/opts/secret.go
generated
vendored
Normal file
98
vendor/github.com/docker/docker-ce/components/cli/opts/secret.go
generated
vendored
Normal file
|
@ -0,0 +1,98 @@
|
|||
package opts
|
||||
|
||||
import (
|
||||
"encoding/csv"
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
swarmtypes "github.com/docker/docker/api/types/swarm"
|
||||
)
|
||||
|
||||
// SecretOpt is a Value type for parsing secrets
|
||||
type SecretOpt struct {
|
||||
values []*swarmtypes.SecretReference
|
||||
}
|
||||
|
||||
// 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 := &swarmtypes.SecretReference{
|
||||
File: &swarmtypes.SecretReferenceFileTarget{
|
||||
UID: "0",
|
||||
GID: "0",
|
||||
Mode: 0444,
|
||||
},
|
||||
}
|
||||
|
||||
// support a simple syntax of --secret foo
|
||||
if len(fields) == 1 {
|
||||
options.File.Name = fields[0]
|
||||
options.SecretName = 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.SecretName = 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 secret request: %s", key)
|
||||
}
|
||||
}
|
||||
|
||||
if options.SecretName == "" {
|
||||
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.SecretName, secret.File.Name)
|
||||
secrets = append(secrets, repr)
|
||||
}
|
||||
return strings.Join(secrets, ", ")
|
||||
}
|
||||
|
||||
// Value returns the secret requests
|
||||
func (o *SecretOpt) Value() []*swarmtypes.SecretReference {
|
||||
return o.values
|
||||
}
|
80
vendor/github.com/docker/docker-ce/components/cli/opts/secret_test.go
generated
vendored
Normal file
80
vendor/github.com/docker/docker-ce/components/cli/opts/secret_test.go
generated
vendored
Normal file
|
@ -0,0 +1,80 @@
|
|||
package opts
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/gotestyourself/gotestyourself/assert"
|
||||
is "github.com/gotestyourself/gotestyourself/assert/cmp"
|
||||
)
|
||||
|
||||
func TestSecretOptionsSimple(t *testing.T) {
|
||||
var opt SecretOpt
|
||||
|
||||
testCase := "app-secret"
|
||||
assert.NilError(t, opt.Set(testCase))
|
||||
|
||||
reqs := opt.Value()
|
||||
assert.Assert(t, is.Len(reqs, 1))
|
||||
req := reqs[0]
|
||||
assert.Check(t, is.Equal("app-secret", req.SecretName))
|
||||
assert.Check(t, is.Equal("app-secret", req.File.Name))
|
||||
assert.Check(t, is.Equal("0", req.File.UID))
|
||||
assert.Check(t, is.Equal("0", req.File.GID))
|
||||
}
|
||||
|
||||
func TestSecretOptionsSourceTarget(t *testing.T) {
|
||||
var opt SecretOpt
|
||||
|
||||
testCase := "source=foo,target=testing"
|
||||
assert.NilError(t, opt.Set(testCase))
|
||||
|
||||
reqs := opt.Value()
|
||||
assert.Assert(t, is.Len(reqs, 1))
|
||||
req := reqs[0]
|
||||
assert.Check(t, is.Equal("foo", req.SecretName))
|
||||
assert.Check(t, is.Equal("testing", req.File.Name))
|
||||
}
|
||||
|
||||
func TestSecretOptionsShorthand(t *testing.T) {
|
||||
var opt SecretOpt
|
||||
|
||||
testCase := "src=foo,target=testing"
|
||||
assert.NilError(t, opt.Set(testCase))
|
||||
|
||||
reqs := opt.Value()
|
||||
assert.Assert(t, is.Len(reqs, 1))
|
||||
req := reqs[0]
|
||||
assert.Check(t, is.Equal("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))
|
||||
|
||||
reqs := opt.Value()
|
||||
assert.Assert(t, is.Len(reqs, 1))
|
||||
req := reqs[0]
|
||||
assert.Check(t, is.Equal("foo", req.SecretName))
|
||||
assert.Check(t, is.Equal("testing", req.File.Name))
|
||||
assert.Check(t, is.Equal("1000", req.File.UID))
|
||||
assert.Check(t, is.Equal("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))
|
||||
|
||||
reqs := opt.Value()
|
||||
assert.Assert(t, is.Len(reqs, 1))
|
||||
req := reqs[0]
|
||||
assert.Check(t, is.Equal("foo", req.SecretName))
|
||||
assert.Check(t, is.Equal("testing", req.File.Name))
|
||||
assert.Check(t, is.Equal("1000", req.File.UID))
|
||||
assert.Check(t, is.Equal("1001", req.File.GID))
|
||||
assert.Check(t, is.Equal(os.FileMode(0444), req.File.Mode))
|
||||
}
|
108
vendor/github.com/docker/docker-ce/components/cli/opts/throttledevice.go
generated
vendored
Normal file
108
vendor/github.com/docker/docker-ce/components/cli/opts/throttledevice.go
generated
vendored
Normal file
|
@ -0,0 +1,108 @@
|
|||
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: 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 "list"
|
||||
}
|
57
vendor/github.com/docker/docker-ce/components/cli/opts/ulimit.go
generated
vendored
Normal file
57
vendor/github.com/docker/docker-ce/components/cli/opts/ulimit.go
generated
vendored
Normal 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
vendor/github.com/docker/docker-ce/components/cli/opts/ulimit_test.go
generated
vendored
Normal file
42
vendor/github.com/docker/docker-ce/components/cli/opts/ulimit_test.go
generated
vendored
Normal 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)
|
||||
}
|
||||
}
|
84
vendor/github.com/docker/docker-ce/components/cli/opts/weightdevice.go
generated
vendored
Normal file
84
vendor/github.com/docker/docker-ce/components/cli/opts/weightdevice.go
generated
vendored
Normal file
|
@ -0,0 +1,84 @@
|
|||
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 {
|
||||
return opt.values
|
||||
}
|
||||
|
||||
// Type returns the option type
|
||||
func (opt *WeightdeviceOpt) Type() string {
|
||||
return "list"
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue