github.com/mheon/docker@v0.11.2-0.20150922122814-44f47903a831/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 "os" 14 "path/filepath" 15 "regexp" 16 "runtime" 17 "sort" 18 "strings" 19 20 "github.com/Sirupsen/logrus" 21 derr "github.com/docker/docker/errors" 22 flag "github.com/docker/docker/pkg/mflag" 23 "github.com/docker/docker/pkg/nat" 24 "github.com/docker/docker/pkg/signal" 25 "github.com/docker/docker/pkg/stringutils" 26 "github.com/docker/docker/pkg/system" 27 "github.com/docker/docker/runconfig" 28 ) 29 30 const ( 31 // NoBaseImageSpecifier is the symbol used by the FROM 32 // command to specify that no base image is to be used. 33 NoBaseImageSpecifier string = "scratch" 34 ) 35 36 // dispatch with no layer / parsing. This is effectively not a command. 37 func nullDispatch(b *builder, args []string, attributes map[string]bool, original string) error { 38 return nil 39 } 40 41 // ENV foo bar 42 // 43 // Sets the environment variable foo to bar, also makes interpolation 44 // in the dockerfile available from the next statement on via ${foo}. 45 // 46 func env(b *builder, args []string, attributes map[string]bool, original string) error { 47 if len(args) == 0 { 48 return derr.ErrorCodeAtLeastOneArg.WithArgs("ENV") 49 } 50 51 if len(args)%2 != 0 { 52 // should never get here, but just in case 53 return derr.ErrorCodeTooManyArgs.WithArgs("ENV") 54 } 55 56 if err := b.BuilderFlags.Parse(); err != nil { 57 return err 58 } 59 60 // TODO/FIXME/NOT USED 61 // Just here to show how to use the builder flags stuff within the 62 // context of a builder command. Will remove once we actually add 63 // a builder command to something! 64 /* 65 flBool1 := b.BuilderFlags.AddBool("bool1", false) 66 flStr1 := b.BuilderFlags.AddString("str1", "HI") 67 68 if err := b.BuilderFlags.Parse(); err != nil { 69 return err 70 } 71 72 fmt.Printf("Bool1:%v\n", flBool1) 73 fmt.Printf("Str1:%v\n", flStr1) 74 */ 75 76 commitStr := "ENV" 77 78 for j := 0; j < len(args); j++ { 79 // name ==> args[j] 80 // value ==> args[j+1] 81 newVar := args[j] + "=" + args[j+1] + "" 82 commitStr += " " + newVar 83 84 gotOne := false 85 for i, envVar := range b.Config.Env { 86 envParts := strings.SplitN(envVar, "=", 2) 87 if envParts[0] == args[j] { 88 b.Config.Env[i] = newVar 89 gotOne = true 90 break 91 } 92 } 93 if !gotOne { 94 b.Config.Env = append(b.Config.Env, newVar) 95 } 96 j++ 97 } 98 99 return b.commit("", b.Config.Cmd, commitStr) 100 } 101 102 // MAINTAINER some text <maybe@an.email.address> 103 // 104 // Sets the maintainer metadata. 105 func maintainer(b *builder, args []string, attributes map[string]bool, original string) error { 106 if len(args) != 1 { 107 return derr.ErrorCodeExactlyOneArg.WithArgs("MAINTAINER") 108 } 109 110 if err := b.BuilderFlags.Parse(); err != nil { 111 return err 112 } 113 114 b.maintainer = args[0] 115 return b.commit("", b.Config.Cmd, fmt.Sprintf("MAINTAINER %s", b.maintainer)) 116 } 117 118 // LABEL some json data describing the image 119 // 120 // Sets the Label variable foo to bar, 121 // 122 func label(b *builder, args []string, attributes map[string]bool, original string) error { 123 if len(args) == 0 { 124 return derr.ErrorCodeAtLeastOneArg.WithArgs("LABEL") 125 } 126 if len(args)%2 != 0 { 127 // should never get here, but just in case 128 return derr.ErrorCodeTooManyArgs.WithArgs("LABEL") 129 } 130 131 if err := b.BuilderFlags.Parse(); err != nil { 132 return err 133 } 134 135 commitStr := "LABEL" 136 137 if b.Config.Labels == nil { 138 b.Config.Labels = map[string]string{} 139 } 140 141 for j := 0; j < len(args); j++ { 142 // name ==> args[j] 143 // value ==> args[j+1] 144 newVar := args[j] + "=" + args[j+1] + "" 145 commitStr += " " + newVar 146 147 b.Config.Labels[args[j]] = args[j+1] 148 j++ 149 } 150 return b.commit("", b.Config.Cmd, commitStr) 151 } 152 153 // ADD foo /path 154 // 155 // Add the file 'foo' to '/path'. Tarball and Remote URL (git, http) handling 156 // exist here. If you do not wish to have this automatic handling, use COPY. 157 // 158 func add(b *builder, args []string, attributes map[string]bool, original string) error { 159 if len(args) < 2 { 160 return derr.ErrorCodeAtLeastTwoArgs.WithArgs("ADD") 161 } 162 163 if err := b.BuilderFlags.Parse(); err != nil { 164 return err 165 } 166 167 return b.runContextCommand(args, true, true, "ADD") 168 } 169 170 // COPY foo /path 171 // 172 // Same as 'ADD' but without the tar and remote url handling. 173 // 174 func dispatchCopy(b *builder, args []string, attributes map[string]bool, original string) error { 175 if len(args) < 2 { 176 return derr.ErrorCodeAtLeastTwoArgs.WithArgs("COPY") 177 } 178 179 if err := b.BuilderFlags.Parse(); err != nil { 180 return err 181 } 182 183 return b.runContextCommand(args, false, false, "COPY") 184 } 185 186 // FROM imagename 187 // 188 // This sets the image the dockerfile will build on top of. 189 // 190 func from(b *builder, args []string, attributes map[string]bool, original string) error { 191 if len(args) != 1 { 192 return derr.ErrorCodeExactlyOneArg.WithArgs("FROM") 193 } 194 195 if err := b.BuilderFlags.Parse(); err != nil { 196 return err 197 } 198 199 name := args[0] 200 201 // Windows cannot support a container with no base image. 202 if name == NoBaseImageSpecifier { 203 if runtime.GOOS == "windows" { 204 return fmt.Errorf("Windows does not support FROM scratch") 205 } 206 b.image = "" 207 b.noBaseImage = true 208 return nil 209 } 210 211 image, err := b.Daemon.Repositories().LookupImage(name) 212 if b.Pull { 213 image, err = b.pullImage(name) 214 if err != nil { 215 return err 216 } 217 } 218 if err != nil { 219 if b.Daemon.Graph().IsNotExist(err, name) { 220 image, err = b.pullImage(name) 221 } 222 223 // note that the top level err will still be !nil here if IsNotExist is 224 // not the error. This approach just simplifies the logic a bit. 225 if err != nil { 226 return err 227 } 228 } 229 230 return b.processImageFrom(image) 231 } 232 233 // ONBUILD RUN echo yo 234 // 235 // ONBUILD triggers run when the image is used in a FROM statement. 236 // 237 // ONBUILD handling has a lot of special-case functionality, the heading in 238 // evaluator.go and comments around dispatch() in the same file explain the 239 // special cases. search for 'OnBuild' in internals.go for additional special 240 // cases. 241 // 242 func onbuild(b *builder, args []string, attributes map[string]bool, original string) error { 243 if len(args) == 0 { 244 return derr.ErrorCodeAtLeastOneArg.WithArgs("ONBUILD") 245 } 246 247 if err := b.BuilderFlags.Parse(); err != nil { 248 return err 249 } 250 251 triggerInstruction := strings.ToUpper(strings.TrimSpace(args[0])) 252 switch triggerInstruction { 253 case "ONBUILD": 254 return derr.ErrorCodeChainOnBuild 255 case "MAINTAINER", "FROM": 256 return derr.ErrorCodeBadOnBuildCmd.WithArgs(triggerInstruction) 257 } 258 259 original = regexp.MustCompile(`(?i)^\s*ONBUILD\s*`).ReplaceAllString(original, "") 260 261 b.Config.OnBuild = append(b.Config.OnBuild, original) 262 return b.commit("", b.Config.Cmd, fmt.Sprintf("ONBUILD %s", original)) 263 } 264 265 // WORKDIR /tmp 266 // 267 // Set the working directory for future RUN/CMD/etc statements. 268 // 269 func workdir(b *builder, args []string, attributes map[string]bool, original string) error { 270 if len(args) != 1 { 271 return derr.ErrorCodeExactlyOneArg.WithArgs("WORKDIR") 272 } 273 274 if err := b.BuilderFlags.Parse(); err != nil { 275 return err 276 } 277 278 // This is from the Dockerfile and will not necessarily be in platform 279 // specific semantics, hence ensure it is converted. 280 workdir := filepath.FromSlash(args[0]) 281 282 if !system.IsAbs(workdir) { 283 current := filepath.FromSlash(b.Config.WorkingDir) 284 workdir = filepath.Join(string(os.PathSeparator), current, workdir) 285 } 286 287 b.Config.WorkingDir = workdir 288 289 return b.commit("", b.Config.Cmd, fmt.Sprintf("WORKDIR %v", workdir)) 290 } 291 292 // RUN some command yo 293 // 294 // run a command and commit the image. Args are automatically prepended with 295 // 'sh -c' under linux or 'cmd /S /C' under Windows, in the event there is 296 // only one argument. The difference in processing: 297 // 298 // RUN echo hi # sh -c echo hi (Linux) 299 // RUN echo hi # cmd /S /C echo hi (Windows) 300 // RUN [ "echo", "hi" ] # echo hi 301 // 302 func run(b *builder, args []string, attributes map[string]bool, original string) error { 303 if b.image == "" && !b.noBaseImage { 304 return derr.ErrorCodeMissingFrom 305 } 306 307 if err := b.BuilderFlags.Parse(); err != nil { 308 return err 309 } 310 311 args = handleJSONArgs(args, attributes) 312 313 if !attributes["json"] { 314 if runtime.GOOS != "windows" { 315 args = append([]string{"/bin/sh", "-c"}, args...) 316 } else { 317 args = append([]string{"cmd", "/S /C"}, args...) 318 } 319 } 320 321 runCmd := flag.NewFlagSet("run", flag.ContinueOnError) 322 runCmd.SetOutput(ioutil.Discard) 323 runCmd.Usage = nil 324 325 config, _, _, err := runconfig.Parse(runCmd, append([]string{b.image}, args...)) 326 if err != nil { 327 return err 328 } 329 330 // stash the cmd 331 cmd := b.Config.Cmd 332 runconfig.Merge(b.Config, config) 333 // stash the config environment 334 env := b.Config.Env 335 336 defer func(cmd *stringutils.StrSlice) { b.Config.Cmd = cmd }(cmd) 337 defer func(env []string) { b.Config.Env = env }(env) 338 339 // derive the net build-time environment for this run. We let config 340 // environment override the build time environment. 341 // This means that we take the b.buildArgs list of env vars and remove 342 // any of those variables that are defined as part of the container. In other 343 // words, anything in b.Config.Env. What's left is the list of build-time env 344 // vars that we need to add to each RUN command - note the list could be empty. 345 // 346 // We don't persist the build time environment with container's config 347 // environment, but just sort and prepend it to the command string at time 348 // of commit. 349 // This helps with tracing back the image's actual environment at the time 350 // of RUN, without leaking it to the final image. It also aids cache 351 // lookup for same image built with same build time environment. 352 cmdBuildEnv := []string{} 353 configEnv := runconfig.ConvertKVStringsToMap(b.Config.Env) 354 for key, val := range b.buildArgs { 355 if !b.isBuildArgAllowed(key) { 356 // skip build-args that are not in allowed list, meaning they have 357 // not been defined by an "ARG" Dockerfile command yet. 358 // This is an error condition but only if there is no "ARG" in the entire 359 // Dockerfile, so we'll generate any necessary errors after we parsed 360 // the entire file (see 'leftoverArgs' processing in evaluator.go ) 361 continue 362 } 363 if _, ok := configEnv[key]; !ok { 364 cmdBuildEnv = append(cmdBuildEnv, fmt.Sprintf("%s=%s", key, val)) 365 } 366 } 367 368 // derive the command to use for probeCache() and to commit in this container. 369 // Note that we only do this if there are any build-time env vars. Also, we 370 // use the special argument "|#" at the start of the args array. This will 371 // avoid conflicts with any RUN command since commands can not 372 // start with | (vertical bar). The "#" (number of build envs) is there to 373 // help ensure proper cache matches. We don't want a RUN command 374 // that starts with "foo=abc" to be considered part of a build-time env var. 375 saveCmd := config.Cmd 376 if len(cmdBuildEnv) > 0 { 377 sort.Strings(cmdBuildEnv) 378 tmpEnv := append([]string{fmt.Sprintf("|%d", len(cmdBuildEnv))}, cmdBuildEnv...) 379 saveCmd = stringutils.NewStrSlice(append(tmpEnv, saveCmd.Slice()...)...) 380 } 381 382 b.Config.Cmd = saveCmd 383 hit, err := b.probeCache() 384 if err != nil { 385 return err 386 } 387 if hit { 388 return nil 389 } 390 391 // set Cmd manually, this is special case only for Dockerfiles 392 b.Config.Cmd = config.Cmd 393 // set build-time environment for 'run'. 394 b.Config.Env = append(b.Config.Env, cmdBuildEnv...) 395 396 logrus.Debugf("[BUILDER] Command to be executed: %v", b.Config.Cmd) 397 398 c, err := b.create() 399 if err != nil { 400 return err 401 } 402 403 // Ensure that we keep the container mounted until the commit 404 // to avoid unmounting and then mounting directly again 405 c.Mount() 406 defer c.Unmount() 407 408 err = b.run(c) 409 if err != nil { 410 return err 411 } 412 413 // revert to original config environment and set the command string to 414 // have the build-time env vars in it (if any) so that future cache look-ups 415 // properly match it. 416 b.Config.Env = env 417 b.Config.Cmd = saveCmd 418 if err := b.commit(c.ID, cmd, "run"); err != nil { 419 return err 420 } 421 422 return nil 423 } 424 425 // CMD foo 426 // 427 // Set the default command to run in the container (which may be empty). 428 // Argument handling is the same as RUN. 429 // 430 func cmd(b *builder, args []string, attributes map[string]bool, original string) error { 431 if err := b.BuilderFlags.Parse(); err != nil { 432 return err 433 } 434 435 cmdSlice := handleJSONArgs(args, attributes) 436 437 if !attributes["json"] { 438 if runtime.GOOS != "windows" { 439 cmdSlice = append([]string{"/bin/sh", "-c"}, cmdSlice...) 440 } else { 441 cmdSlice = append([]string{"cmd", "/S /C"}, cmdSlice...) 442 } 443 } 444 445 b.Config.Cmd = stringutils.NewStrSlice(cmdSlice...) 446 447 if err := b.commit("", b.Config.Cmd, fmt.Sprintf("CMD %q", cmdSlice)); err != nil { 448 return err 449 } 450 451 if len(args) != 0 { 452 b.cmdSet = true 453 } 454 455 return nil 456 } 457 458 // ENTRYPOINT /usr/sbin/nginx 459 // 460 // Set the entrypoint (which defaults to sh -c on linux, or cmd /S /C on Windows) to 461 // /usr/sbin/nginx. Will accept the CMD as the arguments to /usr/sbin/nginx. 462 // 463 // Handles command processing similar to CMD and RUN, only b.Config.Entrypoint 464 // is initialized at NewBuilder time instead of through argument parsing. 465 // 466 func entrypoint(b *builder, args []string, attributes map[string]bool, original string) error { 467 if err := b.BuilderFlags.Parse(); err != nil { 468 return err 469 } 470 471 parsed := handleJSONArgs(args, attributes) 472 473 switch { 474 case attributes["json"]: 475 // ENTRYPOINT ["echo", "hi"] 476 b.Config.Entrypoint = stringutils.NewStrSlice(parsed...) 477 case len(parsed) == 0: 478 // ENTRYPOINT [] 479 b.Config.Entrypoint = nil 480 default: 481 // ENTRYPOINT echo hi 482 if runtime.GOOS != "windows" { 483 b.Config.Entrypoint = stringutils.NewStrSlice("/bin/sh", "-c", parsed[0]) 484 } else { 485 b.Config.Entrypoint = stringutils.NewStrSlice("cmd", "/S /C", parsed[0]) 486 } 487 } 488 489 // when setting the entrypoint if a CMD was not explicitly set then 490 // set the command to nil 491 if !b.cmdSet { 492 b.Config.Cmd = nil 493 } 494 495 if err := b.commit("", b.Config.Cmd, fmt.Sprintf("ENTRYPOINT %q", b.Config.Entrypoint)); err != nil { 496 return err 497 } 498 499 return nil 500 } 501 502 // EXPOSE 6667/tcp 7000/tcp 503 // 504 // Expose ports for links and port mappings. This all ends up in 505 // b.Config.ExposedPorts for runconfig. 506 // 507 func expose(b *builder, args []string, attributes map[string]bool, original string) error { 508 portsTab := args 509 510 if len(args) == 0 { 511 return derr.ErrorCodeAtLeastOneArg.WithArgs("EXPOSE") 512 } 513 514 if err := b.BuilderFlags.Parse(); err != nil { 515 return err 516 } 517 518 if b.Config.ExposedPorts == nil { 519 b.Config.ExposedPorts = make(nat.PortSet) 520 } 521 522 ports, _, err := nat.ParsePortSpecs(portsTab) 523 if err != nil { 524 return err 525 } 526 527 // instead of using ports directly, we build a list of ports and sort it so 528 // the order is consistent. This prevents cache burst where map ordering 529 // changes between builds 530 portList := make([]string, len(ports)) 531 var i int 532 for port := range ports { 533 if _, exists := b.Config.ExposedPorts[port]; !exists { 534 b.Config.ExposedPorts[port] = struct{}{} 535 } 536 portList[i] = string(port) 537 i++ 538 } 539 sort.Strings(portList) 540 return b.commit("", b.Config.Cmd, fmt.Sprintf("EXPOSE %s", strings.Join(portList, " "))) 541 } 542 543 // USER foo 544 // 545 // Set the user to 'foo' for future commands and when running the 546 // ENTRYPOINT/CMD at container run time. 547 // 548 func user(b *builder, args []string, attributes map[string]bool, original string) error { 549 if len(args) != 1 { 550 return derr.ErrorCodeExactlyOneArg.WithArgs("USER") 551 } 552 553 if err := b.BuilderFlags.Parse(); err != nil { 554 return err 555 } 556 557 b.Config.User = args[0] 558 return b.commit("", b.Config.Cmd, fmt.Sprintf("USER %v", args)) 559 } 560 561 // VOLUME /foo 562 // 563 // Expose the volume /foo for use. Will also accept the JSON array form. 564 // 565 func volume(b *builder, args []string, attributes map[string]bool, original string) error { 566 if len(args) == 0 { 567 return derr.ErrorCodeAtLeastOneArg.WithArgs("VOLUME") 568 } 569 570 if err := b.BuilderFlags.Parse(); err != nil { 571 return err 572 } 573 574 if b.Config.Volumes == nil { 575 b.Config.Volumes = map[string]struct{}{} 576 } 577 for _, v := range args { 578 v = strings.TrimSpace(v) 579 if v == "" { 580 return derr.ErrorCodeVolumeEmpty 581 } 582 b.Config.Volumes[v] = struct{}{} 583 } 584 if err := b.commit("", b.Config.Cmd, fmt.Sprintf("VOLUME %v", args)); err != nil { 585 return err 586 } 587 return nil 588 } 589 590 // STOPSIGNAL signal 591 // 592 // Set the signal that will be used to kill the container. 593 func stopSignal(b *builder, args []string, attributes map[string]bool, original string) error { 594 if len(args) != 1 { 595 return fmt.Errorf("STOPSIGNAL requires exactly one argument") 596 } 597 598 sig := args[0] 599 _, err := signal.ParseSignal(sig) 600 if err != nil { 601 return err 602 } 603 604 b.Config.StopSignal = sig 605 return b.commit("", b.Config.Cmd, fmt.Sprintf("STOPSIGNAL %v", args)) 606 } 607 608 // ARG name[=value] 609 // 610 // Adds the variable foo to the trusted list of variables that can be passed 611 // to builder using the --build-arg flag for expansion/subsitution or passing to 'run'. 612 // Dockerfile author may optionally set a default value of this variable. 613 func arg(b *builder, args []string, attributes map[string]bool, original string) error { 614 if len(args) != 1 { 615 return fmt.Errorf("ARG requires exactly one argument definition") 616 } 617 618 var ( 619 name string 620 value string 621 hasDefault bool 622 ) 623 624 arg := args[0] 625 // 'arg' can just be a name or name-value pair. Note that this is different 626 // from 'env' that handles the split of name and value at the parser level. 627 // The reason for doing it differently for 'arg' is that we support just 628 // defining an arg and not assign it a value (while 'env' always expects a 629 // name-value pair). If possible, it will be good to harmonize the two. 630 if strings.Contains(arg, "=") { 631 parts := strings.SplitN(arg, "=", 2) 632 name = parts[0] 633 value = parts[1] 634 hasDefault = true 635 } else { 636 name = arg 637 hasDefault = false 638 } 639 // add the arg to allowed list of build-time args from this step on. 640 b.allowedBuildArgs[name] = true 641 642 // If there is a default value associated with this arg then add it to the 643 // b.buildArgs if one is not already passed to the builder. The args passed 644 // to builder override the defaut value of 'arg'. 645 if _, ok := b.buildArgs[name]; !ok && hasDefault { 646 b.buildArgs[name] = value 647 } 648 649 return b.commit("", b.Config.Cmd, fmt.Sprintf("ARG %s", arg)) 650 }