Skip to content

Commit

Permalink
Merge pull request #1671 from thaJeztah/fix_labels_expanding_env_vars
Browse files Browse the repository at this point in the history
Fix labels copying value from environment variables
  • Loading branch information
vdemeester authored Mar 19, 2019
2 parents fc9ef70 + e5702e0 commit a4a50de
Show file tree
Hide file tree
Showing 11 changed files with 128 additions and 27 deletions.
2 changes: 1 addition & 1 deletion cli/command/config/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ type CreateOptions struct {

func newConfigCreateCommand(dockerCli command.Cli) *cobra.Command {
createOpts := CreateOptions{
Labels: opts.NewListOpts(opts.ValidateEnv),
Labels: opts.NewListOpts(opts.ValidateLabel),
}

cmd := &cobra.Command{
Expand Down
2 changes: 1 addition & 1 deletion cli/command/container/opts.go
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ func addFlags(flags *pflag.FlagSet) *containerOptions {
expose: opts.NewListOpts(nil),
extraHosts: opts.NewListOpts(opts.ValidateExtraHost),
groupAdd: opts.NewListOpts(nil),
labels: opts.NewListOpts(nil),
labels: opts.NewListOpts(opts.ValidateLabel),
labelsFile: opts.NewListOpts(nil),
linkLocalIPs: opts.NewListOpts(nil),
links: opts.NewListOpts(opts.ValidateLink),
Expand Down
2 changes: 1 addition & 1 deletion cli/command/image/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ func newBuildOptions() buildOptions {
tags: opts.NewListOpts(validateTag),
buildArgs: opts.NewListOpts(opts.ValidateEnv),
ulimits: opts.NewUlimitOpt(&ulimits),
labels: opts.NewListOpts(opts.ValidateEnv),
labels: opts.NewListOpts(opts.ValidateLabel),
extraHosts: opts.NewListOpts(opts.ValidateExtraHost),
}
}
Expand Down
2 changes: 1 addition & 1 deletion cli/command/network/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ type createOptions struct {
func newCreateCommand(dockerCli command.Cli) *cobra.Command {
options := createOptions{
driverOpts: *opts.NewMapOpts(nil, nil),
labels: opts.NewListOpts(opts.ValidateEnv),
labels: opts.NewListOpts(opts.ValidateLabel),
ipamAux: *opts.NewMapOpts(nil, nil),
ipamOpt: *opts.NewMapOpts(nil, nil),
}
Expand Down
2 changes: 1 addition & 1 deletion cli/command/secret/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ type createOptions struct {

func newSecretCreateCommand(dockerCli command.Cli) *cobra.Command {
options := createOptions{
labels: opts.NewListOpts(opts.ValidateEnv),
labels: opts.NewListOpts(opts.ValidateLabel),
}

cmd := &cobra.Command{
Expand Down
4 changes: 2 additions & 2 deletions cli/command/service/opts.go
Original file line number Diff line number Diff line change
Expand Up @@ -520,9 +520,9 @@ type serviceOptions struct {

func newServiceOptions() *serviceOptions {
return &serviceOptions{
labels: opts.NewListOpts(opts.ValidateEnv),
labels: opts.NewListOpts(opts.ValidateLabel),
constraints: opts.NewListOpts(nil),
containerLabels: opts.NewListOpts(opts.ValidateEnv),
containerLabels: opts.NewListOpts(opts.ValidateLabel),
env: opts.NewListOpts(opts.ValidateEnv),
envFile: opts.NewListOpts(nil),
groups: opts.NewListOpts(nil),
Expand Down
2 changes: 1 addition & 1 deletion cli/command/volume/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ type createOptions struct {
func newCreateCommand(dockerCli command.Cli) *cobra.Command {
options := createOptions{
driverOpts: *opts.NewMapOpts(nil, nil),
labels: opts.NewListOpts(opts.ValidateEnv),
labels: opts.NewListOpts(opts.ValidateLabel),
}

cmd := &cobra.Command{
Expand Down
4 changes: 2 additions & 2 deletions opts/envfile_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ func TestParseEnvFileBadlyFormattedFile(t *testing.T) {
if _, ok := err.(ErrBadKey); !ok {
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 {
t.Fatalf("Expected [%v], got [%v]", expectedMessage, err.Error())
}
Expand Down Expand Up @@ -134,7 +134,7 @@ another invalid line`
if _, ok := err.(ErrBadKey); !ok {
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 {
t.Fatalf("Expected [%v], got [%v]", expectedMessage, err.Error())
}
Expand Down
4 changes: 2 additions & 2 deletions opts/file.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import (
"unicode/utf8"
)

var whiteSpaces = " \t"
const whiteSpaces = " \t"

// ErrBadKey typed error for bad environment variable
type ErrBadKey struct {
Expand Down Expand Up @@ -51,7 +51,7 @@ func parseKeyValueFile(filename string, emptyFn func(string) (string, bool)) ([]
// 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)}
return []string{}, ErrBadKey{fmt.Sprintf("variable '%s' contains whitespaces", variable)}
}
if len(variable) == 0 {
return []string{}, ErrBadKey{fmt.Sprintf("no variable name on line '%s'", line)}
Expand Down
20 changes: 17 additions & 3 deletions opts/opts.go
Original file line number Diff line number Diff line change
Expand Up @@ -267,10 +267,24 @@ func validateDomain(val string) (string, error) {
}

// 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) {
if strings.Count(val, "=") < 1 {
return "", fmt.Errorf("bad attribute format: %s", val)
arr := strings.SplitN(val, "=", 2)
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
}
Expand Down
111 changes: 99 additions & 12 deletions opts/opts_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import (
"fmt"
"strings"
"testing"

"gotest.tools/assert"
)

func TestValidateIPAddress(t *testing.T) {
Expand Down Expand Up @@ -175,19 +177,104 @@ func TestValidateDNSSearch(t *testing.T) {
}

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)
tests := []struct {
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''`,
},
}
// 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)

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
}
assert.NilError(t, err)
assert.Equal(t, val, tc.value)
})
}
}

Expand Down

0 comments on commit a4a50de

Please sign in to comment.