mirror of https://github.com/docker/cli.git
Merge pull request #1671 from thaJeztah/fix_labels_expanding_env_vars
Fix labels copying value from environment variables
This commit is contained in:
commit
a4a50de4b8
|
@ -25,7 +25,7 @@ type CreateOptions struct {
|
||||||
|
|
||||||
func newConfigCreateCommand(dockerCli command.Cli) *cobra.Command {
|
func newConfigCreateCommand(dockerCli command.Cli) *cobra.Command {
|
||||||
createOpts := CreateOptions{
|
createOpts := CreateOptions{
|
||||||
Labels: opts.NewListOpts(opts.ValidateEnv),
|
Labels: opts.NewListOpts(opts.ValidateLabel),
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
|
|
|
@ -147,7 +147,7 @@ func addFlags(flags *pflag.FlagSet) *containerOptions {
|
||||||
expose: opts.NewListOpts(nil),
|
expose: opts.NewListOpts(nil),
|
||||||
extraHosts: opts.NewListOpts(opts.ValidateExtraHost),
|
extraHosts: opts.NewListOpts(opts.ValidateExtraHost),
|
||||||
groupAdd: opts.NewListOpts(nil),
|
groupAdd: opts.NewListOpts(nil),
|
||||||
labels: opts.NewListOpts(nil),
|
labels: opts.NewListOpts(opts.ValidateLabel),
|
||||||
labelsFile: opts.NewListOpts(nil),
|
labelsFile: opts.NewListOpts(nil),
|
||||||
linkLocalIPs: opts.NewListOpts(nil),
|
linkLocalIPs: opts.NewListOpts(nil),
|
||||||
links: opts.NewListOpts(opts.ValidateLink),
|
links: opts.NewListOpts(opts.ValidateLink),
|
||||||
|
|
|
@ -93,7 +93,7 @@ func newBuildOptions() buildOptions {
|
||||||
tags: opts.NewListOpts(validateTag),
|
tags: opts.NewListOpts(validateTag),
|
||||||
buildArgs: opts.NewListOpts(opts.ValidateEnv),
|
buildArgs: opts.NewListOpts(opts.ValidateEnv),
|
||||||
ulimits: opts.NewUlimitOpt(&ulimits),
|
ulimits: opts.NewUlimitOpt(&ulimits),
|
||||||
labels: opts.NewListOpts(opts.ValidateEnv),
|
labels: opts.NewListOpts(opts.ValidateLabel),
|
||||||
extraHosts: opts.NewListOpts(opts.ValidateExtraHost),
|
extraHosts: opts.NewListOpts(opts.ValidateExtraHost),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,7 +39,7 @@ type createOptions struct {
|
||||||
func newCreateCommand(dockerCli command.Cli) *cobra.Command {
|
func newCreateCommand(dockerCli command.Cli) *cobra.Command {
|
||||||
options := createOptions{
|
options := createOptions{
|
||||||
driverOpts: *opts.NewMapOpts(nil, nil),
|
driverOpts: *opts.NewMapOpts(nil, nil),
|
||||||
labels: opts.NewListOpts(opts.ValidateEnv),
|
labels: opts.NewListOpts(opts.ValidateLabel),
|
||||||
ipamAux: *opts.NewMapOpts(nil, nil),
|
ipamAux: *opts.NewMapOpts(nil, nil),
|
||||||
ipamOpt: *opts.NewMapOpts(nil, nil),
|
ipamOpt: *opts.NewMapOpts(nil, nil),
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,7 +25,7 @@ type createOptions struct {
|
||||||
|
|
||||||
func newSecretCreateCommand(dockerCli command.Cli) *cobra.Command {
|
func newSecretCreateCommand(dockerCli command.Cli) *cobra.Command {
|
||||||
options := createOptions{
|
options := createOptions{
|
||||||
labels: opts.NewListOpts(opts.ValidateEnv),
|
labels: opts.NewListOpts(opts.ValidateLabel),
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
|
|
|
@ -520,9 +520,9 @@ type serviceOptions struct {
|
||||||
|
|
||||||
func newServiceOptions() *serviceOptions {
|
func newServiceOptions() *serviceOptions {
|
||||||
return &serviceOptions{
|
return &serviceOptions{
|
||||||
labels: opts.NewListOpts(opts.ValidateEnv),
|
labels: opts.NewListOpts(opts.ValidateLabel),
|
||||||
constraints: opts.NewListOpts(nil),
|
constraints: opts.NewListOpts(nil),
|
||||||
containerLabels: opts.NewListOpts(opts.ValidateEnv),
|
containerLabels: opts.NewListOpts(opts.ValidateLabel),
|
||||||
env: opts.NewListOpts(opts.ValidateEnv),
|
env: opts.NewListOpts(opts.ValidateEnv),
|
||||||
envFile: opts.NewListOpts(nil),
|
envFile: opts.NewListOpts(nil),
|
||||||
groups: opts.NewListOpts(nil),
|
groups: opts.NewListOpts(nil),
|
||||||
|
|
|
@ -22,7 +22,7 @@ type createOptions struct {
|
||||||
func newCreateCommand(dockerCli command.Cli) *cobra.Command {
|
func newCreateCommand(dockerCli command.Cli) *cobra.Command {
|
||||||
options := createOptions{
|
options := createOptions{
|
||||||
driverOpts: *opts.NewMapOpts(nil, nil),
|
driverOpts: *opts.NewMapOpts(nil, nil),
|
||||||
labels: opts.NewListOpts(opts.ValidateEnv),
|
labels: opts.NewListOpts(opts.ValidateLabel),
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
|
|
|
@ -100,7 +100,7 @@ func TestParseEnvFileBadlyFormattedFile(t *testing.T) {
|
||||||
if _, ok := err.(ErrBadKey); !ok {
|
if _, ok := err.(ErrBadKey); !ok {
|
||||||
t.Fatalf("Expected an ErrBadKey, got [%v]", err)
|
t.Fatalf("Expected an ErrBadKey, got [%v]", err)
|
||||||
}
|
}
|
||||||
expectedMessage := "poorly formatted environment: variable 'f ' has white spaces"
|
expectedMessage := "poorly formatted environment: variable 'f ' contains whitespaces"
|
||||||
if err.Error() != expectedMessage {
|
if err.Error() != expectedMessage {
|
||||||
t.Fatalf("Expected [%v], got [%v]", expectedMessage, err.Error())
|
t.Fatalf("Expected [%v], got [%v]", expectedMessage, err.Error())
|
||||||
}
|
}
|
||||||
|
@ -134,7 +134,7 @@ another invalid line`
|
||||||
if _, ok := err.(ErrBadKey); !ok {
|
if _, ok := err.(ErrBadKey); !ok {
|
||||||
t.Fatalf("Expected an ErrBadKey, got [%v]", err)
|
t.Fatalf("Expected an ErrBadKey, got [%v]", err)
|
||||||
}
|
}
|
||||||
expectedMessage := "poorly formatted environment: variable 'first line' has white spaces"
|
expectedMessage := "poorly formatted environment: variable 'first line' contains whitespaces"
|
||||||
if err.Error() != expectedMessage {
|
if err.Error() != expectedMessage {
|
||||||
t.Fatalf("Expected [%v], got [%v]", expectedMessage, err.Error())
|
t.Fatalf("Expected [%v], got [%v]", expectedMessage, err.Error())
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,7 @@ import (
|
||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
)
|
)
|
||||||
|
|
||||||
var whiteSpaces = " \t"
|
const whiteSpaces = " \t"
|
||||||
|
|
||||||
// ErrBadKey typed error for bad environment variable
|
// ErrBadKey typed error for bad environment variable
|
||||||
type ErrBadKey struct {
|
type ErrBadKey struct {
|
||||||
|
@ -51,7 +51,7 @@ func parseKeyValueFile(filename string, emptyFn func(string) (string, bool)) ([]
|
||||||
// trim the front of a variable, but nothing else
|
// trim the front of a variable, but nothing else
|
||||||
variable := strings.TrimLeft(data[0], whiteSpaces)
|
variable := strings.TrimLeft(data[0], whiteSpaces)
|
||||||
if strings.ContainsAny(variable, whiteSpaces) {
|
if strings.ContainsAny(variable, whiteSpaces) {
|
||||||
return []string{}, ErrBadKey{fmt.Sprintf("variable '%s' has white spaces", variable)}
|
return []string{}, ErrBadKey{fmt.Sprintf("variable '%s' contains whitespaces", variable)}
|
||||||
}
|
}
|
||||||
if len(variable) == 0 {
|
if len(variable) == 0 {
|
||||||
return []string{}, ErrBadKey{fmt.Sprintf("no variable name on line '%s'", line)}
|
return []string{}, ErrBadKey{fmt.Sprintf("no variable name on line '%s'", line)}
|
||||||
|
|
20
opts/opts.go
20
opts/opts.go
|
@ -267,10 +267,24 @@ func validateDomain(val string) (string, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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 of key=value; key must be a non-empty string, and not
|
||||||
|
// contain whitespaces. A value is optional (defaults to an empty string if omitted).
|
||||||
|
//
|
||||||
|
// Leading whitespace is removed during validation but values are kept as-is
|
||||||
|
// otherwise, so any string value is accepted for both, which includes whitespace
|
||||||
|
// (for values) and quotes (surrounding, or embedded in key or value).
|
||||||
|
//
|
||||||
|
// TODO discuss if quotes (and other special characters) should be valid or invalid for keys
|
||||||
|
// TODO discuss if leading/trailing whitespace in keys should be preserved (and valid)
|
||||||
func ValidateLabel(val string) (string, error) {
|
func ValidateLabel(val string) (string, error) {
|
||||||
if strings.Count(val, "=") < 1 {
|
arr := strings.SplitN(val, "=", 2)
|
||||||
return "", fmt.Errorf("bad attribute format: %s", val)
|
key := strings.TrimLeft(arr[0], whiteSpaces)
|
||||||
|
if key == "" {
|
||||||
|
return "", fmt.Errorf("invalid label '%s': empty name", val)
|
||||||
|
}
|
||||||
|
if strings.ContainsAny(key, whiteSpaces) {
|
||||||
|
return "", fmt.Errorf("label '%s' contains whitespaces", key)
|
||||||
}
|
}
|
||||||
return val, nil
|
return val, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,8 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"gotest.tools/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestValidateIPAddress(t *testing.T) {
|
func TestValidateIPAddress(t *testing.T) {
|
||||||
|
@ -175,19 +177,104 @@ func TestValidateDNSSearch(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestValidateLabel(t *testing.T) {
|
func TestValidateLabel(t *testing.T) {
|
||||||
if _, err := ValidateLabel("label"); err == nil || err.Error() != "bad attribute format: label" {
|
tests := []struct {
|
||||||
t.Fatalf("Expected an error [bad attribute format: label], go %v", err)
|
name string
|
||||||
|
value string
|
||||||
|
expectedErr string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "empty",
|
||||||
|
expectedErr: `invalid label '': empty name`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "whitespace only ",
|
||||||
|
value: " ",
|
||||||
|
expectedErr: `invalid label ' ': empty name`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "whitespace around equal-sign",
|
||||||
|
value: " = ",
|
||||||
|
expectedErr: `invalid label ' = ': empty name`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "leading whitespace",
|
||||||
|
value: " label=value",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "whitespaces in key without value",
|
||||||
|
value: "this is a label without value",
|
||||||
|
expectedErr: `label 'this is a label without value' contains whitespaces`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "whitespaces in key",
|
||||||
|
value: "this is a label=value",
|
||||||
|
expectedErr: `label 'this is a label' contains whitespaces`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "whitespaces in value",
|
||||||
|
value: "label=a value that has whitespace",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "trailing whitespace in value",
|
||||||
|
value: "label=value ",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "leading whitespace in value",
|
||||||
|
value: "label= value",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "no value",
|
||||||
|
value: "label",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "no key",
|
||||||
|
value: "=label",
|
||||||
|
expectedErr: `invalid label '=label': empty name`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty value",
|
||||||
|
value: "label=",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "key value",
|
||||||
|
value: "key1=value1",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "double equal-signs",
|
||||||
|
value: "key1=value1=value2",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "multiple equal-signs",
|
||||||
|
value: "key1=value1=value2=value",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "double quotes in key and value",
|
||||||
|
value: `key"with"quotes={"hello"}`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "double quotes around key and value",
|
||||||
|
value: `"quoted-label"="quoted value"`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "single quotes in key and value",
|
||||||
|
value: `key'with'quotes=hello'with'quotes`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "single quotes around key and value",
|
||||||
|
value: `'quoted-label'='quoted value''`,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
if actual, err := ValidateLabel("key1=value1"); err != nil || actual != "key1=value1" {
|
|
||||||
t.Fatalf("Expected [key1=value1], got [%v,%v]", actual, err)
|
for _, tc := range tests {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
val, err := ValidateLabel(tc.value)
|
||||||
|
if tc.expectedErr != "" {
|
||||||
|
assert.Error(t, err, tc.expectedErr)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
// Validate it's working with more than one =
|
assert.NilError(t, err)
|
||||||
if actual, err := ValidateLabel("key1=value1=value2"); err != nil {
|
assert.Equal(t, val, tc.value)
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue