github.com/rita33cool1/iot-system-gateway@v0.0.0-20200911033302-e65bde238cc5/docker-engine/builder/dockerfile/dispatchers.go (about) 1 package dockerfile // import "github.com/docker/docker/builder/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 "runtime" 14 "sort" 15 "strings" 16 17 "github.com/docker/docker/api" 18 "github.com/docker/docker/api/types/container" 19 "github.com/docker/docker/api/types/strslice" 20 "github.com/docker/docker/builder" 21 "github.com/docker/docker/builder/dockerfile/instructions" 22 "github.com/docker/docker/builder/dockerfile/parser" 23 "github.com/docker/docker/builder/dockerfile/shell" 24 "github.com/docker/docker/errdefs" 25 "github.com/docker/docker/image" 26 "github.com/docker/docker/pkg/jsonmessage" 27 "github.com/docker/docker/pkg/signal" 28 "github.com/docker/docker/pkg/system" 29 "github.com/docker/go-connections/nat" 30 "github.com/pkg/errors" 31 "github.com/sirupsen/logrus" 32 ) 33 34 // ENV foo bar 35 // 36 // Sets the environment variable foo to bar, also makes interpolation 37 // in the dockerfile available from the next statement on via ${foo}. 38 // 39 func dispatchEnv(d dispatchRequest, c *instructions.EnvCommand) error { 40 runConfig := d.state.runConfig 41 commitMessage := bytes.NewBufferString("ENV") 42 for _, e := range c.Env { 43 name := e.Key 44 newVar := e.String() 45 46 commitMessage.WriteString(" " + newVar) 47 gotOne := false 48 for i, envVar := range runConfig.Env { 49 envParts := strings.SplitN(envVar, "=", 2) 50 compareFrom := envParts[0] 51 if shell.EqualEnvKeys(compareFrom, name) { 52 runConfig.Env[i] = newVar 53 gotOne = true 54 break 55 } 56 } 57 if !gotOne { 58 runConfig.Env = append(runConfig.Env, newVar) 59 } 60 } 61 return d.builder.commit(d.state, commitMessage.String()) 62 } 63 64 // MAINTAINER some text <maybe@an.email.address> 65 // 66 // Sets the maintainer metadata. 67 func dispatchMaintainer(d dispatchRequest, c *instructions.MaintainerCommand) error { 68 69 d.state.maintainer = c.Maintainer 70 return d.builder.commit(d.state, "MAINTAINER "+c.Maintainer) 71 } 72 73 // LABEL some json data describing the image 74 // 75 // Sets the Label variable foo to bar, 76 // 77 func dispatchLabel(d dispatchRequest, c *instructions.LabelCommand) error { 78 if d.state.runConfig.Labels == nil { 79 d.state.runConfig.Labels = make(map[string]string) 80 } 81 commitStr := "LABEL" 82 for _, v := range c.Labels { 83 d.state.runConfig.Labels[v.Key] = v.Value 84 commitStr += " " + v.String() 85 } 86 return d.builder.commit(d.state, commitStr) 87 } 88 89 // ADD foo /path 90 // 91 // Add the file 'foo' to '/path'. Tarball and Remote URL (git, http) handling 92 // exist here. If you do not wish to have this automatic handling, use COPY. 93 // 94 func dispatchAdd(d dispatchRequest, c *instructions.AddCommand) error { 95 downloader := newRemoteSourceDownloader(d.builder.Output, d.builder.Stdout) 96 copier := copierFromDispatchRequest(d, downloader, nil) 97 defer copier.Cleanup() 98 99 copyInstruction, err := copier.createCopyInstruction(c.SourcesAndDest, "ADD") 100 if err != nil { 101 return err 102 } 103 copyInstruction.chownStr = c.Chown 104 copyInstruction.allowLocalDecompression = true 105 106 return d.builder.performCopy(d.state, copyInstruction) 107 } 108 109 // COPY foo /path 110 // 111 // Same as 'ADD' but without the tar and remote url handling. 112 // 113 func dispatchCopy(d dispatchRequest, c *instructions.CopyCommand) error { 114 var im *imageMount 115 var err error 116 if c.From != "" { 117 im, err = d.getImageMount(c.From) 118 if err != nil { 119 return errors.Wrapf(err, "invalid from flag value %s", c.From) 120 } 121 } 122 copier := copierFromDispatchRequest(d, errOnSourceDownload, im) 123 defer copier.Cleanup() 124 copyInstruction, err := copier.createCopyInstruction(c.SourcesAndDest, "COPY") 125 if err != nil { 126 return err 127 } 128 copyInstruction.chownStr = c.Chown 129 130 return d.builder.performCopy(d.state, copyInstruction) 131 } 132 133 func (d *dispatchRequest) getImageMount(imageRefOrID string) (*imageMount, error) { 134 if imageRefOrID == "" { 135 // TODO: this could return the source in the default case as well? 136 return nil, nil 137 } 138 139 var localOnly bool 140 stage, err := d.stages.get(imageRefOrID) 141 if err != nil { 142 return nil, err 143 } 144 if stage != nil { 145 imageRefOrID = stage.Image 146 localOnly = true 147 } 148 return d.builder.imageSources.Get(imageRefOrID, localOnly) 149 } 150 151 // FROM imagename[:tag | @digest] [AS build-stage-name] 152 // 153 func initializeStage(d dispatchRequest, cmd *instructions.Stage) error { 154 d.builder.imageProber.Reset() 155 image, err := d.getFromImage(d.shlex, cmd.BaseName) 156 if err != nil { 157 return err 158 } 159 state := d.state 160 if err := state.beginStage(cmd.Name, image); err != nil { 161 return err 162 } 163 if len(state.runConfig.OnBuild) > 0 { 164 triggers := state.runConfig.OnBuild 165 state.runConfig.OnBuild = nil 166 return dispatchTriggeredOnBuild(d, triggers) 167 } 168 return nil 169 } 170 171 func dispatchTriggeredOnBuild(d dispatchRequest, triggers []string) error { 172 fmt.Fprintf(d.builder.Stdout, "# Executing %d build trigger", len(triggers)) 173 if len(triggers) > 1 { 174 fmt.Fprint(d.builder.Stdout, "s") 175 } 176 fmt.Fprintln(d.builder.Stdout) 177 for _, trigger := range triggers { 178 d.state.updateRunConfig() 179 ast, err := parser.Parse(strings.NewReader(trigger)) 180 if err != nil { 181 return err 182 } 183 if len(ast.AST.Children) != 1 { 184 return errors.New("onbuild trigger should be a single expression") 185 } 186 cmd, err := instructions.ParseCommand(ast.AST.Children[0]) 187 if err != nil { 188 if instructions.IsUnknownInstruction(err) { 189 buildsFailed.WithValues(metricsUnknownInstructionError).Inc() 190 } 191 return err 192 } 193 err = dispatch(d, cmd) 194 if err != nil { 195 return err 196 } 197 } 198 return nil 199 } 200 201 func (d *dispatchRequest) getExpandedImageName(shlex *shell.Lex, name string) (string, error) { 202 substitutionArgs := []string{} 203 for key, value := range d.state.buildArgs.GetAllMeta() { 204 substitutionArgs = append(substitutionArgs, key+"="+value) 205 } 206 207 name, err := shlex.ProcessWord(name, substitutionArgs) 208 if err != nil { 209 return "", err 210 } 211 return name, nil 212 } 213 func (d *dispatchRequest) getImageOrStage(name string) (builder.Image, error) { 214 var localOnly bool 215 if im, ok := d.stages.getByName(name); ok { 216 name = im.Image 217 localOnly = true 218 } 219 220 // Windows cannot support a container with no base image unless it is LCOW. 221 if name == api.NoBaseImageSpecifier { 222 imageImage := &image.Image{} 223 imageImage.OS = runtime.GOOS 224 if runtime.GOOS == "windows" { 225 optionsOS := system.ParsePlatform(d.builder.options.Platform).OS 226 switch optionsOS { 227 case "windows", "": 228 return nil, errors.New("Windows does not support FROM scratch") 229 case "linux": 230 if !system.LCOWSupported() { 231 return nil, errors.New("Linux containers are not supported on this system") 232 } 233 imageImage.OS = "linux" 234 default: 235 return nil, errors.Errorf("operating system %q is not supported", optionsOS) 236 } 237 } 238 return builder.Image(imageImage), nil 239 } 240 imageMount, err := d.builder.imageSources.Get(name, localOnly) 241 if err != nil { 242 return nil, err 243 } 244 return imageMount.Image(), nil 245 } 246 func (d *dispatchRequest) getFromImage(shlex *shell.Lex, name string) (builder.Image, error) { 247 name, err := d.getExpandedImageName(shlex, name) 248 if err != nil { 249 return nil, err 250 } 251 return d.getImageOrStage(name) 252 } 253 254 func dispatchOnbuild(d dispatchRequest, c *instructions.OnbuildCommand) error { 255 256 d.state.runConfig.OnBuild = append(d.state.runConfig.OnBuild, c.Expression) 257 return d.builder.commit(d.state, "ONBUILD "+c.Expression) 258 } 259 260 // WORKDIR /tmp 261 // 262 // Set the working directory for future RUN/CMD/etc statements. 263 // 264 func dispatchWorkdir(d dispatchRequest, c *instructions.WorkdirCommand) error { 265 runConfig := d.state.runConfig 266 var err error 267 baseImageOS := system.ParsePlatform(d.state.operatingSystem).OS 268 runConfig.WorkingDir, err = normalizeWorkdir(baseImageOS, runConfig.WorkingDir, c.Path) 269 if err != nil { 270 return err 271 } 272 273 // For performance reasons, we explicitly do a create/mkdir now 274 // This avoids having an unnecessary expensive mount/unmount calls 275 // (on Windows in particular) during each container create. 276 // Prior to 1.13, the mkdir was deferred and not executed at this step. 277 if d.builder.disableCommit { 278 // Don't call back into the daemon if we're going through docker commit --change "WORKDIR /foo". 279 // We've already updated the runConfig and that's enough. 280 return nil 281 } 282 283 comment := "WORKDIR " + runConfig.WorkingDir 284 runConfigWithCommentCmd := copyRunConfig(runConfig, withCmdCommentString(comment, baseImageOS)) 285 containerID, err := d.builder.probeAndCreate(d.state, runConfigWithCommentCmd) 286 if err != nil || containerID == "" { 287 return err 288 } 289 if err := d.builder.docker.ContainerCreateWorkdir(containerID); err != nil { 290 return err 291 } 292 293 return d.builder.commitContainer(d.state, containerID, runConfigWithCommentCmd) 294 } 295 296 func resolveCmdLine(cmd instructions.ShellDependantCmdLine, runConfig *container.Config, os string) []string { 297 result := cmd.CmdLine 298 if cmd.PrependShell && result != nil { 299 result = append(getShell(runConfig, os), result...) 300 } 301 return result 302 } 303 304 // RUN some command yo 305 // 306 // run a command and commit the image. Args are automatically prepended with 307 // the current SHELL which defaults to 'sh -c' under linux or 'cmd /S /C' under 308 // Windows, in the event there is only one argument The difference in processing: 309 // 310 // RUN echo hi # sh -c echo hi (Linux and LCOW) 311 // RUN echo hi # cmd /S /C echo hi (Windows) 312 // RUN [ "echo", "hi" ] # echo hi 313 // 314 func dispatchRun(d dispatchRequest, c *instructions.RunCommand) error { 315 if !system.IsOSSupported(d.state.operatingSystem) { 316 return system.ErrNotSupportedOperatingSystem 317 } 318 stateRunConfig := d.state.runConfig 319 cmdFromArgs := resolveCmdLine(c.ShellDependantCmdLine, stateRunConfig, d.state.operatingSystem) 320 buildArgs := d.state.buildArgs.FilterAllowed(stateRunConfig.Env) 321 322 saveCmd := cmdFromArgs 323 if len(buildArgs) > 0 { 324 saveCmd = prependEnvOnCmd(d.state.buildArgs, buildArgs, cmdFromArgs) 325 } 326 327 runConfigForCacheProbe := copyRunConfig(stateRunConfig, 328 withCmd(saveCmd), 329 withEntrypointOverride(saveCmd, nil)) 330 hit, err := d.builder.probeCache(d.state, runConfigForCacheProbe) 331 if err != nil || hit { 332 return err 333 } 334 335 runConfig := copyRunConfig(stateRunConfig, 336 withCmd(cmdFromArgs), 337 withEnv(append(stateRunConfig.Env, buildArgs...)), 338 withEntrypointOverride(saveCmd, strslice.StrSlice{""})) 339 340 // set config as already being escaped, this prevents double escaping on windows 341 runConfig.ArgsEscaped = true 342 343 logrus.Debugf("[BUILDER] Command to be executed: %v", runConfig.Cmd) 344 cID, err := d.builder.create(runConfig) 345 if err != nil { 346 return err 347 } 348 if err := d.builder.containerManager.Run(d.builder.clientCtx, cID, d.builder.Stdout, d.builder.Stderr); err != nil { 349 if err, ok := err.(*statusCodeError); ok { 350 // TODO: change error type, because jsonmessage.JSONError assumes HTTP 351 return &jsonmessage.JSONError{ 352 Message: fmt.Sprintf( 353 "The command '%s' returned a non-zero code: %d", 354 strings.Join(runConfig.Cmd, " "), err.StatusCode()), 355 Code: err.StatusCode(), 356 } 357 } 358 return err 359 } 360 361 return d.builder.commitContainer(d.state, cID, runConfigForCacheProbe) 362 } 363 364 // Derive the command to use for probeCache() and to commit in this container. 365 // Note that we only do this if there are any build-time env vars. Also, we 366 // use the special argument "|#" at the start of the args array. This will 367 // avoid conflicts with any RUN command since commands can not 368 // start with | (vertical bar). The "#" (number of build envs) is there to 369 // help ensure proper cache matches. We don't want a RUN command 370 // that starts with "foo=abc" to be considered part of a build-time env var. 371 // 372 // remove any unreferenced built-in args from the environment variables. 373 // These args are transparent so resulting image should be the same regardless 374 // of the value. 375 func prependEnvOnCmd(buildArgs *buildArgs, buildArgVars []string, cmd strslice.StrSlice) strslice.StrSlice { 376 var tmpBuildEnv []string 377 for _, env := range buildArgVars { 378 key := strings.SplitN(env, "=", 2)[0] 379 if buildArgs.IsReferencedOrNotBuiltin(key) { 380 tmpBuildEnv = append(tmpBuildEnv, env) 381 } 382 } 383 384 sort.Strings(tmpBuildEnv) 385 tmpEnv := append([]string{fmt.Sprintf("|%d", len(tmpBuildEnv))}, tmpBuildEnv...) 386 return strslice.StrSlice(append(tmpEnv, cmd...)) 387 } 388 389 // CMD foo 390 // 391 // Set the default command to run in the container (which may be empty). 392 // Argument handling is the same as RUN. 393 // 394 func dispatchCmd(d dispatchRequest, c *instructions.CmdCommand) error { 395 runConfig := d.state.runConfig 396 optionsOS := system.ParsePlatform(d.builder.options.Platform).OS 397 cmd := resolveCmdLine(c.ShellDependantCmdLine, runConfig, optionsOS) 398 runConfig.Cmd = cmd 399 // set config as already being escaped, this prevents double escaping on windows 400 runConfig.ArgsEscaped = true 401 402 if err := d.builder.commit(d.state, fmt.Sprintf("CMD %q", cmd)); err != nil { 403 return err 404 } 405 406 if len(c.ShellDependantCmdLine.CmdLine) != 0 { 407 d.state.cmdSet = true 408 } 409 410 return nil 411 } 412 413 // HEALTHCHECK foo 414 // 415 // Set the default healthcheck command to run in the container (which may be empty). 416 // Argument handling is the same as RUN. 417 // 418 func dispatchHealthcheck(d dispatchRequest, c *instructions.HealthCheckCommand) error { 419 runConfig := d.state.runConfig 420 if runConfig.Healthcheck != nil { 421 oldCmd := runConfig.Healthcheck.Test 422 if len(oldCmd) > 0 && oldCmd[0] != "NONE" { 423 fmt.Fprintf(d.builder.Stdout, "Note: overriding previous HEALTHCHECK: %v\n", oldCmd) 424 } 425 } 426 runConfig.Healthcheck = c.Health 427 return d.builder.commit(d.state, fmt.Sprintf("HEALTHCHECK %q", runConfig.Healthcheck)) 428 } 429 430 // ENTRYPOINT /usr/sbin/nginx 431 // 432 // Set the entrypoint to /usr/sbin/nginx. Will accept the CMD as the arguments 433 // to /usr/sbin/nginx. Uses the default shell if not in JSON format. 434 // 435 // Handles command processing similar to CMD and RUN, only req.runConfig.Entrypoint 436 // is initialized at newBuilder time instead of through argument parsing. 437 // 438 func dispatchEntrypoint(d dispatchRequest, c *instructions.EntrypointCommand) error { 439 runConfig := d.state.runConfig 440 optionsOS := system.ParsePlatform(d.builder.options.Platform).OS 441 cmd := resolveCmdLine(c.ShellDependantCmdLine, runConfig, optionsOS) 442 runConfig.Entrypoint = cmd 443 if !d.state.cmdSet { 444 runConfig.Cmd = nil 445 } 446 447 return d.builder.commit(d.state, fmt.Sprintf("ENTRYPOINT %q", runConfig.Entrypoint)) 448 } 449 450 // EXPOSE 6667/tcp 7000/tcp 451 // 452 // Expose ports for links and port mappings. This all ends up in 453 // req.runConfig.ExposedPorts for runconfig. 454 // 455 func dispatchExpose(d dispatchRequest, c *instructions.ExposeCommand, envs []string) error { 456 // custom multi word expansion 457 // expose $FOO with FOO="80 443" is expanded as EXPOSE [80,443]. This is the only command supporting word to words expansion 458 // so the word processing has been de-generalized 459 ports := []string{} 460 for _, p := range c.Ports { 461 ps, err := d.shlex.ProcessWords(p, envs) 462 if err != nil { 463 return err 464 } 465 ports = append(ports, ps...) 466 } 467 c.Ports = ports 468 469 ps, _, err := nat.ParsePortSpecs(ports) 470 if err != nil { 471 return err 472 } 473 474 if d.state.runConfig.ExposedPorts == nil { 475 d.state.runConfig.ExposedPorts = make(nat.PortSet) 476 } 477 for p := range ps { 478 d.state.runConfig.ExposedPorts[p] = struct{}{} 479 } 480 481 return d.builder.commit(d.state, "EXPOSE "+strings.Join(c.Ports, " ")) 482 } 483 484 // USER foo 485 // 486 // Set the user to 'foo' for future commands and when running the 487 // ENTRYPOINT/CMD at container run time. 488 // 489 func dispatchUser(d dispatchRequest, c *instructions.UserCommand) error { 490 d.state.runConfig.User = c.User 491 return d.builder.commit(d.state, fmt.Sprintf("USER %v", c.User)) 492 } 493 494 // VOLUME /foo 495 // 496 // Expose the volume /foo for use. Will also accept the JSON array form. 497 // 498 func dispatchVolume(d dispatchRequest, c *instructions.VolumeCommand) error { 499 if d.state.runConfig.Volumes == nil { 500 d.state.runConfig.Volumes = map[string]struct{}{} 501 } 502 for _, v := range c.Volumes { 503 if v == "" { 504 return errors.New("VOLUME specified can not be an empty string") 505 } 506 d.state.runConfig.Volumes[v] = struct{}{} 507 } 508 return d.builder.commit(d.state, fmt.Sprintf("VOLUME %v", c.Volumes)) 509 } 510 511 // STOPSIGNAL signal 512 // 513 // Set the signal that will be used to kill the container. 514 func dispatchStopSignal(d dispatchRequest, c *instructions.StopSignalCommand) error { 515 516 _, err := signal.ParseSignal(c.Signal) 517 if err != nil { 518 return errdefs.InvalidParameter(err) 519 } 520 d.state.runConfig.StopSignal = c.Signal 521 return d.builder.commit(d.state, fmt.Sprintf("STOPSIGNAL %v", c.Signal)) 522 } 523 524 // ARG name[=value] 525 // 526 // Adds the variable foo to the trusted list of variables that can be passed 527 // to builder using the --build-arg flag for expansion/substitution or passing to 'run'. 528 // Dockerfile author may optionally set a default value of this variable. 529 func dispatchArg(d dispatchRequest, c *instructions.ArgCommand) error { 530 531 commitStr := "ARG " + c.Key 532 if c.Value != nil { 533 commitStr += "=" + *c.Value 534 } 535 536 d.state.buildArgs.AddArg(c.Key, c.Value) 537 return d.builder.commit(d.state, commitStr) 538 } 539 540 // SHELL powershell -command 541 // 542 // Set the non-default shell to use. 543 func dispatchShell(d dispatchRequest, c *instructions.ShellCommand) error { 544 d.state.runConfig.Shell = c.Shell 545 return d.builder.commit(d.state, fmt.Sprintf("SHELL %v", d.state.runConfig.Shell)) 546 }