github.com/jiasir/docker@v1.3.3-0.20170609024000-252e610103e7/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 "bytes" 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/container" 23 "github.com/docker/docker/api/types/strslice" 24 "github.com/docker/docker/builder" 25 "github.com/docker/docker/builder/dockerfile/parser" 26 "github.com/docker/docker/pkg/jsonmessage" 27 "github.com/docker/docker/pkg/signal" 28 "github.com/docker/go-connections/nat" 29 "github.com/pkg/errors" 30 ) 31 32 // ENV foo bar 33 // 34 // Sets the environment variable foo to bar, also makes interpolation 35 // in the dockerfile available from the next statement on via ${foo}. 36 // 37 func env(req dispatchRequest) error { 38 if len(req.args) == 0 { 39 return errAtLeastOneArgument("ENV") 40 } 41 42 if len(req.args)%2 != 0 { 43 // should never get here, but just in case 44 return errTooManyArguments("ENV") 45 } 46 47 if err := req.flags.Parse(); err != nil { 48 return err 49 } 50 51 runConfig := req.state.runConfig 52 commitMessage := bytes.NewBufferString("ENV") 53 54 for j := 0; j < len(req.args); j += 2 { 55 if len(req.args[j]) == 0 { 56 return errBlankCommandNames("ENV") 57 } 58 name := req.args[j] 59 value := req.args[j+1] 60 newVar := name + "=" + value 61 commitMessage.WriteString(" " + newVar) 62 63 gotOne := false 64 for i, envVar := range runConfig.Env { 65 envParts := strings.SplitN(envVar, "=", 2) 66 compareFrom := envParts[0] 67 if equalEnvKeys(compareFrom, name) { 68 runConfig.Env[i] = newVar 69 gotOne = true 70 break 71 } 72 } 73 if !gotOne { 74 runConfig.Env = append(runConfig.Env, newVar) 75 } 76 } 77 78 return req.builder.commit(req.state, commitMessage.String()) 79 } 80 81 // MAINTAINER some text <maybe@an.email.address> 82 // 83 // Sets the maintainer metadata. 84 func maintainer(req dispatchRequest) error { 85 if len(req.args) != 1 { 86 return errExactlyOneArgument("MAINTAINER") 87 } 88 89 if err := req.flags.Parse(); err != nil { 90 return err 91 } 92 93 maintainer := req.args[0] 94 req.state.maintainer = maintainer 95 return req.builder.commit(req.state, "MAINTAINER "+maintainer) 96 } 97 98 // LABEL some json data describing the image 99 // 100 // Sets the Label variable foo to bar, 101 // 102 func label(req dispatchRequest) error { 103 if len(req.args) == 0 { 104 return errAtLeastOneArgument("LABEL") 105 } 106 if len(req.args)%2 != 0 { 107 // should never get here, but just in case 108 return errTooManyArguments("LABEL") 109 } 110 111 if err := req.flags.Parse(); err != nil { 112 return err 113 } 114 115 commitStr := "LABEL" 116 runConfig := req.state.runConfig 117 118 if runConfig.Labels == nil { 119 runConfig.Labels = map[string]string{} 120 } 121 122 for j := 0; j < len(req.args); j++ { 123 name := req.args[j] 124 if name == "" { 125 return errBlankCommandNames("LABEL") 126 } 127 128 value := req.args[j+1] 129 commitStr += " " + name + "=" + value 130 131 runConfig.Labels[name] = value 132 j++ 133 } 134 return req.builder.commit(req.state, commitStr) 135 } 136 137 // ADD foo /path 138 // 139 // Add the file 'foo' to '/path'. Tarball and Remote URL (git, http) handling 140 // exist here. If you do not wish to have this automatic handling, use COPY. 141 // 142 func add(req dispatchRequest) error { 143 if len(req.args) < 2 { 144 return errAtLeastTwoArguments("ADD") 145 } 146 147 if err := req.flags.Parse(); err != nil { 148 return err 149 } 150 151 downloader := newRemoteSourceDownloader(req.builder.Output, req.builder.Stdout) 152 copier := copierFromDispatchRequest(req, downloader, nil) 153 defer copier.Cleanup() 154 copyInstruction, err := copier.createCopyInstruction(req.args, "ADD") 155 if err != nil { 156 return err 157 } 158 copyInstruction.allowLocalDecompression = true 159 160 return req.builder.performCopy(req.state, copyInstruction) 161 } 162 163 // COPY foo /path 164 // 165 // Same as 'ADD' but without the tar and remote url handling. 166 // 167 func dispatchCopy(req dispatchRequest) error { 168 if len(req.args) < 2 { 169 return errAtLeastTwoArguments("COPY") 170 } 171 172 flFrom := req.flags.AddString("from", "") 173 if err := req.flags.Parse(); err != nil { 174 return err 175 } 176 177 im, err := req.builder.getImageMount(flFrom) 178 if err != nil { 179 return errors.Wrapf(err, "invalid from flag value %s", flFrom.Value) 180 } 181 182 copier := copierFromDispatchRequest(req, errOnSourceDownload, im) 183 defer copier.Cleanup() 184 copyInstruction, err := copier.createCopyInstruction(req.args, "COPY") 185 if err != nil { 186 return err 187 } 188 189 return req.builder.performCopy(req.state, copyInstruction) 190 } 191 192 func (b *Builder) getImageMount(fromFlag *Flag) (*imageMount, error) { 193 if !fromFlag.IsUsed() { 194 // TODO: this could return the source in the default case as well? 195 return nil, nil 196 } 197 198 imageRefOrID := fromFlag.Value 199 stage, err := b.buildStages.get(fromFlag.Value) 200 if err != nil { 201 return nil, err 202 } 203 if stage != nil { 204 imageRefOrID = stage.ImageID() 205 } 206 return b.imageSources.Get(imageRefOrID) 207 } 208 209 // FROM imagename[:tag | @digest] [AS build-stage-name] 210 // 211 func from(req dispatchRequest) error { 212 stageName, err := parseBuildStageName(req.args) 213 if err != nil { 214 return err 215 } 216 217 if err := req.flags.Parse(); err != nil { 218 return err 219 } 220 221 req.builder.imageProber.Reset() 222 image, err := req.builder.getFromImage(req.shlex, req.args[0]) 223 if err != nil { 224 return err 225 } 226 if err := req.builder.buildStages.add(stageName, image); err != nil { 227 return err 228 } 229 req.state.beginStage(stageName, image) 230 req.builder.buildArgs.ResetAllowed() 231 if image.ImageID() == "" { 232 // Typically this means they used "FROM scratch" 233 return nil 234 } 235 236 return processOnBuild(req) 237 } 238 239 func parseBuildStageName(args []string) (string, error) { 240 stageName := "" 241 switch { 242 case len(args) == 3 && strings.EqualFold(args[1], "as"): 243 stageName = strings.ToLower(args[2]) 244 if ok, _ := regexp.MatchString("^[a-z][a-z0-9-_\\.]*$", stageName); !ok { 245 return "", errors.Errorf("invalid name for build stage: %q, name can't start with a number or contain symbols", stageName) 246 } 247 case len(args) != 1: 248 return "", errors.New("FROM requires either one or three arguments") 249 } 250 251 return stageName, nil 252 } 253 254 // scratchImage is used as a token for the empty base image. It uses buildStage 255 // as a convenient implementation of builder.Image, but is not actually a 256 // buildStage. 257 var scratchImage builder.Image = &buildStage{} 258 259 func (b *Builder) getFromImage(shlex *ShellLex, name string) (builder.Image, error) { 260 substitutionArgs := []string{} 261 for key, value := range b.buildArgs.GetAllMeta() { 262 substitutionArgs = append(substitutionArgs, key+"="+value) 263 } 264 265 name, err := shlex.ProcessWord(name, substitutionArgs) 266 if err != nil { 267 return nil, err 268 } 269 270 if im, ok := b.buildStages.getByName(name); ok { 271 return im, nil 272 } 273 274 // Windows cannot support a container with no base image. 275 if name == api.NoBaseImageSpecifier { 276 if runtime.GOOS == "windows" { 277 return nil, errors.New("Windows does not support FROM scratch") 278 } 279 return scratchImage, nil 280 } 281 imageMount, err := b.imageSources.Get(name) 282 if err != nil { 283 return nil, err 284 } 285 return imageMount.Image(), nil 286 } 287 288 func processOnBuild(req dispatchRequest) error { 289 dispatchState := req.state 290 // Process ONBUILD triggers if they exist 291 if nTriggers := len(dispatchState.runConfig.OnBuild); nTriggers != 0 { 292 word := "trigger" 293 if nTriggers > 1 { 294 word = "triggers" 295 } 296 fmt.Fprintf(req.builder.Stderr, "# Executing %d build %s...\n", nTriggers, word) 297 } 298 299 // Copy the ONBUILD triggers, and remove them from the config, since the config will be committed. 300 onBuildTriggers := dispatchState.runConfig.OnBuild 301 dispatchState.runConfig.OnBuild = []string{} 302 303 // Reset stdin settings as all build actions run without stdin 304 dispatchState.runConfig.OpenStdin = false 305 dispatchState.runConfig.StdinOnce = false 306 307 // parse the ONBUILD triggers by invoking the parser 308 for _, step := range onBuildTriggers { 309 dockerfile, err := parser.Parse(strings.NewReader(step)) 310 if err != nil { 311 return err 312 } 313 314 for _, n := range dockerfile.AST.Children { 315 if err := checkDispatch(n); err != nil { 316 return err 317 } 318 319 upperCasedCmd := strings.ToUpper(n.Value) 320 switch upperCasedCmd { 321 case "ONBUILD": 322 return errors.New("Chaining ONBUILD via `ONBUILD ONBUILD` isn't allowed") 323 case "MAINTAINER", "FROM": 324 return errors.Errorf("%s isn't allowed as an ONBUILD trigger", upperCasedCmd) 325 } 326 } 327 328 if _, err := dispatchFromDockerfile(req.builder, dockerfile, dispatchState, req.source); err != nil { 329 return err 330 } 331 } 332 return nil 333 } 334 335 // ONBUILD RUN echo yo 336 // 337 // ONBUILD triggers run when the image is used in a FROM statement. 338 // 339 // ONBUILD handling has a lot of special-case functionality, the heading in 340 // evaluator.go and comments around dispatch() in the same file explain the 341 // special cases. search for 'OnBuild' in internals.go for additional special 342 // cases. 343 // 344 func onbuild(req dispatchRequest) error { 345 if len(req.args) == 0 { 346 return errAtLeastOneArgument("ONBUILD") 347 } 348 349 if err := req.flags.Parse(); err != nil { 350 return err 351 } 352 353 triggerInstruction := strings.ToUpper(strings.TrimSpace(req.args[0])) 354 switch triggerInstruction { 355 case "ONBUILD": 356 return errors.New("Chaining ONBUILD via `ONBUILD ONBUILD` isn't allowed") 357 case "MAINTAINER", "FROM": 358 return fmt.Errorf("%s isn't allowed as an ONBUILD trigger", triggerInstruction) 359 } 360 361 runConfig := req.state.runConfig 362 original := regexp.MustCompile(`(?i)^\s*ONBUILD\s*`).ReplaceAllString(req.original, "") 363 runConfig.OnBuild = append(runConfig.OnBuild, original) 364 return req.builder.commit(req.state, "ONBUILD "+original) 365 } 366 367 // WORKDIR /tmp 368 // 369 // Set the working directory for future RUN/CMD/etc statements. 370 // 371 func workdir(req dispatchRequest) error { 372 if len(req.args) != 1 { 373 return errExactlyOneArgument("WORKDIR") 374 } 375 376 err := req.flags.Parse() 377 if err != nil { 378 return err 379 } 380 381 runConfig := req.state.runConfig 382 // This is from the Dockerfile and will not necessarily be in platform 383 // specific semantics, hence ensure it is converted. 384 runConfig.WorkingDir, err = normaliseWorkdir(runConfig.WorkingDir, req.args[0]) 385 if err != nil { 386 return err 387 } 388 389 // For performance reasons, we explicitly do a create/mkdir now 390 // This avoids having an unnecessary expensive mount/unmount calls 391 // (on Windows in particular) during each container create. 392 // Prior to 1.13, the mkdir was deferred and not executed at this step. 393 if req.builder.disableCommit { 394 // Don't call back into the daemon if we're going through docker commit --change "WORKDIR /foo". 395 // We've already updated the runConfig and that's enough. 396 return nil 397 } 398 399 comment := "WORKDIR " + runConfig.WorkingDir 400 runConfigWithCommentCmd := copyRunConfig(runConfig, withCmdCommentString(comment)) 401 containerID, err := req.builder.probeAndCreate(req.state, runConfigWithCommentCmd) 402 if err != nil || containerID == "" { 403 return err 404 } 405 if err := req.builder.docker.ContainerCreateWorkdir(containerID); err != nil { 406 return err 407 } 408 409 return req.builder.commitContainer(req.state, containerID, runConfigWithCommentCmd) 410 } 411 412 // RUN some command yo 413 // 414 // run a command and commit the image. Args are automatically prepended with 415 // the current SHELL which defaults to 'sh -c' under linux or 'cmd /S /C' under 416 // Windows, in the event there is only one argument The difference in processing: 417 // 418 // RUN echo hi # sh -c echo hi (Linux) 419 // RUN echo hi # cmd /S /C echo hi (Windows) 420 // RUN [ "echo", "hi" ] # echo hi 421 // 422 func run(req dispatchRequest) error { 423 if !req.state.hasFromImage() { 424 return errors.New("Please provide a source image with `from` prior to run") 425 } 426 427 if err := req.flags.Parse(); err != nil { 428 return err 429 } 430 431 stateRunConfig := req.state.runConfig 432 args := handleJSONArgs(req.args, req.attributes) 433 if !req.attributes["json"] { 434 args = append(getShell(stateRunConfig), args...) 435 } 436 cmdFromArgs := strslice.StrSlice(args) 437 buildArgs := req.builder.buildArgs.FilterAllowed(stateRunConfig.Env) 438 439 saveCmd := cmdFromArgs 440 if len(buildArgs) > 0 { 441 saveCmd = prependEnvOnCmd(req.builder.buildArgs, buildArgs, cmdFromArgs) 442 } 443 444 runConfigForCacheProbe := copyRunConfig(stateRunConfig, 445 withCmd(saveCmd), 446 withEntrypointOverride(saveCmd, nil)) 447 hit, err := req.builder.probeCache(req.state, runConfigForCacheProbe) 448 if err != nil || hit { 449 return err 450 } 451 452 runConfig := copyRunConfig(stateRunConfig, 453 withCmd(cmdFromArgs), 454 withEnv(append(stateRunConfig.Env, buildArgs...)), 455 withEntrypointOverride(saveCmd, strslice.StrSlice{""})) 456 457 // set config as already being escaped, this prevents double escaping on windows 458 runConfig.ArgsEscaped = true 459 460 logrus.Debugf("[BUILDER] Command to be executed: %v", runConfig.Cmd) 461 cID, err := req.builder.create(runConfig) 462 if err != nil { 463 return err 464 } 465 if err := req.builder.containerManager.Run(req.builder.clientCtx, cID, req.builder.Stdout, req.builder.Stderr); err != nil { 466 if err, ok := err.(*statusCodeError); ok { 467 // TODO: change error type, because jsonmessage.JSONError assumes HTTP 468 return &jsonmessage.JSONError{ 469 Message: fmt.Sprintf( 470 "The command '%s' returned a non-zero code: %d", 471 strings.Join(runConfig.Cmd, " "), err.StatusCode()), 472 Code: err.StatusCode(), 473 } 474 } 475 return err 476 } 477 478 return req.builder.commitContainer(req.state, cID, runConfigForCacheProbe) 479 } 480 481 // Derive the command to use for probeCache() and to commit in this container. 482 // Note that we only do this if there are any build-time env vars. Also, we 483 // use the special argument "|#" at the start of the args array. This will 484 // avoid conflicts with any RUN command since commands can not 485 // start with | (vertical bar). The "#" (number of build envs) is there to 486 // help ensure proper cache matches. We don't want a RUN command 487 // that starts with "foo=abc" to be considered part of a build-time env var. 488 // 489 // remove any unreferenced built-in args from the environment variables. 490 // These args are transparent so resulting image should be the same regardless 491 // of the value. 492 func prependEnvOnCmd(buildArgs *buildArgs, buildArgVars []string, cmd strslice.StrSlice) strslice.StrSlice { 493 var tmpBuildEnv []string 494 for _, env := range buildArgVars { 495 key := strings.SplitN(env, "=", 2)[0] 496 if buildArgs.IsReferencedOrNotBuiltin(key) { 497 tmpBuildEnv = append(tmpBuildEnv, env) 498 } 499 } 500 501 sort.Strings(tmpBuildEnv) 502 tmpEnv := append([]string{fmt.Sprintf("|%d", len(tmpBuildEnv))}, tmpBuildEnv...) 503 return strslice.StrSlice(append(tmpEnv, cmd...)) 504 } 505 506 // CMD foo 507 // 508 // Set the default command to run in the container (which may be empty). 509 // Argument handling is the same as RUN. 510 // 511 func cmd(req dispatchRequest) error { 512 if err := req.flags.Parse(); err != nil { 513 return err 514 } 515 516 runConfig := req.state.runConfig 517 cmdSlice := handleJSONArgs(req.args, req.attributes) 518 if !req.attributes["json"] { 519 cmdSlice = append(getShell(runConfig), cmdSlice...) 520 } 521 522 runConfig.Cmd = strslice.StrSlice(cmdSlice) 523 // set config as already being escaped, this prevents double escaping on windows 524 runConfig.ArgsEscaped = true 525 526 if err := req.builder.commit(req.state, fmt.Sprintf("CMD %q", cmdSlice)); err != nil { 527 return err 528 } 529 530 if len(req.args) != 0 { 531 req.state.cmdSet = true 532 } 533 534 return nil 535 } 536 537 // parseOptInterval(flag) is the duration of flag.Value, or 0 if 538 // empty. An error is reported if the value is given and less than minimum duration. 539 func parseOptInterval(f *Flag) (time.Duration, error) { 540 s := f.Value 541 if s == "" { 542 return 0, nil 543 } 544 d, err := time.ParseDuration(s) 545 if err != nil { 546 return 0, err 547 } 548 if d < time.Duration(container.MinimumDuration) { 549 return 0, fmt.Errorf("Interval %#v cannot be less than %s", f.name, container.MinimumDuration) 550 } 551 return d, nil 552 } 553 554 // HEALTHCHECK foo 555 // 556 // Set the default healthcheck command to run in the container (which may be empty). 557 // Argument handling is the same as RUN. 558 // 559 func healthcheck(req dispatchRequest) error { 560 if len(req.args) == 0 { 561 return errAtLeastOneArgument("HEALTHCHECK") 562 } 563 runConfig := req.state.runConfig 564 typ := strings.ToUpper(req.args[0]) 565 args := req.args[1:] 566 if typ == "NONE" { 567 if len(args) != 0 { 568 return errors.New("HEALTHCHECK NONE takes no arguments") 569 } 570 test := strslice.StrSlice{typ} 571 runConfig.Healthcheck = &container.HealthConfig{ 572 Test: test, 573 } 574 } else { 575 if runConfig.Healthcheck != nil { 576 oldCmd := runConfig.Healthcheck.Test 577 if len(oldCmd) > 0 && oldCmd[0] != "NONE" { 578 fmt.Fprintf(req.builder.Stdout, "Note: overriding previous HEALTHCHECK: %v\n", oldCmd) 579 } 580 } 581 582 healthcheck := container.HealthConfig{} 583 584 flInterval := req.flags.AddString("interval", "") 585 flTimeout := req.flags.AddString("timeout", "") 586 flStartPeriod := req.flags.AddString("start-period", "") 587 flRetries := req.flags.AddString("retries", "") 588 589 if err := req.flags.Parse(); err != nil { 590 return err 591 } 592 593 switch typ { 594 case "CMD": 595 cmdSlice := handleJSONArgs(args, req.attributes) 596 if len(cmdSlice) == 0 { 597 return errors.New("Missing command after HEALTHCHECK CMD") 598 } 599 600 if !req.attributes["json"] { 601 typ = "CMD-SHELL" 602 } 603 604 healthcheck.Test = strslice.StrSlice(append([]string{typ}, cmdSlice...)) 605 default: 606 return fmt.Errorf("Unknown type %#v in HEALTHCHECK (try CMD)", typ) 607 } 608 609 interval, err := parseOptInterval(flInterval) 610 if err != nil { 611 return err 612 } 613 healthcheck.Interval = interval 614 615 timeout, err := parseOptInterval(flTimeout) 616 if err != nil { 617 return err 618 } 619 healthcheck.Timeout = timeout 620 621 startPeriod, err := parseOptInterval(flStartPeriod) 622 if err != nil { 623 return err 624 } 625 healthcheck.StartPeriod = startPeriod 626 627 if flRetries.Value != "" { 628 retries, err := strconv.ParseInt(flRetries.Value, 10, 32) 629 if err != nil { 630 return err 631 } 632 if retries < 1 { 633 return fmt.Errorf("--retries must be at least 1 (not %d)", retries) 634 } 635 healthcheck.Retries = int(retries) 636 } else { 637 healthcheck.Retries = 0 638 } 639 640 runConfig.Healthcheck = &healthcheck 641 } 642 643 return req.builder.commit(req.state, fmt.Sprintf("HEALTHCHECK %q", runConfig.Healthcheck)) 644 } 645 646 // ENTRYPOINT /usr/sbin/nginx 647 // 648 // Set the entrypoint to /usr/sbin/nginx. Will accept the CMD as the arguments 649 // to /usr/sbin/nginx. Uses the default shell if not in JSON format. 650 // 651 // Handles command processing similar to CMD and RUN, only req.runConfig.Entrypoint 652 // is initialized at newBuilder time instead of through argument parsing. 653 // 654 func entrypoint(req dispatchRequest) error { 655 if err := req.flags.Parse(); err != nil { 656 return err 657 } 658 659 runConfig := req.state.runConfig 660 parsed := handleJSONArgs(req.args, req.attributes) 661 662 switch { 663 case req.attributes["json"]: 664 // ENTRYPOINT ["echo", "hi"] 665 runConfig.Entrypoint = strslice.StrSlice(parsed) 666 case len(parsed) == 0: 667 // ENTRYPOINT [] 668 runConfig.Entrypoint = nil 669 default: 670 // ENTRYPOINT echo hi 671 runConfig.Entrypoint = strslice.StrSlice(append(getShell(runConfig), parsed[0])) 672 } 673 674 // when setting the entrypoint if a CMD was not explicitly set then 675 // set the command to nil 676 if !req.state.cmdSet { 677 runConfig.Cmd = nil 678 } 679 680 return req.builder.commit(req.state, fmt.Sprintf("ENTRYPOINT %q", runConfig.Entrypoint)) 681 } 682 683 // EXPOSE 6667/tcp 7000/tcp 684 // 685 // Expose ports for links and port mappings. This all ends up in 686 // req.runConfig.ExposedPorts for runconfig. 687 // 688 func expose(req dispatchRequest) error { 689 portsTab := req.args 690 691 if len(req.args) == 0 { 692 return errAtLeastOneArgument("EXPOSE") 693 } 694 695 if err := req.flags.Parse(); err != nil { 696 return err 697 } 698 699 runConfig := req.state.runConfig 700 if runConfig.ExposedPorts == nil { 701 runConfig.ExposedPorts = make(nat.PortSet) 702 } 703 704 ports, _, err := nat.ParsePortSpecs(portsTab) 705 if err != nil { 706 return err 707 } 708 709 // instead of using ports directly, we build a list of ports and sort it so 710 // the order is consistent. This prevents cache burst where map ordering 711 // changes between builds 712 portList := make([]string, len(ports)) 713 var i int 714 for port := range ports { 715 if _, exists := runConfig.ExposedPorts[port]; !exists { 716 runConfig.ExposedPorts[port] = struct{}{} 717 } 718 portList[i] = string(port) 719 i++ 720 } 721 sort.Strings(portList) 722 return req.builder.commit(req.state, "EXPOSE "+strings.Join(portList, " ")) 723 } 724 725 // USER foo 726 // 727 // Set the user to 'foo' for future commands and when running the 728 // ENTRYPOINT/CMD at container run time. 729 // 730 func user(req dispatchRequest) error { 731 if len(req.args) != 1 { 732 return errExactlyOneArgument("USER") 733 } 734 735 if err := req.flags.Parse(); err != nil { 736 return err 737 } 738 739 req.state.runConfig.User = req.args[0] 740 return req.builder.commit(req.state, fmt.Sprintf("USER %v", req.args)) 741 } 742 743 // VOLUME /foo 744 // 745 // Expose the volume /foo for use. Will also accept the JSON array form. 746 // 747 func volume(req dispatchRequest) error { 748 if len(req.args) == 0 { 749 return errAtLeastOneArgument("VOLUME") 750 } 751 752 if err := req.flags.Parse(); err != nil { 753 return err 754 } 755 756 runConfig := req.state.runConfig 757 if runConfig.Volumes == nil { 758 runConfig.Volumes = map[string]struct{}{} 759 } 760 for _, v := range req.args { 761 v = strings.TrimSpace(v) 762 if v == "" { 763 return errors.New("VOLUME specified can not be an empty string") 764 } 765 runConfig.Volumes[v] = struct{}{} 766 } 767 return req.builder.commit(req.state, fmt.Sprintf("VOLUME %v", req.args)) 768 } 769 770 // STOPSIGNAL signal 771 // 772 // Set the signal that will be used to kill the container. 773 func stopSignal(req dispatchRequest) error { 774 if len(req.args) != 1 { 775 return errExactlyOneArgument("STOPSIGNAL") 776 } 777 778 sig := req.args[0] 779 _, err := signal.ParseSignal(sig) 780 if err != nil { 781 return err 782 } 783 784 req.state.runConfig.StopSignal = sig 785 return req.builder.commit(req.state, fmt.Sprintf("STOPSIGNAL %v", req.args)) 786 } 787 788 // ARG name[=value] 789 // 790 // Adds the variable foo to the trusted list of variables that can be passed 791 // to builder using the --build-arg flag for expansion/substitution or passing to 'run'. 792 // Dockerfile author may optionally set a default value of this variable. 793 func arg(req dispatchRequest) error { 794 if len(req.args) != 1 { 795 return errExactlyOneArgument("ARG") 796 } 797 798 var ( 799 name string 800 newValue string 801 hasDefault bool 802 ) 803 804 arg := req.args[0] 805 // 'arg' can just be a name or name-value pair. Note that this is different 806 // from 'env' that handles the split of name and value at the parser level. 807 // The reason for doing it differently for 'arg' is that we support just 808 // defining an arg and not assign it a value (while 'env' always expects a 809 // name-value pair). If possible, it will be good to harmonize the two. 810 if strings.Contains(arg, "=") { 811 parts := strings.SplitN(arg, "=", 2) 812 if len(parts[0]) == 0 { 813 return errBlankCommandNames("ARG") 814 } 815 816 name = parts[0] 817 newValue = parts[1] 818 hasDefault = true 819 } else { 820 name = arg 821 hasDefault = false 822 } 823 824 var value *string 825 if hasDefault { 826 value = &newValue 827 } 828 req.builder.buildArgs.AddArg(name, value) 829 830 // Arg before FROM doesn't add a layer 831 if !req.state.hasFromImage() { 832 req.builder.buildArgs.AddMetaArg(name, value) 833 return nil 834 } 835 return req.builder.commit(req.state, "ARG "+arg) 836 } 837 838 // SHELL powershell -command 839 // 840 // Set the non-default shell to use. 841 func shell(req dispatchRequest) error { 842 if err := req.flags.Parse(); err != nil { 843 return err 844 } 845 shellSlice := handleJSONArgs(req.args, req.attributes) 846 switch { 847 case len(shellSlice) == 0: 848 // SHELL [] 849 return errAtLeastOneArgument("SHELL") 850 case req.attributes["json"]: 851 // SHELL ["powershell", "-command"] 852 req.state.runConfig.Shell = strslice.StrSlice(shellSlice) 853 default: 854 // SHELL powershell -command - not JSON 855 return errNotJSON("SHELL", req.original) 856 } 857 return req.builder.commit(req.state, fmt.Sprintf("SHELL %v", shellSlice)) 858 } 859 860 func errAtLeastOneArgument(command string) error { 861 return fmt.Errorf("%s requires at least one argument", command) 862 } 863 864 func errExactlyOneArgument(command string) error { 865 return fmt.Errorf("%s requires exactly one argument", command) 866 } 867 868 func errAtLeastTwoArguments(command string) error { 869 return fmt.Errorf("%s requires at least two arguments", command) 870 } 871 872 func errBlankCommandNames(command string) error { 873 return fmt.Errorf("%s names can not be blank", command) 874 } 875 876 func errTooManyArguments(command string) error { 877 return fmt.Errorf("Bad input to %s, too many arguments", command) 878 }