github.com/webwurst/docker@v1.7.0/builder/dispatchers.go (about) 1 package builder 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 "path/filepath" 14 "regexp" 15 "runtime" 16 "sort" 17 "strings" 18 19 "github.com/Sirupsen/logrus" 20 "github.com/docker/docker/nat" 21 flag "github.com/docker/docker/pkg/mflag" 22 "github.com/docker/docker/runconfig" 23 ) 24 25 const ( 26 // NoBaseImageSpecifier is the symbol used by the FROM 27 // command to specify that no base image is to be used. 28 NoBaseImageSpecifier string = "scratch" 29 ) 30 31 // dispatch with no layer / parsing. This is effectively not a command. 32 func nullDispatch(b *Builder, args []string, attributes map[string]bool, original string) error { 33 return nil 34 } 35 36 // ENV foo bar 37 // 38 // Sets the environment variable foo to bar, also makes interpolation 39 // in the dockerfile available from the next statement on via ${foo}. 40 // 41 func env(b *Builder, args []string, attributes map[string]bool, original string) error { 42 if runtime.GOOS == "windows" { 43 return fmt.Errorf("ENV is not supported on Windows.") 44 } 45 if len(args) == 0 { 46 return fmt.Errorf("ENV requires at least one argument") 47 } 48 49 if len(args)%2 != 0 { 50 // should never get here, but just in case 51 return fmt.Errorf("Bad input to ENV, too many args") 52 } 53 54 if err := b.BuilderFlags.Parse(); err != nil { 55 return err 56 } 57 58 // TODO/FIXME/NOT USED 59 // Just here to show how to use the builder flags stuff within the 60 // context of a builder command. Will remove once we actually add 61 // a builder command to something! 62 /* 63 flBool1 := b.BuilderFlags.AddBool("bool1", false) 64 flStr1 := b.BuilderFlags.AddString("str1", "HI") 65 66 if err := b.BuilderFlags.Parse(); err != nil { 67 return err 68 } 69 70 fmt.Printf("Bool1:%v\n", flBool1) 71 fmt.Printf("Str1:%v\n", flStr1) 72 */ 73 74 commitStr := "ENV" 75 76 for j := 0; j < len(args); j++ { 77 // name ==> args[j] 78 // value ==> args[j+1] 79 newVar := args[j] + "=" + args[j+1] + "" 80 commitStr += " " + newVar 81 82 gotOne := false 83 for i, envVar := range b.Config.Env { 84 envParts := strings.SplitN(envVar, "=", 2) 85 if envParts[0] == args[j] { 86 b.Config.Env[i] = newVar 87 gotOne = true 88 break 89 } 90 } 91 if !gotOne { 92 b.Config.Env = append(b.Config.Env, newVar) 93 } 94 j++ 95 } 96 97 return b.commit("", b.Config.Cmd, commitStr) 98 } 99 100 // MAINTAINER some text <maybe@an.email.address> 101 // 102 // Sets the maintainer metadata. 103 func maintainer(b *Builder, args []string, attributes map[string]bool, original string) error { 104 if len(args) != 1 { 105 return fmt.Errorf("MAINTAINER requires exactly one argument") 106 } 107 108 if err := b.BuilderFlags.Parse(); err != nil { 109 return err 110 } 111 112 b.maintainer = args[0] 113 return b.commit("", b.Config.Cmd, fmt.Sprintf("MAINTAINER %s", b.maintainer)) 114 } 115 116 // LABEL some json data describing the image 117 // 118 // Sets the Label variable foo to bar, 119 // 120 func label(b *Builder, args []string, attributes map[string]bool, original string) error { 121 if len(args) == 0 { 122 return fmt.Errorf("LABEL requires at least one argument") 123 } 124 if len(args)%2 != 0 { 125 // should never get here, but just in case 126 return fmt.Errorf("Bad input to LABEL, too many args") 127 } 128 129 if err := b.BuilderFlags.Parse(); err != nil { 130 return err 131 } 132 133 commitStr := "LABEL" 134 135 if b.Config.Labels == nil { 136 b.Config.Labels = map[string]string{} 137 } 138 139 for j := 0; j < len(args); j++ { 140 // name ==> args[j] 141 // value ==> args[j+1] 142 newVar := args[j] + "=" + args[j+1] + "" 143 commitStr += " " + newVar 144 145 b.Config.Labels[args[j]] = args[j+1] 146 j++ 147 } 148 return b.commit("", b.Config.Cmd, commitStr) 149 } 150 151 // ADD foo /path 152 // 153 // Add the file 'foo' to '/path'. Tarball and Remote URL (git, http) handling 154 // exist here. If you do not wish to have this automatic handling, use COPY. 155 // 156 func add(b *Builder, args []string, attributes map[string]bool, original string) error { 157 if len(args) < 2 { 158 return fmt.Errorf("ADD requires at least two arguments") 159 } 160 161 if err := b.BuilderFlags.Parse(); err != nil { 162 return err 163 } 164 165 return b.runContextCommand(args, true, true, "ADD") 166 } 167 168 // COPY foo /path 169 // 170 // Same as 'ADD' but without the tar and remote url handling. 171 // 172 func dispatchCopy(b *Builder, args []string, attributes map[string]bool, original string) error { 173 if len(args) < 2 { 174 return fmt.Errorf("COPY requires at least two arguments") 175 } 176 177 if err := b.BuilderFlags.Parse(); err != nil { 178 return err 179 } 180 181 return b.runContextCommand(args, false, false, "COPY") 182 } 183 184 // FROM imagename 185 // 186 // This sets the image the dockerfile will build on top of. 187 // 188 func from(b *Builder, args []string, attributes map[string]bool, original string) error { 189 if len(args) != 1 { 190 return fmt.Errorf("FROM requires one argument") 191 } 192 193 if err := b.BuilderFlags.Parse(); err != nil { 194 return err 195 } 196 197 name := args[0] 198 199 if name == NoBaseImageSpecifier { 200 b.image = "" 201 b.noBaseImage = true 202 return nil 203 } 204 205 image, err := b.Daemon.Repositories().LookupImage(name) 206 if b.Pull { 207 image, err = b.pullImage(name) 208 if err != nil { 209 return err 210 } 211 } 212 if err != nil { 213 if b.Daemon.Graph().IsNotExist(err, name) { 214 image, err = b.pullImage(name) 215 } 216 217 // note that the top level err will still be !nil here if IsNotExist is 218 // not the error. This approach just simplifies the logic a bit. 219 if err != nil { 220 return err 221 } 222 } 223 224 return b.processImageFrom(image) 225 } 226 227 // ONBUILD RUN echo yo 228 // 229 // ONBUILD triggers run when the image is used in a FROM statement. 230 // 231 // ONBUILD handling has a lot of special-case functionality, the heading in 232 // evaluator.go and comments around dispatch() in the same file explain the 233 // special cases. search for 'OnBuild' in internals.go for additional special 234 // cases. 235 // 236 func onbuild(b *Builder, args []string, attributes map[string]bool, original string) error { 237 if len(args) == 0 { 238 return fmt.Errorf("ONBUILD requires at least one argument") 239 } 240 241 if err := b.BuilderFlags.Parse(); err != nil { 242 return err 243 } 244 245 triggerInstruction := strings.ToUpper(strings.TrimSpace(args[0])) 246 switch triggerInstruction { 247 case "ONBUILD": 248 return fmt.Errorf("Chaining ONBUILD via `ONBUILD ONBUILD` isn't allowed") 249 case "MAINTAINER", "FROM": 250 return fmt.Errorf("%s isn't allowed as an ONBUILD trigger", triggerInstruction) 251 } 252 253 original = regexp.MustCompile(`(?i)^\s*ONBUILD\s*`).ReplaceAllString(original, "") 254 255 b.Config.OnBuild = append(b.Config.OnBuild, original) 256 return b.commit("", b.Config.Cmd, fmt.Sprintf("ONBUILD %s", original)) 257 } 258 259 // WORKDIR /tmp 260 // 261 // Set the working directory for future RUN/CMD/etc statements. 262 // 263 func workdir(b *Builder, args []string, attributes map[string]bool, original string) error { 264 if len(args) != 1 { 265 return fmt.Errorf("WORKDIR requires exactly one argument") 266 } 267 268 if err := b.BuilderFlags.Parse(); err != nil { 269 return err 270 } 271 272 workdir := args[0] 273 274 if !filepath.IsAbs(workdir) { 275 workdir = filepath.Join("/", b.Config.WorkingDir, workdir) 276 } 277 278 b.Config.WorkingDir = workdir 279 280 return b.commit("", b.Config.Cmd, fmt.Sprintf("WORKDIR %v", workdir)) 281 } 282 283 // RUN some command yo 284 // 285 // run a command and commit the image. Args are automatically prepended with 286 // 'sh -c' under linux or 'cmd /S /C' under Windows, in the event there is 287 // only one argument. The difference in processing: 288 // 289 // RUN echo hi # sh -c echo hi (Linux) 290 // RUN echo hi # cmd /S /C echo hi (Windows) 291 // RUN [ "echo", "hi" ] # echo hi 292 // 293 func run(b *Builder, args []string, attributes map[string]bool, original string) error { 294 if b.image == "" && !b.noBaseImage { 295 return fmt.Errorf("Please provide a source image with `from` prior to run") 296 } 297 298 if err := b.BuilderFlags.Parse(); err != nil { 299 return err 300 } 301 302 args = handleJsonArgs(args, attributes) 303 304 if !attributes["json"] { 305 if runtime.GOOS != "windows" { 306 args = append([]string{"/bin/sh", "-c"}, args...) 307 } else { 308 args = append([]string{"cmd", "/S /C"}, args...) 309 } 310 } 311 312 runCmd := flag.NewFlagSet("run", flag.ContinueOnError) 313 runCmd.SetOutput(ioutil.Discard) 314 runCmd.Usage = nil 315 316 config, _, _, err := runconfig.Parse(runCmd, append([]string{b.image}, args...)) 317 if err != nil { 318 return err 319 } 320 321 cmd := b.Config.Cmd 322 // set Cmd manually, this is special case only for Dockerfiles 323 b.Config.Cmd = config.Cmd 324 runconfig.Merge(b.Config, config) 325 326 defer func(cmd *runconfig.Command) { b.Config.Cmd = cmd }(cmd) 327 328 logrus.Debugf("[BUILDER] Command to be executed: %v", b.Config.Cmd) 329 330 hit, err := b.probeCache() 331 if err != nil { 332 return err 333 } 334 if hit { 335 return nil 336 } 337 338 c, err := b.create() 339 if err != nil { 340 return err 341 } 342 343 // Ensure that we keep the container mounted until the commit 344 // to avoid unmounting and then mounting directly again 345 c.Mount() 346 defer c.Unmount() 347 348 err = b.run(c) 349 if err != nil { 350 return err 351 } 352 if err := b.commit(c.ID, cmd, "run"); err != nil { 353 return err 354 } 355 356 return nil 357 } 358 359 // CMD foo 360 // 361 // Set the default command to run in the container (which may be empty). 362 // Argument handling is the same as RUN. 363 // 364 func cmd(b *Builder, args []string, attributes map[string]bool, original string) error { 365 if err := b.BuilderFlags.Parse(); err != nil { 366 return err 367 } 368 369 cmdSlice := handleJsonArgs(args, attributes) 370 371 if !attributes["json"] { 372 if runtime.GOOS != "windows" { 373 cmdSlice = append([]string{"/bin/sh", "-c"}, cmdSlice...) 374 } else { 375 cmdSlice = append([]string{"cmd", "/S /C"}, cmdSlice...) 376 } 377 } 378 379 b.Config.Cmd = runconfig.NewCommand(cmdSlice...) 380 381 if err := b.commit("", b.Config.Cmd, fmt.Sprintf("CMD %q", cmdSlice)); err != nil { 382 return err 383 } 384 385 if len(args) != 0 { 386 b.cmdSet = true 387 } 388 389 return nil 390 } 391 392 // ENTRYPOINT /usr/sbin/nginx 393 // 394 // Set the entrypoint (which defaults to sh -c on linux, or cmd /S /C on Windows) to 395 // /usr/sbin/nginx. Will accept the CMD as the arguments to /usr/sbin/nginx. 396 // 397 // Handles command processing similar to CMD and RUN, only b.Config.Entrypoint 398 // is initialized at NewBuilder time instead of through argument parsing. 399 // 400 func entrypoint(b *Builder, args []string, attributes map[string]bool, original string) error { 401 if err := b.BuilderFlags.Parse(); err != nil { 402 return err 403 } 404 405 parsed := handleJsonArgs(args, attributes) 406 407 switch { 408 case attributes["json"]: 409 // ENTRYPOINT ["echo", "hi"] 410 b.Config.Entrypoint = runconfig.NewEntrypoint(parsed...) 411 case len(parsed) == 0: 412 // ENTRYPOINT [] 413 b.Config.Entrypoint = nil 414 default: 415 // ENTRYPOINT echo hi 416 if runtime.GOOS != "windows" { 417 b.Config.Entrypoint = runconfig.NewEntrypoint("/bin/sh", "-c", parsed[0]) 418 } else { 419 b.Config.Entrypoint = runconfig.NewEntrypoint("cmd", "/S /C", parsed[0]) 420 } 421 } 422 423 // when setting the entrypoint if a CMD was not explicitly set then 424 // set the command to nil 425 if !b.cmdSet { 426 b.Config.Cmd = nil 427 } 428 429 if err := b.commit("", b.Config.Cmd, fmt.Sprintf("ENTRYPOINT %q", b.Config.Entrypoint)); err != nil { 430 return err 431 } 432 433 return nil 434 } 435 436 // EXPOSE 6667/tcp 7000/tcp 437 // 438 // Expose ports for links and port mappings. This all ends up in 439 // b.Config.ExposedPorts for runconfig. 440 // 441 func expose(b *Builder, args []string, attributes map[string]bool, original string) error { 442 portsTab := args 443 444 if len(args) == 0 { 445 return fmt.Errorf("EXPOSE requires at least one argument") 446 } 447 448 if err := b.BuilderFlags.Parse(); err != nil { 449 return err 450 } 451 452 if b.Config.ExposedPorts == nil { 453 b.Config.ExposedPorts = make(nat.PortSet) 454 } 455 456 ports, bindingMap, err := nat.ParsePortSpecs(append(portsTab, b.Config.PortSpecs...)) 457 if err != nil { 458 return err 459 } 460 461 for _, bindings := range bindingMap { 462 if bindings[0].HostIp != "" || bindings[0].HostPort != "" { 463 fmt.Fprintf(b.ErrStream, " ---> Using Dockerfile's EXPOSE instruction"+ 464 " to map host ports to container ports (ip:hostPort:containerPort) is deprecated.\n"+ 465 " Please use -p to publish the ports.\n") 466 } 467 } 468 469 // instead of using ports directly, we build a list of ports and sort it so 470 // the order is consistent. This prevents cache burst where map ordering 471 // changes between builds 472 portList := make([]string, len(ports)) 473 var i int 474 for port := range ports { 475 if _, exists := b.Config.ExposedPorts[port]; !exists { 476 b.Config.ExposedPorts[port] = struct{}{} 477 } 478 portList[i] = string(port) 479 i++ 480 } 481 sort.Strings(portList) 482 b.Config.PortSpecs = nil 483 return b.commit("", b.Config.Cmd, fmt.Sprintf("EXPOSE %s", strings.Join(portList, " "))) 484 } 485 486 // USER foo 487 // 488 // Set the user to 'foo' for future commands and when running the 489 // ENTRYPOINT/CMD at container run time. 490 // 491 func user(b *Builder, args []string, attributes map[string]bool, original string) error { 492 if runtime.GOOS == "windows" { 493 return fmt.Errorf("USER is not supported on Windows.") 494 } 495 496 if len(args) != 1 { 497 return fmt.Errorf("USER requires exactly one argument") 498 } 499 500 if err := b.BuilderFlags.Parse(); err != nil { 501 return err 502 } 503 504 b.Config.User = args[0] 505 return b.commit("", b.Config.Cmd, fmt.Sprintf("USER %v", args)) 506 } 507 508 // VOLUME /foo 509 // 510 // Expose the volume /foo for use. Will also accept the JSON array form. 511 // 512 func volume(b *Builder, args []string, attributes map[string]bool, original string) error { 513 if runtime.GOOS == "windows" { 514 return fmt.Errorf("VOLUME is not supported on Windows.") 515 } 516 if len(args) == 0 { 517 return fmt.Errorf("VOLUME requires at least one argument") 518 } 519 520 if err := b.BuilderFlags.Parse(); err != nil { 521 return err 522 } 523 524 if b.Config.Volumes == nil { 525 b.Config.Volumes = map[string]struct{}{} 526 } 527 for _, v := range args { 528 v = strings.TrimSpace(v) 529 if v == "" { 530 return fmt.Errorf("Volume specified can not be an empty string") 531 } 532 b.Config.Volumes[v] = struct{}{} 533 } 534 if err := b.commit("", b.Config.Cmd, fmt.Sprintf("VOLUME %v", args)); err != nil { 535 return err 536 } 537 return nil 538 }