github.com/tompao/docker@v1.9.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 "io/ioutil" 13 "os" 14 "path/filepath" 15 "regexp" 16 "runtime" 17 "sort" 18 "strings" 19 20 "github.com/Sirupsen/logrus" 21 derr "github.com/docker/docker/errors" 22 "github.com/docker/docker/image" 23 flag "github.com/docker/docker/pkg/mflag" 24 "github.com/docker/docker/pkg/nat" 25 "github.com/docker/docker/pkg/signal" 26 "github.com/docker/docker/pkg/stringutils" 27 "github.com/docker/docker/pkg/system" 28 "github.com/docker/docker/runconfig" 29 ) 30 31 const ( 32 // NoBaseImageSpecifier is the symbol used by the FROM 33 // command to specify that no base image is to be used. 34 NoBaseImageSpecifier string = "scratch" 35 ) 36 37 // dispatch with no layer / parsing. This is effectively not a command. 38 func nullDispatch(b *Builder, args []string, attributes map[string]bool, original string) error { 39 return nil 40 } 41 42 // ENV foo bar 43 // 44 // Sets the environment variable foo to bar, also makes interpolation 45 // in the dockerfile available from the next statement on via ${foo}. 46 // 47 func env(b *Builder, args []string, attributes map[string]bool, original string) error { 48 if len(args) == 0 { 49 return derr.ErrorCodeAtLeastOneArg.WithArgs("ENV") 50 } 51 52 if len(args)%2 != 0 { 53 // should never get here, but just in case 54 return derr.ErrorCodeTooManyArgs.WithArgs("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 newVar := args[j] + "=" + args[j+1] + "" 83 commitStr += " " + newVar 84 85 gotOne := false 86 for i, envVar := range b.runConfig.Env { 87 envParts := strings.SplitN(envVar, "=", 2) 88 if envParts[0] == args[j] { 89 b.runConfig.Env[i] = newVar 90 gotOne = true 91 break 92 } 93 } 94 if !gotOne { 95 b.runConfig.Env = append(b.runConfig.Env, newVar) 96 } 97 j++ 98 } 99 100 return b.commit("", b.runConfig.Cmd, commitStr) 101 } 102 103 // MAINTAINER some text <maybe@an.email.address> 104 // 105 // Sets the maintainer metadata. 106 func maintainer(b *Builder, args []string, attributes map[string]bool, original string) error { 107 if len(args) != 1 { 108 return derr.ErrorCodeExactlyOneArg.WithArgs("MAINTAINER") 109 } 110 111 if err := b.flags.Parse(); err != nil { 112 return err 113 } 114 115 b.maintainer = args[0] 116 return b.commit("", b.runConfig.Cmd, fmt.Sprintf("MAINTAINER %s", b.maintainer)) 117 } 118 119 // LABEL some json data describing the image 120 // 121 // Sets the Label variable foo to bar, 122 // 123 func label(b *Builder, args []string, attributes map[string]bool, original string) error { 124 if len(args) == 0 { 125 return derr.ErrorCodeAtLeastOneArg.WithArgs("LABEL") 126 } 127 if len(args)%2 != 0 { 128 // should never get here, but just in case 129 return derr.ErrorCodeTooManyArgs.WithArgs("LABEL") 130 } 131 132 if err := b.flags.Parse(); err != nil { 133 return err 134 } 135 136 commitStr := "LABEL" 137 138 if b.runConfig.Labels == nil { 139 b.runConfig.Labels = map[string]string{} 140 } 141 142 for j := 0; j < len(args); j++ { 143 // name ==> args[j] 144 // value ==> args[j+1] 145 newVar := args[j] + "=" + args[j+1] + "" 146 commitStr += " " + newVar 147 148 b.runConfig.Labels[args[j]] = args[j+1] 149 j++ 150 } 151 return b.commit("", b.runConfig.Cmd, commitStr) 152 } 153 154 // ADD foo /path 155 // 156 // Add the file 'foo' to '/path'. Tarball and Remote URL (git, http) handling 157 // exist here. If you do not wish to have this automatic handling, use COPY. 158 // 159 func add(b *Builder, args []string, attributes map[string]bool, original string) error { 160 if len(args) < 2 { 161 return derr.ErrorCodeAtLeastTwoArgs.WithArgs("ADD") 162 } 163 164 if err := b.flags.Parse(); err != nil { 165 return err 166 } 167 168 return b.runContextCommand(args, true, true, "ADD") 169 } 170 171 // COPY foo /path 172 // 173 // Same as 'ADD' but without the tar and remote url handling. 174 // 175 func dispatchCopy(b *Builder, args []string, attributes map[string]bool, original string) error { 176 if len(args) < 2 { 177 return derr.ErrorCodeAtLeastTwoArgs.WithArgs("COPY") 178 } 179 180 if err := b.flags.Parse(); err != nil { 181 return err 182 } 183 184 return b.runContextCommand(args, false, false, "COPY") 185 } 186 187 // FROM imagename 188 // 189 // This sets the image the dockerfile will build on top of. 190 // 191 func from(b *Builder, args []string, attributes map[string]bool, original string) error { 192 if len(args) != 1 { 193 return derr.ErrorCodeExactlyOneArg.WithArgs("FROM") 194 } 195 196 if err := b.flags.Parse(); err != nil { 197 return err 198 } 199 200 name := args[0] 201 202 // Windows cannot support a container with no base image. 203 if name == NoBaseImageSpecifier { 204 if runtime.GOOS == "windows" { 205 return fmt.Errorf("Windows does not support FROM scratch") 206 } 207 b.image = "" 208 b.noBaseImage = true 209 return nil 210 } 211 212 var ( 213 image *image.Image 214 err error 215 ) 216 // TODO: don't use `name`, instead resolve it to a digest 217 if !b.Pull { 218 image, err = b.docker.LookupImage(name) 219 // TODO: shouldn't we error out if error is different from "not found" ? 220 } 221 if image == nil { 222 image, err = b.docker.Pull(name) 223 if err != nil { 224 return err 225 } 226 } 227 return b.processImageFrom(image) 228 } 229 230 // ONBUILD RUN echo yo 231 // 232 // ONBUILD triggers run when the image is used in a FROM statement. 233 // 234 // ONBUILD handling has a lot of special-case functionality, the heading in 235 // evaluator.go and comments around dispatch() in the same file explain the 236 // special cases. search for 'OnBuild' in internals.go for additional special 237 // cases. 238 // 239 func onbuild(b *Builder, args []string, attributes map[string]bool, original string) error { 240 if len(args) == 0 { 241 return derr.ErrorCodeAtLeastOneArg.WithArgs("ONBUILD") 242 } 243 244 if err := b.flags.Parse(); err != nil { 245 return err 246 } 247 248 triggerInstruction := strings.ToUpper(strings.TrimSpace(args[0])) 249 switch triggerInstruction { 250 case "ONBUILD": 251 return derr.ErrorCodeChainOnBuild 252 case "MAINTAINER", "FROM": 253 return derr.ErrorCodeBadOnBuildCmd.WithArgs(triggerInstruction) 254 } 255 256 original = regexp.MustCompile(`(?i)^\s*ONBUILD\s*`).ReplaceAllString(original, "") 257 258 b.runConfig.OnBuild = append(b.runConfig.OnBuild, original) 259 return b.commit("", b.runConfig.Cmd, fmt.Sprintf("ONBUILD %s", original)) 260 } 261 262 // WORKDIR /tmp 263 // 264 // Set the working directory for future RUN/CMD/etc statements. 265 // 266 func workdir(b *Builder, args []string, attributes map[string]bool, original string) error { 267 if len(args) != 1 { 268 return derr.ErrorCodeExactlyOneArg.WithArgs("WORKDIR") 269 } 270 271 if err := b.flags.Parse(); err != nil { 272 return err 273 } 274 275 // This is from the Dockerfile and will not necessarily be in platform 276 // specific semantics, hence ensure it is converted. 277 workdir := filepath.FromSlash(args[0]) 278 279 if !system.IsAbs(workdir) { 280 current := filepath.FromSlash(b.runConfig.WorkingDir) 281 workdir = filepath.Join(string(os.PathSeparator), current, workdir) 282 } 283 284 b.runConfig.WorkingDir = workdir 285 286 return b.commit("", b.runConfig.Cmd, fmt.Sprintf("WORKDIR %v", workdir)) 287 } 288 289 // RUN some command yo 290 // 291 // run a command and commit the image. Args are automatically prepended with 292 // 'sh -c' under linux or 'cmd /S /C' under Windows, in the event there is 293 // 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 derr.ErrorCodeMissingFrom 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 if runtime.GOOS != "windows" { 312 args = append([]string{"/bin/sh", "-c"}, args...) 313 } else { 314 args = append([]string{"cmd", "/S", "/C"}, args...) 315 } 316 } 317 318 runCmd := flag.NewFlagSet("run", flag.ContinueOnError) 319 runCmd.SetOutput(ioutil.Discard) 320 runCmd.Usage = nil 321 322 config, _, _, err := runconfig.Parse(runCmd, append([]string{b.image}, args...)) 323 if err != nil { 324 return err 325 } 326 327 // stash the cmd 328 cmd := b.runConfig.Cmd 329 runconfig.Merge(b.runConfig, config) 330 // stash the config environment 331 env := b.runConfig.Env 332 333 defer func(cmd *stringutils.StrSlice) { b.runConfig.Cmd = cmd }(cmd) 334 defer func(env []string) { b.runConfig.Env = env }(env) 335 336 // derive the net build-time environment for this run. We let config 337 // environment override the build time environment. 338 // This means that we take the b.buildArgs list of env vars and remove 339 // any of those variables that are defined as part of the container. In other 340 // words, anything in b.Config.Env. What's left is the list of build-time env 341 // vars that we need to add to each RUN command - note the list could be empty. 342 // 343 // We don't persist the build time environment with container's config 344 // environment, but just sort and prepend it to the command string at time 345 // of commit. 346 // This helps with tracing back the image's actual environment at the time 347 // of RUN, without leaking it to the final image. It also aids cache 348 // lookup for same image built with same build time environment. 349 cmdBuildEnv := []string{} 350 configEnv := runconfig.ConvertKVStringsToMap(b.runConfig.Env) 351 for key, val := range b.BuildArgs { 352 if !b.isBuildArgAllowed(key) { 353 // skip build-args that are not in allowed list, meaning they have 354 // not been defined by an "ARG" Dockerfile command yet. 355 // This is an error condition but only if there is no "ARG" in the entire 356 // Dockerfile, so we'll generate any necessary errors after we parsed 357 // the entire file (see 'leftoverArgs' processing in evaluator.go ) 358 continue 359 } 360 if _, ok := configEnv[key]; !ok { 361 cmdBuildEnv = append(cmdBuildEnv, fmt.Sprintf("%s=%s", key, val)) 362 } 363 } 364 365 // derive the command to use for probeCache() and to commit in this container. 366 // Note that we only do this if there are any build-time env vars. Also, we 367 // use the special argument "|#" at the start of the args array. This will 368 // avoid conflicts with any RUN command since commands can not 369 // start with | (vertical bar). The "#" (number of build envs) is there to 370 // help ensure proper cache matches. We don't want a RUN command 371 // that starts with "foo=abc" to be considered part of a build-time env var. 372 saveCmd := config.Cmd 373 if len(cmdBuildEnv) > 0 { 374 sort.Strings(cmdBuildEnv) 375 tmpEnv := append([]string{fmt.Sprintf("|%d", len(cmdBuildEnv))}, cmdBuildEnv...) 376 saveCmd = stringutils.NewStrSlice(append(tmpEnv, saveCmd.Slice()...)...) 377 } 378 379 b.runConfig.Cmd = saveCmd 380 hit, err := b.probeCache() 381 if err != nil { 382 return err 383 } 384 if hit { 385 return nil 386 } 387 388 // set Cmd manually, this is special case only for Dockerfiles 389 b.runConfig.Cmd = config.Cmd 390 // set build-time environment for 'run'. 391 b.runConfig.Env = append(b.runConfig.Env, cmdBuildEnv...) 392 393 logrus.Debugf("[BUILDER] Command to be executed: %v", b.runConfig.Cmd) 394 395 c, err := b.create() 396 if err != nil { 397 return err 398 } 399 400 // Ensure that we keep the container mounted until the commit 401 // to avoid unmounting and then mounting directly again 402 c.Mount() 403 defer c.Unmount() 404 405 err = b.run(c) 406 if err != nil { 407 return err 408 } 409 410 // revert to original config environment and set the command string to 411 // have the build-time env vars in it (if any) so that future cache look-ups 412 // properly match it. 413 b.runConfig.Env = env 414 b.runConfig.Cmd = saveCmd 415 if err := b.commit(c.ID, cmd, "run"); err != nil { 416 return err 417 } 418 419 return nil 420 } 421 422 // CMD foo 423 // 424 // Set the default command to run in the container (which may be empty). 425 // Argument handling is the same as RUN. 426 // 427 func cmd(b *Builder, args []string, attributes map[string]bool, original string) error { 428 if err := b.flags.Parse(); err != nil { 429 return err 430 } 431 432 cmdSlice := handleJSONArgs(args, attributes) 433 434 if !attributes["json"] { 435 if runtime.GOOS != "windows" { 436 cmdSlice = append([]string{"/bin/sh", "-c"}, cmdSlice...) 437 } else { 438 cmdSlice = append([]string{"cmd", "/S", "/C"}, cmdSlice...) 439 } 440 } 441 442 b.runConfig.Cmd = stringutils.NewStrSlice(cmdSlice...) 443 444 if err := b.commit("", b.runConfig.Cmd, fmt.Sprintf("CMD %q", cmdSlice)); err != nil { 445 return err 446 } 447 448 if len(args) != 0 { 449 b.cmdSet = true 450 } 451 452 return nil 453 } 454 455 // ENTRYPOINT /usr/sbin/nginx 456 // 457 // Set the entrypoint (which defaults to sh -c on linux, or cmd /S /C on Windows) to 458 // /usr/sbin/nginx. Will accept the CMD as the arguments to /usr/sbin/nginx. 459 // 460 // Handles command processing similar to CMD and RUN, only b.runConfig.Entrypoint 461 // is initialized at NewBuilder time instead of through argument parsing. 462 // 463 func entrypoint(b *Builder, args []string, attributes map[string]bool, original string) error { 464 if err := b.flags.Parse(); err != nil { 465 return err 466 } 467 468 parsed := handleJSONArgs(args, attributes) 469 470 switch { 471 case attributes["json"]: 472 // ENTRYPOINT ["echo", "hi"] 473 b.runConfig.Entrypoint = stringutils.NewStrSlice(parsed...) 474 case len(parsed) == 0: 475 // ENTRYPOINT [] 476 b.runConfig.Entrypoint = nil 477 default: 478 // ENTRYPOINT echo hi 479 if runtime.GOOS != "windows" { 480 b.runConfig.Entrypoint = stringutils.NewStrSlice("/bin/sh", "-c", parsed[0]) 481 } else { 482 b.runConfig.Entrypoint = stringutils.NewStrSlice("cmd", "/S /C", parsed[0]) 483 } 484 } 485 486 // when setting the entrypoint if a CMD was not explicitly set then 487 // set the command to nil 488 if !b.cmdSet { 489 b.runConfig.Cmd = nil 490 } 491 492 if err := b.commit("", b.runConfig.Cmd, fmt.Sprintf("ENTRYPOINT %q", b.runConfig.Entrypoint)); err != nil { 493 return err 494 } 495 496 return nil 497 } 498 499 // EXPOSE 6667/tcp 7000/tcp 500 // 501 // Expose ports for links and port mappings. This all ends up in 502 // b.runConfig.ExposedPorts for runconfig. 503 // 504 func expose(b *Builder, args []string, attributes map[string]bool, original string) error { 505 portsTab := args 506 507 if len(args) == 0 { 508 return derr.ErrorCodeAtLeastOneArg.WithArgs("EXPOSE") 509 } 510 511 if err := b.flags.Parse(); err != nil { 512 return err 513 } 514 515 if b.runConfig.ExposedPorts == nil { 516 b.runConfig.ExposedPorts = make(nat.PortSet) 517 } 518 519 ports, _, err := nat.ParsePortSpecs(portsTab) 520 if err != nil { 521 return err 522 } 523 524 // instead of using ports directly, we build a list of ports and sort it so 525 // the order is consistent. This prevents cache burst where map ordering 526 // changes between builds 527 portList := make([]string, len(ports)) 528 var i int 529 for port := range ports { 530 if _, exists := b.runConfig.ExposedPorts[port]; !exists { 531 b.runConfig.ExposedPorts[port] = struct{}{} 532 } 533 portList[i] = string(port) 534 i++ 535 } 536 sort.Strings(portList) 537 return b.commit("", b.runConfig.Cmd, fmt.Sprintf("EXPOSE %s", strings.Join(portList, " "))) 538 } 539 540 // USER foo 541 // 542 // Set the user to 'foo' for future commands and when running the 543 // ENTRYPOINT/CMD at container run time. 544 // 545 func user(b *Builder, args []string, attributes map[string]bool, original string) error { 546 if len(args) != 1 { 547 return derr.ErrorCodeExactlyOneArg.WithArgs("USER") 548 } 549 550 if err := b.flags.Parse(); err != nil { 551 return err 552 } 553 554 b.runConfig.User = args[0] 555 return b.commit("", b.runConfig.Cmd, fmt.Sprintf("USER %v", args)) 556 } 557 558 // VOLUME /foo 559 // 560 // Expose the volume /foo for use. Will also accept the JSON array form. 561 // 562 func volume(b *Builder, args []string, attributes map[string]bool, original string) error { 563 if len(args) == 0 { 564 return derr.ErrorCodeAtLeastOneArg.WithArgs("VOLUME") 565 } 566 567 if err := b.flags.Parse(); err != nil { 568 return err 569 } 570 571 if b.runConfig.Volumes == nil { 572 b.runConfig.Volumes = map[string]struct{}{} 573 } 574 for _, v := range args { 575 v = strings.TrimSpace(v) 576 if v == "" { 577 return derr.ErrorCodeVolumeEmpty 578 } 579 b.runConfig.Volumes[v] = struct{}{} 580 } 581 if err := b.commit("", b.runConfig.Cmd, fmt.Sprintf("VOLUME %v", args)); err != nil { 582 return err 583 } 584 return nil 585 } 586 587 // STOPSIGNAL signal 588 // 589 // Set the signal that will be used to kill the container. 590 func stopSignal(b *Builder, args []string, attributes map[string]bool, original string) error { 591 if len(args) != 1 { 592 return fmt.Errorf("STOPSIGNAL requires exactly one argument") 593 } 594 595 sig := args[0] 596 _, err := signal.ParseSignal(sig) 597 if err != nil { 598 return err 599 } 600 601 b.runConfig.StopSignal = sig 602 return b.commit("", b.runConfig.Cmd, fmt.Sprintf("STOPSIGNAL %v", args)) 603 } 604 605 // ARG name[=value] 606 // 607 // Adds the variable foo to the trusted list of variables that can be passed 608 // to builder using the --build-arg flag for expansion/subsitution or passing to 'run'. 609 // Dockerfile author may optionally set a default value of this variable. 610 func arg(b *Builder, args []string, attributes map[string]bool, original string) error { 611 if len(args) != 1 { 612 return fmt.Errorf("ARG requires exactly one argument definition") 613 } 614 615 var ( 616 name string 617 value string 618 hasDefault bool 619 ) 620 621 arg := args[0] 622 // 'arg' can just be a name or name-value pair. Note that this is different 623 // from 'env' that handles the split of name and value at the parser level. 624 // The reason for doing it differently for 'arg' is that we support just 625 // defining an arg and not assign it a value (while 'env' always expects a 626 // name-value pair). If possible, it will be good to harmonize the two. 627 if strings.Contains(arg, "=") { 628 parts := strings.SplitN(arg, "=", 2) 629 name = parts[0] 630 value = parts[1] 631 hasDefault = true 632 } else { 633 name = arg 634 hasDefault = false 635 } 636 // add the arg to allowed list of build-time args from this step on. 637 b.allowedBuildArgs[name] = true 638 639 // If there is a default value associated with this arg then add it to the 640 // b.buildArgs if one is not already passed to the builder. The args passed 641 // to builder override the defaut value of 'arg'. 642 if _, ok := b.BuildArgs[name]; !ok && hasDefault { 643 b.BuildArgs[name] = value 644 } 645 646 return b.commit("", b.runConfig.Cmd, fmt.Sprintf("ARG %s", arg)) 647 }