Closes #17781
This allows for env vars in EXPOSE to be parsed for spaces so that each
"word" is then treated independently instead of as a single word/arg.
Signed-off-by: Doug Davis <dug@us.ibm.com>
| 1 | 1 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,112 @@ |
| 0 |
+hello | hello |
|
| 1 |
+he'll'o | hello |
|
| 2 |
+he'llo | hello |
|
| 3 |
+he\'llo | he'llo |
|
| 4 |
+he\\'llo | he\llo |
|
| 5 |
+abc\tdef | abctdef |
|
| 6 |
+"abc\tdef" | abc\tdef |
|
| 7 |
+'abc\tdef' | abc\tdef |
|
| 8 |
+hello\ | hello |
|
| 9 |
+hello\\ | hello\ |
|
| 10 |
+"hello | hello |
|
| 11 |
+"hello\" | hello" |
|
| 12 |
+"hel'lo" | hel'lo |
|
| 13 |
+'hello | hello |
|
| 14 |
+'hello\' | hello\ |
|
| 15 |
+"''" | '' |
|
| 16 |
+$. | $. |
|
| 17 |
+$1 | |
|
| 18 |
+he$1x | hex |
|
| 19 |
+he$.x | he$.x |
|
| 20 |
+he$pwd. | he. |
|
| 21 |
+he$PWD | he/home |
|
| 22 |
+he\$PWD | he$PWD |
|
| 23 |
+he\\$PWD | he\/home |
|
| 24 |
+he\${} | he${}
|
|
| 25 |
+he\${}xx | he${}xx
|
|
| 26 |
+he${} | he
|
|
| 27 |
+he${}xx | hexx
|
|
| 28 |
+he${hi} | he
|
|
| 29 |
+he${hi}xx | hexx
|
|
| 30 |
+he${PWD} | he/home
|
|
| 31 |
+he${.} | error
|
|
| 32 |
+he${XXX:-000}xx | he000xx
|
|
| 33 |
+he${PWD:-000}xx | he/homexx
|
|
| 34 |
+he${XXX:-$PWD}xx | he/homexx
|
|
| 35 |
+he${XXX:-${PWD:-yyy}}xx | he/homexx
|
|
| 36 |
+he${XXX:-${YYY:-yyy}}xx | heyyyxx
|
|
| 37 |
+he${XXX:YYY} | error
|
|
| 38 |
+he${XXX:+${PWD}}xx | hexx
|
|
| 39 |
+he${PWD:+${XXX}}xx | hexx
|
|
| 40 |
+he${PWD:+${SHELL}}xx | hebashxx
|
|
| 41 |
+he${XXX:+000}xx | hexx
|
|
| 42 |
+he${PWD:+000}xx | he000xx
|
|
| 43 |
+'he${XX}' | he${XX}
|
|
| 44 |
+"he${PWD}" | he/home
|
|
| 45 |
+"he'$PWD'" | he'/home' |
|
| 46 |
+"$PWD" | /home |
|
| 47 |
+'$PWD' | $PWD |
|
| 48 |
+'\$PWD' | \$PWD |
|
| 49 |
+'"hello"' | "hello" |
|
| 50 |
+he\$PWD | he$PWD |
|
| 51 |
+"he\$PWD" | he$PWD |
|
| 52 |
+'he\$PWD' | he\$PWD |
|
| 53 |
+he${PWD | error
|
|
| 54 |
+he${PWD:=000}xx | error
|
|
| 55 |
+he${PWD:+${PWD}:}xx | he/home:xx
|
|
| 56 |
+he${XXX:-\$PWD:}xx | he$PWD:xx
|
|
| 57 |
+he${XXX:-\${PWD}z}xx | he${PWDz}xx
|
|
| 58 |
+안녕하세요 | 안녕하세요 |
|
| 59 |
+안'녕'하세요 | 안녕하세요 |
|
| 60 |
+안'녕하세요 | 안녕하세요 |
|
| 61 |
+안녕\'하세요 | 안녕'하세요 |
|
| 62 |
+안\\'녕하세요 | 안\녕하세요 |
|
| 63 |
+안녕\t하세요 | 안녕t하세요 |
|
| 64 |
+"안녕\t하세요" | 안녕\t하세요 |
|
| 65 |
+'안녕\t하세요 | 안녕\t하세요 |
|
| 66 |
+안녕하세요\ | 안녕하세요 |
|
| 67 |
+안녕하세요\\ | 안녕하세요\ |
|
| 68 |
+"안녕하세요 | 안녕하세요 |
|
| 69 |
+"안녕하세요\" | 안녕하세요" |
|
| 70 |
+"안녕'하세요" | 안녕'하세요 |
|
| 71 |
+'안녕하세요 | 안녕하세요 |
|
| 72 |
+'안녕하세요\' | 안녕하세요\ |
|
| 73 |
+안녕$1x | 안녕x |
|
| 74 |
+안녕$.x | 안녕$.x |
|
| 75 |
+안녕$pwd. | 안녕. |
|
| 76 |
+안녕$PWD | 안녕/home |
|
| 77 |
+안녕\$PWD | 안녕$PWD |
|
| 78 |
+안녕\\$PWD | 안녕\/home |
|
| 79 |
+안녕\${} | 안녕${}
|
|
| 80 |
+안녕\${}xx | 안녕${}xx
|
|
| 81 |
+안녕${} | 안녕
|
|
| 82 |
+안녕${}xx | 안녕xx
|
|
| 83 |
+안녕${hi} | 안녕
|
|
| 84 |
+안녕${hi}xx | 안녕xx
|
|
| 85 |
+안녕${PWD} | 안녕/home
|
|
| 86 |
+안녕${.} | error
|
|
| 87 |
+안녕${XXX:-000}xx | 안녕000xx
|
|
| 88 |
+안녕${PWD:-000}xx | 안녕/homexx
|
|
| 89 |
+안녕${XXX:-$PWD}xx | 안녕/homexx
|
|
| 90 |
+안녕${XXX:-${PWD:-yyy}}xx | 안녕/homexx
|
|
| 91 |
+안녕${XXX:-${YYY:-yyy}}xx | 안녕yyyxx
|
|
| 92 |
+안녕${XXX:YYY} | error
|
|
| 93 |
+안녕${XXX:+${PWD}}xx | 안녕xx
|
|
| 94 |
+안녕${PWD:+${XXX}}xx | 안녕xx
|
|
| 95 |
+안녕${PWD:+${SHELL}}xx | 안녕bashxx
|
|
| 96 |
+안녕${XXX:+000}xx | 안녕xx
|
|
| 97 |
+안녕${PWD:+000}xx | 안녕000xx
|
|
| 98 |
+'안녕${XX}' | 안녕${XX}
|
|
| 99 |
+"안녕${PWD}" | 안녕/home
|
|
| 100 |
+"안녕'$PWD'" | 안녕'/home' |
|
| 101 |
+'"안녕"' | "안녕" |
|
| 102 |
+안녕\$PWD | 안녕$PWD |
|
| 103 |
+"안녕\$PWD" | 안녕$PWD |
|
| 104 |
+'안녕\$PWD' | 안녕\$PWD |
|
| 105 |
+안녕${PWD | error
|
|
| 106 |
+안녕${PWD:=000}xx | error
|
|
| 107 |
+안녕${PWD:+${PWD}:}xx | 안녕/home:xx
|
|
| 108 |
+안녕${XXX:-\$PWD:}xx | 안녕$PWD:xx
|
|
| 109 |
+안녕${XXX:-\${PWD}z}xx | 안녕${PWDz}xx
|
|
| 110 |
+$KOREAN | 한국어 |
|
| 111 |
+안녕$KOREAN | 안녕한국어 |
| ... | ... |
@@ -42,6 +42,19 @@ var replaceEnvAllowed = map[string]struct{}{
|
| 42 | 42 |
command.Arg: {},
|
| 43 | 43 |
} |
| 44 | 44 |
|
| 45 |
+// Certain commands are allowed to have their args split into more |
|
| 46 |
+// words after env var replacements. Meaning: |
|
| 47 |
+// ENV foo="123 456" |
|
| 48 |
+// EXPOSE $foo |
|
| 49 |
+// should result in the same thing as: |
|
| 50 |
+// EXPOSE 123 456 |
|
| 51 |
+// and not treat "123 456" as a single word. |
|
| 52 |
+// Note that: EXPOSE "$foo" and EXPOSE $foo are not the same thing. |
|
| 53 |
+// Quotes will cause it to still be treated as single word. |
|
| 54 |
+var allowWordExpansion = map[string]bool{
|
|
| 55 |
+ command.Expose: true, |
|
| 56 |
+} |
|
| 57 |
+ |
|
| 45 | 58 |
var evaluateTable map[string]func(*Builder, []string, map[string]bool, string) error |
| 46 | 59 |
|
| 47 | 60 |
func init() {
|
| ... | ... |
@@ -92,7 +105,7 @@ func (b *Builder) dispatch(stepN int, ast *parser.Node) error {
|
| 92 | 92 |
attrs := ast.Attributes |
| 93 | 93 |
original := ast.Original |
| 94 | 94 |
flags := ast.Flags |
| 95 |
- strs := []string{}
|
|
| 95 |
+ strList := []string{}
|
|
| 96 | 96 |
msg := fmt.Sprintf("Step %d : %s", stepN+1, upperCasedCmd)
|
| 97 | 97 |
|
| 98 | 98 |
if len(ast.Flags) > 0 {
|
| ... | ... |
@@ -104,7 +117,7 @@ func (b *Builder) dispatch(stepN int, ast *parser.Node) error {
|
| 104 | 104 |
return fmt.Errorf("ONBUILD requires at least one argument")
|
| 105 | 105 |
} |
| 106 | 106 |
ast = ast.Next.Children[0] |
| 107 |
- strs = append(strs, ast.Value) |
|
| 107 |
+ strList = append(strList, ast.Value) |
|
| 108 | 108 |
msg += " " + ast.Value |
| 109 | 109 |
|
| 110 | 110 |
if len(ast.Flags) > 0 {
|
| ... | ... |
@@ -122,9 +135,6 @@ func (b *Builder) dispatch(stepN int, ast *parser.Node) error {
|
| 122 | 122 |
cursor = cursor.Next |
| 123 | 123 |
n++ |
| 124 | 124 |
} |
| 125 |
- l := len(strs) |
|
| 126 |
- strList := make([]string, n+l) |
|
| 127 |
- copy(strList, strs) |
|
| 128 | 125 |
msgList := make([]string, n) |
| 129 | 126 |
|
| 130 | 127 |
var i int |
| ... | ... |
@@ -155,12 +165,24 @@ func (b *Builder) dispatch(stepN int, ast *parser.Node) error {
|
| 155 | 155 |
str = ast.Value |
| 156 | 156 |
if _, ok := replaceEnvAllowed[cmd]; ok {
|
| 157 | 157 |
var err error |
| 158 |
- str, err = ProcessWord(ast.Value, envs) |
|
| 159 |
- if err != nil {
|
|
| 160 |
- return err |
|
| 158 |
+ var words []string |
|
| 159 |
+ |
|
| 160 |
+ if _, ok := allowWordExpansion[cmd]; ok {
|
|
| 161 |
+ words, err = ProcessWords(str, envs) |
|
| 162 |
+ if err != nil {
|
|
| 163 |
+ return err |
|
| 164 |
+ } |
|
| 165 |
+ strList = append(strList, words...) |
|
| 166 |
+ } else {
|
|
| 167 |
+ str, err = ProcessWord(str, envs) |
|
| 168 |
+ if err != nil {
|
|
| 169 |
+ return err |
|
| 170 |
+ } |
|
| 171 |
+ strList = append(strList, str) |
|
| 161 | 172 |
} |
| 173 |
+ } else {
|
|
| 174 |
+ strList = append(strList, str) |
|
| 162 | 175 |
} |
| 163 |
- strList[i+l] = str |
|
| 164 | 176 |
msgList[i] = ast.Value |
| 165 | 177 |
i++ |
| 166 | 178 |
} |
| ... | ... |
@@ -29,17 +29,85 @@ func ProcessWord(word string, env []string) (string, error) {
|
| 29 | 29 |
pos: 0, |
| 30 | 30 |
} |
| 31 | 31 |
sw.scanner.Init(strings.NewReader(word)) |
| 32 |
- return sw.process() |
|
| 32 |
+ word, _, err := sw.process() |
|
| 33 |
+ return word, err |
|
| 33 | 34 |
} |
| 34 | 35 |
|
| 35 |
-func (sw *shellWord) process() (string, error) {
|
|
| 36 |
+// ProcessWords will use the 'env' list of environment variables, |
|
| 37 |
+// and replace any env var references in 'word' then it will also |
|
| 38 |
+// return a slice of strings which represents the 'word' |
|
| 39 |
+// split up based on spaces - taking into account quotes. Note that |
|
| 40 |
+// this splitting is done **after** the env var substitutions are done. |
|
| 41 |
+// Note, each one is trimmed to remove leading and trailing spaces (unless |
|
| 42 |
+// they are quoted", but ProcessWord retains spaces between words. |
|
| 43 |
+func ProcessWords(word string, env []string) ([]string, error) {
|
|
| 44 |
+ sw := &shellWord{
|
|
| 45 |
+ word: word, |
|
| 46 |
+ envs: env, |
|
| 47 |
+ pos: 0, |
|
| 48 |
+ } |
|
| 49 |
+ sw.scanner.Init(strings.NewReader(word)) |
|
| 50 |
+ _, words, err := sw.process() |
|
| 51 |
+ return words, err |
|
| 52 |
+} |
|
| 53 |
+ |
|
| 54 |
+func (sw *shellWord) process() (string, []string, error) {
|
|
| 36 | 55 |
return sw.processStopOn(scanner.EOF) |
| 37 | 56 |
} |
| 38 | 57 |
|
| 58 |
+type wordsStruct struct {
|
|
| 59 |
+ word string |
|
| 60 |
+ words []string |
|
| 61 |
+ inWord bool |
|
| 62 |
+} |
|
| 63 |
+ |
|
| 64 |
+func (w *wordsStruct) addChar(ch rune) {
|
|
| 65 |
+ if unicode.IsSpace(ch) && w.inWord {
|
|
| 66 |
+ if len(w.word) != 0 {
|
|
| 67 |
+ w.words = append(w.words, w.word) |
|
| 68 |
+ w.word = "" |
|
| 69 |
+ w.inWord = false |
|
| 70 |
+ } |
|
| 71 |
+ } else if !unicode.IsSpace(ch) {
|
|
| 72 |
+ w.addRawChar(ch) |
|
| 73 |
+ } |
|
| 74 |
+} |
|
| 75 |
+ |
|
| 76 |
+func (w *wordsStruct) addRawChar(ch rune) {
|
|
| 77 |
+ w.word += string(ch) |
|
| 78 |
+ w.inWord = true |
|
| 79 |
+} |
|
| 80 |
+ |
|
| 81 |
+func (w *wordsStruct) addString(str string) {
|
|
| 82 |
+ var scan scanner.Scanner |
|
| 83 |
+ scan.Init(strings.NewReader(str)) |
|
| 84 |
+ for scan.Peek() != scanner.EOF {
|
|
| 85 |
+ w.addChar(scan.Next()) |
|
| 86 |
+ } |
|
| 87 |
+} |
|
| 88 |
+ |
|
| 89 |
+func (w *wordsStruct) addRawString(str string) {
|
|
| 90 |
+ w.word += str |
|
| 91 |
+ w.inWord = true |
|
| 92 |
+} |
|
| 93 |
+ |
|
| 94 |
+func (w *wordsStruct) getWords() []string {
|
|
| 95 |
+ if len(w.word) > 0 {
|
|
| 96 |
+ w.words = append(w.words, w.word) |
|
| 97 |
+ |
|
| 98 |
+ // Just in case we're called again by mistake |
|
| 99 |
+ w.word = "" |
|
| 100 |
+ w.inWord = false |
|
| 101 |
+ } |
|
| 102 |
+ return w.words |
|
| 103 |
+} |
|
| 104 |
+ |
|
| 39 | 105 |
// Process the word, starting at 'pos', and stop when we get to the |
| 40 | 106 |
// end of the word or the 'stopChar' character |
| 41 |
-func (sw *shellWord) processStopOn(stopChar rune) (string, error) {
|
|
| 107 |
+func (sw *shellWord) processStopOn(stopChar rune) (string, []string, error) {
|
|
| 42 | 108 |
var result string |
| 109 |
+ var words wordsStruct |
|
| 110 |
+ |
|
| 43 | 111 |
var charFuncMapping = map[rune]func() (string, error){
|
| 44 | 112 |
'\'': sw.processSingleQuote, |
| 45 | 113 |
'"': sw.processDoubleQuote, |
| ... | ... |
@@ -57,9 +125,15 @@ func (sw *shellWord) processStopOn(stopChar rune) (string, error) {
|
| 57 | 57 |
// Call special processing func for certain chars |
| 58 | 58 |
tmp, err := fn() |
| 59 | 59 |
if err != nil {
|
| 60 |
- return "", err |
|
| 60 |
+ return "", []string{}, err
|
|
| 61 | 61 |
} |
| 62 | 62 |
result += tmp |
| 63 |
+ |
|
| 64 |
+ if ch == rune('$') {
|
|
| 65 |
+ words.addString(tmp) |
|
| 66 |
+ } else {
|
|
| 67 |
+ words.addRawString(tmp) |
|
| 68 |
+ } |
|
| 63 | 69 |
} else {
|
| 64 | 70 |
// Not special, just add it to the result |
| 65 | 71 |
ch = sw.scanner.Next() |
| ... | ... |
@@ -73,13 +147,16 @@ func (sw *shellWord) processStopOn(stopChar rune) (string, error) {
|
| 73 | 73 |
break |
| 74 | 74 |
} |
| 75 | 75 |
|
| 76 |
+ words.addRawChar(ch) |
|
| 77 |
+ } else {
|
|
| 78 |
+ words.addChar(ch) |
|
| 76 | 79 |
} |
| 77 | 80 |
|
| 78 | 81 |
result += string(ch) |
| 79 | 82 |
} |
| 80 | 83 |
} |
| 81 | 84 |
|
| 82 |
- return result, nil |
|
| 85 |
+ return result, words.getWords(), nil |
|
| 83 | 86 |
} |
| 84 | 87 |
|
| 85 | 88 |
func (sw *shellWord) processSingleQuote() (string, error) {
|
| ... | ... |
@@ -160,7 +237,7 @@ func (sw *shellWord) processDollar() (string, error) {
|
| 160 | 160 |
sw.scanner.Next() // skip over : |
| 161 | 161 |
modifier := sw.scanner.Next() |
| 162 | 162 |
|
| 163 |
- word, err := sw.processStopOn('}')
|
|
| 163 |
+ word, _, err := sw.processStopOn('}')
|
|
| 164 | 164 |
if err != nil {
|
| 165 | 165 |
return "", err |
| 166 | 166 |
} |
| ... | ... |
@@ -7,10 +7,12 @@ import ( |
| 7 | 7 |
"testing" |
| 8 | 8 |
) |
| 9 | 9 |
|
| 10 |
-func TestShellParser(t *testing.T) {
|
|
| 11 |
- file, err := os.Open("words")
|
|
| 10 |
+func TestShellParser4EnvVars(t *testing.T) {
|
|
| 11 |
+ fn := "envVarTest" |
|
| 12 |
+ |
|
| 13 |
+ file, err := os.Open(fn) |
|
| 12 | 14 |
if err != nil {
|
| 13 |
- t.Fatalf("Can't open 'words': %s", err)
|
|
| 15 |
+ t.Fatalf("Can't open '%s': %s", err, fn)
|
|
| 14 | 16 |
} |
| 15 | 17 |
defer file.Close() |
| 16 | 18 |
|
| ... | ... |
@@ -32,7 +34,7 @@ func TestShellParser(t *testing.T) {
|
| 32 | 32 |
|
| 33 | 33 |
words := strings.Split(line, "|") |
| 34 | 34 |
if len(words) != 2 {
|
| 35 |
- t.Fatalf("Error in 'words' - should be 2 words:%q", words)
|
|
| 35 |
+ t.Fatalf("Error in '%s' - should be exactly one | in:%q", fn, line)
|
|
| 36 | 36 |
} |
| 37 | 37 |
|
| 38 | 38 |
words[0] = strings.TrimSpace(words[0]) |
| ... | ... |
@@ -50,6 +52,54 @@ func TestShellParser(t *testing.T) {
|
| 50 | 50 |
} |
| 51 | 51 |
} |
| 52 | 52 |
|
| 53 |
+func TestShellParser4Words(t *testing.T) {
|
|
| 54 |
+ fn := "wordsTest" |
|
| 55 |
+ |
|
| 56 |
+ file, err := os.Open(fn) |
|
| 57 |
+ if err != nil {
|
|
| 58 |
+ t.Fatalf("Can't open '%s': %s", err, fn)
|
|
| 59 |
+ } |
|
| 60 |
+ defer file.Close() |
|
| 61 |
+ |
|
| 62 |
+ envs := []string{}
|
|
| 63 |
+ scanner := bufio.NewScanner(file) |
|
| 64 |
+ for scanner.Scan() {
|
|
| 65 |
+ line := scanner.Text() |
|
| 66 |
+ |
|
| 67 |
+ if strings.HasPrefix(line, "#") {
|
|
| 68 |
+ continue |
|
| 69 |
+ } |
|
| 70 |
+ |
|
| 71 |
+ if strings.HasPrefix(line, "ENV ") {
|
|
| 72 |
+ line = strings.TrimLeft(line[3:], " ") |
|
| 73 |
+ envs = append(envs, line) |
|
| 74 |
+ continue |
|
| 75 |
+ } |
|
| 76 |
+ |
|
| 77 |
+ words := strings.Split(line, "|") |
|
| 78 |
+ if len(words) != 2 {
|
|
| 79 |
+ t.Fatalf("Error in '%s' - should be exactly one | in: %q", fn, line)
|
|
| 80 |
+ } |
|
| 81 |
+ test := strings.TrimSpace(words[0]) |
|
| 82 |
+ expected := strings.Split(strings.TrimLeft(words[1], " "), ",") |
|
| 83 |
+ |
|
| 84 |
+ result, err := ProcessWords(test, envs) |
|
| 85 |
+ |
|
| 86 |
+ if err != nil {
|
|
| 87 |
+ result = []string{"error"}
|
|
| 88 |
+ } |
|
| 89 |
+ |
|
| 90 |
+ if len(result) != len(expected) {
|
|
| 91 |
+ t.Fatalf("Error. %q was suppose to result in %q, but got %q instead", test, expected, result)
|
|
| 92 |
+ } |
|
| 93 |
+ for i, w := range expected {
|
|
| 94 |
+ if w != result[i] {
|
|
| 95 |
+ t.Fatalf("Error. %q was suppose to result in %q, but got %q instead", test, expected, result)
|
|
| 96 |
+ } |
|
| 97 |
+ } |
|
| 98 |
+ } |
|
| 99 |
+} |
|
| 100 |
+ |
|
| 53 | 101 |
func TestGetEnv(t *testing.T) {
|
| 54 | 102 |
sw := &shellWord{
|
| 55 | 103 |
word: "", |
| 56 | 104 |
deleted file mode 100644 |
| ... | ... |
@@ -1,112 +0,0 @@ |
| 1 |
-hello | hello |
|
| 2 |
-he'll'o | hello |
|
| 3 |
-he'llo | hello |
|
| 4 |
-he\'llo | he'llo |
|
| 5 |
-he\\'llo | he\llo |
|
| 6 |
-abc\tdef | abctdef |
|
| 7 |
-"abc\tdef" | abc\tdef |
|
| 8 |
-'abc\tdef' | abc\tdef |
|
| 9 |
-hello\ | hello |
|
| 10 |
-hello\\ | hello\ |
|
| 11 |
-"hello | hello |
|
| 12 |
-"hello\" | hello" |
|
| 13 |
-"hel'lo" | hel'lo |
|
| 14 |
-'hello | hello |
|
| 15 |
-'hello\' | hello\ |
|
| 16 |
-"''" | '' |
|
| 17 |
-$. | $. |
|
| 18 |
-$1 | |
|
| 19 |
-he$1x | hex |
|
| 20 |
-he$.x | he$.x |
|
| 21 |
-he$pwd. | he. |
|
| 22 |
-he$PWD | he/home |
|
| 23 |
-he\$PWD | he$PWD |
|
| 24 |
-he\\$PWD | he\/home |
|
| 25 |
-he\${} | he${}
|
|
| 26 |
-he\${}xx | he${}xx
|
|
| 27 |
-he${} | he
|
|
| 28 |
-he${}xx | hexx
|
|
| 29 |
-he${hi} | he
|
|
| 30 |
-he${hi}xx | hexx
|
|
| 31 |
-he${PWD} | he/home
|
|
| 32 |
-he${.} | error
|
|
| 33 |
-he${XXX:-000}xx | he000xx
|
|
| 34 |
-he${PWD:-000}xx | he/homexx
|
|
| 35 |
-he${XXX:-$PWD}xx | he/homexx
|
|
| 36 |
-he${XXX:-${PWD:-yyy}}xx | he/homexx
|
|
| 37 |
-he${XXX:-${YYY:-yyy}}xx | heyyyxx
|
|
| 38 |
-he${XXX:YYY} | error
|
|
| 39 |
-he${XXX:+${PWD}}xx | hexx
|
|
| 40 |
-he${PWD:+${XXX}}xx | hexx
|
|
| 41 |
-he${PWD:+${SHELL}}xx | hebashxx
|
|
| 42 |
-he${XXX:+000}xx | hexx
|
|
| 43 |
-he${PWD:+000}xx | he000xx
|
|
| 44 |
-'he${XX}' | he${XX}
|
|
| 45 |
-"he${PWD}" | he/home
|
|
| 46 |
-"he'$PWD'" | he'/home' |
|
| 47 |
-"$PWD" | /home |
|
| 48 |
-'$PWD' | $PWD |
|
| 49 |
-'\$PWD' | \$PWD |
|
| 50 |
-'"hello"' | "hello" |
|
| 51 |
-he\$PWD | he$PWD |
|
| 52 |
-"he\$PWD" | he$PWD |
|
| 53 |
-'he\$PWD' | he\$PWD |
|
| 54 |
-he${PWD | error
|
|
| 55 |
-he${PWD:=000}xx | error
|
|
| 56 |
-he${PWD:+${PWD}:}xx | he/home:xx
|
|
| 57 |
-he${XXX:-\$PWD:}xx | he$PWD:xx
|
|
| 58 |
-he${XXX:-\${PWD}z}xx | he${PWDz}xx
|
|
| 59 |
-안녕하세요 | 안녕하세요 |
|
| 60 |
-안'녕'하세요 | 안녕하세요 |
|
| 61 |
-안'녕하세요 | 안녕하세요 |
|
| 62 |
-안녕\'하세요 | 안녕'하세요 |
|
| 63 |
-안\\'녕하세요 | 안\녕하세요 |
|
| 64 |
-안녕\t하세요 | 안녕t하세요 |
|
| 65 |
-"안녕\t하세요" | 안녕\t하세요 |
|
| 66 |
-'안녕\t하세요 | 안녕\t하세요 |
|
| 67 |
-안녕하세요\ | 안녕하세요 |
|
| 68 |
-안녕하세요\\ | 안녕하세요\ |
|
| 69 |
-"안녕하세요 | 안녕하세요 |
|
| 70 |
-"안녕하세요\" | 안녕하세요" |
|
| 71 |
-"안녕'하세요" | 안녕'하세요 |
|
| 72 |
-'안녕하세요 | 안녕하세요 |
|
| 73 |
-'안녕하세요\' | 안녕하세요\ |
|
| 74 |
-안녕$1x | 안녕x |
|
| 75 |
-안녕$.x | 안녕$.x |
|
| 76 |
-안녕$pwd. | 안녕. |
|
| 77 |
-안녕$PWD | 안녕/home |
|
| 78 |
-안녕\$PWD | 안녕$PWD |
|
| 79 |
-안녕\\$PWD | 안녕\/home |
|
| 80 |
-안녕\${} | 안녕${}
|
|
| 81 |
-안녕\${}xx | 안녕${}xx
|
|
| 82 |
-안녕${} | 안녕
|
|
| 83 |
-안녕${}xx | 안녕xx
|
|
| 84 |
-안녕${hi} | 안녕
|
|
| 85 |
-안녕${hi}xx | 안녕xx
|
|
| 86 |
-안녕${PWD} | 안녕/home
|
|
| 87 |
-안녕${.} | error
|
|
| 88 |
-안녕${XXX:-000}xx | 안녕000xx
|
|
| 89 |
-안녕${PWD:-000}xx | 안녕/homexx
|
|
| 90 |
-안녕${XXX:-$PWD}xx | 안녕/homexx
|
|
| 91 |
-안녕${XXX:-${PWD:-yyy}}xx | 안녕/homexx
|
|
| 92 |
-안녕${XXX:-${YYY:-yyy}}xx | 안녕yyyxx
|
|
| 93 |
-안녕${XXX:YYY} | error
|
|
| 94 |
-안녕${XXX:+${PWD}}xx | 안녕xx
|
|
| 95 |
-안녕${PWD:+${XXX}}xx | 안녕xx
|
|
| 96 |
-안녕${PWD:+${SHELL}}xx | 안녕bashxx
|
|
| 97 |
-안녕${XXX:+000}xx | 안녕xx
|
|
| 98 |
-안녕${PWD:+000}xx | 안녕000xx
|
|
| 99 |
-'안녕${XX}' | 안녕${XX}
|
|
| 100 |
-"안녕${PWD}" | 안녕/home
|
|
| 101 |
-"안녕'$PWD'" | 안녕'/home' |
|
| 102 |
-'"안녕"' | "안녕" |
|
| 103 |
-안녕\$PWD | 안녕$PWD |
|
| 104 |
-"안녕\$PWD" | 안녕$PWD |
|
| 105 |
-'안녕\$PWD' | 안녕\$PWD |
|
| 106 |
-안녕${PWD | error
|
|
| 107 |
-안녕${PWD:=000}xx | error
|
|
| 108 |
-안녕${PWD:+${PWD}:}xx | 안녕/home:xx
|
|
| 109 |
-안녕${XXX:-\$PWD:}xx | 안녕$PWD:xx
|
|
| 110 |
-안녕${XXX:-\${PWD}z}xx | 안녕${PWDz}xx
|
|
| 111 |
-$KOREAN | 한국어 |
|
| 112 |
-안녕$KOREAN | 안녕한국어 |
| 113 | 1 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,25 @@ |
| 0 |
+hello | hello |
|
| 1 |
+hello${hi}bye | hellobye
|
|
| 2 |
+ENV hi=hi |
|
| 3 |
+hello${hi}bye | hellohibye
|
|
| 4 |
+ENV space=abc def |
|
| 5 |
+hello${space}bye | helloabc,defbye
|
|
| 6 |
+hello"${space}"bye | helloabc defbye
|
|
| 7 |
+hello "${space}"bye | hello,abc defbye
|
|
| 8 |
+ENV leading= ab c |
|
| 9 |
+hello${leading}def | hello,ab,cdef
|
|
| 10 |
+hello"${leading}" def | hello ab c,def
|
|
| 11 |
+hello"${leading}" | hello ab c
|
|
| 12 |
+hello${leading} | hello,ab,c
|
|
| 13 |
+# next line MUST have 3 trailing spaces, don't erase them! |
|
| 14 |
+ENV trailing=ab c |
|
| 15 |
+hello${trailing} | helloab,c
|
|
| 16 |
+hello${trailing}d | helloab,c,d
|
|
| 17 |
+hello"${trailing}"d | helloab c d
|
|
| 18 |
+# next line MUST have 3 trailing spaces, don't erase them! |
|
| 19 |
+hel"lo${trailing}" | helloab c
|
|
| 20 |
+hello" there " | hello there |
|
| 21 |
+hello there | hello,there |
|
| 22 |
+hello\ there | hello there |
|
| 23 |
+hello" there | hello there |
|
| 24 |
+hello\" there | hello",there |
| ... | ... |
@@ -150,6 +150,8 @@ func (s *DockerSuite) TestBuildEnvironmentReplacementExpose(c *check.C) {
|
| 150 | 150 |
FROM scratch |
| 151 | 151 |
ENV port 80 |
| 152 | 152 |
EXPOSE ${port}
|
| 153 |
+ ENV ports " 99 100 " |
|
| 154 |
+ EXPOSE ${ports}
|
|
| 153 | 155 |
`, true) |
| 154 | 156 |
if err != nil {
|
| 155 | 157 |
c.Fatal(err) |
| ... | ... |
@@ -166,8 +168,13 @@ func (s *DockerSuite) TestBuildEnvironmentReplacementExpose(c *check.C) {
|
| 166 | 166 |
c.Fatal(err) |
| 167 | 167 |
} |
| 168 | 168 |
|
| 169 |
- if _, ok := exposedPorts["80/tcp"]; !ok {
|
|
| 170 |
- c.Fatal("Exposed port 80 from environment not in Config.ExposedPorts on image")
|
|
| 169 |
+ exp := []int{80, 99, 100}
|
|
| 170 |
+ |
|
| 171 |
+ for _, p := range exp {
|
|
| 172 |
+ tmp := fmt.Sprintf("%d/tcp", p)
|
|
| 173 |
+ if _, ok := exposedPorts[tmp]; !ok {
|
|
| 174 |
+ c.Fatalf("Exposed port %d from environment not in Config.ExposedPorts on image", p)
|
|
| 175 |
+ } |
|
| 171 | 176 |
} |
| 172 | 177 |
|
| 173 | 178 |
} |