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