1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
|
package imagebuilder
import (
"fmt"
"io"
"strings"
"github.com/openshift/imagebuilder/dockerfile/command"
"github.com/openshift/imagebuilder/dockerfile/parser"
)
// ParseDockerfile parses the provided stream as a canonical Dockerfile
func ParseDockerfile(r io.Reader) (*parser.Node, error) {
result, err := parser.Parse(r)
if err != nil {
return nil, err
}
return result.AST, nil
}
// Environment variable interpolation will happen on these statements only.
var replaceEnvAllowed = map[string]bool{
command.Env: true,
command.Label: true,
command.Add: true,
command.Copy: true,
command.Workdir: true,
command.Expose: true,
command.Volume: true,
command.User: true,
command.StopSignal: true,
command.Arg: true,
}
// Certain commands are allowed to have their args split into more
// words after env var replacements. Meaning:
// ENV foo="123 456"
// EXPOSE $foo
// should result in the same thing as:
// EXPOSE 123 456
// and not treat "123 456" as a single word.
// Note that: EXPOSE "$foo" and EXPOSE $foo are not the same thing.
// Quotes will cause it to still be treated as single word.
var allowWordExpansion = map[string]bool{
command.Expose: true,
}
// Step represents the input Env and the output command after all
// post processing of the command arguments is done.
type Step struct {
Env []string
Command string
Args []string
Flags []string
Attrs map[string]bool
Message string
Original string
}
// Resolve transforms a parsed Dockerfile line into a command to execute,
// resolving any arguments.
//
// Almost all nodes will have this structure:
// Child[Node, Node, Node] where Child is from parser.Node.Children and each
// node comes from parser.Node.Next. This forms a "line" with a statement and
// arguments and we process them in this normalized form by hitting
// evaluateTable with the leaf nodes of the command and the Builder object.
//
// ONBUILD is a special case; in this case the parser will emit:
// Child[Node, Child[Node, Node...]] where the first node is the literal
// "onbuild" and the child entrypoint is the command of the ONBUILD statement,
// such as `RUN` in ONBUILD RUN foo. There is special case logic in here to
// deal with that, at least until it becomes more of a general concern with new
// features.
func (b *Step) Resolve(ast *parser.Node) error {
cmd := ast.Value
upperCasedCmd := strings.ToUpper(cmd)
// To ensure the user is given a decent error message if the platform
// on which the daemon is running does not support a builder command.
if err := platformSupports(strings.ToLower(cmd)); err != nil {
return err
}
attrs := ast.Attributes
original := ast.Original
flags := ast.Flags
strList := []string{}
msg := upperCasedCmd
if len(ast.Flags) > 0 {
msg += " " + strings.Join(ast.Flags, " ")
}
if cmd == "onbuild" {
if ast.Next == nil {
return fmt.Errorf("ONBUILD requires at least one argument")
}
ast = ast.Next.Children[0]
strList = append(strList, ast.Value)
msg += " " + ast.Value
if len(ast.Flags) > 0 {
msg += " " + strings.Join(ast.Flags, " ")
}
}
// count the number of nodes that we are going to traverse first
// so we can pre-create the argument and message array. This speeds up the
// allocation of those list a lot when they have a lot of arguments
cursor := ast
var n int
for cursor.Next != nil {
cursor = cursor.Next
n++
}
msgList := make([]string, n)
var i int
envs := b.Env
for ast.Next != nil {
ast = ast.Next
str := ast.Value
if replaceEnvAllowed[cmd] {
var err error
var words []string
if allowWordExpansion[cmd] {
words, err = ProcessWords(str, envs)
if err != nil {
return err
}
strList = append(strList, words...)
} else {
str, err = ProcessWord(str, envs)
if err != nil {
return err
}
strList = append(strList, str)
}
} else {
strList = append(strList, str)
}
msgList[i] = ast.Value
i++
}
msg += " " + strings.Join(msgList, " ")
b.Message = msg
b.Command = cmd
b.Args = strList
b.Original = original
b.Attrs = attrs
b.Flags = flags
return nil
}
|