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