github.com/fcwu/docker@v1.4.2-0.20150115145920-2a69ca89f0df/api/client/commands.go (about) 1 package client 2 3 import ( 4 "bufio" 5 "bytes" 6 "encoding/base64" 7 "encoding/json" 8 "errors" 9 "fmt" 10 "io" 11 "io/ioutil" 12 "net/http" 13 "net/url" 14 "os" 15 "os/exec" 16 "path" 17 "path/filepath" 18 "runtime" 19 "strconv" 20 "strings" 21 "text/tabwriter" 22 "text/template" 23 "time" 24 25 log "github.com/Sirupsen/logrus" 26 "github.com/docker/docker/api" 27 "github.com/docker/docker/dockerversion" 28 "github.com/docker/docker/engine" 29 "github.com/docker/docker/graph" 30 "github.com/docker/docker/nat" 31 "github.com/docker/docker/opts" 32 "github.com/docker/docker/pkg/archive" 33 "github.com/docker/docker/pkg/fileutils" 34 flag "github.com/docker/docker/pkg/mflag" 35 "github.com/docker/docker/pkg/parsers" 36 "github.com/docker/docker/pkg/parsers/filters" 37 "github.com/docker/docker/pkg/promise" 38 "github.com/docker/docker/pkg/signal" 39 "github.com/docker/docker/pkg/term" 40 "github.com/docker/docker/pkg/timeutils" 41 "github.com/docker/docker/pkg/units" 42 "github.com/docker/docker/pkg/urlutil" 43 "github.com/docker/docker/registry" 44 "github.com/docker/docker/runconfig" 45 "github.com/docker/docker/utils" 46 ) 47 48 const ( 49 tarHeaderSize = 512 50 ) 51 52 var ( 53 acceptedImageFilterTags = map[string]struct{}{"dangling": {}} 54 ) 55 56 func (cli *DockerCli) CmdHelp(args ...string) error { 57 if len(args) > 1 { 58 method, exists := cli.getMethod(args[:2]...) 59 if exists { 60 method("--help") 61 return nil 62 } 63 } 64 if len(args) > 0 { 65 method, exists := cli.getMethod(args[0]) 66 if !exists { 67 fmt.Fprintf(cli.err, "docker: '%s' is not a docker command. See 'docker --help'.\n", args[0]) 68 os.Exit(1) 69 } else { 70 method("--help") 71 return nil 72 } 73 } 74 75 flag.Usage() 76 77 return nil 78 } 79 80 func (cli *DockerCli) CmdBuild(args ...string) error { 81 cmd := cli.Subcmd("build", "PATH | URL | -", "Build a new image from the source code at PATH", true) 82 tag := cmd.String([]string{"t", "-tag"}, "", "Repository name (and optionally a tag) to be applied to the resulting image in case of success") 83 suppressOutput := cmd.Bool([]string{"q", "-quiet"}, false, "Suppress the verbose output generated by the containers") 84 noCache := cmd.Bool([]string{"#no-cache", "-no-cache"}, false, "Do not use cache when building the image") 85 rm := cmd.Bool([]string{"#rm", "-rm"}, true, "Remove intermediate containers after a successful build") 86 forceRm := cmd.Bool([]string{"-force-rm"}, false, "Always remove intermediate containers, even after unsuccessful builds") 87 pull := cmd.Bool([]string{"-pull"}, false, "Always attempt to pull a newer version of the image") 88 dockerfileName := cmd.String([]string{"f", "-file"}, "", "Name of the Dockerfile(Default is 'Dockerfile' at context root)") 89 90 cmd.Require(flag.Exact, 1) 91 92 utils.ParseFlags(cmd, args, true) 93 94 var ( 95 context archive.Archive 96 isRemote bool 97 err error 98 ) 99 100 _, err = exec.LookPath("git") 101 hasGit := err == nil 102 if cmd.Arg(0) == "-" { 103 // As a special case, 'docker build -' will build from either an empty context with the 104 // contents of stdin as a Dockerfile, or a tar-ed context from stdin. 105 buf := bufio.NewReader(cli.in) 106 magic, err := buf.Peek(tarHeaderSize) 107 if err != nil && err != io.EOF { 108 return fmt.Errorf("failed to peek context header from STDIN: %v", err) 109 } 110 if !archive.IsArchive(magic) { 111 dockerfile, err := ioutil.ReadAll(buf) 112 if err != nil { 113 return fmt.Errorf("failed to read Dockerfile from STDIN: %v", err) 114 } 115 if *dockerfileName == "" { 116 *dockerfileName = api.DefaultDockerfileName 117 } 118 context, err = archive.Generate(*dockerfileName, string(dockerfile)) 119 } else { 120 context = ioutil.NopCloser(buf) 121 } 122 } else if urlutil.IsURL(cmd.Arg(0)) && (!urlutil.IsGitURL(cmd.Arg(0)) || !hasGit) { 123 isRemote = true 124 } else { 125 root := cmd.Arg(0) 126 if urlutil.IsGitURL(root) { 127 remoteURL := cmd.Arg(0) 128 if !urlutil.IsGitTransport(remoteURL) { 129 remoteURL = "https://" + remoteURL 130 } 131 132 root, err = ioutil.TempDir("", "docker-build-git") 133 if err != nil { 134 return err 135 } 136 defer os.RemoveAll(root) 137 138 if output, err := exec.Command("git", "clone", "--recursive", remoteURL, root).CombinedOutput(); err != nil { 139 return fmt.Errorf("Error trying to use git: %s (%s)", err, output) 140 } 141 } 142 if _, err := os.Stat(root); err != nil { 143 return err 144 } 145 146 absRoot, err := filepath.Abs(root) 147 if err != nil { 148 return err 149 } 150 151 var filename string // path to Dockerfile 152 var origDockerfile string // used for error msg 153 154 if *dockerfileName == "" { 155 // No -f/--file was specified so use the default 156 origDockerfile = api.DefaultDockerfileName 157 *dockerfileName = origDockerfile 158 filename = path.Join(absRoot, *dockerfileName) 159 } else { 160 origDockerfile = *dockerfileName 161 if filename, err = filepath.Abs(*dockerfileName); err != nil { 162 return err 163 } 164 165 // Verify that 'filename' is within the build context 166 if !strings.HasSuffix(absRoot, string(os.PathSeparator)) { 167 absRoot += string(os.PathSeparator) 168 } 169 if !strings.HasPrefix(filename, absRoot) { 170 return fmt.Errorf("The Dockerfile (%s) must be within the build context (%s)", *dockerfileName, root) 171 } 172 173 // Now reset the dockerfileName to be relative to the build context 174 *dockerfileName = filename[len(absRoot):] 175 } 176 177 if _, err = os.Stat(filename); os.IsNotExist(err) { 178 return fmt.Errorf("Can not locate Dockerfile: %s", origDockerfile) 179 } 180 var includes []string = []string{"."} 181 182 excludes, err := utils.ReadDockerIgnore(path.Join(root, ".dockerignore")) 183 if err != nil { 184 return err 185 } 186 187 // If .dockerignore mentions .dockerignore or the Dockerfile 188 // then make sure we send both files over to the daemon 189 // because Dockerfile is, obviously, needed no matter what, and 190 // .dockerignore is needed to know if either one needs to be 191 // removed. The deamon will remove them for us, if needed, after it 192 // parses the Dockerfile. 193 keepThem1, _ := fileutils.Matches(".dockerignore", excludes) 194 keepThem2, _ := fileutils.Matches(*dockerfileName, excludes) 195 if keepThem1 || keepThem2 { 196 includes = append(includes, ".dockerignore", *dockerfileName) 197 } 198 199 if err = utils.ValidateContextDirectory(root, excludes); err != nil { 200 return fmt.Errorf("Error checking context is accessible: '%s'. Please check permissions and try again.", err) 201 } 202 options := &archive.TarOptions{ 203 Compression: archive.Uncompressed, 204 ExcludePatterns: excludes, 205 IncludeFiles: includes, 206 } 207 context, err = archive.TarWithOptions(root, options) 208 if err != nil { 209 return err 210 } 211 } 212 var body io.Reader 213 // Setup an upload progress bar 214 // FIXME: ProgressReader shouldn't be this annoying to use 215 if context != nil { 216 sf := utils.NewStreamFormatter(false) 217 body = utils.ProgressReader(context, 0, cli.out, sf, true, "", "Sending build context to Docker daemon") 218 } 219 // Send the build context 220 v := &url.Values{} 221 222 //Check if the given image name can be resolved 223 if *tag != "" { 224 repository, tag := parsers.ParseRepositoryTag(*tag) 225 if _, _, err := registry.ResolveRepositoryName(repository); err != nil { 226 return err 227 } 228 if len(tag) > 0 { 229 if err := graph.ValidateTagName(tag); err != nil { 230 return err 231 } 232 } 233 } 234 235 v.Set("t", *tag) 236 237 if *suppressOutput { 238 v.Set("q", "1") 239 } 240 if isRemote { 241 v.Set("remote", cmd.Arg(0)) 242 } 243 if *noCache { 244 v.Set("nocache", "1") 245 } 246 if *rm { 247 v.Set("rm", "1") 248 } else { 249 v.Set("rm", "0") 250 } 251 252 if *forceRm { 253 v.Set("forcerm", "1") 254 } 255 256 if *pull { 257 v.Set("pull", "1") 258 } 259 260 v.Set("dockerfile", *dockerfileName) 261 262 cli.LoadConfigFile() 263 264 headers := http.Header(make(map[string][]string)) 265 buf, err := json.Marshal(cli.configFile) 266 if err != nil { 267 return err 268 } 269 headers.Add("X-Registry-Config", base64.URLEncoding.EncodeToString(buf)) 270 271 if context != nil { 272 headers.Set("Content-Type", "application/tar") 273 } 274 err = cli.stream("POST", fmt.Sprintf("/build?%s", v.Encode()), body, cli.out, headers) 275 if jerr, ok := err.(*utils.JSONError); ok { 276 // If no error code is set, default to 1 277 if jerr.Code == 0 { 278 jerr.Code = 1 279 } 280 return &utils.StatusError{Status: jerr.Message, StatusCode: jerr.Code} 281 } 282 return err 283 } 284 285 // 'docker login': login / register a user to registry service. 286 func (cli *DockerCli) CmdLogin(args ...string) error { 287 cmd := cli.Subcmd("login", "[SERVER]", "Register or log in to a Docker registry server, if no server is specified \""+registry.IndexServerAddress()+"\" is the default.", true) 288 cmd.Require(flag.Max, 1) 289 290 var username, password, email string 291 292 cmd.StringVar(&username, []string{"u", "-username"}, "", "Username") 293 cmd.StringVar(&password, []string{"p", "-password"}, "", "Password") 294 cmd.StringVar(&email, []string{"e", "-email"}, "", "Email") 295 296 utils.ParseFlags(cmd, args, true) 297 298 serverAddress := registry.IndexServerAddress() 299 if len(cmd.Args()) > 0 { 300 serverAddress = cmd.Arg(0) 301 } 302 303 promptDefault := func(prompt string, configDefault string) { 304 if configDefault == "" { 305 fmt.Fprintf(cli.out, "%s: ", prompt) 306 } else { 307 fmt.Fprintf(cli.out, "%s (%s): ", prompt, configDefault) 308 } 309 } 310 311 readInput := func(in io.Reader, out io.Writer) string { 312 reader := bufio.NewReader(in) 313 line, _, err := reader.ReadLine() 314 if err != nil { 315 fmt.Fprintln(out, err.Error()) 316 os.Exit(1) 317 } 318 return string(line) 319 } 320 321 cli.LoadConfigFile() 322 authconfig, ok := cli.configFile.Configs[serverAddress] 323 if !ok { 324 authconfig = registry.AuthConfig{} 325 } 326 327 if username == "" { 328 promptDefault("Username", authconfig.Username) 329 username = readInput(cli.in, cli.out) 330 if username == "" { 331 username = authconfig.Username 332 } 333 } 334 // Assume that a different username means they may not want to use 335 // the password or email from the config file, so prompt them 336 if username != authconfig.Username { 337 if password == "" { 338 oldState, err := term.SaveState(cli.inFd) 339 if err != nil { 340 return err 341 } 342 fmt.Fprintf(cli.out, "Password: ") 343 term.DisableEcho(cli.inFd, oldState) 344 345 password = readInput(cli.in, cli.out) 346 fmt.Fprint(cli.out, "\n") 347 348 term.RestoreTerminal(cli.inFd, oldState) 349 if password == "" { 350 return fmt.Errorf("Error : Password Required") 351 } 352 } 353 354 if email == "" { 355 promptDefault("Email", authconfig.Email) 356 email = readInput(cli.in, cli.out) 357 if email == "" { 358 email = authconfig.Email 359 } 360 } 361 } else { 362 // However, if they don't override the username use the 363 // password or email from the cmd line if specified. IOW, allow 364 // then to change/overide them. And if not specified, just 365 // use what's in the config file 366 if password == "" { 367 password = authconfig.Password 368 } 369 if email == "" { 370 email = authconfig.Email 371 } 372 } 373 authconfig.Username = username 374 authconfig.Password = password 375 authconfig.Email = email 376 authconfig.ServerAddress = serverAddress 377 cli.configFile.Configs[serverAddress] = authconfig 378 379 stream, statusCode, err := cli.call("POST", "/auth", cli.configFile.Configs[serverAddress], false) 380 if statusCode == 401 { 381 delete(cli.configFile.Configs, serverAddress) 382 registry.SaveConfig(cli.configFile) 383 return err 384 } 385 if err != nil { 386 return err 387 } 388 var out2 engine.Env 389 err = out2.Decode(stream) 390 if err != nil { 391 cli.configFile, _ = registry.LoadConfig(os.Getenv("HOME")) 392 return err 393 } 394 registry.SaveConfig(cli.configFile) 395 if out2.Get("Status") != "" { 396 fmt.Fprintf(cli.out, "%s\n", out2.Get("Status")) 397 } 398 return nil 399 } 400 401 // log out from a Docker registry 402 func (cli *DockerCli) CmdLogout(args ...string) error { 403 cmd := cli.Subcmd("logout", "[SERVER]", "Log out from a Docker registry, if no server is specified \""+registry.IndexServerAddress()+"\" is the default.", true) 404 cmd.Require(flag.Max, 1) 405 406 utils.ParseFlags(cmd, args, false) 407 serverAddress := registry.IndexServerAddress() 408 if len(cmd.Args()) > 0 { 409 serverAddress = cmd.Arg(0) 410 } 411 412 cli.LoadConfigFile() 413 if _, ok := cli.configFile.Configs[serverAddress]; !ok { 414 fmt.Fprintf(cli.out, "Not logged in to %s\n", serverAddress) 415 } else { 416 fmt.Fprintf(cli.out, "Remove login credentials for %s\n", serverAddress) 417 delete(cli.configFile.Configs, serverAddress) 418 419 if err := registry.SaveConfig(cli.configFile); err != nil { 420 return fmt.Errorf("Failed to save docker config: %v", err) 421 } 422 } 423 return nil 424 } 425 426 // 'docker wait': block until a container stops 427 func (cli *DockerCli) CmdWait(args ...string) error { 428 cmd := cli.Subcmd("wait", "CONTAINER [CONTAINER...]", "Block until a container stops, then print its exit code.", true) 429 cmd.Require(flag.Min, 1) 430 431 utils.ParseFlags(cmd, args, true) 432 433 var encounteredError error 434 for _, name := range cmd.Args() { 435 status, err := waitForExit(cli, name) 436 if err != nil { 437 fmt.Fprintf(cli.err, "%s\n", err) 438 encounteredError = fmt.Errorf("Error: failed to wait one or more containers") 439 } else { 440 fmt.Fprintf(cli.out, "%d\n", status) 441 } 442 } 443 return encounteredError 444 } 445 446 // 'docker version': show version information 447 func (cli *DockerCli) CmdVersion(args ...string) error { 448 cmd := cli.Subcmd("version", "", "Show the Docker version information.", true) 449 cmd.Require(flag.Exact, 0) 450 451 utils.ParseFlags(cmd, args, false) 452 453 if dockerversion.VERSION != "" { 454 fmt.Fprintf(cli.out, "Client version: %s\n", dockerversion.VERSION) 455 } 456 fmt.Fprintf(cli.out, "Client API version: %s\n", api.APIVERSION) 457 fmt.Fprintf(cli.out, "Go version (client): %s\n", runtime.Version()) 458 if dockerversion.GITCOMMIT != "" { 459 fmt.Fprintf(cli.out, "Git commit (client): %s\n", dockerversion.GITCOMMIT) 460 } 461 fmt.Fprintf(cli.out, "OS/Arch (client): %s/%s\n", runtime.GOOS, runtime.GOARCH) 462 463 body, _, err := readBody(cli.call("GET", "/version", nil, false)) 464 if err != nil { 465 return err 466 } 467 468 out := engine.NewOutput() 469 remoteVersion, err := out.AddEnv() 470 if err != nil { 471 log.Errorf("Error reading remote version: %s", err) 472 return err 473 } 474 if _, err := out.Write(body); err != nil { 475 log.Errorf("Error reading remote version: %s", err) 476 return err 477 } 478 out.Close() 479 fmt.Fprintf(cli.out, "Server version: %s\n", remoteVersion.Get("Version")) 480 if apiVersion := remoteVersion.Get("ApiVersion"); apiVersion != "" { 481 fmt.Fprintf(cli.out, "Server API version: %s\n", apiVersion) 482 } 483 fmt.Fprintf(cli.out, "Go version (server): %s\n", remoteVersion.Get("GoVersion")) 484 fmt.Fprintf(cli.out, "Git commit (server): %s\n", remoteVersion.Get("GitCommit")) 485 return nil 486 } 487 488 // 'docker info': display system-wide information. 489 func (cli *DockerCli) CmdInfo(args ...string) error { 490 cmd := cli.Subcmd("info", "", "Display system-wide information", true) 491 cmd.Require(flag.Exact, 0) 492 utils.ParseFlags(cmd, args, false) 493 494 body, _, err := readBody(cli.call("GET", "/info", nil, false)) 495 if err != nil { 496 return err 497 } 498 499 out := engine.NewOutput() 500 remoteInfo, err := out.AddEnv() 501 if err != nil { 502 return err 503 } 504 505 if _, err := out.Write(body); err != nil { 506 log.Errorf("Error reading remote info: %s", err) 507 return err 508 } 509 out.Close() 510 511 if remoteInfo.Exists("Containers") { 512 fmt.Fprintf(cli.out, "Containers: %d\n", remoteInfo.GetInt("Containers")) 513 } 514 if remoteInfo.Exists("Images") { 515 fmt.Fprintf(cli.out, "Images: %d\n", remoteInfo.GetInt("Images")) 516 } 517 if remoteInfo.Exists("Driver") { 518 fmt.Fprintf(cli.out, "Storage Driver: %s\n", remoteInfo.Get("Driver")) 519 } 520 if remoteInfo.Exists("DriverStatus") { 521 var driverStatus [][2]string 522 if err := remoteInfo.GetJson("DriverStatus", &driverStatus); err != nil { 523 return err 524 } 525 for _, pair := range driverStatus { 526 fmt.Fprintf(cli.out, " %s: %s\n", pair[0], pair[1]) 527 } 528 } 529 if remoteInfo.Exists("ExecutionDriver") { 530 fmt.Fprintf(cli.out, "Execution Driver: %s\n", remoteInfo.Get("ExecutionDriver")) 531 } 532 if remoteInfo.Exists("KernelVersion") { 533 fmt.Fprintf(cli.out, "Kernel Version: %s\n", remoteInfo.Get("KernelVersion")) 534 } 535 if remoteInfo.Exists("OperatingSystem") { 536 fmt.Fprintf(cli.out, "Operating System: %s\n", remoteInfo.Get("OperatingSystem")) 537 } 538 if remoteInfo.Exists("NCPU") { 539 fmt.Fprintf(cli.out, "CPUs: %d\n", remoteInfo.GetInt("NCPU")) 540 } 541 if remoteInfo.Exists("MemTotal") { 542 fmt.Fprintf(cli.out, "Total Memory: %s\n", units.BytesSize(float64(remoteInfo.GetInt64("MemTotal")))) 543 } 544 if remoteInfo.Exists("Name") { 545 fmt.Fprintf(cli.out, "Name: %s\n", remoteInfo.Get("Name")) 546 } 547 if remoteInfo.Exists("ID") { 548 fmt.Fprintf(cli.out, "ID: %s\n", remoteInfo.Get("ID")) 549 } 550 551 if remoteInfo.GetBool("Debug") || os.Getenv("DEBUG") != "" { 552 if remoteInfo.Exists("Debug") { 553 fmt.Fprintf(cli.out, "Debug mode (server): %v\n", remoteInfo.GetBool("Debug")) 554 } 555 fmt.Fprintf(cli.out, "Debug mode (client): %v\n", os.Getenv("DEBUG") != "") 556 if remoteInfo.Exists("NFd") { 557 fmt.Fprintf(cli.out, "Fds: %d\n", remoteInfo.GetInt("NFd")) 558 } 559 if remoteInfo.Exists("NGoroutines") { 560 fmt.Fprintf(cli.out, "Goroutines: %d\n", remoteInfo.GetInt("NGoroutines")) 561 } 562 if remoteInfo.Exists("NEventsListener") { 563 fmt.Fprintf(cli.out, "EventsListeners: %d\n", remoteInfo.GetInt("NEventsListener")) 564 } 565 if initSha1 := remoteInfo.Get("InitSha1"); initSha1 != "" { 566 fmt.Fprintf(cli.out, "Init SHA1: %s\n", initSha1) 567 } 568 if initPath := remoteInfo.Get("InitPath"); initPath != "" { 569 fmt.Fprintf(cli.out, "Init Path: %s\n", initPath) 570 } 571 if root := remoteInfo.Get("DockerRootDir"); root != "" { 572 fmt.Fprintf(cli.out, "Docker Root Dir: %s\n", root) 573 } 574 } 575 576 if len(remoteInfo.GetList("IndexServerAddress")) != 0 { 577 cli.LoadConfigFile() 578 u := cli.configFile.Configs[remoteInfo.Get("IndexServerAddress")].Username 579 if len(u) > 0 { 580 fmt.Fprintf(cli.out, "Username: %v\n", u) 581 fmt.Fprintf(cli.out, "Registry: %v\n", remoteInfo.GetList("IndexServerAddress")) 582 } 583 } 584 if remoteInfo.Exists("MemoryLimit") && !remoteInfo.GetBool("MemoryLimit") { 585 fmt.Fprintf(cli.err, "WARNING: No memory limit support\n") 586 } 587 if remoteInfo.Exists("SwapLimit") && !remoteInfo.GetBool("SwapLimit") { 588 fmt.Fprintf(cli.err, "WARNING: No swap limit support\n") 589 } 590 if remoteInfo.Exists("IPv4Forwarding") && !remoteInfo.GetBool("IPv4Forwarding") { 591 fmt.Fprintf(cli.err, "WARNING: IPv4 forwarding is disabled.\n") 592 } 593 if remoteInfo.Exists("Labels") { 594 fmt.Fprintln(cli.out, "Labels:") 595 for _, attribute := range remoteInfo.GetList("Labels") { 596 fmt.Fprintf(cli.out, " %s\n", attribute) 597 } 598 } 599 600 return nil 601 } 602 603 func (cli *DockerCli) CmdStop(args ...string) error { 604 cmd := cli.Subcmd("stop", "CONTAINER [CONTAINER...]", "Stop a running container by sending SIGTERM and then SIGKILL after a grace period", true) 605 nSeconds := cmd.Int([]string{"t", "-time"}, 10, "Number of seconds to wait for the container to stop before killing it. Default is 10 seconds.") 606 cmd.Require(flag.Min, 1) 607 608 utils.ParseFlags(cmd, args, true) 609 610 v := url.Values{} 611 v.Set("t", strconv.Itoa(*nSeconds)) 612 613 var encounteredError error 614 for _, name := range cmd.Args() { 615 _, _, err := readBody(cli.call("POST", "/containers/"+name+"/stop?"+v.Encode(), nil, false)) 616 if err != nil { 617 fmt.Fprintf(cli.err, "%s\n", err) 618 encounteredError = fmt.Errorf("Error: failed to stop one or more containers") 619 } else { 620 fmt.Fprintf(cli.out, "%s\n", name) 621 } 622 } 623 return encounteredError 624 } 625 626 func (cli *DockerCli) CmdRestart(args ...string) error { 627 cmd := cli.Subcmd("restart", "CONTAINER [CONTAINER...]", "Restart a running container", true) 628 nSeconds := cmd.Int([]string{"t", "-time"}, 10, "Number of seconds to try to stop for before killing the container. Once killed it will then be restarted. Default is 10 seconds.") 629 cmd.Require(flag.Min, 1) 630 631 utils.ParseFlags(cmd, args, true) 632 633 v := url.Values{} 634 v.Set("t", strconv.Itoa(*nSeconds)) 635 636 var encounteredError error 637 for _, name := range cmd.Args() { 638 _, _, err := readBody(cli.call("POST", "/containers/"+name+"/restart?"+v.Encode(), nil, false)) 639 if err != nil { 640 fmt.Fprintf(cli.err, "%s\n", err) 641 encounteredError = fmt.Errorf("Error: failed to restart one or more containers") 642 } else { 643 fmt.Fprintf(cli.out, "%s\n", name) 644 } 645 } 646 return encounteredError 647 } 648 649 func (cli *DockerCli) forwardAllSignals(cid string) chan os.Signal { 650 sigc := make(chan os.Signal, 128) 651 signal.CatchAll(sigc) 652 go func() { 653 for s := range sigc { 654 if s == signal.SIGCHLD { 655 continue 656 } 657 var sig string 658 for sigStr, sigN := range signal.SignalMap { 659 if sigN == s { 660 sig = sigStr 661 break 662 } 663 } 664 if sig == "" { 665 log.Errorf("Unsupported signal: %v. Discarding.", s) 666 } 667 if _, _, err := readBody(cli.call("POST", fmt.Sprintf("/containers/%s/kill?signal=%s", cid, sig), nil, false)); err != nil { 668 log.Debugf("Error sending signal: %s", err) 669 } 670 } 671 }() 672 return sigc 673 } 674 675 func (cli *DockerCli) CmdStart(args ...string) error { 676 var ( 677 cErr chan error 678 tty bool 679 680 cmd = cli.Subcmd("start", "CONTAINER [CONTAINER...]", "Restart a stopped container", true) 681 attach = cmd.Bool([]string{"a", "-attach"}, false, "Attach container's STDOUT and STDERR and forward all signals to the process") 682 openStdin = cmd.Bool([]string{"i", "-interactive"}, false, "Attach container's STDIN") 683 ) 684 685 cmd.Require(flag.Min, 1) 686 utils.ParseFlags(cmd, args, true) 687 688 hijacked := make(chan io.Closer) 689 690 if *attach || *openStdin { 691 if cmd.NArg() > 1 { 692 return fmt.Errorf("You cannot start and attach multiple containers at once.") 693 } 694 695 stream, _, err := cli.call("GET", "/containers/"+cmd.Arg(0)+"/json", nil, false) 696 if err != nil { 697 return err 698 } 699 700 env := engine.Env{} 701 if err := env.Decode(stream); err != nil { 702 return err 703 } 704 config := env.GetSubEnv("Config") 705 tty = config.GetBool("Tty") 706 707 if !tty { 708 sigc := cli.forwardAllSignals(cmd.Arg(0)) 709 defer signal.StopCatch(sigc) 710 } 711 712 var in io.ReadCloser 713 714 v := url.Values{} 715 v.Set("stream", "1") 716 717 if *openStdin && config.GetBool("OpenStdin") { 718 v.Set("stdin", "1") 719 in = cli.in 720 } 721 722 v.Set("stdout", "1") 723 v.Set("stderr", "1") 724 725 cErr = promise.Go(func() error { 726 return cli.hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?"+v.Encode(), tty, in, cli.out, cli.err, hijacked, nil) 727 }) 728 } else { 729 close(hijacked) 730 } 731 732 // Acknowledge the hijack before starting 733 select { 734 case closer := <-hijacked: 735 // Make sure that the hijack gets closed when returning (results 736 // in closing the hijack chan and freeing server's goroutines) 737 if closer != nil { 738 defer closer.Close() 739 } 740 case err := <-cErr: 741 if err != nil { 742 return err 743 } 744 } 745 746 var encounteredError error 747 for _, name := range cmd.Args() { 748 _, _, err := readBody(cli.call("POST", "/containers/"+name+"/start", nil, false)) 749 if err != nil { 750 if !*attach && !*openStdin { 751 fmt.Fprintf(cli.err, "%s\n", err) 752 } 753 encounteredError = fmt.Errorf("Error: failed to start one or more containers") 754 } else { 755 if !*attach && !*openStdin { 756 fmt.Fprintf(cli.out, "%s\n", name) 757 } 758 } 759 } 760 if encounteredError != nil { 761 if *openStdin || *attach { 762 cli.in.Close() 763 } 764 return encounteredError 765 } 766 767 if *openStdin || *attach { 768 if tty && cli.isTerminalOut { 769 if err := cli.monitorTtySize(cmd.Arg(0), false); err != nil { 770 log.Errorf("Error monitoring TTY size: %s", err) 771 } 772 } 773 if attchErr := <-cErr; attchErr != nil { 774 return attchErr 775 } 776 _, status, err := getExitCode(cli, cmd.Arg(0)) 777 if err != nil { 778 return err 779 } 780 if status != 0 { 781 return &utils.StatusError{StatusCode: status} 782 } 783 } 784 return nil 785 } 786 787 func (cli *DockerCli) CmdUnpause(args ...string) error { 788 cmd := cli.Subcmd("unpause", "CONTAINER", "Unpause all processes within a container", true) 789 cmd.Require(flag.Exact, 1) 790 utils.ParseFlags(cmd, args, false) 791 792 var encounteredError error 793 for _, name := range cmd.Args() { 794 if _, _, err := readBody(cli.call("POST", fmt.Sprintf("/containers/%s/unpause", name), nil, false)); err != nil { 795 fmt.Fprintf(cli.err, "%s\n", err) 796 encounteredError = fmt.Errorf("Error: failed to unpause container named %s", name) 797 } else { 798 fmt.Fprintf(cli.out, "%s\n", name) 799 } 800 } 801 return encounteredError 802 } 803 804 func (cli *DockerCli) CmdPause(args ...string) error { 805 cmd := cli.Subcmd("pause", "CONTAINER", "Pause all processes within a container", true) 806 cmd.Require(flag.Exact, 1) 807 utils.ParseFlags(cmd, args, false) 808 809 var encounteredError error 810 for _, name := range cmd.Args() { 811 if _, _, err := readBody(cli.call("POST", fmt.Sprintf("/containers/%s/pause", name), nil, false)); err != nil { 812 fmt.Fprintf(cli.err, "%s\n", err) 813 encounteredError = fmt.Errorf("Error: failed to pause container named %s", name) 814 } else { 815 fmt.Fprintf(cli.out, "%s\n", name) 816 } 817 } 818 return encounteredError 819 } 820 821 func (cli *DockerCli) CmdInspect(args ...string) error { 822 cmd := cli.Subcmd("inspect", "CONTAINER|IMAGE [CONTAINER|IMAGE...]", "Return low-level information on a container or image", true) 823 tmplStr := cmd.String([]string{"f", "#format", "-format"}, "", "Format the output using the given go template.") 824 cmd.Require(flag.Min, 1) 825 826 utils.ParseFlags(cmd, args, true) 827 828 var tmpl *template.Template 829 if *tmplStr != "" { 830 var err error 831 if tmpl, err = template.New("").Funcs(funcMap).Parse(*tmplStr); err != nil { 832 fmt.Fprintf(cli.err, "Template parsing error: %v\n", err) 833 return &utils.StatusError{StatusCode: 64, 834 Status: "Template parsing error: " + err.Error()} 835 } 836 } 837 838 indented := new(bytes.Buffer) 839 indented.WriteByte('[') 840 status := 0 841 842 for _, name := range cmd.Args() { 843 obj, _, err := readBody(cli.call("GET", "/containers/"+name+"/json", nil, false)) 844 if err != nil { 845 obj, _, err = readBody(cli.call("GET", "/images/"+name+"/json", nil, false)) 846 if err != nil { 847 if strings.Contains(err.Error(), "No such") { 848 fmt.Fprintf(cli.err, "Error: No such image or container: %s\n", name) 849 } else { 850 fmt.Fprintf(cli.err, "%s", err) 851 } 852 status = 1 853 continue 854 } 855 } 856 857 if tmpl == nil { 858 if err = json.Indent(indented, obj, "", " "); err != nil { 859 fmt.Fprintf(cli.err, "%s\n", err) 860 status = 1 861 continue 862 } 863 } else { 864 // Has template, will render 865 var value interface{} 866 if err := json.Unmarshal(obj, &value); err != nil { 867 fmt.Fprintf(cli.err, "%s\n", err) 868 status = 1 869 continue 870 } 871 if err := tmpl.Execute(cli.out, value); err != nil { 872 return err 873 } 874 cli.out.Write([]byte{'\n'}) 875 } 876 indented.WriteString(",") 877 } 878 879 if indented.Len() > 1 { 880 // Remove trailing ',' 881 indented.Truncate(indented.Len() - 1) 882 } 883 indented.WriteString("]\n") 884 885 if tmpl == nil { 886 if _, err := io.Copy(cli.out, indented); err != nil { 887 return err 888 } 889 } 890 891 if status != 0 { 892 return &utils.StatusError{StatusCode: status} 893 } 894 return nil 895 } 896 897 func (cli *DockerCli) CmdTop(args ...string) error { 898 cmd := cli.Subcmd("top", "CONTAINER [ps OPTIONS]", "Display the running processes of a container", true) 899 cmd.Require(flag.Min, 1) 900 901 utils.ParseFlags(cmd, args, true) 902 903 val := url.Values{} 904 if cmd.NArg() > 1 { 905 val.Set("ps_args", strings.Join(cmd.Args()[1:], " ")) 906 } 907 908 stream, _, err := cli.call("GET", "/containers/"+cmd.Arg(0)+"/top?"+val.Encode(), nil, false) 909 if err != nil { 910 return err 911 } 912 var procs engine.Env 913 if err := procs.Decode(stream); err != nil { 914 return err 915 } 916 w := tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0) 917 fmt.Fprintln(w, strings.Join(procs.GetList("Titles"), "\t")) 918 processes := [][]string{} 919 if err := procs.GetJson("Processes", &processes); err != nil { 920 return err 921 } 922 for _, proc := range processes { 923 fmt.Fprintln(w, strings.Join(proc, "\t")) 924 } 925 w.Flush() 926 return nil 927 } 928 929 func (cli *DockerCli) CmdPort(args ...string) error { 930 cmd := cli.Subcmd("port", "CONTAINER [PRIVATE_PORT[/PROTO]]", "List port mappings for the CONTAINER, or lookup the public-facing port that is NAT-ed to the PRIVATE_PORT", true) 931 cmd.Require(flag.Min, 1) 932 utils.ParseFlags(cmd, args, true) 933 934 stream, _, err := cli.call("GET", "/containers/"+cmd.Arg(0)+"/json", nil, false) 935 if err != nil { 936 return err 937 } 938 939 env := engine.Env{} 940 if err := env.Decode(stream); err != nil { 941 return err 942 } 943 ports := nat.PortMap{} 944 if err := env.GetSubEnv("NetworkSettings").GetJson("Ports", &ports); err != nil { 945 return err 946 } 947 948 if cmd.NArg() == 2 { 949 var ( 950 port = cmd.Arg(1) 951 proto = "tcp" 952 parts = strings.SplitN(port, "/", 2) 953 ) 954 955 if len(parts) == 2 && len(parts[1]) != 0 { 956 port = parts[0] 957 proto = parts[1] 958 } 959 natPort := port + "/" + proto 960 if frontends, exists := ports[nat.Port(port+"/"+proto)]; exists && frontends != nil { 961 for _, frontend := range frontends { 962 fmt.Fprintf(cli.out, "%s:%s\n", frontend.HostIp, frontend.HostPort) 963 } 964 return nil 965 } 966 return fmt.Errorf("Error: No public port '%s' published for %s", natPort, cmd.Arg(0)) 967 } 968 969 for from, frontends := range ports { 970 for _, frontend := range frontends { 971 fmt.Fprintf(cli.out, "%s -> %s:%s\n", from, frontend.HostIp, frontend.HostPort) 972 } 973 } 974 975 return nil 976 } 977 978 // 'docker rmi IMAGE' removes all images with the name IMAGE 979 func (cli *DockerCli) CmdRmi(args ...string) error { 980 var ( 981 cmd = cli.Subcmd("rmi", "IMAGE [IMAGE...]", "Remove one or more images", true) 982 force = cmd.Bool([]string{"f", "-force"}, false, "Force removal of the image") 983 noprune = cmd.Bool([]string{"-no-prune"}, false, "Do not delete untagged parents") 984 ) 985 cmd.Require(flag.Min, 1) 986 987 utils.ParseFlags(cmd, args, true) 988 989 v := url.Values{} 990 if *force { 991 v.Set("force", "1") 992 } 993 if *noprune { 994 v.Set("noprune", "1") 995 } 996 997 var encounteredError error 998 for _, name := range cmd.Args() { 999 body, _, err := readBody(cli.call("DELETE", "/images/"+name+"?"+v.Encode(), nil, false)) 1000 if err != nil { 1001 fmt.Fprintf(cli.err, "%s\n", err) 1002 encounteredError = fmt.Errorf("Error: failed to remove one or more images") 1003 } else { 1004 outs := engine.NewTable("Created", 0) 1005 if _, err := outs.ReadListFrom(body); err != nil { 1006 fmt.Fprintf(cli.err, "%s\n", err) 1007 encounteredError = fmt.Errorf("Error: failed to remove one or more images") 1008 continue 1009 } 1010 for _, out := range outs.Data { 1011 if out.Get("Deleted") != "" { 1012 fmt.Fprintf(cli.out, "Deleted: %s\n", out.Get("Deleted")) 1013 } else { 1014 fmt.Fprintf(cli.out, "Untagged: %s\n", out.Get("Untagged")) 1015 } 1016 } 1017 } 1018 } 1019 return encounteredError 1020 } 1021 1022 func (cli *DockerCli) CmdHistory(args ...string) error { 1023 cmd := cli.Subcmd("history", "IMAGE", "Show the history of an image", true) 1024 quiet := cmd.Bool([]string{"q", "-quiet"}, false, "Only show numeric IDs") 1025 noTrunc := cmd.Bool([]string{"#notrunc", "-no-trunc"}, false, "Don't truncate output") 1026 cmd.Require(flag.Exact, 1) 1027 1028 utils.ParseFlags(cmd, args, true) 1029 1030 body, _, err := readBody(cli.call("GET", "/images/"+cmd.Arg(0)+"/history", nil, false)) 1031 if err != nil { 1032 return err 1033 } 1034 1035 outs := engine.NewTable("Created", 0) 1036 if _, err := outs.ReadListFrom(body); err != nil { 1037 return err 1038 } 1039 1040 w := tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0) 1041 if !*quiet { 1042 fmt.Fprintln(w, "IMAGE\tCREATED\tCREATED BY\tSIZE") 1043 } 1044 1045 for _, out := range outs.Data { 1046 outID := out.Get("Id") 1047 if !*quiet { 1048 if *noTrunc { 1049 fmt.Fprintf(w, "%s\t", outID) 1050 } else { 1051 fmt.Fprintf(w, "%s\t", utils.TruncateID(outID)) 1052 } 1053 1054 fmt.Fprintf(w, "%s ago\t", units.HumanDuration(time.Now().UTC().Sub(time.Unix(out.GetInt64("Created"), 0)))) 1055 1056 if *noTrunc { 1057 fmt.Fprintf(w, "%s\t", out.Get("CreatedBy")) 1058 } else { 1059 fmt.Fprintf(w, "%s\t", utils.Trunc(out.Get("CreatedBy"), 45)) 1060 } 1061 fmt.Fprintf(w, "%s\n", units.HumanSize(float64(out.GetInt64("Size")))) 1062 } else { 1063 if *noTrunc { 1064 fmt.Fprintln(w, outID) 1065 } else { 1066 fmt.Fprintln(w, utils.TruncateID(outID)) 1067 } 1068 } 1069 } 1070 w.Flush() 1071 return nil 1072 } 1073 1074 func (cli *DockerCli) CmdRm(args ...string) error { 1075 cmd := cli.Subcmd("rm", "CONTAINER [CONTAINER...]", "Remove one or more containers", true) 1076 v := cmd.Bool([]string{"v", "-volumes"}, false, "Remove the volumes associated with the container") 1077 link := cmd.Bool([]string{"l", "#link", "-link"}, false, "Remove the specified link and not the underlying container") 1078 force := cmd.Bool([]string{"f", "-force"}, false, "Force the removal of a running container (uses SIGKILL)") 1079 cmd.Require(flag.Min, 1) 1080 1081 utils.ParseFlags(cmd, args, true) 1082 1083 val := url.Values{} 1084 if *v { 1085 val.Set("v", "1") 1086 } 1087 if *link { 1088 val.Set("link", "1") 1089 } 1090 1091 if *force { 1092 val.Set("force", "1") 1093 } 1094 1095 var encounteredError error 1096 for _, name := range cmd.Args() { 1097 _, _, err := readBody(cli.call("DELETE", "/containers/"+name+"?"+val.Encode(), nil, false)) 1098 if err != nil { 1099 fmt.Fprintf(cli.err, "%s\n", err) 1100 encounteredError = fmt.Errorf("Error: failed to remove one or more containers") 1101 } else { 1102 fmt.Fprintf(cli.out, "%s\n", name) 1103 } 1104 } 1105 return encounteredError 1106 } 1107 1108 // 'docker kill NAME' kills a running container 1109 func (cli *DockerCli) CmdKill(args ...string) error { 1110 cmd := cli.Subcmd("kill", "CONTAINER [CONTAINER...]", "Kill a running container using SIGKILL or a specified signal", true) 1111 signal := cmd.String([]string{"s", "-signal"}, "KILL", "Signal to send to the container") 1112 cmd.Require(flag.Min, 1) 1113 1114 utils.ParseFlags(cmd, args, true) 1115 1116 var encounteredError error 1117 for _, name := range cmd.Args() { 1118 if _, _, err := readBody(cli.call("POST", fmt.Sprintf("/containers/%s/kill?signal=%s", name, *signal), nil, false)); err != nil { 1119 fmt.Fprintf(cli.err, "%s\n", err) 1120 encounteredError = fmt.Errorf("Error: failed to kill one or more containers") 1121 } else { 1122 fmt.Fprintf(cli.out, "%s\n", name) 1123 } 1124 } 1125 return encounteredError 1126 } 1127 1128 func (cli *DockerCli) CmdImport(args ...string) error { 1129 cmd := cli.Subcmd("import", "URL|- [REPOSITORY[:TAG]]", "Create an empty filesystem image and import the contents of the tarball (.tar, .tar.gz, .tgz, .bzip, .tar.xz, .txz) into it, then optionally tag it.", true) 1130 cmd.Require(flag.Min, 1) 1131 1132 utils.ParseFlags(cmd, args, true) 1133 1134 var ( 1135 v = url.Values{} 1136 src = cmd.Arg(0) 1137 repository = cmd.Arg(1) 1138 ) 1139 1140 v.Set("fromSrc", src) 1141 v.Set("repo", repository) 1142 1143 if cmd.NArg() == 3 { 1144 fmt.Fprintf(cli.err, "[DEPRECATED] The format 'URL|- [REPOSITORY [TAG]]' as been deprecated. Please use URL|- [REPOSITORY[:TAG]]\n") 1145 v.Set("tag", cmd.Arg(2)) 1146 } 1147 1148 if repository != "" { 1149 //Check if the given image name can be resolved 1150 repo, _ := parsers.ParseRepositoryTag(repository) 1151 if _, _, err := registry.ResolveRepositoryName(repo); err != nil { 1152 return err 1153 } 1154 } 1155 1156 var in io.Reader 1157 1158 if src == "-" { 1159 in = cli.in 1160 } 1161 1162 return cli.stream("POST", "/images/create?"+v.Encode(), in, cli.out, nil) 1163 } 1164 1165 func (cli *DockerCli) CmdPush(args ...string) error { 1166 cmd := cli.Subcmd("push", "NAME[:TAG]", "Push an image or a repository to the registry", true) 1167 cmd.Require(flag.Exact, 1) 1168 1169 utils.ParseFlags(cmd, args, true) 1170 1171 name := cmd.Arg(0) 1172 1173 cli.LoadConfigFile() 1174 1175 remote, tag := parsers.ParseRepositoryTag(name) 1176 1177 // Resolve the Repository name from fqn to hostname + name 1178 hostname, _, err := registry.ResolveRepositoryName(remote) 1179 if err != nil { 1180 return err 1181 } 1182 // Resolve the Auth config relevant for this server 1183 authConfig := cli.configFile.ResolveAuthConfig(hostname) 1184 // If we're not using a custom registry, we know the restrictions 1185 // applied to repository names and can warn the user in advance. 1186 // Custom repositories can have different rules, and we must also 1187 // allow pushing by image ID. 1188 if len(strings.SplitN(name, "/", 2)) == 1 { 1189 username := cli.configFile.Configs[registry.IndexServerAddress()].Username 1190 if username == "" { 1191 username = "<user>" 1192 } 1193 return fmt.Errorf("You cannot push a \"root\" repository. Please rename your repository in <user>/<repo> (ex: %s/%s)", username, name) 1194 } 1195 1196 v := url.Values{} 1197 v.Set("tag", tag) 1198 push := func(authConfig registry.AuthConfig) error { 1199 buf, err := json.Marshal(authConfig) 1200 if err != nil { 1201 return err 1202 } 1203 registryAuthHeader := []string{ 1204 base64.URLEncoding.EncodeToString(buf), 1205 } 1206 1207 return cli.stream("POST", "/images/"+remote+"/push?"+v.Encode(), nil, cli.out, map[string][]string{ 1208 "X-Registry-Auth": registryAuthHeader, 1209 }) 1210 } 1211 1212 if err := push(authConfig); err != nil { 1213 if strings.Contains(err.Error(), "Status 401") { 1214 fmt.Fprintln(cli.out, "\nPlease login prior to push:") 1215 if err := cli.CmdLogin(hostname); err != nil { 1216 return err 1217 } 1218 authConfig := cli.configFile.ResolveAuthConfig(hostname) 1219 return push(authConfig) 1220 } 1221 return err 1222 } 1223 return nil 1224 } 1225 1226 func (cli *DockerCli) CmdPull(args ...string) error { 1227 cmd := cli.Subcmd("pull", "NAME[:TAG]", "Pull an image or a repository from the registry", true) 1228 allTags := cmd.Bool([]string{"a", "-all-tags"}, false, "Download all tagged images in the repository") 1229 cmd.Require(flag.Exact, 1) 1230 1231 utils.ParseFlags(cmd, args, true) 1232 1233 var ( 1234 v = url.Values{} 1235 remote = cmd.Arg(0) 1236 newRemote = remote 1237 ) 1238 taglessRemote, tag := parsers.ParseRepositoryTag(remote) 1239 if tag == "" && !*allTags { 1240 newRemote = taglessRemote + ":" + graph.DEFAULTTAG 1241 } 1242 if tag != "" && *allTags { 1243 return fmt.Errorf("tag can't be used with --all-tags/-a") 1244 } 1245 1246 v.Set("fromImage", newRemote) 1247 1248 // Resolve the Repository name from fqn to hostname + name 1249 hostname, _, err := registry.ResolveRepositoryName(taglessRemote) 1250 if err != nil { 1251 return err 1252 } 1253 1254 cli.LoadConfigFile() 1255 1256 // Resolve the Auth config relevant for this server 1257 authConfig := cli.configFile.ResolveAuthConfig(hostname) 1258 1259 pull := func(authConfig registry.AuthConfig) error { 1260 buf, err := json.Marshal(authConfig) 1261 if err != nil { 1262 return err 1263 } 1264 registryAuthHeader := []string{ 1265 base64.URLEncoding.EncodeToString(buf), 1266 } 1267 1268 return cli.stream("POST", "/images/create?"+v.Encode(), nil, cli.out, map[string][]string{ 1269 "X-Registry-Auth": registryAuthHeader, 1270 }) 1271 } 1272 1273 if err := pull(authConfig); err != nil { 1274 if strings.Contains(err.Error(), "Status 401") { 1275 fmt.Fprintln(cli.out, "\nPlease login prior to pull:") 1276 if err := cli.CmdLogin(hostname); err != nil { 1277 return err 1278 } 1279 authConfig := cli.configFile.ResolveAuthConfig(hostname) 1280 return pull(authConfig) 1281 } 1282 return err 1283 } 1284 1285 return nil 1286 } 1287 1288 func (cli *DockerCli) CmdPull2(args ...string) error { 1289 cmd := cli.Subcmd("pull2", "NAME[:TAG]", "Pull an image or a repository from the registry", true) 1290 if err := cmd.Parse(args); err != nil { 1291 return nil 1292 } 1293 1294 if cmd.NArg() != 1 { 1295 cmd.Usage() 1296 return nil 1297 } 1298 var ( 1299 v = url.Values{} 1300 remote = cmd.Arg(0) 1301 newRemote = remote 1302 ) 1303 taglessRemote, tag := parsers.ParseRepositoryTag(remote) 1304 if tag == "" { 1305 return fmt.Errorf("tag is required!") 1306 } 1307 1308 v.Set("fromImage", newRemote) 1309 1310 // Resolve the Repository name from fqn to hostname + name 1311 hostname, _, err := registry.ResolveRepositoryName(taglessRemote) 1312 if err != nil { 1313 return err 1314 } 1315 1316 cli.LoadConfigFile() 1317 1318 // Resolve the Auth config relevant for this server 1319 authConfig := cli.configFile.ResolveAuthConfig(hostname) 1320 1321 pull2 := func(authConfig registry.AuthConfig) error { 1322 buf, err := json.Marshal(authConfig) 1323 if err != nil { 1324 return err 1325 } 1326 registryAuthHeader := []string{ 1327 base64.URLEncoding.EncodeToString(buf), 1328 } 1329 1330 return cli.stream("POST", "/images/create2?"+v.Encode(), nil, cli.out, map[string][]string{ 1331 "X-Registry-Auth": registryAuthHeader, 1332 }) 1333 } 1334 1335 if err := pull2(authConfig); err != nil { 1336 if strings.Contains(err.Error(), "Status 401") { 1337 fmt.Fprintln(cli.out, "\nPlease login prior to pull:") 1338 if err := cli.CmdLogin(hostname); err != nil { 1339 return err 1340 } 1341 authConfig := cli.configFile.ResolveAuthConfig(hostname) 1342 return pull2(authConfig) 1343 } 1344 return err 1345 } 1346 1347 return nil 1348 } 1349 1350 func (cli *DockerCli) CmdImages(args ...string) error { 1351 cmd := cli.Subcmd("images", "[REPOSITORY]", "List images", true) 1352 quiet := cmd.Bool([]string{"q", "-quiet"}, false, "Only show numeric IDs") 1353 all := cmd.Bool([]string{"a", "-all"}, false, "Show all images (by default filter out the intermediate image layers)") 1354 noTrunc := cmd.Bool([]string{"#notrunc", "-no-trunc"}, false, "Don't truncate output") 1355 // FIXME: --viz and --tree are deprecated. Remove them in a future version. 1356 flViz := cmd.Bool([]string{"#v", "#viz", "#-viz"}, false, "Output graph in graphviz format") 1357 flTree := cmd.Bool([]string{"#t", "#tree", "#-tree"}, false, "Output graph in tree format") 1358 1359 flFilter := opts.NewListOpts(nil) 1360 cmd.Var(&flFilter, []string{"f", "-filter"}, "Provide filter values (i.e. 'dangling=true')") 1361 cmd.Require(flag.Max, 1) 1362 1363 utils.ParseFlags(cmd, args, true) 1364 1365 // Consolidate all filter flags, and sanity check them early. 1366 // They'll get process in the daemon/server. 1367 imageFilterArgs := filters.Args{} 1368 for _, f := range flFilter.GetAll() { 1369 var err error 1370 imageFilterArgs, err = filters.ParseFlag(f, imageFilterArgs) 1371 if err != nil { 1372 return err 1373 } 1374 } 1375 1376 for name := range imageFilterArgs { 1377 if _, ok := acceptedImageFilterTags[name]; !ok { 1378 return fmt.Errorf("Invalid filter '%s'", name) 1379 } 1380 } 1381 1382 matchName := cmd.Arg(0) 1383 // FIXME: --viz and --tree are deprecated. Remove them in a future version. 1384 if *flViz || *flTree { 1385 v := url.Values{ 1386 "all": []string{"1"}, 1387 } 1388 if len(imageFilterArgs) > 0 { 1389 filterJson, err := filters.ToParam(imageFilterArgs) 1390 if err != nil { 1391 return err 1392 } 1393 v.Set("filters", filterJson) 1394 } 1395 1396 body, _, err := readBody(cli.call("GET", "/images/json?"+v.Encode(), nil, false)) 1397 if err != nil { 1398 return err 1399 } 1400 1401 outs := engine.NewTable("Created", 0) 1402 if _, err := outs.ReadListFrom(body); err != nil { 1403 return err 1404 } 1405 1406 var ( 1407 printNode func(cli *DockerCli, noTrunc bool, image *engine.Env, prefix string) 1408 startImage *engine.Env 1409 1410 roots = engine.NewTable("Created", outs.Len()) 1411 byParent = make(map[string]*engine.Table) 1412 ) 1413 1414 for _, image := range outs.Data { 1415 if image.Get("ParentId") == "" { 1416 roots.Add(image) 1417 } else { 1418 if children, exists := byParent[image.Get("ParentId")]; exists { 1419 children.Add(image) 1420 } else { 1421 byParent[image.Get("ParentId")] = engine.NewTable("Created", 1) 1422 byParent[image.Get("ParentId")].Add(image) 1423 } 1424 } 1425 1426 if matchName != "" { 1427 if matchName == image.Get("Id") || matchName == utils.TruncateID(image.Get("Id")) { 1428 startImage = image 1429 } 1430 1431 for _, repotag := range image.GetList("RepoTags") { 1432 if repotag == matchName { 1433 startImage = image 1434 } 1435 } 1436 } 1437 } 1438 1439 if *flViz { 1440 fmt.Fprintf(cli.out, "digraph docker {\n") 1441 printNode = (*DockerCli).printVizNode 1442 } else { 1443 printNode = (*DockerCli).printTreeNode 1444 } 1445 1446 if startImage != nil { 1447 root := engine.NewTable("Created", 1) 1448 root.Add(startImage) 1449 cli.WalkTree(*noTrunc, root, byParent, "", printNode) 1450 } else if matchName == "" { 1451 cli.WalkTree(*noTrunc, roots, byParent, "", printNode) 1452 } 1453 if *flViz { 1454 fmt.Fprintf(cli.out, " base [style=invisible]\n}\n") 1455 } 1456 } else { 1457 v := url.Values{} 1458 if len(imageFilterArgs) > 0 { 1459 filterJson, err := filters.ToParam(imageFilterArgs) 1460 if err != nil { 1461 return err 1462 } 1463 v.Set("filters", filterJson) 1464 } 1465 1466 if cmd.NArg() == 1 { 1467 // FIXME rename this parameter, to not be confused with the filters flag 1468 v.Set("filter", matchName) 1469 } 1470 if *all { 1471 v.Set("all", "1") 1472 } 1473 1474 body, _, err := readBody(cli.call("GET", "/images/json?"+v.Encode(), nil, false)) 1475 1476 if err != nil { 1477 return err 1478 } 1479 1480 outs := engine.NewTable("Created", 0) 1481 if _, err := outs.ReadListFrom(body); err != nil { 1482 return err 1483 } 1484 1485 w := tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0) 1486 if !*quiet { 1487 fmt.Fprintln(w, "REPOSITORY\tTAG\tIMAGE ID\tCREATED\tVIRTUAL SIZE") 1488 } 1489 1490 for _, out := range outs.Data { 1491 for _, repotag := range out.GetList("RepoTags") { 1492 1493 repo, tag := parsers.ParseRepositoryTag(repotag) 1494 outID := out.Get("Id") 1495 if !*noTrunc { 1496 outID = utils.TruncateID(outID) 1497 } 1498 1499 if !*quiet { 1500 fmt.Fprintf(w, "%s\t%s\t%s\t%s ago\t%s\n", repo, tag, outID, units.HumanDuration(time.Now().UTC().Sub(time.Unix(out.GetInt64("Created"), 0))), units.HumanSize(float64(out.GetInt64("VirtualSize")))) 1501 } else { 1502 fmt.Fprintln(w, outID) 1503 } 1504 } 1505 } 1506 1507 if !*quiet { 1508 w.Flush() 1509 } 1510 } 1511 return nil 1512 } 1513 1514 // FIXME: --viz and --tree are deprecated. Remove them in a future version. 1515 func (cli *DockerCli) WalkTree(noTrunc bool, images *engine.Table, byParent map[string]*engine.Table, prefix string, printNode func(cli *DockerCli, noTrunc bool, image *engine.Env, prefix string)) { 1516 length := images.Len() 1517 if length > 1 { 1518 for index, image := range images.Data { 1519 if index+1 == length { 1520 printNode(cli, noTrunc, image, prefix+"└─") 1521 if subimages, exists := byParent[image.Get("Id")]; exists { 1522 cli.WalkTree(noTrunc, subimages, byParent, prefix+" ", printNode) 1523 } 1524 } else { 1525 printNode(cli, noTrunc, image, prefix+"\u251C─") 1526 if subimages, exists := byParent[image.Get("Id")]; exists { 1527 cli.WalkTree(noTrunc, subimages, byParent, prefix+"\u2502 ", printNode) 1528 } 1529 } 1530 } 1531 } else { 1532 for _, image := range images.Data { 1533 printNode(cli, noTrunc, image, prefix+"└─") 1534 if subimages, exists := byParent[image.Get("Id")]; exists { 1535 cli.WalkTree(noTrunc, subimages, byParent, prefix+" ", printNode) 1536 } 1537 } 1538 } 1539 } 1540 1541 // FIXME: --viz and --tree are deprecated. Remove them in a future version. 1542 func (cli *DockerCli) printVizNode(noTrunc bool, image *engine.Env, prefix string) { 1543 var ( 1544 imageID string 1545 parentID string 1546 ) 1547 if noTrunc { 1548 imageID = image.Get("Id") 1549 parentID = image.Get("ParentId") 1550 } else { 1551 imageID = utils.TruncateID(image.Get("Id")) 1552 parentID = utils.TruncateID(image.Get("ParentId")) 1553 } 1554 if parentID == "" { 1555 fmt.Fprintf(cli.out, " base -> \"%s\" [style=invis]\n", imageID) 1556 } else { 1557 fmt.Fprintf(cli.out, " \"%s\" -> \"%s\"\n", parentID, imageID) 1558 } 1559 if image.GetList("RepoTags")[0] != "<none>:<none>" { 1560 fmt.Fprintf(cli.out, " \"%s\" [label=\"%s\\n%s\",shape=box,fillcolor=\"paleturquoise\",style=\"filled,rounded\"];\n", 1561 imageID, imageID, strings.Join(image.GetList("RepoTags"), "\\n")) 1562 } 1563 } 1564 1565 // FIXME: --viz and --tree are deprecated. Remove them in a future version. 1566 func (cli *DockerCli) printTreeNode(noTrunc bool, image *engine.Env, prefix string) { 1567 var imageID string 1568 if noTrunc { 1569 imageID = image.Get("Id") 1570 } else { 1571 imageID = utils.TruncateID(image.Get("Id")) 1572 } 1573 1574 fmt.Fprintf(cli.out, "%s%s Virtual Size: %s", prefix, imageID, units.HumanSize(float64(image.GetInt64("VirtualSize")))) 1575 if image.GetList("RepoTags")[0] != "<none>:<none>" { 1576 fmt.Fprintf(cli.out, " Tags: %s\n", strings.Join(image.GetList("RepoTags"), ", ")) 1577 } else { 1578 fmt.Fprint(cli.out, "\n") 1579 } 1580 } 1581 1582 func (cli *DockerCli) CmdPs(args ...string) error { 1583 var ( 1584 err error 1585 1586 psFilterArgs = filters.Args{} 1587 v = url.Values{} 1588 1589 cmd = cli.Subcmd("ps", "", "List containers", true) 1590 quiet = cmd.Bool([]string{"q", "-quiet"}, false, "Only display numeric IDs") 1591 size = cmd.Bool([]string{"s", "-size"}, false, "Display total file sizes") 1592 all = cmd.Bool([]string{"a", "-all"}, false, "Show all containers. Only running containers are shown by default.") 1593 noTrunc = cmd.Bool([]string{"#notrunc", "-no-trunc"}, false, "Don't truncate output") 1594 nLatest = cmd.Bool([]string{"l", "-latest"}, false, "Show only the latest created container, include non-running ones.") 1595 since = cmd.String([]string{"#sinceId", "#-since-id", "-since"}, "", "Show only containers created since Id or Name, include non-running ones.") 1596 before = cmd.String([]string{"#beforeId", "#-before-id", "-before"}, "", "Show only container created before Id or Name, include non-running ones.") 1597 last = cmd.Int([]string{"n"}, -1, "Show n last created containers, include non-running ones.") 1598 flFilter = opts.NewListOpts(nil) 1599 ) 1600 cmd.Require(flag.Exact, 0) 1601 1602 cmd.Var(&flFilter, []string{"f", "-filter"}, "Provide filter values. Valid filters:\nexited=<int> - containers with exit code of <int>\nstatus=(restarting|running|paused|exited)") 1603 1604 utils.ParseFlags(cmd, args, true) 1605 if *last == -1 && *nLatest { 1606 *last = 1 1607 } 1608 1609 if *all { 1610 v.Set("all", "1") 1611 } 1612 1613 if *last != -1 { 1614 v.Set("limit", strconv.Itoa(*last)) 1615 } 1616 1617 if *since != "" { 1618 v.Set("since", *since) 1619 } 1620 1621 if *before != "" { 1622 v.Set("before", *before) 1623 } 1624 1625 if *size { 1626 v.Set("size", "1") 1627 } 1628 1629 // Consolidate all filter flags, and sanity check them. 1630 // They'll get processed in the daemon/server. 1631 for _, f := range flFilter.GetAll() { 1632 if psFilterArgs, err = filters.ParseFlag(f, psFilterArgs); err != nil { 1633 return err 1634 } 1635 } 1636 1637 if len(psFilterArgs) > 0 { 1638 filterJson, err := filters.ToParam(psFilterArgs) 1639 if err != nil { 1640 return err 1641 } 1642 1643 v.Set("filters", filterJson) 1644 } 1645 1646 body, _, err := readBody(cli.call("GET", "/containers/json?"+v.Encode(), nil, false)) 1647 if err != nil { 1648 return err 1649 } 1650 1651 outs := engine.NewTable("Created", 0) 1652 if _, err := outs.ReadListFrom(body); err != nil { 1653 return err 1654 } 1655 1656 w := tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0) 1657 if !*quiet { 1658 fmt.Fprint(w, "CONTAINER ID\tIMAGE\tCOMMAND\tCREATED\tSTATUS\tPORTS\tNAMES") 1659 1660 if *size { 1661 fmt.Fprintln(w, "\tSIZE") 1662 } else { 1663 fmt.Fprint(w, "\n") 1664 } 1665 } 1666 1667 stripNamePrefix := func(ss []string) []string { 1668 for i, s := range ss { 1669 ss[i] = s[1:] 1670 } 1671 1672 return ss 1673 } 1674 1675 for _, out := range outs.Data { 1676 outID := out.Get("Id") 1677 1678 if !*noTrunc { 1679 outID = utils.TruncateID(outID) 1680 } 1681 1682 if *quiet { 1683 fmt.Fprintln(w, outID) 1684 1685 continue 1686 } 1687 1688 var ( 1689 outNames = stripNamePrefix(out.GetList("Names")) 1690 outCommand = strconv.Quote(out.Get("Command")) 1691 ports = engine.NewTable("", 0) 1692 ) 1693 1694 if !*noTrunc { 1695 outCommand = utils.Trunc(outCommand, 20) 1696 1697 // only display the default name for the container with notrunc is passed 1698 for _, name := range outNames { 1699 if len(strings.Split(name, "/")) == 1 { 1700 outNames = []string{name} 1701 1702 break 1703 } 1704 } 1705 } 1706 1707 ports.ReadListFrom([]byte(out.Get("Ports"))) 1708 1709 image := out.Get("Image") 1710 if image == "" { 1711 image = "<no image>" 1712 } 1713 1714 fmt.Fprintf(w, "%s\t%s\t%s\t%s ago\t%s\t%s\t%s\t", outID, image, outCommand, 1715 units.HumanDuration(time.Now().UTC().Sub(time.Unix(out.GetInt64("Created"), 0))), 1716 out.Get("Status"), api.DisplayablePorts(ports), strings.Join(outNames, ",")) 1717 1718 if *size { 1719 if out.GetInt("SizeRootFs") > 0 { 1720 fmt.Fprintf(w, "%s (virtual %s)\n", units.HumanSize(float64(out.GetInt64("SizeRw"))), units.HumanSize(float64(out.GetInt64("SizeRootFs")))) 1721 } else { 1722 fmt.Fprintf(w, "%s\n", units.HumanSize(float64(out.GetInt64("SizeRw")))) 1723 } 1724 1725 continue 1726 } 1727 1728 fmt.Fprint(w, "\n") 1729 } 1730 1731 if !*quiet { 1732 w.Flush() 1733 } 1734 1735 return nil 1736 } 1737 1738 func (cli *DockerCli) CmdCommit(args ...string) error { 1739 cmd := cli.Subcmd("commit", "CONTAINER [REPOSITORY[:TAG]]", "Create a new image from a container's changes", true) 1740 flPause := cmd.Bool([]string{"p", "-pause"}, true, "Pause container during commit") 1741 flComment := cmd.String([]string{"m", "-message"}, "", "Commit message") 1742 flAuthor := cmd.String([]string{"a", "#author", "-author"}, "", "Author (e.g., \"John Hannibal Smith <hannibal@a-team.com>\")") 1743 // FIXME: --run is deprecated, it will be replaced with inline Dockerfile commands. 1744 flConfig := cmd.String([]string{"#run", "#-run"}, "", "This option is deprecated and will be removed in a future version in favor of inline Dockerfile-compatible commands") 1745 cmd.Require(flag.Max, 2) 1746 cmd.Require(flag.Min, 1) 1747 utils.ParseFlags(cmd, args, true) 1748 1749 var ( 1750 name = cmd.Arg(0) 1751 repository, tag = parsers.ParseRepositoryTag(cmd.Arg(1)) 1752 ) 1753 1754 //Check if the given image name can be resolved 1755 if repository != "" { 1756 if _, _, err := registry.ResolveRepositoryName(repository); err != nil { 1757 return err 1758 } 1759 } 1760 1761 v := url.Values{} 1762 v.Set("container", name) 1763 v.Set("repo", repository) 1764 v.Set("tag", tag) 1765 v.Set("comment", *flComment) 1766 v.Set("author", *flAuthor) 1767 1768 if *flPause != true { 1769 v.Set("pause", "0") 1770 } 1771 1772 var ( 1773 config *runconfig.Config 1774 env engine.Env 1775 ) 1776 if *flConfig != "" { 1777 config = &runconfig.Config{} 1778 if err := json.Unmarshal([]byte(*flConfig), config); err != nil { 1779 return err 1780 } 1781 } 1782 stream, _, err := cli.call("POST", "/commit?"+v.Encode(), config, false) 1783 if err != nil { 1784 return err 1785 } 1786 if err := env.Decode(stream); err != nil { 1787 return err 1788 } 1789 1790 fmt.Fprintf(cli.out, "%s\n", env.Get("Id")) 1791 return nil 1792 } 1793 1794 func (cli *DockerCli) CmdEvents(args ...string) error { 1795 cmd := cli.Subcmd("events", "", "Get real time events from the server", true) 1796 since := cmd.String([]string{"#since", "-since"}, "", "Show all events created since timestamp") 1797 until := cmd.String([]string{"-until"}, "", "Stream events until this timestamp") 1798 flFilter := opts.NewListOpts(nil) 1799 cmd.Var(&flFilter, []string{"f", "-filter"}, "Provide filter values (i.e. 'event=stop')") 1800 cmd.Require(flag.Exact, 0) 1801 1802 utils.ParseFlags(cmd, args, true) 1803 1804 var ( 1805 v = url.Values{} 1806 loc = time.FixedZone(time.Now().Zone()) 1807 eventFilterArgs = filters.Args{} 1808 ) 1809 1810 // Consolidate all filter flags, and sanity check them early. 1811 // They'll get process in the daemon/server. 1812 for _, f := range flFilter.GetAll() { 1813 var err error 1814 eventFilterArgs, err = filters.ParseFlag(f, eventFilterArgs) 1815 if err != nil { 1816 return err 1817 } 1818 } 1819 var setTime = func(key, value string) { 1820 format := timeutils.RFC3339NanoFixed 1821 if len(value) < len(format) { 1822 format = format[:len(value)] 1823 } 1824 if t, err := time.ParseInLocation(format, value, loc); err == nil { 1825 v.Set(key, strconv.FormatInt(t.Unix(), 10)) 1826 } else { 1827 v.Set(key, value) 1828 } 1829 } 1830 if *since != "" { 1831 setTime("since", *since) 1832 } 1833 if *until != "" { 1834 setTime("until", *until) 1835 } 1836 if len(eventFilterArgs) > 0 { 1837 filterJson, err := filters.ToParam(eventFilterArgs) 1838 if err != nil { 1839 return err 1840 } 1841 v.Set("filters", filterJson) 1842 } 1843 if err := cli.stream("GET", "/events?"+v.Encode(), nil, cli.out, nil); err != nil { 1844 return err 1845 } 1846 return nil 1847 } 1848 1849 func (cli *DockerCli) CmdExport(args ...string) error { 1850 cmd := cli.Subcmd("export", "CONTAINER", "Export the contents of a filesystem as a tar archive to STDOUT", true) 1851 cmd.Require(flag.Exact, 1) 1852 1853 utils.ParseFlags(cmd, args, true) 1854 1855 if err := cli.stream("GET", "/containers/"+cmd.Arg(0)+"/export", nil, cli.out, nil); err != nil { 1856 return err 1857 } 1858 return nil 1859 } 1860 1861 func (cli *DockerCli) CmdDiff(args ...string) error { 1862 cmd := cli.Subcmd("diff", "CONTAINER", "Inspect changes on a container's filesystem", true) 1863 cmd.Require(flag.Exact, 1) 1864 1865 utils.ParseFlags(cmd, args, true) 1866 1867 body, _, err := readBody(cli.call("GET", "/containers/"+cmd.Arg(0)+"/changes", nil, false)) 1868 1869 if err != nil { 1870 return err 1871 } 1872 1873 outs := engine.NewTable("", 0) 1874 if _, err := outs.ReadListFrom(body); err != nil { 1875 return err 1876 } 1877 for _, change := range outs.Data { 1878 var kind string 1879 switch change.GetInt("Kind") { 1880 case archive.ChangeModify: 1881 kind = "C" 1882 case archive.ChangeAdd: 1883 kind = "A" 1884 case archive.ChangeDelete: 1885 kind = "D" 1886 } 1887 fmt.Fprintf(cli.out, "%s %s\n", kind, change.Get("Path")) 1888 } 1889 return nil 1890 } 1891 1892 func (cli *DockerCli) CmdLogs(args ...string) error { 1893 var ( 1894 cmd = cli.Subcmd("logs", "CONTAINER", "Fetch the logs of a container", true) 1895 follow = cmd.Bool([]string{"f", "-follow"}, false, "Follow log output") 1896 times = cmd.Bool([]string{"t", "-timestamps"}, false, "Show timestamps") 1897 tail = cmd.String([]string{"-tail"}, "all", "Output the specified number of lines at the end of logs (defaults to all logs)") 1898 ) 1899 cmd.Require(flag.Exact, 1) 1900 1901 utils.ParseFlags(cmd, args, true) 1902 1903 name := cmd.Arg(0) 1904 1905 stream, _, err := cli.call("GET", "/containers/"+name+"/json", nil, false) 1906 if err != nil { 1907 return err 1908 } 1909 1910 env := engine.Env{} 1911 if err := env.Decode(stream); err != nil { 1912 return err 1913 } 1914 1915 v := url.Values{} 1916 v.Set("stdout", "1") 1917 v.Set("stderr", "1") 1918 1919 if *times { 1920 v.Set("timestamps", "1") 1921 } 1922 1923 if *follow { 1924 v.Set("follow", "1") 1925 } 1926 v.Set("tail", *tail) 1927 1928 return cli.streamHelper("GET", "/containers/"+name+"/logs?"+v.Encode(), env.GetSubEnv("Config").GetBool("Tty"), nil, cli.out, cli.err, nil) 1929 } 1930 1931 func (cli *DockerCli) CmdAttach(args ...string) error { 1932 var ( 1933 cmd = cli.Subcmd("attach", "CONTAINER", "Attach to a running container", true) 1934 noStdin = cmd.Bool([]string{"#nostdin", "-no-stdin"}, false, "Do not attach STDIN") 1935 proxy = cmd.Bool([]string{"#sig-proxy", "-sig-proxy"}, true, "Proxy all received signals to the process (non-TTY mode only). SIGCHLD, SIGKILL, and SIGSTOP are not proxied.") 1936 ) 1937 cmd.Require(flag.Exact, 1) 1938 1939 utils.ParseFlags(cmd, args, true) 1940 name := cmd.Arg(0) 1941 1942 stream, _, err := cli.call("GET", "/containers/"+name+"/json", nil, false) 1943 if err != nil { 1944 return err 1945 } 1946 1947 env := engine.Env{} 1948 if err := env.Decode(stream); err != nil { 1949 return err 1950 } 1951 1952 if !env.GetSubEnv("State").GetBool("Running") { 1953 return fmt.Errorf("You cannot attach to a stopped container, start it first") 1954 } 1955 1956 var ( 1957 config = env.GetSubEnv("Config") 1958 tty = config.GetBool("Tty") 1959 ) 1960 1961 if err := cli.CheckTtyInput(!*noStdin, tty); err != nil { 1962 return err 1963 } 1964 1965 if tty && cli.isTerminalOut { 1966 if err := cli.monitorTtySize(cmd.Arg(0), false); err != nil { 1967 log.Debugf("Error monitoring TTY size: %s", err) 1968 } 1969 } 1970 1971 var in io.ReadCloser 1972 1973 v := url.Values{} 1974 v.Set("stream", "1") 1975 if !*noStdin && config.GetBool("OpenStdin") { 1976 v.Set("stdin", "1") 1977 in = cli.in 1978 } 1979 1980 v.Set("stdout", "1") 1981 v.Set("stderr", "1") 1982 1983 if *proxy && !tty { 1984 sigc := cli.forwardAllSignals(cmd.Arg(0)) 1985 defer signal.StopCatch(sigc) 1986 } 1987 1988 if err := cli.hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?"+v.Encode(), tty, in, cli.out, cli.err, nil, nil); err != nil { 1989 return err 1990 } 1991 1992 _, status, err := getExitCode(cli, cmd.Arg(0)) 1993 if err != nil { 1994 return err 1995 } 1996 if status != 0 { 1997 return &utils.StatusError{StatusCode: status} 1998 } 1999 2000 return nil 2001 } 2002 2003 func (cli *DockerCli) CmdSearch(args ...string) error { 2004 cmd := cli.Subcmd("search", "TERM", "Search the Docker Hub for images", true) 2005 noTrunc := cmd.Bool([]string{"#notrunc", "-no-trunc"}, false, "Don't truncate output") 2006 trusted := cmd.Bool([]string{"#t", "#trusted", "#-trusted"}, false, "Only show trusted builds") 2007 automated := cmd.Bool([]string{"-automated"}, false, "Only show automated builds") 2008 stars := cmd.Int([]string{"s", "#stars", "-stars"}, 0, "Only displays with at least x stars") 2009 cmd.Require(flag.Exact, 1) 2010 2011 utils.ParseFlags(cmd, args, true) 2012 2013 v := url.Values{} 2014 v.Set("term", cmd.Arg(0)) 2015 2016 body, _, err := readBody(cli.call("GET", "/images/search?"+v.Encode(), nil, true)) 2017 2018 if err != nil { 2019 return err 2020 } 2021 outs := engine.NewTable("star_count", 0) 2022 if _, err := outs.ReadListFrom(body); err != nil { 2023 return err 2024 } 2025 w := tabwriter.NewWriter(cli.out, 10, 1, 3, ' ', 0) 2026 fmt.Fprintf(w, "NAME\tDESCRIPTION\tSTARS\tOFFICIAL\tAUTOMATED\n") 2027 for _, out := range outs.Data { 2028 if ((*automated || *trusted) && (!out.GetBool("is_trusted") && !out.GetBool("is_automated"))) || (*stars > out.GetInt("star_count")) { 2029 continue 2030 } 2031 desc := strings.Replace(out.Get("description"), "\n", " ", -1) 2032 desc = strings.Replace(desc, "\r", " ", -1) 2033 if !*noTrunc && len(desc) > 45 { 2034 desc = utils.Trunc(desc, 42) + "..." 2035 } 2036 fmt.Fprintf(w, "%s\t%s\t%d\t", out.Get("name"), desc, out.GetInt("star_count")) 2037 if out.GetBool("is_official") { 2038 fmt.Fprint(w, "[OK]") 2039 2040 } 2041 fmt.Fprint(w, "\t") 2042 if out.GetBool("is_automated") || out.GetBool("is_trusted") { 2043 fmt.Fprint(w, "[OK]") 2044 } 2045 fmt.Fprint(w, "\n") 2046 } 2047 w.Flush() 2048 return nil 2049 } 2050 2051 // Ports type - Used to parse multiple -p flags 2052 type ports []int 2053 2054 func (cli *DockerCli) CmdTag(args ...string) error { 2055 cmd := cli.Subcmd("tag", "IMAGE[:TAG] [REGISTRYHOST/][USERNAME/]NAME[:TAG]", "Tag an image into a repository", true) 2056 force := cmd.Bool([]string{"f", "#force", "-force"}, false, "Force") 2057 cmd.Require(flag.Exact, 2) 2058 2059 utils.ParseFlags(cmd, args, true) 2060 2061 var ( 2062 repository, tag = parsers.ParseRepositoryTag(cmd.Arg(1)) 2063 v = url.Values{} 2064 ) 2065 2066 //Check if the given image name can be resolved 2067 if _, _, err := registry.ResolveRepositoryName(repository); err != nil { 2068 return err 2069 } 2070 v.Set("repo", repository) 2071 v.Set("tag", tag) 2072 2073 if *force { 2074 v.Set("force", "1") 2075 } 2076 2077 if _, _, err := readBody(cli.call("POST", "/images/"+cmd.Arg(0)+"/tag?"+v.Encode(), nil, false)); err != nil { 2078 return err 2079 } 2080 return nil 2081 } 2082 2083 func (cli *DockerCli) pullImage(image string) error { 2084 return cli.pullImageCustomOut(image, cli.out) 2085 } 2086 2087 func (cli *DockerCli) pullImageCustomOut(image string, out io.Writer) error { 2088 v := url.Values{} 2089 repos, tag := parsers.ParseRepositoryTag(image) 2090 // pull only the image tagged 'latest' if no tag was specified 2091 if tag == "" { 2092 tag = graph.DEFAULTTAG 2093 } 2094 v.Set("fromImage", repos) 2095 v.Set("tag", tag) 2096 2097 // Resolve the Repository name from fqn to hostname + name 2098 hostname, _, err := registry.ResolveRepositoryName(repos) 2099 if err != nil { 2100 return err 2101 } 2102 2103 // Load the auth config file, to be able to pull the image 2104 cli.LoadConfigFile() 2105 2106 // Resolve the Auth config relevant for this server 2107 authConfig := cli.configFile.ResolveAuthConfig(hostname) 2108 buf, err := json.Marshal(authConfig) 2109 if err != nil { 2110 return err 2111 } 2112 2113 registryAuthHeader := []string{ 2114 base64.URLEncoding.EncodeToString(buf), 2115 } 2116 if err = cli.stream("POST", "/images/create?"+v.Encode(), nil, out, map[string][]string{"X-Registry-Auth": registryAuthHeader}); err != nil { 2117 return err 2118 } 2119 return nil 2120 } 2121 2122 type cidFile struct { 2123 path string 2124 file *os.File 2125 written bool 2126 } 2127 2128 func newCIDFile(path string) (*cidFile, error) { 2129 if _, err := os.Stat(path); err == nil { 2130 return nil, fmt.Errorf("Container ID file found, make sure the other container isn't running or delete %s", path) 2131 } 2132 2133 f, err := os.Create(path) 2134 if err != nil { 2135 return nil, fmt.Errorf("Failed to create the container ID file: %s", err) 2136 } 2137 2138 return &cidFile{path: path, file: f}, nil 2139 } 2140 2141 func (cid *cidFile) Close() error { 2142 cid.file.Close() 2143 2144 if !cid.written { 2145 if err := os.Remove(cid.path); err != nil { 2146 return fmt.Errorf("failed to remove the CID file '%s': %s \n", cid.path, err) 2147 } 2148 } 2149 2150 return nil 2151 } 2152 2153 func (cid *cidFile) Write(id string) error { 2154 if _, err := cid.file.Write([]byte(id)); err != nil { 2155 return fmt.Errorf("Failed to write the container ID to the file: %s", err) 2156 } 2157 cid.written = true 2158 return nil 2159 } 2160 2161 func (cli *DockerCli) createContainer(config *runconfig.Config, hostConfig *runconfig.HostConfig, cidfile, name string) (engine.Env, error) { 2162 containerValues := url.Values{} 2163 if name != "" { 2164 containerValues.Set("name", name) 2165 } 2166 2167 mergedConfig := runconfig.MergeConfigs(config, hostConfig) 2168 2169 var containerIDFile *cidFile 2170 if cidfile != "" { 2171 var err error 2172 if containerIDFile, err = newCIDFile(cidfile); err != nil { 2173 return nil, err 2174 } 2175 defer containerIDFile.Close() 2176 } 2177 2178 //create the container 2179 stream, statusCode, err := cli.call("POST", "/containers/create?"+containerValues.Encode(), mergedConfig, false) 2180 //if image not found try to pull it 2181 if statusCode == 404 { 2182 repo, tag := parsers.ParseRepositoryTag(config.Image) 2183 if tag == "" { 2184 tag = graph.DEFAULTTAG 2185 } 2186 fmt.Fprintf(cli.err, "Unable to find image '%s:%s' locally\n", repo, tag) 2187 2188 // we don't want to write to stdout anything apart from container.ID 2189 if err = cli.pullImageCustomOut(config.Image, cli.err); err != nil { 2190 return nil, err 2191 } 2192 // Retry 2193 if stream, _, err = cli.call("POST", "/containers/create?"+containerValues.Encode(), mergedConfig, false); err != nil { 2194 return nil, err 2195 } 2196 } else if err != nil { 2197 return nil, err 2198 } 2199 2200 var result engine.Env 2201 if err := result.Decode(stream); err != nil { 2202 return nil, err 2203 } 2204 2205 for _, warning := range result.GetList("Warnings") { 2206 fmt.Fprintf(cli.err, "WARNING: %s\n", warning) 2207 } 2208 2209 if containerIDFile != nil { 2210 if err = containerIDFile.Write(result.Get("Id")); err != nil { 2211 return nil, err 2212 } 2213 } 2214 2215 return result, nil 2216 2217 } 2218 2219 func (cli *DockerCli) CmdCreate(args ...string) error { 2220 cmd := cli.Subcmd("create", "IMAGE [COMMAND] [ARG...]", "Create a new container", true) 2221 2222 // These are flags not stored in Config/HostConfig 2223 var ( 2224 flName = cmd.String([]string{"-name"}, "", "Assign a name to the container") 2225 ) 2226 2227 config, hostConfig, cmd, err := runconfig.Parse(cmd, args) 2228 if err != nil { 2229 return &utils.StatusError{StatusCode: 1} 2230 } 2231 if config.Image == "" { 2232 cmd.Usage() 2233 return nil 2234 } 2235 2236 createResult, err := cli.createContainer(config, hostConfig, hostConfig.ContainerIDFile, *flName) 2237 if err != nil { 2238 return err 2239 } 2240 2241 fmt.Fprintf(cli.out, "%s\n", createResult.Get("Id")) 2242 2243 return nil 2244 } 2245 2246 func (cli *DockerCli) CmdRun(args ...string) error { 2247 // FIXME: just use runconfig.Parse already 2248 cmd := cli.Subcmd("run", "IMAGE [COMMAND] [ARG...]", "Run a command in a new container", true) 2249 2250 // These are flags not stored in Config/HostConfig 2251 var ( 2252 flAutoRemove = cmd.Bool([]string{"#rm", "-rm"}, false, "Automatically remove the container when it exits (incompatible with -d)") 2253 flDetach = cmd.Bool([]string{"d", "-detach"}, false, "Detached mode: run the container in the background and print the new container ID") 2254 flSigProxy = cmd.Bool([]string{"#sig-proxy", "-sig-proxy"}, true, "Proxy received signals to the process (non-TTY mode only). SIGCHLD, SIGSTOP, and SIGKILL are not proxied.") 2255 flName = cmd.String([]string{"#name", "-name"}, "", "Assign a name to the container") 2256 flAttach *opts.ListOpts 2257 2258 ErrConflictAttachDetach = fmt.Errorf("Conflicting options: -a and -d") 2259 ErrConflictRestartPolicyAndAutoRemove = fmt.Errorf("Conflicting options: --restart and --rm") 2260 ErrConflictDetachAutoRemove = fmt.Errorf("Conflicting options: --rm and -d") 2261 ) 2262 2263 config, hostConfig, cmd, err := runconfig.Parse(cmd, args) 2264 // just in case the Parse does not exit 2265 if err != nil { 2266 return &utils.StatusError{StatusCode: 1} 2267 } 2268 if config.Image == "" { 2269 cmd.Usage() 2270 return nil 2271 } 2272 2273 if !*flDetach { 2274 if err := cli.CheckTtyInput(config.AttachStdin, config.Tty); err != nil { 2275 return err 2276 } 2277 } else { 2278 if fl := cmd.Lookup("attach"); fl != nil { 2279 flAttach = fl.Value.(*opts.ListOpts) 2280 if flAttach.Len() != 0 { 2281 return ErrConflictAttachDetach 2282 } 2283 } 2284 if *flAutoRemove { 2285 return ErrConflictDetachAutoRemove 2286 } 2287 2288 config.AttachStdin = false 2289 config.AttachStdout = false 2290 config.AttachStderr = false 2291 config.StdinOnce = false 2292 } 2293 2294 // Disable flSigProxy when in TTY mode 2295 sigProxy := *flSigProxy 2296 if config.Tty { 2297 sigProxy = false 2298 } 2299 2300 runResult, err := cli.createContainer(config, hostConfig, hostConfig.ContainerIDFile, *flName) 2301 if err != nil { 2302 return err 2303 } 2304 2305 if sigProxy { 2306 sigc := cli.forwardAllSignals(runResult.Get("Id")) 2307 defer signal.StopCatch(sigc) 2308 } 2309 2310 var ( 2311 waitDisplayId chan struct{} 2312 errCh chan error 2313 ) 2314 2315 if !config.AttachStdout && !config.AttachStderr { 2316 // Make this asynchronous to allow the client to write to stdin before having to read the ID 2317 waitDisplayId = make(chan struct{}) 2318 go func() { 2319 defer close(waitDisplayId) 2320 fmt.Fprintf(cli.out, "%s\n", runResult.Get("Id")) 2321 }() 2322 } 2323 2324 if *flAutoRemove && (hostConfig.RestartPolicy.Name == "always" || hostConfig.RestartPolicy.Name == "on-failure") { 2325 return ErrConflictRestartPolicyAndAutoRemove 2326 } 2327 2328 // We need to instantiate the chan because the select needs it. It can 2329 // be closed but can't be uninitialized. 2330 hijacked := make(chan io.Closer) 2331 2332 // Block the return until the chan gets closed 2333 defer func() { 2334 log.Debugf("End of CmdRun(), Waiting for hijack to finish.") 2335 if _, ok := <-hijacked; ok { 2336 log.Errorf("Hijack did not finish (chan still open)") 2337 } 2338 }() 2339 2340 if config.AttachStdin || config.AttachStdout || config.AttachStderr { 2341 var ( 2342 out, stderr io.Writer 2343 in io.ReadCloser 2344 v = url.Values{} 2345 ) 2346 v.Set("stream", "1") 2347 2348 if config.AttachStdin { 2349 v.Set("stdin", "1") 2350 in = cli.in 2351 } 2352 if config.AttachStdout { 2353 v.Set("stdout", "1") 2354 out = cli.out 2355 } 2356 if config.AttachStderr { 2357 v.Set("stderr", "1") 2358 if config.Tty { 2359 stderr = cli.out 2360 } else { 2361 stderr = cli.err 2362 } 2363 } 2364 2365 errCh = promise.Go(func() error { 2366 return cli.hijack("POST", "/containers/"+runResult.Get("Id")+"/attach?"+v.Encode(), config.Tty, in, out, stderr, hijacked, nil) 2367 }) 2368 } else { 2369 close(hijacked) 2370 } 2371 2372 // Acknowledge the hijack before starting 2373 select { 2374 case closer := <-hijacked: 2375 // Make sure that the hijack gets closed when returning (results 2376 // in closing the hijack chan and freeing server's goroutines) 2377 if closer != nil { 2378 defer closer.Close() 2379 } 2380 case err := <-errCh: 2381 if err != nil { 2382 log.Debugf("Error hijack: %s", err) 2383 return err 2384 } 2385 } 2386 2387 //start the container 2388 if _, _, err = readBody(cli.call("POST", "/containers/"+runResult.Get("Id")+"/start", nil, false)); err != nil { 2389 return err 2390 } 2391 2392 if (config.AttachStdin || config.AttachStdout || config.AttachStderr) && config.Tty && cli.isTerminalOut { 2393 if err := cli.monitorTtySize(runResult.Get("Id"), false); err != nil { 2394 log.Errorf("Error monitoring TTY size: %s", err) 2395 } 2396 } 2397 2398 if errCh != nil { 2399 if err := <-errCh; err != nil { 2400 log.Debugf("Error hijack: %s", err) 2401 return err 2402 } 2403 } 2404 2405 // Detached mode: wait for the id to be displayed and return. 2406 if !config.AttachStdout && !config.AttachStderr { 2407 // Detached mode 2408 <-waitDisplayId 2409 return nil 2410 } 2411 2412 var status int 2413 2414 // Attached mode 2415 if *flAutoRemove { 2416 // Autoremove: wait for the container to finish, retrieve 2417 // the exit code and remove the container 2418 if _, _, err := readBody(cli.call("POST", "/containers/"+runResult.Get("Id")+"/wait", nil, false)); err != nil { 2419 return err 2420 } 2421 if _, status, err = getExitCode(cli, runResult.Get("Id")); err != nil { 2422 return err 2423 } 2424 if _, _, err := readBody(cli.call("DELETE", "/containers/"+runResult.Get("Id")+"?v=1", nil, false)); err != nil { 2425 return err 2426 } 2427 } else { 2428 // No Autoremove: Simply retrieve the exit code 2429 if !config.Tty { 2430 // In non-TTY mode, we can't detach, so we must wait for container exit 2431 if status, err = waitForExit(cli, runResult.Get("Id")); err != nil { 2432 return err 2433 } 2434 } else { 2435 // In TTY mode, there is a race: if the process dies too slowly, the state could 2436 // be updated after the getExitCode call and result in the wrong exit code being reported 2437 if _, status, err = getExitCode(cli, runResult.Get("Id")); err != nil { 2438 return err 2439 } 2440 } 2441 } 2442 if status != 0 { 2443 return &utils.StatusError{StatusCode: status} 2444 } 2445 return nil 2446 } 2447 2448 func (cli *DockerCli) CmdCp(args ...string) error { 2449 cmd := cli.Subcmd("cp", "CONTAINER:PATH HOSTPATH", "Copy files/folders from the PATH to the HOSTPATH", true) 2450 cmd.Require(flag.Exact, 2) 2451 2452 utils.ParseFlags(cmd, args, true) 2453 2454 var copyData engine.Env 2455 info := strings.Split(cmd.Arg(0), ":") 2456 2457 if len(info) != 2 { 2458 return fmt.Errorf("Error: Path not specified") 2459 } 2460 2461 copyData.Set("Resource", info[1]) 2462 copyData.Set("HostPath", cmd.Arg(1)) 2463 2464 stream, statusCode, err := cli.call("POST", "/containers/"+info[0]+"/copy", copyData, false) 2465 if stream != nil { 2466 defer stream.Close() 2467 } 2468 if statusCode == 404 { 2469 return fmt.Errorf("No such container: %v", info[0]) 2470 } 2471 if err != nil { 2472 return err 2473 } 2474 2475 if statusCode == 200 { 2476 if err := archive.Untar(stream, copyData.Get("HostPath"), &archive.TarOptions{NoLchown: true}); err != nil { 2477 return err 2478 } 2479 } 2480 return nil 2481 } 2482 2483 func (cli *DockerCli) CmdSave(args ...string) error { 2484 cmd := cli.Subcmd("save", "IMAGE [IMAGE...]", "Save an image(s) to a tar archive (streamed to STDOUT by default)", true) 2485 outfile := cmd.String([]string{"o", "-output"}, "", "Write to an file, instead of STDOUT") 2486 cmd.Require(flag.Min, 1) 2487 2488 utils.ParseFlags(cmd, args, true) 2489 2490 var ( 2491 output io.Writer = cli.out 2492 err error 2493 ) 2494 if *outfile != "" { 2495 output, err = os.Create(*outfile) 2496 if err != nil { 2497 return err 2498 } 2499 } else if cli.isTerminalOut { 2500 return errors.New("Cowardly refusing to save to a terminal. Use the -o flag or redirect.") 2501 } 2502 2503 if len(cmd.Args()) == 1 { 2504 image := cmd.Arg(0) 2505 if err := cli.stream("GET", "/images/"+image+"/get", nil, output, nil); err != nil { 2506 return err 2507 } 2508 } else { 2509 v := url.Values{} 2510 for _, arg := range cmd.Args() { 2511 v.Add("names", arg) 2512 } 2513 if err := cli.stream("GET", "/images/get?"+v.Encode(), nil, output, nil); err != nil { 2514 return err 2515 } 2516 } 2517 return nil 2518 } 2519 2520 func (cli *DockerCli) CmdLoad(args ...string) error { 2521 cmd := cli.Subcmd("load", "", "Load an image from a tar archive on STDIN", true) 2522 infile := cmd.String([]string{"i", "-input"}, "", "Read from a tar archive file, instead of STDIN") 2523 cmd.Require(flag.Exact, 0) 2524 2525 utils.ParseFlags(cmd, args, true) 2526 2527 var ( 2528 input io.Reader = cli.in 2529 err error 2530 ) 2531 if *infile != "" { 2532 input, err = os.Open(*infile) 2533 if err != nil { 2534 return err 2535 } 2536 } 2537 if err := cli.stream("POST", "/images/load", input, cli.out, nil); err != nil { 2538 return err 2539 } 2540 return nil 2541 } 2542 2543 func (cli *DockerCli) CmdExec(args ...string) error { 2544 cmd := cli.Subcmd("exec", "CONTAINER COMMAND [ARG...]", "Run a command in a running container", true) 2545 2546 execConfig, err := runconfig.ParseExec(cmd, args) 2547 // just in case the ParseExec does not exit 2548 if execConfig.Container == "" || err != nil { 2549 return &utils.StatusError{StatusCode: 1} 2550 } 2551 2552 stream, _, err := cli.call("POST", "/containers/"+execConfig.Container+"/exec", execConfig, false) 2553 if err != nil { 2554 return err 2555 } 2556 2557 var execResult engine.Env 2558 if err := execResult.Decode(stream); err != nil { 2559 return err 2560 } 2561 2562 execID := execResult.Get("Id") 2563 2564 if execID == "" { 2565 fmt.Fprintf(cli.out, "exec ID empty") 2566 return nil 2567 } 2568 2569 if !execConfig.Detach { 2570 if err := cli.CheckTtyInput(execConfig.AttachStdin, execConfig.Tty); err != nil { 2571 return err 2572 } 2573 } else { 2574 if _, _, err := readBody(cli.call("POST", "/exec/"+execID+"/start", execConfig, false)); err != nil { 2575 return err 2576 } 2577 // For now don't print this - wait for when we support exec wait() 2578 // fmt.Fprintf(cli.out, "%s\n", execID) 2579 return nil 2580 } 2581 2582 // Interactive exec requested. 2583 var ( 2584 out, stderr io.Writer 2585 in io.ReadCloser 2586 hijacked = make(chan io.Closer) 2587 errCh chan error 2588 ) 2589 2590 // Block the return until the chan gets closed 2591 defer func() { 2592 log.Debugf("End of CmdExec(), Waiting for hijack to finish.") 2593 if _, ok := <-hijacked; ok { 2594 log.Errorf("Hijack did not finish (chan still open)") 2595 } 2596 }() 2597 2598 if execConfig.AttachStdin { 2599 in = cli.in 2600 } 2601 if execConfig.AttachStdout { 2602 out = cli.out 2603 } 2604 if execConfig.AttachStderr { 2605 if execConfig.Tty { 2606 stderr = cli.out 2607 } else { 2608 stderr = cli.err 2609 } 2610 } 2611 errCh = promise.Go(func() error { 2612 return cli.hijack("POST", "/exec/"+execID+"/start", execConfig.Tty, in, out, stderr, hijacked, execConfig) 2613 }) 2614 2615 // Acknowledge the hijack before starting 2616 select { 2617 case closer := <-hijacked: 2618 // Make sure that hijack gets closed when returning. (result 2619 // in closing hijack chan and freeing server's goroutines. 2620 if closer != nil { 2621 defer closer.Close() 2622 } 2623 case err := <-errCh: 2624 if err != nil { 2625 log.Debugf("Error hijack: %s", err) 2626 return err 2627 } 2628 } 2629 2630 if execConfig.Tty && cli.isTerminalIn { 2631 if err := cli.monitorTtySize(execID, true); err != nil { 2632 log.Errorf("Error monitoring TTY size: %s", err) 2633 } 2634 } 2635 2636 if err := <-errCh; err != nil { 2637 log.Debugf("Error hijack: %s", err) 2638 return err 2639 } 2640 2641 var status int 2642 if _, status, err = getExecExitCode(cli, execID); err != nil { 2643 return err 2644 } 2645 2646 if status != 0 { 2647 return &utils.StatusError{StatusCode: status} 2648 } 2649 2650 return nil 2651 }