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