diff --git a/builder/shell_parser.go b/builder/shell_parser.go index d086645eb0..1ea44f64e5 100644 --- a/builder/shell_parser.go +++ b/builder/shell_parser.go @@ -157,7 +157,40 @@ func (sw *shellWord) processDollar() (string, error) { sw.next() return sw.getEnv(name), nil } - return "", fmt.Errorf("Unsupported ${} substitution: %s", sw.word) + if ch == ':' { + // Special ${xx:...} format processing + // Yes it allows for recursive $'s in the ... spot + + sw.next() // skip over : + modifier := sw.next() + + word, err := sw.processStopOn('}') + if err != nil { + return "", err + } + + // Grab the current value of the variable in question so we + // can use to to determine what to do based on the modifier + newValue := sw.getEnv(name) + + switch modifier { + case '+': + if newValue != "" { + newValue = word + } + return newValue, nil + + case '-': + if newValue == "" { + newValue = word + } + return newValue, nil + + default: + return "", fmt.Errorf("Unsupported modifier (%c) in substitution: %s", modifier, sw.word) + } + } + return "", fmt.Errorf("Missing ':' in substitution: %s", sw.word) } // $xxx case name := sw.processName() diff --git a/builder/words b/builder/words index 5cac826a68..1114a7e466 100644 --- a/builder/words +++ b/builder/words @@ -30,6 +30,17 @@ 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' @@ -41,3 +52,7 @@ 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 diff --git a/docs/sources/reference/builder.md b/docs/sources/reference/builder.md index adf42ae764..d837541aa2 100644 --- a/docs/sources/reference/builder.md +++ b/docs/sources/reference/builder.md @@ -113,18 +113,30 @@ images. > replacement at the time. After 1.3 this behavior will be preserved and > canonical. -Environment variables (declared with [the `ENV` statement](#env)) can also be used in -certain instructions as variables to be interpreted by the `Dockerfile`. Escapes -are also handled for including variable-like syntax into a statement literally. +Environment variables (declared with [the `ENV` statement](#env)) can also be +used in certain instructions as variables to be interpreted by the +`Dockerfile`. Escapes are also handled for including variable-like syntax +into a statement literally. Environment variables are notated in the `Dockerfile` either with `$variable_name` or `${variable_name}`. They are treated equivalently and the brace syntax is typically used to address issues with variable names with no whitespace, like `${foo}_bar`. +The `${variable_name}` syntax also supports a few of the standard `bash` +modifiers as specified below: + +* `${variable:-word}` indicates that if `variable` is set then the result + will be that value. If `variable` is not set then `word` will be the result. +* `${variable:+word}` indiates that if `variable` is set then `word` will be + the result, otherwise the result is the empty string. + +In all cases, `word` can be any string, including additional environment +variables. + Escaping is possible by adding a `\` before the variable: `\$foo` or `\${foo}`, for example, will translate to `$foo` and `${foo}` literals respectively. - + Example (parsed representation is displayed after the `#`): FROM busybox diff --git a/integration-cli/docker_cli_build_test.go b/integration-cli/docker_cli_build_test.go index cec62419b3..e907afb5f2 100644 --- a/integration-cli/docker_cli_build_test.go +++ b/integration-cli/docker_cli_build_test.go @@ -214,13 +214,19 @@ func TestBuildEnvironmentReplacementAddCopy(t *testing.T) { ENV baz foo ENV quux bar ENV dot . + ENV fee fff + ENV gee ggg ADD ${baz} ${dot} COPY ${quux} ${dot} + ADD ${zzz:-${fee}} ${dot} + COPY ${zzz:-${gee}} ${dot} `, map[string]string{ "foo": "test1", "bar": "test2", + "fff": "test3", + "ggg": "test4", }) if err != nil { @@ -286,6 +292,11 @@ func TestBuildEnvironmentReplacementEnv(t *testing.T) { if parts[1] != "zzz" { t.Fatalf("%s should be 'foo' but instead its %q", parts[0], parts[1]) } + } else if strings.HasPrefix(parts[0], "env") { + envCount++ + if parts[1] != "foo" { + t.Fatalf("%s should be 'foo' but instead its %q", parts[0], parts[1]) + } } } @@ -4069,6 +4080,27 @@ RUN [ "$abc" = "'foo'" ] ENV abc \"foo\" RUN [ "$abc" = '"foo"' ] +ENV abc=ABC +RUN [ "$abc" = "ABC" ] +ENV def=${abc:-DEF} +RUN [ "$def" = "ABC" ] +ENV def=${ccc:-DEF} +RUN [ "$def" = "DEF" ] +ENV def=${ccc:-${def}xx} +RUN [ "$def" = "DEFxx" ] +ENV def=${def:+ALT} +RUN [ "$def" = "ALT" ] +ENV def=${def:+${abc}:} +RUN [ "$def" = "ABC:" ] +ENV def=${ccc:-\$abc:} +RUN [ "$def" = '$abc:' ] +ENV def=${ccc:-\${abc}:} +RUN [ "$def" = '${abc:}' ] +ENV mypath=${mypath:+$mypath:}/home +RUN [ "$mypath" = '/home' ] +ENV mypath=${mypath:+$mypath:}/away +RUN [ "$mypath" = '/home:/away' ] + ENV e1=bar ENV e2=$e1 ENV e3=$e11