Browse Source

config: case-insensitive env vars on Windows (#9438)

Signed-off-by: IKEDA Yasuyuki <[email protected]>
ikedam 3 years ago
parent
commit
bbaaa6a9de
4 changed files with 186 additions and 9 deletions
  1. 2 8
      pkg/compose/build.go
  2. 68 0
      pkg/compose/envresolver.go
  3. 115 0
      pkg/compose/envresolver_test.go
  4. 1 1
      pkg/compose/run.go

+ 2 - 8
pkg/compose/build.go

@@ -50,10 +50,7 @@ func (s *composeService) build(ctx context.Context, project *types.Project, opti
 	opts := map[string]build.Options{}
 	var imagesToBuild []string
 
-	args := flatten(options.Args.Resolve(func(s string) (string, bool) {
-		s, ok := project.Environment[s]
-		return s, ok
-	}))
+	args := flatten(options.Args.Resolve(envResolver(project.Environment)))
 
 	services, err := project.GetServices(options.Services...)
 	if err != nil {
@@ -214,10 +211,7 @@ func (s *composeService) toBuildOptions(project *types.Project, service types.Se
 	var tags []string
 	tags = append(tags, imageTag)
 
-	buildArgs := flatten(service.Build.Args.Resolve(func(s string) (string, bool) {
-		s, ok := project.Environment[s]
-		return s, ok
-	}))
+	buildArgs := flatten(service.Build.Args.Resolve(envResolver(project.Environment)))
 
 	var plats []specs.Platform
 	if platform, ok := project.Environment["DOCKER_DEFAULT_PLATFORM"]; ok {

+ 68 - 0
pkg/compose/envresolver.go

@@ -0,0 +1,68 @@
+/*
+   Copyright 2020 Docker Compose CLI authors
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+*/
+
+package compose
+
+import (
+	"runtime"
+	"strings"
+)
+
+var (
+	// isCaseInsensitiveEnvVars is true on platforms where environment variable names are treated case-insensitively.
+	isCaseInsensitiveEnvVars = (runtime.GOOS == "windows")
+)
+
+// envResolver returns resolver for environment variables suitable for the current platform.
+// Expected to be used with `MappingWithEquals.Resolve`.
+// Updates in `environment` may not be reflected.
+func envResolver(environment map[string]string) func(string) (string, bool) {
+	return envResolverWithCase(environment, isCaseInsensitiveEnvVars)
+}
+
+// envResolverWithCase returns resolver for environment variables with the specified case-sensitive condition.
+// Expected to be used with `MappingWithEquals.Resolve`.
+// Updates in `environment` may not be reflected.
+func envResolverWithCase(environment map[string]string, caseInsensitive bool) func(string) (string, bool) {
+	if environment == nil {
+		return func(s string) (string, bool) {
+			return "", false
+		}
+	}
+	if !caseInsensitive {
+		return func(s string) (string, bool) {
+			v, ok := environment[s]
+			return v, ok
+		}
+	}
+	// variable names must be treated case-insensitively.
+	// Resolves in this way:
+	// * Return the value if its name matches with the passed name case-sensitively.
+	// * Otherwise, return the value if its lower-cased name matches lower-cased passed name.
+	//     * The value is indefinite if multiple variable matches.
+	loweredEnvironment := make(map[string]string, len(environment))
+	for k, v := range environment {
+		loweredEnvironment[strings.ToLower(k)] = v
+	}
+	return func(s string) (string, bool) {
+		v, ok := environment[s]
+		if ok {
+			return v, ok
+		}
+		v, ok = loweredEnvironment[strings.ToLower(s)]
+		return v, ok
+	}
+}

+ 115 - 0
pkg/compose/envresolver_test.go

@@ -0,0 +1,115 @@
+/*
+   Copyright 2020 Docker Compose CLI authors
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+*/
+
+package compose
+
+import (
+	"testing"
+
+	"gotest.tools/assert"
+)
+
+func Test_EnvResolverWithCase(t *testing.T) {
+	tests := []struct {
+		name            string
+		environment     map[string]string
+		caseInsensitive bool
+		search          string
+		expectedValue   string
+		expectedOk      bool
+	}{
+		{
+			name: "case sensitive/case match",
+			environment: map[string]string{
+				"Env1": "Value1",
+				"Env2": "Value2",
+			},
+			caseInsensitive: false,
+			search:          "Env1",
+			expectedValue:   "Value1",
+			expectedOk:      true,
+		},
+		{
+			name: "case sensitive/case unmatch",
+			environment: map[string]string{
+				"Env1": "Value1",
+				"Env2": "Value2",
+			},
+			caseInsensitive: false,
+			search:          "ENV1",
+			expectedValue:   "",
+			expectedOk:      false,
+		},
+		{
+			name:            "case sensitive/nil environment",
+			environment:     nil,
+			caseInsensitive: false,
+			search:          "Env1",
+			expectedValue:   "",
+			expectedOk:      false,
+		},
+		{
+			name: "case insensitive/case match",
+			environment: map[string]string{
+				"Env1": "Value1",
+				"Env2": "Value2",
+			},
+			caseInsensitive: true,
+			search:          "Env1",
+			expectedValue:   "Value1",
+			expectedOk:      true,
+		},
+		{
+			name: "case insensitive/case unmatch",
+			environment: map[string]string{
+				"Env1": "Value1",
+				"Env2": "Value2",
+			},
+			caseInsensitive: true,
+			search:          "ENV1",
+			expectedValue:   "Value1",
+			expectedOk:      true,
+		},
+		{
+			name: "case insensitive/unmatch",
+			environment: map[string]string{
+				"Env1": "Value1",
+				"Env2": "Value2",
+			},
+			caseInsensitive: true,
+			search:          "Env3",
+			expectedValue:   "",
+			expectedOk:      false,
+		},
+		{
+			name:            "case insensitive/nil environment",
+			environment:     nil,
+			caseInsensitive: true,
+			search:          "Env1",
+			expectedValue:   "",
+			expectedOk:      false,
+		},
+	}
+
+	for _, test := range tests {
+		t.Run(test.name, func(t *testing.T) {
+			f := envResolverWithCase(test.environment, test.caseInsensitive)
+			v, ok := f(test.search)
+			assert.Equal(t, v, test.expectedValue)
+			assert.Equal(t, ok, test.expectedOk)
+		})
+	}
+}

+ 1 - 1
pkg/compose/run.go

@@ -116,7 +116,7 @@ func applyRunOptions(project *types.Project, service *types.ServiceConfig, opts
 	if len(opts.Environment) > 0 {
 		cmdEnv := types.NewMappingWithEquals(opts.Environment)
 		serviceOverrideEnv := cmdEnv.Resolve(func(s string) (string, bool) {
-			v, ok := project.Environment[s]
+			v, ok := envResolver(project.Environment)(s)
 			return v, ok
 		}).RemoveEmpty()
 		service.Environment.OverrideBy(serviceOverrideEnv)