github.com/fabiokung/docker@v0.11.2-0.20170222101415-4534dcd49497/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 "errors" 12 "fmt" 13 "regexp" 14 "runtime" 15 "sort" 16 "strconv" 17 "strings" 18 "time" 19 20 "github.com/Sirupsen/logrus" 21 "github.com/docker/docker/api" 22 "github.com/docker/docker/api/types" 23 "github.com/docker/docker/api/types/container" 24 "github.com/docker/docker/api/types/strslice" 25 "github.com/docker/docker/builder" 26 "github.com/docker/docker/pkg/signal" 27 runconfigopts "github.com/docker/docker/runconfig/opts" 28 "github.com/docker/go-connections/nat" 29 ) 30 31 // ENV foo bar 32 // 33 // Sets the environment variable foo to bar, also makes interpolation 34 // in the dockerfile available from the next statement on via ${foo}. 35 // 36 func env(b *Builder, args []string, attributes map[string]bool, original string) error { 37 if len(args) == 0 { 38 return errAtLeastOneArgument("ENV") 39 } 40 41 if len(args)%2 != 0 { 42 // should never get here, but just in case 43 return errTooManyArguments("ENV") 44 } 45 46 if err := b.flags.Parse(); err != nil { 47 return err 48 } 49 50 // TODO/FIXME/NOT USED 51 // Just here to show how to use the builder flags stuff within the 52 // context of a builder command. Will remove once we actually add 53 // a builder command to something! 54 /* 55 flBool1 := b.flags.AddBool("bool1", false) 56 flStr1 := b.flags.AddString("str1", "HI") 57 58 if err := b.flags.Parse(); err != nil { 59 return err 60 } 61 62 fmt.Printf("Bool1:%v\n", flBool1) 63 fmt.Printf("Str1:%v\n", flStr1) 64 */ 65 66 commitStr := "ENV" 67 68 for j := 0; j < len(args); j++ { 69 // name ==> args[j] 70 // value ==> args[j+1] 71 72 if len(args[j]) == 0 { 73 return errBlankCommandNames("ENV") 74 } 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 compareFrom := envParts[0] 82 compareTo := args[j] 83 if runtime.GOOS == "windows" { 84 // Case insensitive environment variables on Windows 85 compareFrom = strings.ToUpper(compareFrom) 86 compareTo = strings.ToUpper(compareTo) 87 } 88 if compareFrom == compareTo { 89 b.runConfig.Env[i] = newVar 90 gotOne = true 91 break 92 } 93 } 94 if !gotOne { 95 b.runConfig.Env = append(b.runConfig.Env, newVar) 96 } 97 j++ 98 } 99 100 return b.commit("", b.runConfig.Cmd, commitStr) 101 } 102 103 // MAINTAINER some text <maybe@an.email.address> 104 // 105 // Sets the maintainer metadata. 106 func maintainer(b *Builder, args []string, attributes map[string]bool, original string) error { 107 if len(args) != 1 { 108 return errExactlyOneArgument("MAINTAINER") 109 } 110 111 if err := b.flags.Parse(); err != nil { 112 return err 113 } 114 115 b.maintainer = args[0] 116 return b.commit("", b.runConfig.Cmd, fmt.Sprintf("MAINTAINER %s", b.maintainer)) 117 } 118 119 // LABEL some json data describing the image 120 // 121 // Sets the Label variable foo to bar, 122 // 123 func label(b *Builder, args []string, attributes map[string]bool, original string) error { 124 if len(args) == 0 { 125 return errAtLeastOneArgument("LABEL") 126 } 127 if len(args)%2 != 0 { 128 // should never get here, but just in case 129 return errTooManyArguments("LABEL") 130 } 131 132 if err := b.flags.Parse(); err != nil { 133 return err 134 } 135 136 commitStr := "LABEL" 137 138 if b.runConfig.Labels == nil { 139 b.runConfig.Labels = map[string]string{} 140 } 141 142 for j := 0; j < len(args); j++ { 143 // name ==> args[j] 144 // value ==> args[j+1] 145 146 if len(args[j]) == 0 { 147 return errBlankCommandNames("LABEL") 148 } 149 150 newVar := args[j] + "=" + args[j+1] + "" 151 commitStr += " " + newVar 152 153 b.runConfig.Labels[args[j]] = args[j+1] 154 j++ 155 } 156 return b.commit("", b.runConfig.Cmd, commitStr) 157 } 158 159 // ADD foo /path 160 // 161 // Add the file 'foo' to '/path'. Tarball and Remote URL (git, http) handling 162 // exist here. If you do not wish to have this automatic handling, use COPY. 163 // 164 func add(b *Builder, args []string, attributes map[string]bool, original string) error { 165 if len(args) < 2 { 166 return errAtLeastTwoArguments("ADD") 167 } 168 169 if err := b.flags.Parse(); err != nil { 170 return err 171 } 172 173 return b.runContextCommand(args, true, true, "ADD") 174 } 175 176 // COPY foo /path 177 // 178 // Same as 'ADD' but without the tar and remote url handling. 179 // 180 func dispatchCopy(b *Builder, args []string, attributes map[string]bool, original string) error { 181 if len(args) < 2 { 182 return errAtLeastTwoArguments("COPY") 183 } 184 185 if err := b.flags.Parse(); err != nil { 186 return err 187 } 188 189 return b.runContextCommand(args, false, false, "COPY") 190 } 191 192 // FROM imagename 193 // 194 // This sets the image the dockerfile will build on top of. 195 // 196 func from(b *Builder, args []string, attributes map[string]bool, original string) error { 197 if len(args) != 1 { 198 return errExactlyOneArgument("FROM") 199 } 200 201 if err := b.flags.Parse(); err != nil { 202 return err 203 } 204 205 name := args[0] 206 207 var image builder.Image 208 209 // Windows cannot support a container with no base image. 210 if name == api.NoBaseImageSpecifier { 211 if runtime.GOOS == "windows" { 212 return errors.New("Windows does not support FROM scratch") 213 } 214 b.image = "" 215 b.noBaseImage = true 216 } else { 217 // TODO: don't use `name`, instead resolve it to a digest 218 if !b.options.PullParent { 219 image, _ = b.docker.GetImageOnBuild(name) 220 // TODO: shouldn't we error out if error is different from "not found" ? 221 } 222 if image == nil { 223 var err error 224 image, err = b.docker.PullOnBuild(b.clientCtx, name, b.options.AuthConfigs, b.Output) 225 if err != nil { 226 return err 227 } 228 } 229 } 230 b.from = image 231 232 return b.processImageFrom(image) 233 } 234 235 // ONBUILD RUN echo yo 236 // 237 // ONBUILD triggers run when the image is used in a FROM statement. 238 // 239 // ONBUILD handling has a lot of special-case functionality, the heading in 240 // evaluator.go and comments around dispatch() in the same file explain the 241 // special cases. search for 'OnBuild' in internals.go for additional special 242 // cases. 243 // 244 func onbuild(b *Builder, args []string, attributes map[string]bool, original string) error { 245 if len(args) == 0 { 246 return errAtLeastOneArgument("ONBUILD") 247 } 248 249 if err := b.flags.Parse(); err != nil { 250 return err 251 } 252 253 triggerInstruction := strings.ToUpper(strings.TrimSpace(args[0])) 254 switch triggerInstruction { 255 case "ONBUILD": 256 return errors.New("Chaining ONBUILD via `ONBUILD ONBUILD` isn't allowed") 257 case "MAINTAINER", "FROM": 258 return fmt.Errorf("%s isn't allowed as an ONBUILD trigger", triggerInstruction) 259 } 260 261 original = regexp.MustCompile(`(?i)^\s*ONBUILD\s*`).ReplaceAllString(original, "") 262 263 b.runConfig.OnBuild = append(b.runConfig.OnBuild, original) 264 return b.commit("", b.runConfig.Cmd, fmt.Sprintf("ONBUILD %s", original)) 265 } 266 267 // WORKDIR /tmp 268 // 269 // Set the working directory for future RUN/CMD/etc statements. 270 // 271 func workdir(b *Builder, args []string, attributes map[string]bool, original string) error { 272 if len(args) != 1 { 273 return errExactlyOneArgument("WORKDIR") 274 } 275 276 err := b.flags.Parse() 277 if err != nil { 278 return err 279 } 280 281 // This is from the Dockerfile and will not necessarily be in platform 282 // specific semantics, hence ensure it is converted. 283 b.runConfig.WorkingDir, err = normaliseWorkdir(b.runConfig.WorkingDir, args[0]) 284 if err != nil { 285 return err 286 } 287 288 // For performance reasons, we explicitly do a create/mkdir now 289 // This avoids having an unnecessary expensive mount/unmount calls 290 // (on Windows in particular) during each container create. 291 // Prior to 1.13, the mkdir was deferred and not executed at this step. 292 if b.disableCommit { 293 // Don't call back into the daemon if we're going through docker commit --change "WORKDIR /foo". 294 // We've already updated the runConfig and that's enough. 295 return nil 296 } 297 b.runConfig.Image = b.image 298 299 cmd := b.runConfig.Cmd 300 comment := "WORKDIR " + b.runConfig.WorkingDir 301 // reset the command for cache detection 302 b.runConfig.Cmd = strslice.StrSlice(append(getShell(b.runConfig), "#(nop) "+comment)) 303 defer func(cmd strslice.StrSlice) { b.runConfig.Cmd = cmd }(cmd) 304 305 if hit, err := b.probeCache(); err != nil { 306 return err 307 } else if hit { 308 return nil 309 } 310 311 container, err := b.docker.ContainerCreate(types.ContainerCreateConfig{ 312 Config: b.runConfig, 313 // Set a log config to override any default value set on the daemon 314 HostConfig: &container.HostConfig{LogConfig: defaultLogConfig}, 315 }) 316 if err != nil { 317 return err 318 } 319 b.tmpContainers[container.ID] = struct{}{} 320 if err := b.docker.ContainerCreateWorkdir(container.ID); err != nil { 321 return err 322 } 323 324 return b.commit(container.ID, cmd, comment) 325 } 326 327 // RUN some command yo 328 // 329 // run a command and commit the image. Args are automatically prepended with 330 // the current SHELL which defaults to 'sh -c' under linux or 'cmd /S /C' under 331 // Windows, in the event there is only one argument The difference in processing: 332 // 333 // RUN echo hi # sh -c echo hi (Linux) 334 // RUN echo hi # cmd /S /C echo hi (Windows) 335 // RUN [ "echo", "hi" ] # echo hi 336 // 337 func run(b *Builder, args []string, attributes map[string]bool, original string) error { 338 if b.image == "" && !b.noBaseImage { 339 return errors.New("Please provide a source image with `from` prior to run") 340 } 341 342 if err := b.flags.Parse(); err != nil { 343 return err 344 } 345 346 args = handleJSONArgs(args, attributes) 347 348 if !attributes["json"] { 349 args = append(getShell(b.runConfig), args...) 350 } 351 config := &container.Config{ 352 Cmd: strslice.StrSlice(args), 353 Image: b.image, 354 } 355 356 // stash the cmd 357 cmd := b.runConfig.Cmd 358 if len(b.runConfig.Entrypoint) == 0 && len(b.runConfig.Cmd) == 0 { 359 b.runConfig.Cmd = config.Cmd 360 } 361 362 // stash the config environment 363 env := b.runConfig.Env 364 365 defer func(cmd strslice.StrSlice) { b.runConfig.Cmd = cmd }(cmd) 366 defer func(env []string) { b.runConfig.Env = env }(env) 367 368 // derive the net build-time environment for this run. We let config 369 // environment override the build time environment. 370 // This means that we take the b.buildArgs list of env vars and remove 371 // any of those variables that are defined as part of the container. In other 372 // words, anything in b.Config.Env. What's left is the list of build-time env 373 // vars that we need to add to each RUN command - note the list could be empty. 374 // 375 // We don't persist the build time environment with container's config 376 // environment, but just sort and prepend it to the command string at time 377 // of commit. 378 // This helps with tracing back the image's actual environment at the time 379 // of RUN, without leaking it to the final image. It also aids cache 380 // lookup for same image built with same build time environment. 381 cmdBuildEnv := []string{} 382 configEnv := runconfigopts.ConvertKVStringsToMap(b.runConfig.Env) 383 for key, val := range b.options.BuildArgs { 384 if !b.isBuildArgAllowed(key) { 385 // skip build-args that are not in allowed list, meaning they have 386 // not been defined by an "ARG" Dockerfile command yet. 387 // This is an error condition but only if there is no "ARG" in the entire 388 // Dockerfile, so we'll generate any necessary errors after we parsed 389 // the entire file (see 'leftoverArgs' processing in evaluator.go ) 390 continue 391 } 392 if _, ok := configEnv[key]; !ok && val != nil { 393 cmdBuildEnv = append(cmdBuildEnv, fmt.Sprintf("%s=%s", key, *val)) 394 } 395 } 396 397 // derive the command to use for probeCache() and to commit in this container. 398 // Note that we only do this if there are any build-time env vars. Also, we 399 // use the special argument "|#" at the start of the args array. This will 400 // avoid conflicts with any RUN command since commands can not 401 // start with | (vertical bar). The "#" (number of build envs) is there to 402 // help ensure proper cache matches. We don't want a RUN command 403 // that starts with "foo=abc" to be considered part of a build-time env var. 404 saveCmd := config.Cmd 405 if len(cmdBuildEnv) > 0 { 406 sort.Strings(cmdBuildEnv) 407 tmpEnv := append([]string{fmt.Sprintf("|%d", len(cmdBuildEnv))}, cmdBuildEnv...) 408 saveCmd = strslice.StrSlice(append(tmpEnv, saveCmd...)) 409 } 410 411 b.runConfig.Cmd = saveCmd 412 hit, err := b.probeCache() 413 if err != nil { 414 return err 415 } 416 if hit { 417 return nil 418 } 419 420 // set Cmd manually, this is special case only for Dockerfiles 421 b.runConfig.Cmd = config.Cmd 422 // set build-time environment for 'run'. 423 b.runConfig.Env = append(b.runConfig.Env, cmdBuildEnv...) 424 // set config as already being escaped, this prevents double escaping on windows 425 b.runConfig.ArgsEscaped = true 426 427 logrus.Debugf("[BUILDER] Command to be executed: %v", b.runConfig.Cmd) 428 429 cID, err := b.create() 430 if err != nil { 431 return err 432 } 433 434 if err := b.run(cID); err != nil { 435 return err 436 } 437 438 // revert to original config environment and set the command string to 439 // have the build-time env vars in it (if any) so that future cache look-ups 440 // properly match it. 441 b.runConfig.Env = env 442 b.runConfig.Cmd = saveCmd 443 return b.commit(cID, cmd, "run") 444 } 445 446 // CMD foo 447 // 448 // Set the default command to run in the container (which may be empty). 449 // Argument handling is the same as RUN. 450 // 451 func cmd(b *Builder, args []string, attributes map[string]bool, original string) error { 452 if err := b.flags.Parse(); err != nil { 453 return err 454 } 455 456 cmdSlice := handleJSONArgs(args, attributes) 457 458 if !attributes["json"] { 459 cmdSlice = append(getShell(b.runConfig), cmdSlice...) 460 } 461 462 b.runConfig.Cmd = strslice.StrSlice(cmdSlice) 463 // set config as already being escaped, this prevents double escaping on windows 464 b.runConfig.ArgsEscaped = true 465 466 if err := b.commit("", b.runConfig.Cmd, fmt.Sprintf("CMD %q", cmdSlice)); err != nil { 467 return err 468 } 469 470 if len(args) != 0 { 471 b.cmdSet = true 472 } 473 474 return nil 475 } 476 477 // parseOptInterval(flag) is the duration of flag.Value, or 0 if 478 // empty. An error is reported if the value is given and is not positive. 479 func parseOptInterval(f *Flag) (time.Duration, error) { 480 s := f.Value 481 if s == "" { 482 return 0, nil 483 } 484 d, err := time.ParseDuration(s) 485 if err != nil { 486 return 0, err 487 } 488 if d <= 0 { 489 return 0, fmt.Errorf("Interval %#v must be positive", f.name) 490 } 491 return d, nil 492 } 493 494 // HEALTHCHECK foo 495 // 496 // Set the default healthcheck command to run in the container (which may be empty). 497 // Argument handling is the same as RUN. 498 // 499 func healthcheck(b *Builder, args []string, attributes map[string]bool, original string) error { 500 if len(args) == 0 { 501 return errAtLeastOneArgument("HEALTHCHECK") 502 } 503 typ := strings.ToUpper(args[0]) 504 args = args[1:] 505 if typ == "NONE" { 506 if len(args) != 0 { 507 return errors.New("HEALTHCHECK NONE takes no arguments") 508 } 509 test := strslice.StrSlice{typ} 510 b.runConfig.Healthcheck = &container.HealthConfig{ 511 Test: test, 512 } 513 } else { 514 if b.runConfig.Healthcheck != nil { 515 oldCmd := b.runConfig.Healthcheck.Test 516 if len(oldCmd) > 0 && oldCmd[0] != "NONE" { 517 fmt.Fprintf(b.Stdout, "Note: overriding previous HEALTHCHECK: %v\n", oldCmd) 518 } 519 } 520 521 healthcheck := container.HealthConfig{} 522 523 flInterval := b.flags.AddString("interval", "") 524 flTimeout := b.flags.AddString("timeout", "") 525 flRetries := b.flags.AddString("retries", "") 526 527 if err := b.flags.Parse(); err != nil { 528 return err 529 } 530 531 switch typ { 532 case "CMD": 533 cmdSlice := handleJSONArgs(args, attributes) 534 if len(cmdSlice) == 0 { 535 return errors.New("Missing command after HEALTHCHECK CMD") 536 } 537 538 if !attributes["json"] { 539 typ = "CMD-SHELL" 540 } 541 542 healthcheck.Test = strslice.StrSlice(append([]string{typ}, cmdSlice...)) 543 default: 544 return fmt.Errorf("Unknown type %#v in HEALTHCHECK (try CMD)", typ) 545 } 546 547 interval, err := parseOptInterval(flInterval) 548 if err != nil { 549 return err 550 } 551 healthcheck.Interval = interval 552 553 timeout, err := parseOptInterval(flTimeout) 554 if err != nil { 555 return err 556 } 557 healthcheck.Timeout = timeout 558 559 if flRetries.Value != "" { 560 retries, err := strconv.ParseInt(flRetries.Value, 10, 32) 561 if err != nil { 562 return err 563 } 564 if retries < 1 { 565 return fmt.Errorf("--retries must be at least 1 (not %d)", retries) 566 } 567 healthcheck.Retries = int(retries) 568 } else { 569 healthcheck.Retries = 0 570 } 571 572 b.runConfig.Healthcheck = &healthcheck 573 } 574 575 return b.commit("", b.runConfig.Cmd, fmt.Sprintf("HEALTHCHECK %q", b.runConfig.Healthcheck)) 576 } 577 578 // ENTRYPOINT /usr/sbin/nginx 579 // 580 // Set the entrypoint to /usr/sbin/nginx. Will accept the CMD as the arguments 581 // to /usr/sbin/nginx. Uses the default shell if not in JSON format. 582 // 583 // Handles command processing similar to CMD and RUN, only b.runConfig.Entrypoint 584 // is initialized at NewBuilder time instead of through argument parsing. 585 // 586 func entrypoint(b *Builder, args []string, attributes map[string]bool, original string) error { 587 if err := b.flags.Parse(); err != nil { 588 return err 589 } 590 591 parsed := handleJSONArgs(args, attributes) 592 593 switch { 594 case attributes["json"]: 595 // ENTRYPOINT ["echo", "hi"] 596 b.runConfig.Entrypoint = strslice.StrSlice(parsed) 597 case len(parsed) == 0: 598 // ENTRYPOINT [] 599 b.runConfig.Entrypoint = nil 600 default: 601 // ENTRYPOINT echo hi 602 b.runConfig.Entrypoint = strslice.StrSlice(append(getShell(b.runConfig), parsed[0])) 603 } 604 605 // when setting the entrypoint if a CMD was not explicitly set then 606 // set the command to nil 607 if !b.cmdSet { 608 b.runConfig.Cmd = nil 609 } 610 611 if err := b.commit("", b.runConfig.Cmd, fmt.Sprintf("ENTRYPOINT %q", b.runConfig.Entrypoint)); err != nil { 612 return err 613 } 614 615 return nil 616 } 617 618 // EXPOSE 6667/tcp 7000/tcp 619 // 620 // Expose ports for links and port mappings. This all ends up in 621 // b.runConfig.ExposedPorts for runconfig. 622 // 623 func expose(b *Builder, args []string, attributes map[string]bool, original string) error { 624 portsTab := args 625 626 if len(args) == 0 { 627 return errAtLeastOneArgument("EXPOSE") 628 } 629 630 if err := b.flags.Parse(); err != nil { 631 return err 632 } 633 634 if b.runConfig.ExposedPorts == nil { 635 b.runConfig.ExposedPorts = make(nat.PortSet) 636 } 637 638 ports, _, err := nat.ParsePortSpecs(portsTab) 639 if err != nil { 640 return err 641 } 642 643 // instead of using ports directly, we build a list of ports and sort it so 644 // the order is consistent. This prevents cache burst where map ordering 645 // changes between builds 646 portList := make([]string, len(ports)) 647 var i int 648 for port := range ports { 649 if _, exists := b.runConfig.ExposedPorts[port]; !exists { 650 b.runConfig.ExposedPorts[port] = struct{}{} 651 } 652 portList[i] = string(port) 653 i++ 654 } 655 sort.Strings(portList) 656 return b.commit("", b.runConfig.Cmd, fmt.Sprintf("EXPOSE %s", strings.Join(portList, " "))) 657 } 658 659 // USER foo 660 // 661 // Set the user to 'foo' for future commands and when running the 662 // ENTRYPOINT/CMD at container run time. 663 // 664 func user(b *Builder, args []string, attributes map[string]bool, original string) error { 665 if len(args) != 1 { 666 return errExactlyOneArgument("USER") 667 } 668 669 if err := b.flags.Parse(); err != nil { 670 return err 671 } 672 673 b.runConfig.User = args[0] 674 return b.commit("", b.runConfig.Cmd, fmt.Sprintf("USER %v", args)) 675 } 676 677 // VOLUME /foo 678 // 679 // Expose the volume /foo for use. Will also accept the JSON array form. 680 // 681 func volume(b *Builder, args []string, attributes map[string]bool, original string) error { 682 if len(args) == 0 { 683 return errAtLeastOneArgument("VOLUME") 684 } 685 686 if err := b.flags.Parse(); err != nil { 687 return err 688 } 689 690 if b.runConfig.Volumes == nil { 691 b.runConfig.Volumes = map[string]struct{}{} 692 } 693 for _, v := range args { 694 v = strings.TrimSpace(v) 695 if v == "" { 696 return errors.New("VOLUME specified can not be an empty string") 697 } 698 b.runConfig.Volumes[v] = struct{}{} 699 } 700 if err := b.commit("", b.runConfig.Cmd, fmt.Sprintf("VOLUME %v", args)); err != nil { 701 return err 702 } 703 return nil 704 } 705 706 // STOPSIGNAL signal 707 // 708 // Set the signal that will be used to kill the container. 709 func stopSignal(b *Builder, args []string, attributes map[string]bool, original string) error { 710 if len(args) != 1 { 711 return errExactlyOneArgument("STOPSIGNAL") 712 } 713 714 sig := args[0] 715 _, err := signal.ParseSignal(sig) 716 if err != nil { 717 return err 718 } 719 720 b.runConfig.StopSignal = sig 721 return b.commit("", b.runConfig.Cmd, fmt.Sprintf("STOPSIGNAL %v", args)) 722 } 723 724 // ARG name[=value] 725 // 726 // Adds the variable foo to the trusted list of variables that can be passed 727 // to builder using the --build-arg flag for expansion/substitution or passing to 'run'. 728 // Dockerfile author may optionally set a default value of this variable. 729 func arg(b *Builder, args []string, attributes map[string]bool, original string) error { 730 if len(args) != 1 { 731 return errExactlyOneArgument("ARG") 732 } 733 734 var ( 735 name string 736 newValue string 737 hasDefault bool 738 ) 739 740 arg := args[0] 741 // 'arg' can just be a name or name-value pair. Note that this is different 742 // from 'env' that handles the split of name and value at the parser level. 743 // The reason for doing it differently for 'arg' is that we support just 744 // defining an arg and not assign it a value (while 'env' always expects a 745 // name-value pair). If possible, it will be good to harmonize the two. 746 if strings.Contains(arg, "=") { 747 parts := strings.SplitN(arg, "=", 2) 748 if len(parts[0]) == 0 { 749 return errBlankCommandNames("ARG") 750 } 751 752 name = parts[0] 753 newValue = parts[1] 754 hasDefault = true 755 } else { 756 name = arg 757 hasDefault = false 758 } 759 // add the arg to allowed list of build-time args from this step on. 760 b.allowedBuildArgs[name] = true 761 762 // If there is a default value associated with this arg then add it to the 763 // b.buildArgs if one is not already passed to the builder. The args passed 764 // to builder override the default value of 'arg'. Note that a 'nil' for 765 // a value means that the user specified "--build-arg FOO" and "FOO" wasn't 766 // defined as an env var - and in that case we DO want to use the default 767 // value specified in the ARG cmd. 768 if baValue, ok := b.options.BuildArgs[name]; (!ok || baValue == nil) && hasDefault { 769 b.options.BuildArgs[name] = &newValue 770 } 771 772 return b.commit("", b.runConfig.Cmd, fmt.Sprintf("ARG %s", arg)) 773 } 774 775 // SHELL powershell -command 776 // 777 // Set the non-default shell to use. 778 func shell(b *Builder, args []string, attributes map[string]bool, original string) error { 779 if err := b.flags.Parse(); err != nil { 780 return err 781 } 782 shellSlice := handleJSONArgs(args, attributes) 783 switch { 784 case len(shellSlice) == 0: 785 // SHELL [] 786 return errAtLeastOneArgument("SHELL") 787 case attributes["json"]: 788 // SHELL ["powershell", "-command"] 789 b.runConfig.Shell = strslice.StrSlice(shellSlice) 790 default: 791 // SHELL powershell -command - not JSON 792 return errNotJSON("SHELL", original) 793 } 794 return b.commit("", b.runConfig.Cmd, fmt.Sprintf("SHELL %v", shellSlice)) 795 } 796 797 func errAtLeastOneArgument(command string) error { 798 return fmt.Errorf("%s requires at least one argument", command) 799 } 800 801 func errExactlyOneArgument(command string) error { 802 return fmt.Errorf("%s requires exactly one argument", command) 803 } 804 805 func errAtLeastTwoArguments(command string) error { 806 return fmt.Errorf("%s requires at least two arguments", command) 807 } 808 809 func errBlankCommandNames(command string) error { 810 return fmt.Errorf("%s names can not be blank", command) 811 } 812 813 func errTooManyArguments(command string) error { 814 return fmt.Errorf("Bad input to %s, too many arguments", command) 815 } 816 817 // getShell is a helper function which gets the right shell for prefixing the 818 // shell-form of RUN, ENTRYPOINT and CMD instructions 819 func getShell(c *container.Config) []string { 820 if 0 == len(c.Shell) { 821 return defaultShell[:] 822 } 823 return c.Shell[:] 824 }