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