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