github.com/mheon/docker@v0.11.2-0.20150922122814-44f47903a831/builder/evaluator.go (about) 1 // Package builder 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 resposibility 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 builder 21 22 import ( 23 "fmt" 24 "io" 25 "os" 26 "path/filepath" 27 "runtime" 28 "strings" 29 30 "github.com/Sirupsen/logrus" 31 "github.com/docker/docker/api" 32 "github.com/docker/docker/builder/command" 33 "github.com/docker/docker/builder/parser" 34 "github.com/docker/docker/cliconfig" 35 "github.com/docker/docker/daemon" 36 "github.com/docker/docker/pkg/fileutils" 37 "github.com/docker/docker/pkg/streamformatter" 38 "github.com/docker/docker/pkg/stringid" 39 "github.com/docker/docker/pkg/symlink" 40 "github.com/docker/docker/pkg/tarsum" 41 "github.com/docker/docker/pkg/ulimit" 42 "github.com/docker/docker/runconfig" 43 "github.com/docker/docker/utils" 44 ) 45 46 // Environment variable interpolation will happen on these statements only. 47 var replaceEnvAllowed = map[string]struct{}{ 48 command.Env: {}, 49 command.Label: {}, 50 command.Add: {}, 51 command.Copy: {}, 52 command.Workdir: {}, 53 command.Expose: {}, 54 command.Volume: {}, 55 command.User: {}, 56 command.StopSignal: {}, 57 command.Arg: {}, 58 } 59 60 var evaluateTable map[string]func(*builder, []string, map[string]bool, string) error 61 62 func init() { 63 evaluateTable = map[string]func(*builder, []string, map[string]bool, string) error{ 64 command.Env: env, 65 command.Label: label, 66 command.Maintainer: maintainer, 67 command.Add: add, 68 command.Copy: dispatchCopy, // copy() is a go builtin 69 command.From: from, 70 command.Onbuild: onbuild, 71 command.Workdir: workdir, 72 command.Run: run, 73 command.Cmd: cmd, 74 command.Entrypoint: entrypoint, 75 command.Expose: expose, 76 command.Volume: volume, 77 command.User: user, 78 command.StopSignal: stopSignal, 79 command.Arg: arg, 80 } 81 } 82 83 // builder is an internal struct, used to maintain configuration of the Dockerfile's 84 // processing as it evaluates the parsing result. 85 type builder struct { 86 Daemon *daemon.Daemon 87 88 // effectively stdio for the run. Because it is not stdio, I said 89 // "Effectively". Do not use stdio anywhere in this package for any reason. 90 OutStream io.Writer 91 ErrStream io.Writer 92 93 Verbose bool 94 UtilizeCache bool 95 cacheBusted bool 96 97 // controls how images and containers are handled between steps. 98 Remove bool 99 ForceRemove bool 100 Pull bool 101 102 // set this to true if we want the builder to not commit between steps. 103 // This is useful when we only want to use the evaluator table to generate 104 // the final configs of the Dockerfile but dont want the layers 105 disableCommit bool 106 107 // Registry server auth configs used to pull images when handling `FROM`. 108 AuthConfigs map[string]cliconfig.AuthConfig 109 110 // Deprecated, original writer used for ImagePull. To be removed. 111 OutOld io.Writer 112 StreamFormatter *streamformatter.StreamFormatter 113 114 Config *runconfig.Config // runconfig for cmd, run, entrypoint etc. 115 116 buildArgs map[string]string // build-time args received in build context for expansion/substitution and commands in 'run'. 117 allowedBuildArgs map[string]bool // list of build-time args that are allowed for expansion/substitution and passing to commands in 'run'. 118 119 // both of these are controlled by the Remove and ForceRemove options in BuildOpts 120 TmpContainers map[string]struct{} // a map of containers used for removes 121 122 dockerfileName string // name of Dockerfile 123 dockerfile *parser.Node // the syntax tree of the dockerfile 124 image string // image name for commit processing 125 maintainer string // maintainer name. could probably be removed. 126 cmdSet bool // indicates is CMD was set in current Dockerfile 127 BuilderFlags *BFlags // current cmd's BuilderFlags - temporary 128 context tarsum.TarSum // the context is a tarball that is uploaded by the client 129 contextPath string // the path of the temporary directory the local context is unpacked to (server side) 130 noBaseImage bool // indicates that this build does not start from any base image, but is being built from an empty file system. 131 132 // Set resource restrictions for build containers 133 cpuSetCpus string 134 cpuSetMems string 135 cpuShares int64 136 cpuPeriod int64 137 cpuQuota int64 138 cgroupParent string 139 memory int64 140 memorySwap int64 141 ulimits []*ulimit.Ulimit 142 143 cancelled <-chan struct{} // When closed, job was cancelled. 144 145 activeImages []string 146 id string // Used to hold reference images 147 } 148 149 // Run the builder with the context. This is the lynchpin of this package. This 150 // will (barring errors): 151 // 152 // * call readContext() which will set up the temporary directory and unpack 153 // the context into it. 154 // * read the dockerfile 155 // * parse the dockerfile 156 // * walk the parse tree and execute it by dispatching to handlers. If Remove 157 // or ForceRemove is set, additional cleanup around containers happens after 158 // processing. 159 // * Print a happy message and return the image ID. 160 // 161 func (b *builder) Run(context io.Reader) (string, error) { 162 if err := b.readContext(context); err != nil { 163 return "", err 164 } 165 166 defer func() { 167 if err := os.RemoveAll(b.contextPath); err != nil { 168 logrus.Debugf("[BUILDER] failed to remove temporary context: %s", err) 169 } 170 }() 171 172 if err := b.readDockerfile(); err != nil { 173 return "", err 174 } 175 176 // some initializations that would not have been supplied by the caller. 177 b.Config = &runconfig.Config{} 178 179 b.TmpContainers = map[string]struct{}{} 180 181 for i, n := range b.dockerfile.Children { 182 select { 183 case <-b.cancelled: 184 logrus.Debug("Builder: build cancelled!") 185 fmt.Fprintf(b.OutStream, "Build cancelled") 186 return "", fmt.Errorf("Build cancelled") 187 default: 188 // Not cancelled yet, keep going... 189 } 190 if err := b.dispatch(i, n); err != nil { 191 if b.ForceRemove { 192 b.clearTmp() 193 } 194 return "", err 195 } 196 fmt.Fprintf(b.OutStream, " ---> %s\n", stringid.TruncateID(b.image)) 197 if b.Remove { 198 b.clearTmp() 199 } 200 } 201 202 // check if there are any leftover build-args that were passed but not 203 // consumed during build. Return an error, if there are any. 204 leftoverArgs := []string{} 205 for arg := range b.buildArgs { 206 if !b.isBuildArgAllowed(arg) { 207 leftoverArgs = append(leftoverArgs, arg) 208 } 209 } 210 if len(leftoverArgs) > 0 { 211 return "", fmt.Errorf("One or more build-args %v were not consumed, failing build.", leftoverArgs) 212 } 213 214 if b.image == "" { 215 return "", fmt.Errorf("No image was generated. Is your Dockerfile empty?") 216 } 217 218 fmt.Fprintf(b.OutStream, "Successfully built %s\n", stringid.TruncateID(b.image)) 219 return b.image, nil 220 } 221 222 // Reads a Dockerfile from the current context. It assumes that the 223 // 'filename' is a relative path from the root of the context 224 func (b *builder) readDockerfile() error { 225 // If no -f was specified then look for 'Dockerfile'. If we can't find 226 // that then look for 'dockerfile'. If neither are found then default 227 // back to 'Dockerfile' and use that in the error message. 228 if b.dockerfileName == "" { 229 b.dockerfileName = api.DefaultDockerfileName 230 tmpFN := filepath.Join(b.contextPath, api.DefaultDockerfileName) 231 if _, err := os.Lstat(tmpFN); err != nil { 232 tmpFN = filepath.Join(b.contextPath, strings.ToLower(api.DefaultDockerfileName)) 233 if _, err := os.Lstat(tmpFN); err == nil { 234 b.dockerfileName = strings.ToLower(api.DefaultDockerfileName) 235 } 236 } 237 } 238 239 origFile := b.dockerfileName 240 241 filename, err := symlink.FollowSymlinkInScope(filepath.Join(b.contextPath, origFile), b.contextPath) 242 if err != nil { 243 return fmt.Errorf("The Dockerfile (%s) must be within the build context", origFile) 244 } 245 246 fi, err := os.Lstat(filename) 247 if os.IsNotExist(err) { 248 return fmt.Errorf("Cannot locate specified Dockerfile: %s", origFile) 249 } 250 if fi.Size() == 0 { 251 return fmt.Errorf("The Dockerfile (%s) cannot be empty", origFile) 252 } 253 254 f, err := os.Open(filename) 255 if err != nil { 256 return err 257 } 258 259 b.dockerfile, err = parser.Parse(f) 260 f.Close() 261 262 if err != nil { 263 return err 264 } 265 266 // After the Dockerfile has been parsed, we need to check the .dockerignore 267 // file for either "Dockerfile" or ".dockerignore", and if either are 268 // present then erase them from the build context. These files should never 269 // have been sent from the client but we did send them to make sure that 270 // we had the Dockerfile to actually parse, and then we also need the 271 // .dockerignore file to know whether either file should be removed. 272 // Note that this assumes the Dockerfile has been read into memory and 273 // is now safe to be removed. 274 275 excludes, _ := utils.ReadDockerIgnore(filepath.Join(b.contextPath, ".dockerignore")) 276 if rm, _ := fileutils.Matches(".dockerignore", excludes); rm == true { 277 os.Remove(filepath.Join(b.contextPath, ".dockerignore")) 278 b.context.(tarsum.BuilderContext).Remove(".dockerignore") 279 } 280 if rm, _ := fileutils.Matches(b.dockerfileName, excludes); rm == true { 281 os.Remove(filepath.Join(b.contextPath, b.dockerfileName)) 282 b.context.(tarsum.BuilderContext).Remove(b.dockerfileName) 283 } 284 285 return nil 286 } 287 288 // determine if build arg is part of built-in args or user 289 // defined args in Dockerfile at any point in time. 290 func (b *builder) isBuildArgAllowed(arg string) bool { 291 if _, ok := BuiltinAllowedBuildArgs[arg]; ok { 292 return true 293 } 294 if _, ok := b.allowedBuildArgs[arg]; ok { 295 return true 296 } 297 return false 298 } 299 300 // This method is the entrypoint to all statement handling routines. 301 // 302 // Almost all nodes will have this structure: 303 // Child[Node, Node, Node] where Child is from parser.Node.Children and each 304 // node comes from parser.Node.Next. This forms a "line" with a statement and 305 // arguments and we process them in this normalized form by hitting 306 // evaluateTable with the leaf nodes of the command and the Builder object. 307 // 308 // ONBUILD is a special case; in this case the parser will emit: 309 // Child[Node, Child[Node, Node...]] where the first node is the literal 310 // "onbuild" and the child entrypoint is the command of the ONBUILD statmeent, 311 // such as `RUN` in ONBUILD RUN foo. There is special case logic in here to 312 // deal with that, at least until it becomes more of a general concern with new 313 // features. 314 func (b *builder) dispatch(stepN int, ast *parser.Node) error { 315 cmd := ast.Value 316 317 // To ensure the user is give a decent error message if the platform 318 // on which the daemon is running does not support a builder command. 319 if err := platformSupports(strings.ToLower(cmd)); err != nil { 320 return err 321 } 322 323 attrs := ast.Attributes 324 original := ast.Original 325 flags := ast.Flags 326 strs := []string{} 327 msg := fmt.Sprintf("Step %d : %s", stepN+1, strings.ToUpper(cmd)) 328 329 if len(ast.Flags) > 0 { 330 msg += " " + strings.Join(ast.Flags, " ") 331 } 332 333 if cmd == "onbuild" { 334 if ast.Next == nil { 335 return fmt.Errorf("ONBUILD requires at least one argument") 336 } 337 ast = ast.Next.Children[0] 338 strs = append(strs, ast.Value) 339 msg += " " + ast.Value 340 341 if len(ast.Flags) > 0 { 342 msg += " " + strings.Join(ast.Flags, " ") 343 } 344 345 } 346 347 // count the number of nodes that we are going to traverse first 348 // so we can pre-create the argument and message array. This speeds up the 349 // allocation of those list a lot when they have a lot of arguments 350 cursor := ast 351 var n int 352 for cursor.Next != nil { 353 cursor = cursor.Next 354 n++ 355 } 356 l := len(strs) 357 strList := make([]string, n+l) 358 copy(strList, strs) 359 msgList := make([]string, n) 360 361 var i int 362 // Append the build-time args to config-environment. 363 // This allows builder config to override the variables, making the behavior similar to 364 // a shell script i.e. `ENV foo bar` overrides value of `foo` passed in build 365 // context. But `ENV foo $foo` will use the value from build context if one 366 // isn't already been defined by a previous ENV primitive. 367 // Note, we get this behavior because we know that ProcessWord() will 368 // stop on the first occurrence of a variable name and not notice 369 // a subsequent one. So, putting the buildArgs list after the Config.Env 370 // list, in 'envs', is safe. 371 envs := b.Config.Env 372 for key, val := range b.buildArgs { 373 if !b.isBuildArgAllowed(key) { 374 // skip build-args that are not in allowed list, meaning they have 375 // not been defined by an "ARG" Dockerfile command yet. 376 // This is an error condition but only if there is no "ARG" in the entire 377 // Dockerfile, so we'll generate any necessary errors after we parsed 378 // the entire file (see 'leftoverArgs' processing in evaluator.go ) 379 continue 380 } 381 envs = append(envs, fmt.Sprintf("%s=%s", key, val)) 382 } 383 for ast.Next != nil { 384 ast = ast.Next 385 var str string 386 str = ast.Value 387 if _, ok := replaceEnvAllowed[cmd]; ok { 388 var err error 389 str, err = ProcessWord(ast.Value, envs) 390 if err != nil { 391 return err 392 } 393 } 394 strList[i+l] = str 395 msgList[i] = ast.Value 396 i++ 397 } 398 399 msg += " " + strings.Join(msgList, " ") 400 fmt.Fprintln(b.OutStream, msg) 401 402 // XXX yes, we skip any cmds that are not valid; the parser should have 403 // picked these out already. 404 if f, ok := evaluateTable[cmd]; ok { 405 b.BuilderFlags = NewBFlags() 406 b.BuilderFlags.Args = flags 407 return f(b, strList, attrs, original) 408 } 409 410 return fmt.Errorf("Unknown instruction: %s", strings.ToUpper(cmd)) 411 } 412 413 // platformSupports is a short-term function to give users a quality error 414 // message if a Dockerfile uses a command not supported on the platform. 415 func platformSupports(command string) error { 416 if runtime.GOOS != "windows" { 417 return nil 418 } 419 switch command { 420 case "expose", "volume", "user", "stopsignal", "arg": 421 return fmt.Errorf("The daemon on this platform does not support the command '%s'", command) 422 } 423 return nil 424 }