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