mirror of https://github.com/docker/cli.git
Move some validators from opts to runconfig/opts.
These validators are only used by runconfig.Parse() or some other part of the client, so move them into the client-side package. Signed-off-by: Daniel Nephin <dnephin@docker.com>
This commit is contained in:
parent
cef8b71ff4
commit
e73db35cbf
|
@ -1,67 +0,0 @@
|
||||||
package opts
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ParseEnvFile reads a file with environment variables enumerated by lines
|
|
||||||
//
|
|
||||||
// ``Environment variable names used by the utilities in the Shell and
|
|
||||||
// Utilities volume of IEEE Std 1003.1-2001 consist solely of uppercase
|
|
||||||
// letters, digits, and the '_' (underscore) from the characters defined in
|
|
||||||
// Portable Character Set and do not begin with a digit. *But*, other
|
|
||||||
// characters may be permitted by an implementation; applications shall
|
|
||||||
// tolerate the presence of such names.''
|
|
||||||
// -- http://pubs.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap08.html
|
|
||||||
//
|
|
||||||
// As of #16585, it's up to application inside docker to validate or not
|
|
||||||
// environment variables, that's why we just strip leading whitespace and
|
|
||||||
// nothing more.
|
|
||||||
func ParseEnvFile(filename string) ([]string, error) {
|
|
||||||
fh, err := os.Open(filename)
|
|
||||||
if err != nil {
|
|
||||||
return []string{}, err
|
|
||||||
}
|
|
||||||
defer fh.Close()
|
|
||||||
|
|
||||||
lines := []string{}
|
|
||||||
scanner := bufio.NewScanner(fh)
|
|
||||||
for scanner.Scan() {
|
|
||||||
// trim the line from all leading whitespace first
|
|
||||||
line := strings.TrimLeft(scanner.Text(), whiteSpaces)
|
|
||||||
// line is not empty, and not starting with '#'
|
|
||||||
if len(line) > 0 && !strings.HasPrefix(line, "#") {
|
|
||||||
data := strings.SplitN(line, "=", 2)
|
|
||||||
|
|
||||||
// trim the front of a variable, but nothing else
|
|
||||||
variable := strings.TrimLeft(data[0], whiteSpaces)
|
|
||||||
if strings.ContainsAny(variable, whiteSpaces) {
|
|
||||||
return []string{}, ErrBadEnvVariable{fmt.Sprintf("variable '%s' has white spaces", variable)}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(data) > 1 {
|
|
||||||
|
|
||||||
// pass the value through, no trimming
|
|
||||||
lines = append(lines, fmt.Sprintf("%s=%s", variable, data[1]))
|
|
||||||
} else {
|
|
||||||
// if only a pass-through variable is given, clean it up.
|
|
||||||
lines = append(lines, fmt.Sprintf("%s=%s", strings.TrimSpace(line), os.Getenv(line)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return lines, scanner.Err()
|
|
||||||
}
|
|
||||||
|
|
||||||
var whiteSpaces = " \t"
|
|
||||||
|
|
||||||
// ErrBadEnvVariable typed error for bad environment variable
|
|
||||||
type ErrBadEnvVariable struct {
|
|
||||||
msg string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e ErrBadEnvVariable) Error() string {
|
|
||||||
return fmt.Sprintf("poorly formatted environment: %s", e.msg)
|
|
||||||
}
|
|
|
@ -1,142 +0,0 @@
|
||||||
package opts
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
"reflect"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func tmpFileWithContent(content string, t *testing.T) string {
|
|
||||||
tmpFile, err := ioutil.TempFile("", "envfile-test")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
defer tmpFile.Close()
|
|
||||||
|
|
||||||
tmpFile.WriteString(content)
|
|
||||||
return tmpFile.Name()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test ParseEnvFile for a file with a few well formatted lines
|
|
||||||
func TestParseEnvFileGoodFile(t *testing.T) {
|
|
||||||
content := `foo=bar
|
|
||||||
baz=quux
|
|
||||||
# comment
|
|
||||||
|
|
||||||
_foobar=foobaz
|
|
||||||
with.dots=working
|
|
||||||
and_underscore=working too
|
|
||||||
`
|
|
||||||
// Adding a newline + a line with pure whitespace.
|
|
||||||
// This is being done like this instead of the block above
|
|
||||||
// because it's common for editors to trim trailing whitespace
|
|
||||||
// from lines, which becomes annoying since that's the
|
|
||||||
// exact thing we need to test.
|
|
||||||
content += "\n \t "
|
|
||||||
tmpFile := tmpFileWithContent(content, t)
|
|
||||||
defer os.Remove(tmpFile)
|
|
||||||
|
|
||||||
lines, err := ParseEnvFile(tmpFile)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
expectedLines := []string{
|
|
||||||
"foo=bar",
|
|
||||||
"baz=quux",
|
|
||||||
"_foobar=foobaz",
|
|
||||||
"with.dots=working",
|
|
||||||
"and_underscore=working too",
|
|
||||||
}
|
|
||||||
|
|
||||||
if !reflect.DeepEqual(lines, expectedLines) {
|
|
||||||
t.Fatal("lines not equal to expected_lines")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test ParseEnvFile for an empty file
|
|
||||||
func TestParseEnvFileEmptyFile(t *testing.T) {
|
|
||||||
tmpFile := tmpFileWithContent("", t)
|
|
||||||
defer os.Remove(tmpFile)
|
|
||||||
|
|
||||||
lines, err := ParseEnvFile(tmpFile)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(lines) != 0 {
|
|
||||||
t.Fatal("lines not empty; expected empty")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test ParseEnvFile for a non existent file
|
|
||||||
func TestParseEnvFileNonExistentFile(t *testing.T) {
|
|
||||||
_, err := ParseEnvFile("foo_bar_baz")
|
|
||||||
if err == nil {
|
|
||||||
t.Fatal("ParseEnvFile succeeded; expected failure")
|
|
||||||
}
|
|
||||||
if _, ok := err.(*os.PathError); !ok {
|
|
||||||
t.Fatalf("Expected a PathError, got [%v]", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test ParseEnvFile for a badly formatted file
|
|
||||||
func TestParseEnvFileBadlyFormattedFile(t *testing.T) {
|
|
||||||
content := `foo=bar
|
|
||||||
f =quux
|
|
||||||
`
|
|
||||||
|
|
||||||
tmpFile := tmpFileWithContent(content, t)
|
|
||||||
defer os.Remove(tmpFile)
|
|
||||||
|
|
||||||
_, err := ParseEnvFile(tmpFile)
|
|
||||||
if err == nil {
|
|
||||||
t.Fatalf("Expected a ErrBadEnvVariable, got nothing")
|
|
||||||
}
|
|
||||||
if _, ok := err.(ErrBadEnvVariable); !ok {
|
|
||||||
t.Fatalf("Expected a ErrBadEnvVariable, got [%v]", err)
|
|
||||||
}
|
|
||||||
expectedMessage := "poorly formatted environment: variable 'f ' has white spaces"
|
|
||||||
if err.Error() != expectedMessage {
|
|
||||||
t.Fatalf("Expected [%v], got [%v]", expectedMessage, err.Error())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test ParseEnvFile for a file with a line exceeding bufio.MaxScanTokenSize
|
|
||||||
func TestParseEnvFileLineTooLongFile(t *testing.T) {
|
|
||||||
content := strings.Repeat("a", bufio.MaxScanTokenSize+42)
|
|
||||||
content = fmt.Sprint("foo=", content)
|
|
||||||
|
|
||||||
tmpFile := tmpFileWithContent(content, t)
|
|
||||||
defer os.Remove(tmpFile)
|
|
||||||
|
|
||||||
_, err := ParseEnvFile(tmpFile)
|
|
||||||
if err == nil {
|
|
||||||
t.Fatal("ParseEnvFile succeeded; expected failure")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParseEnvFile with a random file, pass through
|
|
||||||
func TestParseEnvFileRandomFile(t *testing.T) {
|
|
||||||
content := `first line
|
|
||||||
another invalid line`
|
|
||||||
tmpFile := tmpFileWithContent(content, t)
|
|
||||||
defer os.Remove(tmpFile)
|
|
||||||
|
|
||||||
_, err := ParseEnvFile(tmpFile)
|
|
||||||
|
|
||||||
if err == nil {
|
|
||||||
t.Fatalf("Expected a ErrBadEnvVariable, got nothing")
|
|
||||||
}
|
|
||||||
if _, ok := err.(ErrBadEnvVariable); !ok {
|
|
||||||
t.Fatalf("Expected a ErrBadEnvvariable, got [%v]", err)
|
|
||||||
}
|
|
||||||
expectedMessage := "poorly formatted environment: variable 'first line' has white spaces"
|
|
||||||
if err.Error() != expectedMessage {
|
|
||||||
t.Fatalf("Expected [%v], got [%v]", expectedMessage, err.Error())
|
|
||||||
}
|
|
||||||
}
|
|
62
opts/opts.go
62
opts/opts.go
|
@ -3,7 +3,6 @@ package opts
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
@ -152,34 +151,6 @@ type ValidatorFctType func(val string) (string, error)
|
||||||
// ValidatorFctListType defines a validator function that returns a validated list of string and/or an error
|
// ValidatorFctListType defines a validator function that returns a validated list of string and/or an error
|
||||||
type ValidatorFctListType func(val string) ([]string, error)
|
type ValidatorFctListType func(val string) ([]string, error)
|
||||||
|
|
||||||
// ValidateAttach validates that the specified string is a valid attach option.
|
|
||||||
func ValidateAttach(val string) (string, error) {
|
|
||||||
s := strings.ToLower(val)
|
|
||||||
for _, str := range []string{"stdin", "stdout", "stderr"} {
|
|
||||||
if s == str {
|
|
||||||
return s, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return val, fmt.Errorf("valid streams are STDIN, STDOUT and STDERR")
|
|
||||||
}
|
|
||||||
|
|
||||||
// ValidateEnv validates an environment variable and returns it.
|
|
||||||
// If no value is specified, it returns the current value using os.Getenv.
|
|
||||||
//
|
|
||||||
// As on ParseEnvFile and related to #16585, environment variable names
|
|
||||||
// are not validate what so ever, it's up to application inside docker
|
|
||||||
// to validate them or not.
|
|
||||||
func ValidateEnv(val string) (string, error) {
|
|
||||||
arr := strings.Split(val, "=")
|
|
||||||
if len(arr) > 1 {
|
|
||||||
return val, nil
|
|
||||||
}
|
|
||||||
if !doesEnvExist(val) {
|
|
||||||
return val, nil
|
|
||||||
}
|
|
||||||
return fmt.Sprintf("%s=%s", val, os.Getenv(val)), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ValidateIPAddress validates an Ip address.
|
// ValidateIPAddress validates an Ip address.
|
||||||
func ValidateIPAddress(val string) (string, error) {
|
func ValidateIPAddress(val string) (string, error) {
|
||||||
var ip = net.ParseIP(strings.TrimSpace(val))
|
var ip = net.ParseIP(strings.TrimSpace(val))
|
||||||
|
@ -189,15 +160,6 @@ func ValidateIPAddress(val string) (string, error) {
|
||||||
return "", fmt.Errorf("%s is not an ip address", val)
|
return "", fmt.Errorf("%s is not an ip address", val)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ValidateMACAddress validates a MAC address.
|
|
||||||
func ValidateMACAddress(val string) (string, error) {
|
|
||||||
_, err := net.ParseMAC(strings.TrimSpace(val))
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return val, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ValidateDNSSearch validates domain for resolvconf search configuration.
|
// ValidateDNSSearch validates domain for resolvconf search configuration.
|
||||||
// A zero length domain is represented by a dot (.).
|
// A zero length domain is represented by a dot (.).
|
||||||
func ValidateDNSSearch(val string) (string, error) {
|
func ValidateDNSSearch(val string) (string, error) {
|
||||||
|
@ -218,20 +180,6 @@ func validateDomain(val string) (string, error) {
|
||||||
return "", fmt.Errorf("%s is not a valid domain", val)
|
return "", fmt.Errorf("%s is not a valid domain", val)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ValidateExtraHost validates that the specified string is a valid extrahost and returns it.
|
|
||||||
// ExtraHost are in the form of name:ip where the ip has to be a valid ip (ipv4 or ipv6).
|
|
||||||
func ValidateExtraHost(val string) (string, error) {
|
|
||||||
// allow for IPv6 addresses in extra hosts by only splitting on first ":"
|
|
||||||
arr := strings.SplitN(val, ":", 2)
|
|
||||||
if len(arr) != 2 || len(arr[0]) == 0 {
|
|
||||||
return "", fmt.Errorf("bad format for add-host: %q", val)
|
|
||||||
}
|
|
||||||
if _, err := ValidateIPAddress(arr[1]); err != nil {
|
|
||||||
return "", fmt.Errorf("invalid IP address in add-host: %q", arr[1])
|
|
||||||
}
|
|
||||||
return val, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ValidateLabel validates that the specified string is a valid label, and returns it.
|
// ValidateLabel validates that the specified string is a valid label, and returns it.
|
||||||
// Labels are in the form on key=value.
|
// Labels are in the form on key=value.
|
||||||
func ValidateLabel(val string) (string, error) {
|
func ValidateLabel(val string) (string, error) {
|
||||||
|
@ -240,13 +188,3 @@ func ValidateLabel(val string) (string, error) {
|
||||||
}
|
}
|
||||||
return val, nil
|
return val, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func doesEnvExist(name string) bool {
|
|
||||||
for _, entry := range os.Environ() {
|
|
||||||
parts := strings.SplitN(entry, "=", 2)
|
|
||||||
if parts[0] == name {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
|
@ -2,7 +2,6 @@ package opts
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
@ -55,20 +54,6 @@ func TestMapOpts(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestValidateMACAddress(t *testing.T) {
|
|
||||||
if _, err := ValidateMACAddress(`92:d0:c6:0a:29:33`); err != nil {
|
|
||||||
t.Fatalf("ValidateMACAddress(`92:d0:c6:0a:29:33`) got %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := ValidateMACAddress(`92:d0:c6:0a:33`); err == nil {
|
|
||||||
t.Fatalf("ValidateMACAddress(`92:d0:c6:0a:33`) succeeded; expected failure on invalid MAC")
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := ValidateMACAddress(`random invalid string`); err == nil {
|
|
||||||
t.Fatalf("ValidateMACAddress(`random invalid string`) succeeded; expected failure on invalid MAC")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestListOptsWithoutValidator(t *testing.T) {
|
func TestListOptsWithoutValidator(t *testing.T) {
|
||||||
o := NewListOpts(nil)
|
o := NewListOpts(nil)
|
||||||
o.Set("foo")
|
o.Set("foo")
|
||||||
|
@ -188,92 +173,6 @@ func TestValidateDNSSearch(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestValidateExtraHosts(t *testing.T) {
|
|
||||||
valid := []string{
|
|
||||||
`myhost:192.168.0.1`,
|
|
||||||
`thathost:10.0.2.1`,
|
|
||||||
`anipv6host:2003:ab34:e::1`,
|
|
||||||
`ipv6local:::1`,
|
|
||||||
}
|
|
||||||
|
|
||||||
invalid := map[string]string{
|
|
||||||
`myhost:192.notanipaddress.1`: `invalid IP`,
|
|
||||||
`thathost-nosemicolon10.0.0.1`: `bad format`,
|
|
||||||
`anipv6host:::::1`: `invalid IP`,
|
|
||||||
`ipv6local:::0::`: `invalid IP`,
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, extrahost := range valid {
|
|
||||||
if _, err := ValidateExtraHost(extrahost); err != nil {
|
|
||||||
t.Fatalf("ValidateExtraHost(`"+extrahost+"`) should succeed: error %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for extraHost, expectedError := range invalid {
|
|
||||||
if _, err := ValidateExtraHost(extraHost); err == nil {
|
|
||||||
t.Fatalf("ValidateExtraHost(`%q`) should have failed validation", extraHost)
|
|
||||||
} else {
|
|
||||||
if !strings.Contains(err.Error(), expectedError) {
|
|
||||||
t.Fatalf("ValidateExtraHost(`%q`) error should contain %q", extraHost, expectedError)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestValidateAttach(t *testing.T) {
|
|
||||||
valid := []string{
|
|
||||||
"stdin",
|
|
||||||
"stdout",
|
|
||||||
"stderr",
|
|
||||||
"STDIN",
|
|
||||||
"STDOUT",
|
|
||||||
"STDERR",
|
|
||||||
}
|
|
||||||
if _, err := ValidateAttach("invalid"); err == nil {
|
|
||||||
t.Fatalf("Expected error with [valid streams are STDIN, STDOUT and STDERR], got nothing")
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, attach := range valid {
|
|
||||||
value, err := ValidateAttach(attach)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if value != strings.ToLower(attach) {
|
|
||||||
t.Fatalf("Expected [%v], got [%v]", attach, value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestValidateEnv(t *testing.T) {
|
|
||||||
valids := map[string]string{
|
|
||||||
"a": "a",
|
|
||||||
"something": "something",
|
|
||||||
"_=a": "_=a",
|
|
||||||
"env1=value1": "env1=value1",
|
|
||||||
"_env1=value1": "_env1=value1",
|
|
||||||
"env2=value2=value3": "env2=value2=value3",
|
|
||||||
"env3=abc!qwe": "env3=abc!qwe",
|
|
||||||
"env_4=value 4": "env_4=value 4",
|
|
||||||
"PATH": fmt.Sprintf("PATH=%v", os.Getenv("PATH")),
|
|
||||||
"PATH=something": "PATH=something",
|
|
||||||
"asd!qwe": "asd!qwe",
|
|
||||||
"1asd": "1asd",
|
|
||||||
"123": "123",
|
|
||||||
"some space": "some space",
|
|
||||||
" some space before": " some space before",
|
|
||||||
"some space after ": "some space after ",
|
|
||||||
}
|
|
||||||
for value, expected := range valids {
|
|
||||||
actual, err := ValidateEnv(value)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if actual != expected {
|
|
||||||
t.Fatalf("Expected [%v], got [%v]", expected, actual)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestValidateLabel(t *testing.T) {
|
func TestValidateLabel(t *testing.T) {
|
||||||
if _, err := ValidateLabel("label"); err == nil || err.Error() != "bad attribute format: label" {
|
if _, err := ValidateLabel("label"); err == nil || err.Error() != "bad attribute format: label" {
|
||||||
t.Fatalf("Expected an error [bad attribute format: label], go %v", err)
|
t.Fatalf("Expected an error [bad attribute format: label], go %v", err)
|
||||||
|
|
Loading…
Reference in New Issue