diff --git a/builder/dockerfile/dispatchers.go b/builder/dockerfile/dispatchers.go index 64f0341d40..32bbc42dbe 100644 --- a/builder/dockerfile/dispatchers.go +++ b/builder/dockerfile/dispatchers.go @@ -71,14 +71,20 @@ func env(b *Builder, args []string, attributes map[string]bool, original string) if len(args[j]) == 0 { return errBlankCommandNames("ENV") } - newVar := args[j] + "=" + args[j+1] + "" commitStr += " " + newVar gotOne := false for i, envVar := range b.runConfig.Env { envParts := strings.SplitN(envVar, "=", 2) - if envParts[0] == args[j] { + compareFrom := envParts[0] + compareTo := args[j] + if runtime.GOOS == "windows" { + // Case insensitive environment variables on Windows + compareFrom = strings.ToUpper(compareFrom) + compareTo = strings.ToUpper(compareTo) + } + if compareFrom == compareTo { b.runConfig.Env[i] = newVar gotOne = true break diff --git a/builder/dockerfile/envVarTest b/builder/dockerfile/envVarTest index 1a7fe975a7..067dca9a54 100644 --- a/builder/dockerfile/envVarTest +++ b/builder/dockerfile/envVarTest @@ -1,112 +1,116 @@ -hello | hello -he'll'o | hello -he'llo | hello -he\'llo | he'llo -he\\'llo | he\llo -abc\tdef | abctdef -"abc\tdef" | abc\tdef -'abc\tdef' | abc\tdef -hello\ | hello -hello\\ | hello\ -"hello | hello -"hello\" | hello" -"hel'lo" | hel'lo -'hello | hello -'hello\' | hello\ -"''" | '' -$. | $. -$1 | -he$1x | hex -he$.x | he$.x -he$pwd. | he. -he$PWD | he/home -he\$PWD | he$PWD -he\\$PWD | he\/home -he\${} | he${} -he\${}xx | he${}xx -he${} | he -he${}xx | hexx -he${hi} | he -he${hi}xx | hexx -he${PWD} | he/home -he${.} | error -he${XXX:-000}xx | he000xx -he${PWD:-000}xx | he/homexx -he${XXX:-$PWD}xx | he/homexx -he${XXX:-${PWD:-yyy}}xx | he/homexx -he${XXX:-${YYY:-yyy}}xx | heyyyxx -he${XXX:YYY} | error -he${XXX:+${PWD}}xx | hexx -he${PWD:+${XXX}}xx | hexx -he${PWD:+${SHELL}}xx | hebashxx -he${XXX:+000}xx | hexx -he${PWD:+000}xx | he000xx -'he${XX}' | he${XX} -"he${PWD}" | he/home -"he'$PWD'" | he'/home' -"$PWD" | /home -'$PWD' | $PWD -'\$PWD' | \$PWD -'"hello"' | "hello" -he\$PWD | he$PWD -"he\$PWD" | he$PWD -'he\$PWD' | he\$PWD -he${PWD | error -he${PWD:=000}xx | error -he${PWD:+${PWD}:}xx | he/home:xx -he${XXX:-\$PWD:}xx | he$PWD:xx -he${XXX:-\${PWD}z}xx | he${PWDz}xx -안녕하세요 | 안녕하세요 -안'녕'하세요 | 안녕하세요 -안'녕하세요 | 안녕하세요 -안녕\'하세요 | 안녕'하세요 -안\\'녕하세요 | 안\녕하세요 -안녕\t하세요 | 안녕t하세요 -"안녕\t하세요" | 안녕\t하세요 -'안녕\t하세요 | 안녕\t하세요 -안녕하세요\ | 안녕하세요 -안녕하세요\\ | 안녕하세요\ -"안녕하세요 | 안녕하세요 -"안녕하세요\" | 안녕하세요" -"안녕'하세요" | 안녕'하세요 -'안녕하세요 | 안녕하세요 -'안녕하세요\' | 안녕하세요\ -안녕$1x | 안녕x -안녕$.x | 안녕$.x -안녕$pwd. | 안녕. -안녕$PWD | 안녕/home -안녕\$PWD | 안녕$PWD -안녕\\$PWD | 안녕\/home -안녕\${} | 안녕${} -안녕\${}xx | 안녕${}xx -안녕${} | 안녕 -안녕${}xx | 안녕xx -안녕${hi} | 안녕 -안녕${hi}xx | 안녕xx -안녕${PWD} | 안녕/home -안녕${.} | error -안녕${XXX:-000}xx | 안녕000xx -안녕${PWD:-000}xx | 안녕/homexx -안녕${XXX:-$PWD}xx | 안녕/homexx -안녕${XXX:-${PWD:-yyy}}xx | 안녕/homexx -안녕${XXX:-${YYY:-yyy}}xx | 안녕yyyxx -안녕${XXX:YYY} | error -안녕${XXX:+${PWD}}xx | 안녕xx -안녕${PWD:+${XXX}}xx | 안녕xx -안녕${PWD:+${SHELL}}xx | 안녕bashxx -안녕${XXX:+000}xx | 안녕xx -안녕${PWD:+000}xx | 안녕000xx -'안녕${XX}' | 안녕${XX} -"안녕${PWD}" | 안녕/home -"안녕'$PWD'" | 안녕'/home' -'"안녕"' | "안녕" -안녕\$PWD | 안녕$PWD -"안녕\$PWD" | 안녕$PWD -'안녕\$PWD' | 안녕\$PWD -안녕${PWD | error -안녕${PWD:=000}xx | error -안녕${PWD:+${PWD}:}xx | 안녕/home:xx -안녕${XXX:-\$PWD:}xx | 안녕$PWD:xx -안녕${XXX:-\${PWD}z}xx | 안녕${PWDz}xx -$KOREAN | 한국어 -안녕$KOREAN | 안녕한국어 +A|hello | hello +A|he'll'o | hello +A|he'llo | hello +A|he\'llo | he'llo +A|he\\'llo | he\llo +A|abc\tdef | abctdef +A|"abc\tdef" | abc\tdef +A|'abc\tdef' | abc\tdef +A|hello\ | hello +A|hello\\ | hello\ +A|"hello | hello +A|"hello\" | hello" +A|"hel'lo" | hel'lo +A|'hello | hello +A|'hello\' | hello\ +A|"''" | '' +A|$. | $. +A|$1 | +A|he$1x | hex +A|he$.x | he$.x +# Next one is different on Windows as $pwd==$PWD +U|he$pwd. | he. +W|he$pwd. | he/home. +A|he$PWD | he/home +A|he\$PWD | he$PWD +A|he\\$PWD | he\/home +A|he\${} | he${} +A|he\${}xx | he${}xx +A|he${} | he +A|he${}xx | hexx +A|he${hi} | he +A|he${hi}xx | hexx +A|he${PWD} | he/home +A|he${.} | error +A|he${XXX:-000}xx | he000xx +A|he${PWD:-000}xx | he/homexx +A|he${XXX:-$PWD}xx | he/homexx +A|he${XXX:-${PWD:-yyy}}xx | he/homexx +A|he${XXX:-${YYY:-yyy}}xx | heyyyxx +A|he${XXX:YYY} | error +A|he${XXX:+${PWD}}xx | hexx +A|he${PWD:+${XXX}}xx | hexx +A|he${PWD:+${SHELL}}xx | hebashxx +A|he${XXX:+000}xx | hexx +A|he${PWD:+000}xx | he000xx +A|'he${XX}' | he${XX} +A|"he${PWD}" | he/home +A|"he'$PWD'" | he'/home' +A|"$PWD" | /home +A|'$PWD' | $PWD +A|'\$PWD' | \$PWD +A|'"hello"' | "hello" +A|he\$PWD | he$PWD +A|"he\$PWD" | he$PWD +A|'he\$PWD' | he\$PWD +A|he${PWD | error +A|he${PWD:=000}xx | error +A|he${PWD:+${PWD}:}xx | he/home:xx +A|he${XXX:-\$PWD:}xx | he$PWD:xx +A|he${XXX:-\${PWD}z}xx | he${PWDz}xx +A|안녕하세요 | 안녕하세요 +A|안'녕'하세요 | 안녕하세요 +A|안'녕하세요 | 안녕하세요 +A|안녕\'하세요 | 안녕'하세요 +A|안\\'녕하세요 | 안\녕하세요 +A|안녕\t하세요 | 안녕t하세요 +A|"안녕\t하세요" | 안녕\t하세요 +A|'안녕\t하세요 | 안녕\t하세요 +A|안녕하세요\ | 안녕하세요 +A|안녕하세요\\ | 안녕하세요\ +A|"안녕하세요 | 안녕하세요 +A|"안녕하세요\" | 안녕하세요" +A|"안녕'하세요" | 안녕'하세요 +A|'안녕하세요 | 안녕하세요 +A|'안녕하세요\' | 안녕하세요\ +A|안녕$1x | 안녕x +A|안녕$.x | 안녕$.x +# Next one is different on Windows as $pwd==$PWD +U|안녕$pwd. | 안녕. +W|안녕$pwd. | 안녕/home. +A|안녕$PWD | 안녕/home +A|안녕\$PWD | 안녕$PWD +A|안녕\\$PWD | 안녕\/home +A|안녕\${} | 안녕${} +A|안녕\${}xx | 안녕${}xx +A|안녕${} | 안녕 +A|안녕${}xx | 안녕xx +A|안녕${hi} | 안녕 +A|안녕${hi}xx | 안녕xx +A|안녕${PWD} | 안녕/home +A|안녕${.} | error +A|안녕${XXX:-000}xx | 안녕000xx +A|안녕${PWD:-000}xx | 안녕/homexx +A|안녕${XXX:-$PWD}xx | 안녕/homexx +A|안녕${XXX:-${PWD:-yyy}}xx | 안녕/homexx +A|안녕${XXX:-${YYY:-yyy}}xx | 안녕yyyxx +A|안녕${XXX:YYY} | error +A|안녕${XXX:+${PWD}}xx | 안녕xx +A|안녕${PWD:+${XXX}}xx | 안녕xx +A|안녕${PWD:+${SHELL}}xx | 안녕bashxx +A|안녕${XXX:+000}xx | 안녕xx +A|안녕${PWD:+000}xx | 안녕000xx +A|'안녕${XX}' | 안녕${XX} +A|"안녕${PWD}" | 안녕/home +A|"안녕'$PWD'" | 안녕'/home' +A|'"안녕"' | "안녕" +A|안녕\$PWD | 안녕$PWD +A|"안녕\$PWD" | 안녕$PWD +A|'안녕\$PWD' | 안녕\$PWD +A|안녕${PWD | error +A|안녕${PWD:=000}xx | error +A|안녕${PWD:+${PWD}:}xx | 안녕/home:xx +A|안녕${XXX:-\$PWD:}xx | 안녕$PWD:xx +A|안녕${XXX:-\${PWD}z}xx | 안녕${PWDz}xx +A|$KOREAN | 한국어 +A|안녕$KOREAN | 안녕한국어 diff --git a/builder/dockerfile/shell_parser.go b/builder/dockerfile/shell_parser.go index 3a095299a8..189afd1fdb 100644 --- a/builder/dockerfile/shell_parser.go +++ b/builder/dockerfile/shell_parser.go @@ -8,6 +8,7 @@ package dockerfile import ( "fmt" + "runtime" "strings" "text/scanner" "unicode" @@ -298,9 +299,16 @@ func (sw *shellWord) processName() string { } func (sw *shellWord) getEnv(name string) string { + if runtime.GOOS == "windows" { + // Case-insensitive environment variables on Windows + name = strings.ToUpper(name) + } for _, env := range sw.envs { i := strings.Index(env, "=") if i < 0 { + if runtime.GOOS == "windows" { + env = strings.ToUpper(env) + } if name == env { // Should probably never get here, but just in case treat // it like "var" and "var=" are the same @@ -308,7 +316,11 @@ func (sw *shellWord) getEnv(name string) string { } continue } - if name != env[:i] { + compareName := env[:i] + if runtime.GOOS == "windows" { + compareName = strings.ToUpper(compareName) + } + if name != compareName { continue } return env[i+1:] diff --git a/builder/dockerfile/shell_parser_test.go b/builder/dockerfile/shell_parser_test.go index 35dea63fb1..6cf691c077 100644 --- a/builder/dockerfile/shell_parser_test.go +++ b/builder/dockerfile/shell_parser_test.go @@ -3,12 +3,14 @@ package dockerfile import ( "bufio" "os" + "runtime" "strings" "testing" ) func TestShellParser4EnvVars(t *testing.T) { fn := "envVarTest" + lineCount := 0 file, err := os.Open(fn) if err != nil { @@ -20,6 +22,7 @@ func TestShellParser4EnvVars(t *testing.T) { envs := []string{"PWD=/home", "SHELL=bash", "KOREAN=한국어"} for scanner.Scan() { line := scanner.Text() + lineCount++ // Trim comments and blank lines i := strings.Index(line, "#") @@ -33,21 +36,30 @@ func TestShellParser4EnvVars(t *testing.T) { } words := strings.Split(line, "|") - if len(words) != 2 { + if len(words) != 3 { t.Fatalf("Error in '%s' - should be exactly one | in:%q", fn, line) } words[0] = strings.TrimSpace(words[0]) words[1] = strings.TrimSpace(words[1]) + words[2] = strings.TrimSpace(words[2]) - newWord, err := ProcessWord(words[0], envs, '\\') - - if err != nil { - newWord = "error" + // Key W=Windows; A=All; U=Unix + if (words[0] != "W") && (words[0] != "A") && (words[0] != "U") { + t.Fatalf("Invalid tag %s at line %d of %s. Must be W, A or U", words[0], lineCount, fn) } - if newWord != words[1] { - t.Fatalf("Error. Src: %s Calc: %s Expected: %s", words[0], newWord, words[1]) + if ((words[0] == "W" || words[0] == "A") && runtime.GOOS == "windows") || + ((words[0] == "U" || words[0] == "A") && runtime.GOOS != "windows") { + newWord, err := ProcessWord(words[1], envs, '\\') + + if err != nil { + newWord = "error" + } + + if newWord != words[2] { + t.Fatalf("Error. Src: %s Calc: %s Expected: %s at line %d", words[1], newWord, words[2], lineCount) + } } } } diff --git a/daemon/commit.go b/daemon/commit.go index ae6f51be3c..333f7f2f93 100644 --- a/daemon/commit.go +++ b/daemon/commit.go @@ -46,6 +46,11 @@ func merge(userConf, imageConf *containertypes.Config) error { imageEnvKey := strings.Split(imageEnv, "=")[0] for _, userEnv := range userConf.Env { userEnvKey := strings.Split(userEnv, "=")[0] + if runtime.GOOS == "windows" { + // Case insensitive environment variables on Windows + imageEnvKey = strings.ToUpper(imageEnvKey) + userEnvKey = strings.ToUpper(userEnvKey) + } if imageEnvKey == userEnvKey { found = true break diff --git a/integration-cli/docker_cli_build_test.go b/integration-cli/docker_cli_build_test.go index eea4712fe7..e11f22d2f1 100644 --- a/integration-cli/docker_cli_build_test.go +++ b/integration-cli/docker_cli_build_test.go @@ -7311,3 +7311,19 @@ RUN ["cat", "/foo/file"] c.Fatal(err) } } + +// Case-insensitive environment variables on Windows +func (s *DockerSuite) TestBuildWindowsEnvCaseInsensitive(c *check.C) { + testRequires(c, DaemonIsWindows) + name := "testbuildwindowsenvcaseinsensitive" + if _, err := buildImage(name, ` + FROM `+WindowsBaseImage+` + ENV FOO=bar foo=bar + `, true); err != nil { + c.Fatal(err) + } + res := inspectFieldJSON(c, name, "Config.Env") + if res != `["foo=bar"]` { // Should not have FOO=bar in it - takes the last one processed. And only one entry as deduped. + c.Fatalf("Case insensitive environment variables on Windows failed. Got %s", res) + } +}