github.com/mephux/docker@v1.6.0-rc5/builder/dispatchers.go (about) 1 package builder 2 3 // This file contains the dispatchers for each command. Note that 4 // `nullDispatch` is not actually a command, but support for commands we parse 5 // but do nothing with. 6 // 7 // See evaluator.go for a higher level discussion of the whole evaluator 8 // package. 9 10 import ( 11 "fmt" 12 "io/ioutil" 13 "path/filepath" 14 "regexp" 15 "sort" 16 "strings" 17 18 log "github.com/Sirupsen/logrus" 19 "github.com/docker/docker/nat" 20 flag "github.com/docker/docker/pkg/mflag" 21 "github.com/docker/docker/runconfig" 22 ) 23 24 const ( 25 // NoBaseImageSpecifier is the symbol used by the FROM 26 // command to specify that no base image is to be used. 27 NoBaseImageSpecifier string = "scratch" 28 ) 29 30 // dispatch with no layer / parsing. This is effectively not a command. 31 func nullDispatch(b *Builder, args []string, attributes map[string]bool, original string) error { 32 return nil 33 } 34 35 // ENV foo bar 36 // 37 // Sets the environment variable foo to bar, also makes interpolation 38 // in the dockerfile available from the next statement on via ${foo}. 39 // 40 func env(b *Builder, args []string, attributes map[string]bool, original string) error { 41 if len(args) == 0 { 42 return fmt.Errorf("ENV requires at least one argument") 43 } 44 45 if len(args)%2 != 0 { 46 // should never get here, but just in case 47 return fmt.Errorf("Bad input to ENV, too many args") 48 } 49 50 commitStr := "ENV" 51 52 for j := 0; j < len(args); j++ { 53 // name ==> args[j] 54 // value ==> args[j+1] 55 newVar := args[j] + "=" + args[j+1] + "" 56 commitStr += " " + newVar 57 58 gotOne := false 59 for i, envVar := range b.Config.Env { 60 envParts := strings.SplitN(envVar, "=", 2) 61 if envParts[0] == args[j] { 62 b.Config.Env[i] = newVar 63 gotOne = true 64 break 65 } 66 } 67 if !gotOne { 68 b.Config.Env = append(b.Config.Env, newVar) 69 } 70 j++ 71 } 72 73 return b.commit("", b.Config.Cmd, commitStr) 74 } 75 76 // MAINTAINER some text <maybe@an.email.address> 77 // 78 // Sets the maintainer metadata. 79 func maintainer(b *Builder, args []string, attributes map[string]bool, original string) error { 80 if len(args) != 1 { 81 return fmt.Errorf("MAINTAINER requires exactly one argument") 82 } 83 84 b.maintainer = args[0] 85 return b.commit("", b.Config.Cmd, fmt.Sprintf("MAINTAINER %s", b.maintainer)) 86 } 87 88 // LABEL some json data describing the image 89 // 90 // Sets the Label variable foo to bar, 91 // 92 func label(b *Builder, args []string, attributes map[string]bool, original string) error { 93 if len(args) == 0 { 94 return fmt.Errorf("LABEL requires at least one argument") 95 } 96 if len(args)%2 != 0 { 97 // should never get here, but just in case 98 return fmt.Errorf("Bad input to LABEL, too many args") 99 } 100 101 commitStr := "LABEL" 102 103 if b.Config.Labels == nil { 104 b.Config.Labels = map[string]string{} 105 } 106 107 for j := 0; j < len(args); j++ { 108 // name ==> args[j] 109 // value ==> args[j+1] 110 newVar := args[j] + "=" + args[j+1] + "" 111 commitStr += " " + newVar 112 113 b.Config.Labels[args[j]] = args[j+1] 114 j++ 115 } 116 return b.commit("", b.Config.Cmd, commitStr) 117 } 118 119 // ADD foo /path 120 // 121 // Add the file 'foo' to '/path'. Tarball and Remote URL (git, http) handling 122 // exist here. If you do not wish to have this automatic handling, use COPY. 123 // 124 func add(b *Builder, args []string, attributes map[string]bool, original string) error { 125 if len(args) < 2 { 126 return fmt.Errorf("ADD requires at least two arguments") 127 } 128 129 return b.runContextCommand(args, true, true, "ADD") 130 } 131 132 // COPY foo /path 133 // 134 // Same as 'ADD' but without the tar and remote url handling. 135 // 136 func dispatchCopy(b *Builder, args []string, attributes map[string]bool, original string) error { 137 if len(args) < 2 { 138 return fmt.Errorf("COPY requires at least two arguments") 139 } 140 141 return b.runContextCommand(args, false, false, "COPY") 142 } 143 144 // FROM imagename 145 // 146 // This sets the image the dockerfile will build on top of. 147 // 148 func from(b *Builder, args []string, attributes map[string]bool, original string) error { 149 if len(args) != 1 { 150 return fmt.Errorf("FROM requires one argument") 151 } 152 153 name := args[0] 154 155 if name == NoBaseImageSpecifier { 156 b.image = "" 157 b.noBaseImage = true 158 return nil 159 } 160 161 image, err := b.Daemon.Repositories().LookupImage(name) 162 if b.Pull { 163 image, err = b.pullImage(name) 164 if err != nil { 165 return err 166 } 167 } 168 if err != nil { 169 if b.Daemon.Graph().IsNotExist(err) { 170 image, err = b.pullImage(name) 171 } 172 173 // note that the top level err will still be !nil here if IsNotExist is 174 // not the error. This approach just simplifies hte logic a bit. 175 if err != nil { 176 return err 177 } 178 } 179 180 return b.processImageFrom(image) 181 } 182 183 // ONBUILD RUN echo yo 184 // 185 // ONBUILD triggers run when the image is used in a FROM statement. 186 // 187 // ONBUILD handling has a lot of special-case functionality, the heading in 188 // evaluator.go and comments around dispatch() in the same file explain the 189 // special cases. search for 'OnBuild' in internals.go for additional special 190 // cases. 191 // 192 func onbuild(b *Builder, args []string, attributes map[string]bool, original string) error { 193 if len(args) == 0 { 194 return fmt.Errorf("ONBUILD requires at least one argument") 195 } 196 197 triggerInstruction := strings.ToUpper(strings.TrimSpace(args[0])) 198 switch triggerInstruction { 199 case "ONBUILD": 200 return fmt.Errorf("Chaining ONBUILD via `ONBUILD ONBUILD` isn't allowed") 201 case "MAINTAINER", "FROM": 202 return fmt.Errorf("%s isn't allowed as an ONBUILD trigger", triggerInstruction) 203 } 204 205 original = regexp.MustCompile(`(?i)^\s*ONBUILD\s*`).ReplaceAllString(original, "") 206 207 b.Config.OnBuild = append(b.Config.OnBuild, original) 208 return b.commit("", b.Config.Cmd, fmt.Sprintf("ONBUILD %s", original)) 209 } 210 211 // WORKDIR /tmp 212 // 213 // Set the working directory for future RUN/CMD/etc statements. 214 // 215 func workdir(b *Builder, args []string, attributes map[string]bool, original string) error { 216 if len(args) != 1 { 217 return fmt.Errorf("WORKDIR requires exactly one argument") 218 } 219 220 workdir := args[0] 221 222 if !filepath.IsAbs(workdir) { 223 workdir = filepath.Join("/", b.Config.WorkingDir, workdir) 224 } 225 226 b.Config.WorkingDir = workdir 227 228 return b.commit("", b.Config.Cmd, fmt.Sprintf("WORKDIR %v", workdir)) 229 } 230 231 // RUN some command yo 232 // 233 // run a command and commit the image. Args are automatically prepended with 234 // 'sh -c' in the event there is only one argument. The difference in 235 // processing: 236 // 237 // RUN echo hi # sh -c echo hi 238 // RUN [ "echo", "hi" ] # echo hi 239 // 240 func run(b *Builder, args []string, attributes map[string]bool, original string) error { 241 if b.image == "" && !b.noBaseImage { 242 return fmt.Errorf("Please provide a source image with `from` prior to run") 243 } 244 245 args = handleJsonArgs(args, attributes) 246 247 if !attributes["json"] { 248 args = append([]string{"/bin/sh", "-c"}, args...) 249 } 250 251 runCmd := flag.NewFlagSet("run", flag.ContinueOnError) 252 runCmd.SetOutput(ioutil.Discard) 253 runCmd.Usage = nil 254 255 config, _, _, err := runconfig.Parse(runCmd, append([]string{b.image}, args...)) 256 if err != nil { 257 return err 258 } 259 260 cmd := b.Config.Cmd 261 // set Cmd manually, this is special case only for Dockerfiles 262 b.Config.Cmd = config.Cmd 263 runconfig.Merge(b.Config, config) 264 265 defer func(cmd []string) { b.Config.Cmd = cmd }(cmd) 266 267 log.Debugf("[BUILDER] Command to be executed: %v", b.Config.Cmd) 268 269 hit, err := b.probeCache() 270 if err != nil { 271 return err 272 } 273 if hit { 274 return nil 275 } 276 277 c, err := b.create() 278 if err != nil { 279 return err 280 } 281 282 // Ensure that we keep the container mounted until the commit 283 // to avoid unmounting and then mounting directly again 284 c.Mount() 285 defer c.Unmount() 286 287 err = b.run(c) 288 if err != nil { 289 return err 290 } 291 if err := b.commit(c.ID, cmd, "run"); err != nil { 292 return err 293 } 294 295 return nil 296 } 297 298 // CMD foo 299 // 300 // Set the default command to run in the container (which may be empty). 301 // Argument handling is the same as RUN. 302 // 303 func cmd(b *Builder, args []string, attributes map[string]bool, original string) error { 304 b.Config.Cmd = handleJsonArgs(args, attributes) 305 306 if !attributes["json"] { 307 b.Config.Cmd = append([]string{"/bin/sh", "-c"}, b.Config.Cmd...) 308 } 309 310 if err := b.commit("", b.Config.Cmd, fmt.Sprintf("CMD %q", b.Config.Cmd)); err != nil { 311 return err 312 } 313 314 if len(args) != 0 { 315 b.cmdSet = true 316 } 317 318 return nil 319 } 320 321 // ENTRYPOINT /usr/sbin/nginx 322 // 323 // Set the entrypoint (which defaults to sh -c) to /usr/sbin/nginx. Will 324 // accept the CMD as the arguments to /usr/sbin/nginx. 325 // 326 // Handles command processing similar to CMD and RUN, only b.Config.Entrypoint 327 // is initialized at NewBuilder time instead of through argument parsing. 328 // 329 func entrypoint(b *Builder, args []string, attributes map[string]bool, original string) error { 330 parsed := handleJsonArgs(args, attributes) 331 332 switch { 333 case attributes["json"]: 334 // ENTRYPOINT ["echo", "hi"] 335 b.Config.Entrypoint = parsed 336 case len(parsed) == 0: 337 // ENTRYPOINT [] 338 b.Config.Entrypoint = nil 339 default: 340 // ENTRYPOINT echo hi 341 b.Config.Entrypoint = []string{"/bin/sh", "-c", parsed[0]} 342 } 343 344 // when setting the entrypoint if a CMD was not explicitly set then 345 // set the command to nil 346 if !b.cmdSet { 347 b.Config.Cmd = nil 348 } 349 350 if err := b.commit("", b.Config.Cmd, fmt.Sprintf("ENTRYPOINT %q", b.Config.Entrypoint)); err != nil { 351 return err 352 } 353 354 return nil 355 } 356 357 // EXPOSE 6667/tcp 7000/tcp 358 // 359 // Expose ports for links and port mappings. This all ends up in 360 // b.Config.ExposedPorts for runconfig. 361 // 362 func expose(b *Builder, args []string, attributes map[string]bool, original string) error { 363 portsTab := args 364 365 if len(args) == 0 { 366 return fmt.Errorf("EXPOSE requires at least one argument") 367 } 368 369 if b.Config.ExposedPorts == nil { 370 b.Config.ExposedPorts = make(nat.PortSet) 371 } 372 373 ports, bindingMap, err := nat.ParsePortSpecs(append(portsTab, b.Config.PortSpecs...)) 374 if err != nil { 375 return err 376 } 377 378 for _, bindings := range bindingMap { 379 if bindings[0].HostIp != "" || bindings[0].HostPort != "" { 380 fmt.Fprintf(b.ErrStream, " ---> Using Dockerfile's EXPOSE instruction"+ 381 " to map host ports to container ports (ip:hostPort:containerPort) is deprecated.\n"+ 382 " Please use -p to publish the ports.\n") 383 } 384 } 385 386 // instead of using ports directly, we build a list of ports and sort it so 387 // the order is consistent. This prevents cache burst where map ordering 388 // changes between builds 389 portList := make([]string, len(ports)) 390 var i int 391 for port := range ports { 392 if _, exists := b.Config.ExposedPorts[port]; !exists { 393 b.Config.ExposedPorts[port] = struct{}{} 394 } 395 portList[i] = string(port) 396 i++ 397 } 398 sort.Strings(portList) 399 b.Config.PortSpecs = nil 400 return b.commit("", b.Config.Cmd, fmt.Sprintf("EXPOSE %s", strings.Join(portList, " "))) 401 } 402 403 // USER foo 404 // 405 // Set the user to 'foo' for future commands and when running the 406 // ENTRYPOINT/CMD at container run time. 407 // 408 func user(b *Builder, args []string, attributes map[string]bool, original string) error { 409 if len(args) != 1 { 410 return fmt.Errorf("USER requires exactly one argument") 411 } 412 413 b.Config.User = args[0] 414 return b.commit("", b.Config.Cmd, fmt.Sprintf("USER %v", args)) 415 } 416 417 // VOLUME /foo 418 // 419 // Expose the volume /foo for use. Will also accept the JSON array form. 420 // 421 func volume(b *Builder, args []string, attributes map[string]bool, original string) error { 422 if len(args) == 0 { 423 return fmt.Errorf("VOLUME requires at least one argument") 424 } 425 426 if b.Config.Volumes == nil { 427 b.Config.Volumes = map[string]struct{}{} 428 } 429 for _, v := range args { 430 v = strings.TrimSpace(v) 431 if v == "" { 432 return fmt.Errorf("Volume specified can not be an empty string") 433 } 434 b.Config.Volumes[v] = struct{}{} 435 } 436 if err := b.commit("", b.Config.Cmd, fmt.Sprintf("VOLUME %v", args)); err != nil { 437 return err 438 } 439 return nil 440 } 441 442 // INSERT is no longer accepted, but we still parse it. 443 func insert(b *Builder, args []string, attributes map[string]bool, original string) error { 444 return fmt.Errorf("INSERT has been deprecated. Please use ADD instead") 445 }