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