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