github.com/vieux/docker@v0.6.3-0.20161004191708-e097c2a938c7/builder/dockerfile/evaluator.go (about) 1 // Package dockerfile is the evaluation step in the Dockerfile parse/evaluate pipeline. 2 // 3 // It incorporates a dispatch table based on the parser.Node values (see the 4 // parser package for more information) that are yielded from the parser itself. 5 // Calling NewBuilder with the BuildOpts struct can be used to customize the 6 // experience for execution purposes only. Parsing is controlled in the parser 7 // package, and this division of responsibility should be respected. 8 // 9 // Please see the jump table targets for the actual invocations, most of which 10 // will call out to the functions in internals.go to deal with their tasks. 11 // 12 // ONBUILD is a special case, which is covered in the onbuild() func in 13 // dispatchers.go. 14 // 15 // The evaluator uses the concept of "steps", which are usually each processable 16 // line in the Dockerfile. Each step is numbered and certain actions are taken 17 // before and after each step, such as creating an image ID and removing temporary 18 // containers and images. Note that ONBUILD creates a kinda-sorta "sub run" which 19 // includes its own set of steps (usually only one of them). 20 package dockerfile 21 22 import ( 23 "fmt" 24 "strings" 25 26 "github.com/docker/docker/builder/dockerfile/command" 27 "github.com/docker/docker/builder/dockerfile/parser" 28 ) 29 30 // Environment variable interpolation will happen on these statements only. 31 var replaceEnvAllowed = map[string]bool{ 32 command.Env: true, 33 command.Label: true, 34 command.Add: true, 35 command.Copy: true, 36 command.Workdir: true, 37 command.Expose: true, 38 command.Volume: true, 39 command.User: true, 40 command.StopSignal: true, 41 command.Arg: true, 42 } 43 44 // Certain commands are allowed to have their args split into more 45 // words after env var replacements. Meaning: 46 // ENV foo="123 456" 47 // EXPOSE $foo 48 // should result in the same thing as: 49 // EXPOSE 123 456 50 // and not treat "123 456" as a single word. 51 // Note that: EXPOSE "$foo" and EXPOSE $foo are not the same thing. 52 // Quotes will cause it to still be treated as single word. 53 var allowWordExpansion = map[string]bool{ 54 command.Expose: true, 55 } 56 57 var evaluateTable map[string]func(*Builder, []string, map[string]bool, string) error 58 59 func init() { 60 evaluateTable = map[string]func(*Builder, []string, map[string]bool, string) error{ 61 command.Add: add, 62 command.Arg: arg, 63 command.Cmd: cmd, 64 command.Copy: dispatchCopy, // copy() is a go builtin 65 command.Entrypoint: entrypoint, 66 command.Env: env, 67 command.Expose: expose, 68 command.From: from, 69 command.Healthcheck: healthcheck, 70 command.Label: label, 71 command.Maintainer: maintainer, 72 command.Onbuild: onbuild, 73 command.Run: run, 74 command.Shell: shell, 75 command.StopSignal: stopSignal, 76 command.User: user, 77 command.Volume: volume, 78 command.Workdir: workdir, 79 } 80 } 81 82 // This method is the entrypoint to all statement handling routines. 83 // 84 // Almost all nodes will have this structure: 85 // Child[Node, Node, Node] where Child is from parser.Node.Children and each 86 // node comes from parser.Node.Next. This forms a "line" with a statement and 87 // arguments and we process them in this normalized form by hitting 88 // evaluateTable with the leaf nodes of the command and the Builder object. 89 // 90 // ONBUILD is a special case; in this case the parser will emit: 91 // Child[Node, Child[Node, Node...]] where the first node is the literal 92 // "onbuild" and the child entrypoint is the command of the ONBUILD statement, 93 // such as `RUN` in ONBUILD RUN foo. There is special case logic in here to 94 // deal with that, at least until it becomes more of a general concern with new 95 // features. 96 func (b *Builder) dispatch(stepN int, stepTotal int, ast *parser.Node) error { 97 cmd := ast.Value 98 upperCasedCmd := strings.ToUpper(cmd) 99 100 // To ensure the user is given a decent error message if the platform 101 // on which the daemon is running does not support a builder command. 102 if err := platformSupports(strings.ToLower(cmd)); err != nil { 103 return err 104 } 105 106 attrs := ast.Attributes 107 original := ast.Original 108 flags := ast.Flags 109 strList := []string{} 110 msg := fmt.Sprintf("Step %d/%d : %s", stepN+1, stepTotal, upperCasedCmd) 111 112 if len(ast.Flags) > 0 { 113 msg += " " + strings.Join(ast.Flags, " ") 114 } 115 116 if cmd == "onbuild" { 117 if ast.Next == nil { 118 return fmt.Errorf("ONBUILD requires at least one argument") 119 } 120 ast = ast.Next.Children[0] 121 strList = append(strList, ast.Value) 122 msg += " " + ast.Value 123 124 if len(ast.Flags) > 0 { 125 msg += " " + strings.Join(ast.Flags, " ") 126 } 127 128 } 129 130 // count the number of nodes that we are going to traverse first 131 // so we can pre-create the argument and message array. This speeds up the 132 // allocation of those list a lot when they have a lot of arguments 133 cursor := ast 134 var n int 135 for cursor.Next != nil { 136 cursor = cursor.Next 137 n++ 138 } 139 msgList := make([]string, n) 140 141 var i int 142 // Append the build-time args to config-environment. 143 // This allows builder config to override the variables, making the behavior similar to 144 // a shell script i.e. `ENV foo bar` overrides value of `foo` passed in build 145 // context. But `ENV foo $foo` will use the value from build context if one 146 // isn't already been defined by a previous ENV primitive. 147 // Note, we get this behavior because we know that ProcessWord() will 148 // stop on the first occurrence of a variable name and not notice 149 // a subsequent one. So, putting the buildArgs list after the Config.Env 150 // list, in 'envs', is safe. 151 envs := b.runConfig.Env 152 for key, val := range b.options.BuildArgs { 153 if !b.isBuildArgAllowed(key) { 154 // skip build-args that are not in allowed list, meaning they have 155 // not been defined by an "ARG" Dockerfile command yet. 156 // This is an error condition but only if there is no "ARG" in the entire 157 // Dockerfile, so we'll generate any necessary errors after we parsed 158 // the entire file (see 'leftoverArgs' processing in evaluator.go ) 159 continue 160 } 161 envs = append(envs, fmt.Sprintf("%s=%s", key, val)) 162 } 163 for ast.Next != nil { 164 ast = ast.Next 165 var str string 166 str = ast.Value 167 if replaceEnvAllowed[cmd] { 168 var err error 169 var words []string 170 171 if allowWordExpansion[cmd] { 172 words, err = ProcessWords(str, envs) 173 if err != nil { 174 return err 175 } 176 strList = append(strList, words...) 177 } else { 178 str, err = ProcessWord(str, envs) 179 if err != nil { 180 return err 181 } 182 strList = append(strList, str) 183 } 184 } else { 185 strList = append(strList, str) 186 } 187 msgList[i] = ast.Value 188 i++ 189 } 190 191 msg += " " + strings.Join(msgList, " ") 192 fmt.Fprintln(b.Stdout, msg) 193 194 // XXX yes, we skip any cmds that are not valid; the parser should have 195 // picked these out already. 196 if f, ok := evaluateTable[cmd]; ok { 197 b.flags = NewBFlags() 198 b.flags.Args = flags 199 return f(b, strList, attrs, original) 200 } 201 202 return fmt.Errorf("Unknown instruction: %s", upperCasedCmd) 203 } 204 205 // checkDispatch does a simple check for syntax errors of the Dockerfile. 206 // Because some of the instructions can only be validated through runtime, 207 // arg, env, etc., this syntax check will not be complete and could not replace 208 // the runtime check. Instead, this function is only a helper that allows 209 // user to find out the obvious error in Dockerfile earlier on. 210 // onbuild bool: indicate if instruction XXX is part of `ONBUILD XXX` trigger 211 func (b *Builder) checkDispatch(ast *parser.Node, onbuild bool) error { 212 cmd := ast.Value 213 upperCasedCmd := strings.ToUpper(cmd) 214 215 // To ensure the user is given a decent error message if the platform 216 // on which the daemon is running does not support a builder command. 217 if err := platformSupports(strings.ToLower(cmd)); err != nil { 218 return err 219 } 220 221 // The instruction itself is ONBUILD, we will make sure it follows with at 222 // least one argument 223 if upperCasedCmd == "ONBUILD" { 224 if ast.Next == nil { 225 return fmt.Errorf("ONBUILD requires at least one argument") 226 } 227 } 228 229 // The instruction is part of ONBUILD trigger (not the instruction itself) 230 if onbuild { 231 switch upperCasedCmd { 232 case "ONBUILD": 233 return fmt.Errorf("Chaining ONBUILD via `ONBUILD ONBUILD` isn't allowed") 234 case "MAINTAINER", "FROM": 235 return fmt.Errorf("%s isn't allowed as an ONBUILD trigger", upperCasedCmd) 236 } 237 } 238 239 if _, ok := evaluateTable[cmd]; ok { 240 return nil 241 } 242 243 return fmt.Errorf("Unknown instruction: %s", upperCasedCmd) 244 }