github.com/darciopacifico/docker@v1.9.0-rc1/integration-cli/docker_utils.go (about) 1 package main 2 3 import ( 4 "bufio" 5 "bytes" 6 "encoding/json" 7 "errors" 8 "fmt" 9 "io" 10 "io/ioutil" 11 "net" 12 "net/http" 13 "net/http/httptest" 14 "net/http/httputil" 15 "net/url" 16 "os" 17 "os/exec" 18 "path" 19 "path/filepath" 20 "strconv" 21 "strings" 22 "time" 23 24 "github.com/docker/docker/api/types" 25 "github.com/docker/docker/opts" 26 "github.com/docker/docker/pkg/httputils" 27 "github.com/docker/docker/pkg/integration" 28 "github.com/docker/docker/pkg/ioutils" 29 "github.com/docker/docker/pkg/stringutils" 30 "github.com/go-check/check" 31 ) 32 33 // Daemon represents a Docker daemon for the testing framework. 34 type Daemon struct { 35 // Defaults to "daemon" 36 // Useful to set to --daemon or -d for checking backwards compatibility 37 Command string 38 GlobalFlags []string 39 40 id string 41 c *check.C 42 logFile *os.File 43 folder string 44 root string 45 stdin io.WriteCloser 46 stdout, stderr io.ReadCloser 47 cmd *exec.Cmd 48 storageDriver string 49 execDriver string 50 wait chan error 51 userlandProxy bool 52 useDefaultHost bool 53 } 54 55 // NewDaemon returns a Daemon instance to be used for testing. 56 // This will create a directory such as d123456789 in the folder specified by $DEST. 57 // The daemon will not automatically start. 58 func NewDaemon(c *check.C) *Daemon { 59 dest := os.Getenv("DEST") 60 if dest == "" { 61 c.Fatal("Please set the DEST environment variable") 62 } 63 64 id := fmt.Sprintf("d%d", time.Now().UnixNano()%100000000) 65 dir := filepath.Join(dest, id) 66 daemonFolder, err := filepath.Abs(dir) 67 if err != nil { 68 c.Fatalf("Could not make %q an absolute path: %v", dir, err) 69 } 70 daemonRoot := filepath.Join(daemonFolder, "root") 71 72 if err := os.MkdirAll(daemonRoot, 0755); err != nil { 73 c.Fatalf("Could not create daemon root %q: %v", dir, err) 74 } 75 76 userlandProxy := true 77 if env := os.Getenv("DOCKER_USERLANDPROXY"); env != "" { 78 if val, err := strconv.ParseBool(env); err != nil { 79 userlandProxy = val 80 } 81 } 82 83 return &Daemon{ 84 Command: "daemon", 85 id: id, 86 c: c, 87 folder: daemonFolder, 88 root: daemonRoot, 89 storageDriver: os.Getenv("DOCKER_GRAPHDRIVER"), 90 execDriver: os.Getenv("DOCKER_EXECDRIVER"), 91 userlandProxy: userlandProxy, 92 } 93 } 94 95 // Start will start the daemon and return once it is ready to receive requests. 96 // You can specify additional daemon flags. 97 func (d *Daemon) Start(arg ...string) error { 98 dockerBinary, err := exec.LookPath(dockerBinary) 99 if err != nil { 100 d.c.Fatalf("[%s] could not find docker binary in $PATH: %v", d.id, err) 101 } 102 103 args := append(d.GlobalFlags, 104 d.Command, 105 "--graph", d.root, 106 "--pidfile", fmt.Sprintf("%s/docker.pid", d.folder), 107 fmt.Sprintf("--userland-proxy=%t", d.userlandProxy), 108 ) 109 if !d.useDefaultHost { 110 args = append(args, []string{"--host", d.sock()}...) 111 } 112 if root := os.Getenv("DOCKER_REMAP_ROOT"); root != "" { 113 args = append(args, []string{"--userns-remap", root}...) 114 } 115 116 // If we don't explicitly set the log-level or debug flag(-D) then 117 // turn on debug mode 118 foundIt := false 119 for _, a := range arg { 120 if strings.Contains(a, "--log-level") || strings.Contains(a, "-D") || strings.Contains(a, "--debug") { 121 foundIt = true 122 } 123 } 124 if !foundIt { 125 args = append(args, "--debug") 126 } 127 128 if d.storageDriver != "" { 129 args = append(args, "--storage-driver", d.storageDriver) 130 } 131 if d.execDriver != "" { 132 args = append(args, "--exec-driver", d.execDriver) 133 } 134 135 args = append(args, arg...) 136 d.cmd = exec.Command(dockerBinary, args...) 137 138 d.logFile, err = os.OpenFile(filepath.Join(d.folder, "docker.log"), os.O_RDWR|os.O_CREATE|os.O_APPEND, 0600) 139 if err != nil { 140 d.c.Fatalf("[%s] Could not create %s/docker.log: %v", d.id, d.folder, err) 141 } 142 143 d.cmd.Stdout = d.logFile 144 d.cmd.Stderr = d.logFile 145 146 if err := d.cmd.Start(); err != nil { 147 return fmt.Errorf("[%s] could not start daemon container: %v", d.id, err) 148 } 149 150 wait := make(chan error) 151 152 go func() { 153 wait <- d.cmd.Wait() 154 d.c.Logf("[%s] exiting daemon", d.id) 155 close(wait) 156 }() 157 158 d.wait = wait 159 160 tick := time.Tick(500 * time.Millisecond) 161 // make sure daemon is ready to receive requests 162 startTime := time.Now().Unix() 163 for { 164 d.c.Logf("[%s] waiting for daemon to start", d.id) 165 if time.Now().Unix()-startTime > 5 { 166 // After 5 seconds, give up 167 return fmt.Errorf("[%s] Daemon exited and never started", d.id) 168 } 169 select { 170 case <-time.After(2 * time.Second): 171 return fmt.Errorf("[%s] timeout: daemon does not respond", d.id) 172 case <-tick: 173 var ( 174 c net.Conn 175 err error 176 ) 177 if d.useDefaultHost { 178 c, err = net.Dial("unix", "/var/run/docker.sock") 179 } else { 180 c, err = net.Dial("unix", filepath.Join(d.folder, "docker.sock")) 181 } 182 if err != nil { 183 continue 184 } 185 186 client := httputil.NewClientConn(c, nil) 187 defer client.Close() 188 189 req, err := http.NewRequest("GET", "/_ping", nil) 190 if err != nil { 191 d.c.Fatalf("[%s] could not create new request: %v", d.id, err) 192 } 193 194 resp, err := client.Do(req) 195 if err != nil { 196 continue 197 } 198 if resp.StatusCode != http.StatusOK { 199 d.c.Logf("[%s] received status != 200 OK: %s", d.id, resp.Status) 200 } 201 d.c.Logf("[%s] daemon started", d.id) 202 d.root, err = d.queryRootDir() 203 if err != nil { 204 return fmt.Errorf("[%s] error querying daemon for root directory: %v", d.id, err) 205 } 206 return nil 207 } 208 } 209 } 210 211 // StartWithBusybox will first start the daemon with Daemon.Start() 212 // then save the busybox image from the main daemon and load it into this Daemon instance. 213 func (d *Daemon) StartWithBusybox(arg ...string) error { 214 if err := d.Start(arg...); err != nil { 215 return err 216 } 217 bb := filepath.Join(d.folder, "busybox.tar") 218 if _, err := os.Stat(bb); err != nil { 219 if !os.IsNotExist(err) { 220 return fmt.Errorf("unexpected error on busybox.tar stat: %v", err) 221 } 222 // saving busybox image from main daemon 223 if err := exec.Command(dockerBinary, "save", "--output", bb, "busybox:latest").Run(); err != nil { 224 return fmt.Errorf("could not save busybox image: %v", err) 225 } 226 } 227 // loading busybox image to this daemon 228 if _, err := d.Cmd("load", "--input", bb); err != nil { 229 return fmt.Errorf("could not load busybox image: %v", err) 230 } 231 if err := os.Remove(bb); err != nil { 232 d.c.Logf("Could not remove %s: %v", bb, err) 233 } 234 return nil 235 } 236 237 // Stop will send a SIGINT every second and wait for the daemon to stop. 238 // If it timeouts, a SIGKILL is sent. 239 // Stop will not delete the daemon directory. If a purged daemon is needed, 240 // instantiate a new one with NewDaemon. 241 func (d *Daemon) Stop() error { 242 if d.cmd == nil || d.wait == nil { 243 return errors.New("daemon not started") 244 } 245 246 defer func() { 247 d.logFile.Close() 248 d.cmd = nil 249 }() 250 251 i := 1 252 tick := time.Tick(time.Second) 253 254 if err := d.cmd.Process.Signal(os.Interrupt); err != nil { 255 return fmt.Errorf("could not send signal: %v", err) 256 } 257 out1: 258 for { 259 select { 260 case err := <-d.wait: 261 return err 262 case <-time.After(15 * time.Second): 263 // time for stopping jobs and run onShutdown hooks 264 d.c.Log("timeout") 265 break out1 266 } 267 } 268 269 out2: 270 for { 271 select { 272 case err := <-d.wait: 273 return err 274 case <-tick: 275 i++ 276 if i > 4 { 277 d.c.Logf("tried to interrupt daemon for %d times, now try to kill it", i) 278 break out2 279 } 280 d.c.Logf("Attempt #%d: daemon is still running with pid %d", i, d.cmd.Process.Pid) 281 if err := d.cmd.Process.Signal(os.Interrupt); err != nil { 282 return fmt.Errorf("could not send signal: %v", err) 283 } 284 } 285 } 286 287 if err := d.cmd.Process.Kill(); err != nil { 288 d.c.Logf("Could not kill daemon: %v", err) 289 return err 290 } 291 292 return nil 293 } 294 295 // Restart will restart the daemon by first stopping it and then starting it. 296 func (d *Daemon) Restart(arg ...string) error { 297 d.Stop() 298 return d.Start(arg...) 299 } 300 301 func (d *Daemon) queryRootDir() (string, error) { 302 // update daemon root by asking /info endpoint (to support user 303 // namespaced daemon with root remapped uid.gid directory) 304 var ( 305 conn net.Conn 306 err error 307 ) 308 if d.useDefaultHost { 309 conn, err = net.Dial("unix", "/var/run/docker.sock") 310 } else { 311 conn, err = net.Dial("unix", filepath.Join(d.folder, "docker.sock")) 312 } 313 if err != nil { 314 return "", err 315 } 316 client := httputil.NewClientConn(conn, nil) 317 318 req, err := http.NewRequest("GET", "/info", nil) 319 if err != nil { 320 client.Close() 321 return "", err 322 } 323 req.Header.Set("Content-Type", "application/json") 324 325 resp, err := client.Do(req) 326 if err != nil { 327 client.Close() 328 return "", err 329 } 330 body := ioutils.NewReadCloserWrapper(resp.Body, func() error { 331 defer client.Close() 332 return resp.Body.Close() 333 }) 334 335 type Info struct { 336 DockerRootDir string 337 } 338 var b []byte 339 var i Info 340 b, err = readBody(body) 341 if err == nil && resp.StatusCode == 200 { 342 // read the docker root dir 343 if err = json.Unmarshal(b, &i); err == nil { 344 return i.DockerRootDir, nil 345 } 346 } 347 return "", err 348 } 349 350 func (d *Daemon) sock() string { 351 return fmt.Sprintf("unix://%s/docker.sock", d.folder) 352 } 353 354 // Cmd will execute a docker CLI command against this Daemon. 355 // Example: d.Cmd("version") will run docker -H unix://path/to/unix.sock version 356 func (d *Daemon) Cmd(name string, arg ...string) (string, error) { 357 args := []string{"--host", d.sock(), name} 358 args = append(args, arg...) 359 c := exec.Command(dockerBinary, args...) 360 b, err := c.CombinedOutput() 361 return string(b), err 362 } 363 364 // CmdWithArgs will execute a docker CLI command against a daemon with the 365 // given additional arguments 366 func (d *Daemon) CmdWithArgs(daemonArgs []string, name string, arg ...string) (string, error) { 367 args := append(daemonArgs, name) 368 args = append(args, arg...) 369 c := exec.Command(dockerBinary, args...) 370 b, err := c.CombinedOutput() 371 return string(b), err 372 } 373 374 // LogfileName returns the path the the daemon's log file 375 func (d *Daemon) LogfileName() string { 376 return d.logFile.Name() 377 } 378 379 func daemonHost() string { 380 daemonURLStr := "unix://" + opts.DefaultUnixSocket 381 if daemonHostVar := os.Getenv("DOCKER_HOST"); daemonHostVar != "" { 382 daemonURLStr = daemonHostVar 383 } 384 return daemonURLStr 385 } 386 387 func sockConn(timeout time.Duration) (net.Conn, error) { 388 daemon := daemonHost() 389 daemonURL, err := url.Parse(daemon) 390 if err != nil { 391 return nil, fmt.Errorf("could not parse url %q: %v", daemon, err) 392 } 393 394 var c net.Conn 395 switch daemonURL.Scheme { 396 case "unix": 397 return net.DialTimeout(daemonURL.Scheme, daemonURL.Path, timeout) 398 case "tcp": 399 return net.DialTimeout(daemonURL.Scheme, daemonURL.Host, timeout) 400 default: 401 return c, fmt.Errorf("unknown scheme %v (%s)", daemonURL.Scheme, daemon) 402 } 403 } 404 405 func sockRequest(method, endpoint string, data interface{}) (int, []byte, error) { 406 jsonData := bytes.NewBuffer(nil) 407 if err := json.NewEncoder(jsonData).Encode(data); err != nil { 408 return -1, nil, err 409 } 410 411 res, body, err := sockRequestRaw(method, endpoint, jsonData, "application/json") 412 if err != nil { 413 return -1, nil, err 414 } 415 b, err := readBody(body) 416 return res.StatusCode, b, err 417 } 418 419 func sockRequestRaw(method, endpoint string, data io.Reader, ct string) (*http.Response, io.ReadCloser, error) { 420 req, client, err := newRequestClient(method, endpoint, data, ct) 421 if err != nil { 422 return nil, nil, err 423 } 424 425 resp, err := client.Do(req) 426 if err != nil { 427 client.Close() 428 return nil, nil, err 429 } 430 body := ioutils.NewReadCloserWrapper(resp.Body, func() error { 431 defer resp.Body.Close() 432 return client.Close() 433 }) 434 435 return resp, body, nil 436 } 437 438 func sockRequestHijack(method, endpoint string, data io.Reader, ct string) (net.Conn, *bufio.Reader, error) { 439 req, client, err := newRequestClient(method, endpoint, data, ct) 440 if err != nil { 441 return nil, nil, err 442 } 443 444 client.Do(req) 445 conn, br := client.Hijack() 446 return conn, br, nil 447 } 448 449 func newRequestClient(method, endpoint string, data io.Reader, ct string) (*http.Request, *httputil.ClientConn, error) { 450 c, err := sockConn(time.Duration(10 * time.Second)) 451 if err != nil { 452 return nil, nil, fmt.Errorf("could not dial docker daemon: %v", err) 453 } 454 455 client := httputil.NewClientConn(c, nil) 456 457 req, err := http.NewRequest(method, endpoint, data) 458 if err != nil { 459 client.Close() 460 return nil, nil, fmt.Errorf("could not create new request: %v", err) 461 } 462 463 if ct != "" { 464 req.Header.Set("Content-Type", ct) 465 } 466 return req, client, nil 467 } 468 469 func readBody(b io.ReadCloser) ([]byte, error) { 470 defer b.Close() 471 return ioutil.ReadAll(b) 472 } 473 474 func deleteContainer(container string) error { 475 container = strings.TrimSpace(strings.Replace(container, "\n", " ", -1)) 476 rmArgs := strings.Split(fmt.Sprintf("rm -fv %v", container), " ") 477 exitCode, err := runCommand(exec.Command(dockerBinary, rmArgs...)) 478 // set error manually if not set 479 if exitCode != 0 && err == nil { 480 err = fmt.Errorf("failed to remove container: `docker rm` exit is non-zero") 481 } 482 483 return err 484 } 485 486 func getAllContainers() (string, error) { 487 getContainersCmd := exec.Command(dockerBinary, "ps", "-q", "-a") 488 out, exitCode, err := runCommandWithOutput(getContainersCmd) 489 if exitCode != 0 && err == nil { 490 err = fmt.Errorf("failed to get a list of containers: %v\n", out) 491 } 492 493 return out, err 494 } 495 496 func deleteAllContainers() error { 497 containers, err := getAllContainers() 498 if err != nil { 499 fmt.Println(containers) 500 return err 501 } 502 503 if err = deleteContainer(containers); err != nil { 504 return err 505 } 506 return nil 507 } 508 509 func deleteAllVolumes() error { 510 volumes, err := getAllVolumes() 511 if err != nil { 512 return err 513 } 514 var errors []string 515 for _, v := range volumes { 516 status, b, err := sockRequest("DELETE", "/volumes/"+v.Name, nil) 517 if err != nil { 518 errors = append(errors, err.Error()) 519 continue 520 } 521 if status != http.StatusNoContent { 522 errors = append(errors, fmt.Sprintf("error deleting volume %s: %s", v.Name, string(b))) 523 } 524 } 525 if len(errors) > 0 { 526 return fmt.Errorf(strings.Join(errors, "\n")) 527 } 528 return nil 529 } 530 531 func getAllVolumes() ([]*types.Volume, error) { 532 var volumes types.VolumesListResponse 533 _, b, err := sockRequest("GET", "/volumes", nil) 534 if err != nil { 535 return nil, err 536 } 537 if err := json.Unmarshal(b, &volumes); err != nil { 538 return nil, err 539 } 540 return volumes.Volumes, nil 541 } 542 543 var protectedImages = map[string]struct{}{} 544 545 func init() { 546 out, err := exec.Command(dockerBinary, "images").CombinedOutput() 547 if err != nil { 548 panic(err) 549 } 550 lines := strings.Split(string(out), "\n")[1:] 551 for _, l := range lines { 552 if l == "" { 553 continue 554 } 555 fields := strings.Fields(l) 556 imgTag := fields[0] + ":" + fields[1] 557 // just for case if we have dangling images in tested daemon 558 if imgTag != "<none>:<none>" { 559 protectedImages[imgTag] = struct{}{} 560 } 561 } 562 563 // Obtain the daemon platform so that it can be used by tests to make 564 // intelligent decisions about how to configure themselves, and validate 565 // that the target platform is valid. 566 res, _, err := sockRequestRaw("GET", "/version", nil, "application/json") 567 if err != nil || res == nil || (res != nil && res.StatusCode != http.StatusOK) { 568 panic(fmt.Errorf("Init failed to get version: %v. Res=%v", err.Error(), res)) 569 } 570 svrHeader, _ := httputils.ParseServerHeader(res.Header.Get("Server")) 571 daemonPlatform = svrHeader.OS 572 if daemonPlatform != "linux" && daemonPlatform != "windows" { 573 panic("Cannot run tests against platform: " + daemonPlatform) 574 } 575 } 576 577 func deleteAllImages() error { 578 out, err := exec.Command(dockerBinary, "images").CombinedOutput() 579 if err != nil { 580 return err 581 } 582 lines := strings.Split(string(out), "\n")[1:] 583 var imgs []string 584 for _, l := range lines { 585 if l == "" { 586 continue 587 } 588 fields := strings.Fields(l) 589 imgTag := fields[0] + ":" + fields[1] 590 if _, ok := protectedImages[imgTag]; !ok { 591 if fields[0] == "<none>" { 592 imgs = append(imgs, fields[2]) 593 continue 594 } 595 imgs = append(imgs, imgTag) 596 } 597 } 598 if len(imgs) == 0 { 599 return nil 600 } 601 args := append([]string{"rmi", "-f"}, imgs...) 602 if err := exec.Command(dockerBinary, args...).Run(); err != nil { 603 return err 604 } 605 return nil 606 } 607 608 func getPausedContainers() (string, error) { 609 getPausedContainersCmd := exec.Command(dockerBinary, "ps", "-f", "status=paused", "-q", "-a") 610 out, exitCode, err := runCommandWithOutput(getPausedContainersCmd) 611 if exitCode != 0 && err == nil { 612 err = fmt.Errorf("failed to get a list of paused containers: %v\n", out) 613 } 614 615 return out, err 616 } 617 618 func getSliceOfPausedContainers() ([]string, error) { 619 out, err := getPausedContainers() 620 if err == nil { 621 if len(out) == 0 { 622 return nil, err 623 } 624 slice := strings.Split(strings.TrimSpace(out), "\n") 625 return slice, err 626 } 627 return []string{out}, err 628 } 629 630 func unpauseContainer(container string) error { 631 unpauseCmd := exec.Command(dockerBinary, "unpause", container) 632 exitCode, err := runCommand(unpauseCmd) 633 if exitCode != 0 && err == nil { 634 err = fmt.Errorf("failed to unpause container") 635 } 636 637 return nil 638 } 639 640 func unpauseAllContainers() error { 641 containers, err := getPausedContainers() 642 if err != nil { 643 fmt.Println(containers) 644 return err 645 } 646 647 containers = strings.Replace(containers, "\n", " ", -1) 648 containers = strings.Trim(containers, " ") 649 containerList := strings.Split(containers, " ") 650 651 for _, value := range containerList { 652 if err = unpauseContainer(value); err != nil { 653 return err 654 } 655 } 656 657 return nil 658 } 659 660 func deleteImages(images ...string) error { 661 args := []string{"rmi", "-f"} 662 args = append(args, images...) 663 rmiCmd := exec.Command(dockerBinary, args...) 664 exitCode, err := runCommand(rmiCmd) 665 // set error manually if not set 666 if exitCode != 0 && err == nil { 667 err = fmt.Errorf("failed to remove image: `docker rmi` exit is non-zero") 668 } 669 return err 670 } 671 672 func imageExists(image string) error { 673 inspectCmd := exec.Command(dockerBinary, "inspect", image) 674 exitCode, err := runCommand(inspectCmd) 675 if exitCode != 0 && err == nil { 676 err = fmt.Errorf("couldn't find image %q", image) 677 } 678 return err 679 } 680 681 func pullImageIfNotExist(image string) error { 682 if err := imageExists(image); err != nil { 683 pullCmd := exec.Command(dockerBinary, "pull", image) 684 _, exitCode, err := runCommandWithOutput(pullCmd) 685 686 if err != nil || exitCode != 0 { 687 return fmt.Errorf("image %q wasn't found locally and it couldn't be pulled: %s", image, err) 688 } 689 } 690 return nil 691 } 692 693 func dockerCmdWithError(args ...string) (string, int, error) { 694 return integration.DockerCmdWithError(dockerBinary, args...) 695 } 696 697 func dockerCmdWithStdoutStderr(c *check.C, args ...string) (string, string, int) { 698 return integration.DockerCmdWithStdoutStderr(dockerBinary, c, args...) 699 } 700 701 func dockerCmd(c *check.C, args ...string) (string, int) { 702 return integration.DockerCmd(dockerBinary, c, args...) 703 } 704 705 // execute a docker command with a timeout 706 func dockerCmdWithTimeout(timeout time.Duration, args ...string) (string, int, error) { 707 return integration.DockerCmdWithTimeout(dockerBinary, timeout, args...) 708 } 709 710 // execute a docker command in a directory 711 func dockerCmdInDir(c *check.C, path string, args ...string) (string, int, error) { 712 return integration.DockerCmdInDir(dockerBinary, path, args...) 713 } 714 715 // execute a docker command in a directory with a timeout 716 func dockerCmdInDirWithTimeout(timeout time.Duration, path string, args ...string) (string, int, error) { 717 return integration.DockerCmdInDirWithTimeout(dockerBinary, timeout, path, args...) 718 } 719 720 func findContainerIP(c *check.C, id string, vargs ...string) string { 721 args := append(vargs, "inspect", "--format='{{ .NetworkSettings.IPAddress }}'", id) 722 cmd := exec.Command(dockerBinary, args...) 723 out, _, err := runCommandWithOutput(cmd) 724 if err != nil { 725 c.Fatal(err, out) 726 } 727 728 return strings.Trim(out, " \r\n'") 729 } 730 731 func (d *Daemon) findContainerIP(id string) string { 732 return findContainerIP(d.c, id, "--host", d.sock()) 733 } 734 735 func getContainerCount() (int, error) { 736 const containers = "Containers:" 737 738 cmd := exec.Command(dockerBinary, "info") 739 out, _, err := runCommandWithOutput(cmd) 740 if err != nil { 741 return 0, err 742 } 743 744 lines := strings.Split(out, "\n") 745 for _, line := range lines { 746 if strings.Contains(line, containers) { 747 output := strings.TrimSpace(line) 748 output = strings.TrimLeft(output, containers) 749 output = strings.Trim(output, " ") 750 containerCount, err := strconv.Atoi(output) 751 if err != nil { 752 return 0, err 753 } 754 return containerCount, nil 755 } 756 } 757 return 0, fmt.Errorf("couldn't find the Container count in the output") 758 } 759 760 // FakeContext creates directories that can be used as a build context 761 type FakeContext struct { 762 Dir string 763 } 764 765 // Add a file at a path, creating directories where necessary 766 func (f *FakeContext) Add(file, content string) error { 767 return f.addFile(file, []byte(content)) 768 } 769 770 func (f *FakeContext) addFile(file string, content []byte) error { 771 filepath := path.Join(f.Dir, file) 772 dirpath := path.Dir(filepath) 773 if dirpath != "." { 774 if err := os.MkdirAll(dirpath, 0755); err != nil { 775 return err 776 } 777 } 778 return ioutil.WriteFile(filepath, content, 0644) 779 780 } 781 782 // Delete a file at a path 783 func (f *FakeContext) Delete(file string) error { 784 filepath := path.Join(f.Dir, file) 785 return os.RemoveAll(filepath) 786 } 787 788 // Close deletes the context 789 func (f *FakeContext) Close() error { 790 return os.RemoveAll(f.Dir) 791 } 792 793 func fakeContextFromNewTempDir() (*FakeContext, error) { 794 tmp, err := ioutil.TempDir("", "fake-context") 795 if err != nil { 796 return nil, err 797 } 798 if err := os.Chmod(tmp, 0755); err != nil { 799 return nil, err 800 } 801 return fakeContextFromDir(tmp), nil 802 } 803 804 func fakeContextFromDir(dir string) *FakeContext { 805 return &FakeContext{dir} 806 } 807 808 func fakeContextWithFiles(files map[string]string) (*FakeContext, error) { 809 ctx, err := fakeContextFromNewTempDir() 810 if err != nil { 811 return nil, err 812 } 813 for file, content := range files { 814 if err := ctx.Add(file, content); err != nil { 815 ctx.Close() 816 return nil, err 817 } 818 } 819 return ctx, nil 820 } 821 822 func fakeContextAddDockerfile(ctx *FakeContext, dockerfile string) error { 823 if err := ctx.Add("Dockerfile", dockerfile); err != nil { 824 ctx.Close() 825 return err 826 } 827 return nil 828 } 829 830 func fakeContext(dockerfile string, files map[string]string) (*FakeContext, error) { 831 ctx, err := fakeContextWithFiles(files) 832 if err != nil { 833 return nil, err 834 } 835 if err := fakeContextAddDockerfile(ctx, dockerfile); err != nil { 836 return nil, err 837 } 838 return ctx, nil 839 } 840 841 // FakeStorage is a static file server. It might be running locally or remotely 842 // on test host. 843 type FakeStorage interface { 844 Close() error 845 URL() string 846 CtxDir() string 847 } 848 849 func fakeBinaryStorage(archives map[string]*bytes.Buffer) (FakeStorage, error) { 850 ctx, err := fakeContextFromNewTempDir() 851 if err != nil { 852 return nil, err 853 } 854 for name, content := range archives { 855 if err := ctx.addFile(name, content.Bytes()); err != nil { 856 return nil, err 857 } 858 } 859 return fakeStorageWithContext(ctx) 860 } 861 862 // fakeStorage returns either a local or remote (at daemon machine) file server 863 func fakeStorage(files map[string]string) (FakeStorage, error) { 864 ctx, err := fakeContextWithFiles(files) 865 if err != nil { 866 return nil, err 867 } 868 return fakeStorageWithContext(ctx) 869 } 870 871 // fakeStorageWithContext returns either a local or remote (at daemon machine) file server 872 func fakeStorageWithContext(ctx *FakeContext) (FakeStorage, error) { 873 if isLocalDaemon { 874 return newLocalFakeStorage(ctx) 875 } 876 return newRemoteFileServer(ctx) 877 } 878 879 // localFileStorage is a file storage on the running machine 880 type localFileStorage struct { 881 *FakeContext 882 *httptest.Server 883 } 884 885 func (s *localFileStorage) URL() string { 886 return s.Server.URL 887 } 888 889 func (s *localFileStorage) CtxDir() string { 890 return s.FakeContext.Dir 891 } 892 893 func (s *localFileStorage) Close() error { 894 defer s.Server.Close() 895 return s.FakeContext.Close() 896 } 897 898 func newLocalFakeStorage(ctx *FakeContext) (*localFileStorage, error) { 899 handler := http.FileServer(http.Dir(ctx.Dir)) 900 server := httptest.NewServer(handler) 901 return &localFileStorage{ 902 FakeContext: ctx, 903 Server: server, 904 }, nil 905 } 906 907 // remoteFileServer is a containerized static file server started on the remote 908 // testing machine to be used in URL-accepting docker build functionality. 909 type remoteFileServer struct { 910 host string // hostname/port web server is listening to on docker host e.g. 0.0.0.0:43712 911 container string 912 image string 913 ctx *FakeContext 914 } 915 916 func (f *remoteFileServer) URL() string { 917 u := url.URL{ 918 Scheme: "http", 919 Host: f.host} 920 return u.String() 921 } 922 923 func (f *remoteFileServer) CtxDir() string { 924 return f.ctx.Dir 925 } 926 927 func (f *remoteFileServer) Close() error { 928 defer func() { 929 if f.ctx != nil { 930 f.ctx.Close() 931 } 932 if f.image != "" { 933 deleteImages(f.image) 934 } 935 }() 936 if f.container == "" { 937 return nil 938 } 939 return deleteContainer(f.container) 940 } 941 942 func newRemoteFileServer(ctx *FakeContext) (*remoteFileServer, error) { 943 var ( 944 image = fmt.Sprintf("fileserver-img-%s", strings.ToLower(stringutils.GenerateRandomAlphaOnlyString(10))) 945 container = fmt.Sprintf("fileserver-cnt-%s", strings.ToLower(stringutils.GenerateRandomAlphaOnlyString(10))) 946 ) 947 948 // Build the image 949 if err := fakeContextAddDockerfile(ctx, `FROM httpserver 950 COPY . /static`); err != nil { 951 return nil, fmt.Errorf("Cannot add Dockerfile to context: %v", err) 952 } 953 if _, err := buildImageFromContext(image, ctx, false); err != nil { 954 return nil, fmt.Errorf("failed building file storage container image: %v", err) 955 } 956 957 // Start the container 958 runCmd := exec.Command(dockerBinary, "run", "-d", "-P", "--name", container, image) 959 if out, ec, err := runCommandWithOutput(runCmd); err != nil { 960 return nil, fmt.Errorf("failed to start file storage container. ec=%v\nout=%s\nerr=%v", ec, out, err) 961 } 962 963 // Find out the system assigned port 964 out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "port", container, "80/tcp")) 965 if err != nil { 966 return nil, fmt.Errorf("failed to find container port: err=%v\nout=%s", err, out) 967 } 968 969 fileserverHostPort := strings.Trim(out, "\n") 970 _, port, err := net.SplitHostPort(fileserverHostPort) 971 if err != nil { 972 return nil, fmt.Errorf("unable to parse file server host:port: %v", err) 973 } 974 975 dockerHostURL, err := url.Parse(daemonHost()) 976 if err != nil { 977 return nil, fmt.Errorf("unable to parse daemon host URL: %v", err) 978 } 979 980 host, _, err := net.SplitHostPort(dockerHostURL.Host) 981 if err != nil { 982 return nil, fmt.Errorf("unable to parse docker daemon host:port: %v", err) 983 } 984 985 return &remoteFileServer{ 986 container: container, 987 image: image, 988 host: fmt.Sprintf("%s:%s", host, port), 989 ctx: ctx}, nil 990 } 991 992 func inspectFieldAndMarshall(name, field string, output interface{}) error { 993 str, err := inspectFieldJSON(name, field) 994 if err != nil { 995 return err 996 } 997 998 return json.Unmarshal([]byte(str), output) 999 } 1000 1001 func inspectFilter(name, filter string) (string, error) { 1002 format := fmt.Sprintf("{{%s}}", filter) 1003 inspectCmd := exec.Command(dockerBinary, "inspect", "-f", format, name) 1004 out, exitCode, err := runCommandWithOutput(inspectCmd) 1005 if err != nil || exitCode != 0 { 1006 return "", fmt.Errorf("failed to inspect container %s: %s", name, out) 1007 } 1008 return strings.TrimSpace(out), nil 1009 } 1010 1011 func inspectField(name, field string) (string, error) { 1012 return inspectFilter(name, fmt.Sprintf(".%s", field)) 1013 } 1014 1015 func inspectFieldJSON(name, field string) (string, error) { 1016 return inspectFilter(name, fmt.Sprintf("json .%s", field)) 1017 } 1018 1019 func inspectFieldMap(name, path, field string) (string, error) { 1020 return inspectFilter(name, fmt.Sprintf("index .%s %q", path, field)) 1021 } 1022 1023 func inspectMountSourceField(name, destination string) (string, error) { 1024 m, err := inspectMountPoint(name, destination) 1025 if err != nil { 1026 return "", err 1027 } 1028 return m.Source, nil 1029 } 1030 1031 func inspectMountPoint(name, destination string) (types.MountPoint, error) { 1032 out, err := inspectFieldJSON(name, "Mounts") 1033 if err != nil { 1034 return types.MountPoint{}, err 1035 } 1036 1037 return inspectMountPointJSON(out, destination) 1038 } 1039 1040 var errMountNotFound = errors.New("mount point not found") 1041 1042 func inspectMountPointJSON(j, destination string) (types.MountPoint, error) { 1043 var mp []types.MountPoint 1044 if err := unmarshalJSON([]byte(j), &mp); err != nil { 1045 return types.MountPoint{}, err 1046 } 1047 1048 var m *types.MountPoint 1049 for _, c := range mp { 1050 if c.Destination == destination { 1051 m = &c 1052 break 1053 } 1054 } 1055 1056 if m == nil { 1057 return types.MountPoint{}, errMountNotFound 1058 } 1059 1060 return *m, nil 1061 } 1062 1063 func getIDByName(name string) (string, error) { 1064 return inspectField(name, "Id") 1065 } 1066 1067 // getContainerState returns the exit code of the container 1068 // and true if it's running 1069 // the exit code should be ignored if it's running 1070 func getContainerState(c *check.C, id string) (int, bool, error) { 1071 var ( 1072 exitStatus int 1073 running bool 1074 ) 1075 out, exitCode := dockerCmd(c, "inspect", "--format={{.State.Running}} {{.State.ExitCode}}", id) 1076 if exitCode != 0 { 1077 return 0, false, fmt.Errorf("%q doesn't exist: %s", id, out) 1078 } 1079 1080 out = strings.Trim(out, "\n") 1081 splitOutput := strings.Split(out, " ") 1082 if len(splitOutput) != 2 { 1083 return 0, false, fmt.Errorf("failed to get container state: output is broken") 1084 } 1085 if splitOutput[0] == "true" { 1086 running = true 1087 } 1088 if n, err := strconv.Atoi(splitOutput[1]); err == nil { 1089 exitStatus = n 1090 } else { 1091 return 0, false, fmt.Errorf("failed to get container state: couldn't parse integer") 1092 } 1093 1094 return exitStatus, running, nil 1095 } 1096 1097 func buildImageCmd(name, dockerfile string, useCache bool, buildFlags ...string) *exec.Cmd { 1098 args := []string{"-D", "build", "-t", name} 1099 if !useCache { 1100 args = append(args, "--no-cache") 1101 } 1102 args = append(args, buildFlags...) 1103 args = append(args, "-") 1104 buildCmd := exec.Command(dockerBinary, args...) 1105 buildCmd.Stdin = strings.NewReader(dockerfile) 1106 return buildCmd 1107 1108 } 1109 1110 func buildImageWithOut(name, dockerfile string, useCache bool, buildFlags ...string) (string, string, error) { 1111 buildCmd := buildImageCmd(name, dockerfile, useCache, buildFlags...) 1112 out, exitCode, err := runCommandWithOutput(buildCmd) 1113 if err != nil || exitCode != 0 { 1114 return "", out, fmt.Errorf("failed to build the image: %s", out) 1115 } 1116 id, err := getIDByName(name) 1117 if err != nil { 1118 return "", out, err 1119 } 1120 return id, out, nil 1121 } 1122 1123 func buildImageWithStdoutStderr(name, dockerfile string, useCache bool, buildFlags ...string) (string, string, string, error) { 1124 buildCmd := buildImageCmd(name, dockerfile, useCache, buildFlags...) 1125 stdout, stderr, exitCode, err := runCommandWithStdoutStderr(buildCmd) 1126 if err != nil || exitCode != 0 { 1127 return "", stdout, stderr, fmt.Errorf("failed to build the image: %s", stdout) 1128 } 1129 id, err := getIDByName(name) 1130 if err != nil { 1131 return "", stdout, stderr, err 1132 } 1133 return id, stdout, stderr, nil 1134 } 1135 1136 func buildImage(name, dockerfile string, useCache bool, buildFlags ...string) (string, error) { 1137 id, _, err := buildImageWithOut(name, dockerfile, useCache, buildFlags...) 1138 return id, err 1139 } 1140 1141 func buildImageFromContext(name string, ctx *FakeContext, useCache bool, buildFlags ...string) (string, error) { 1142 args := []string{"build", "-t", name} 1143 if !useCache { 1144 args = append(args, "--no-cache") 1145 } 1146 args = append(args, buildFlags...) 1147 args = append(args, ".") 1148 buildCmd := exec.Command(dockerBinary, args...) 1149 buildCmd.Dir = ctx.Dir 1150 out, exitCode, err := runCommandWithOutput(buildCmd) 1151 if err != nil || exitCode != 0 { 1152 return "", fmt.Errorf("failed to build the image: %s", out) 1153 } 1154 return getIDByName(name) 1155 } 1156 1157 func buildImageFromPath(name, path string, useCache bool, buildFlags ...string) (string, error) { 1158 args := []string{"build", "-t", name} 1159 if !useCache { 1160 args = append(args, "--no-cache") 1161 } 1162 args = append(args, buildFlags...) 1163 args = append(args, path) 1164 buildCmd := exec.Command(dockerBinary, args...) 1165 out, exitCode, err := runCommandWithOutput(buildCmd) 1166 if err != nil || exitCode != 0 { 1167 return "", fmt.Errorf("failed to build the image: %s", out) 1168 } 1169 return getIDByName(name) 1170 } 1171 1172 type gitServer interface { 1173 URL() string 1174 Close() error 1175 } 1176 1177 type localGitServer struct { 1178 *httptest.Server 1179 } 1180 1181 func (r *localGitServer) Close() error { 1182 r.Server.Close() 1183 return nil 1184 } 1185 1186 func (r *localGitServer) URL() string { 1187 return r.Server.URL 1188 } 1189 1190 type fakeGit struct { 1191 root string 1192 server gitServer 1193 RepoURL string 1194 } 1195 1196 func (g *fakeGit) Close() { 1197 g.server.Close() 1198 os.RemoveAll(g.root) 1199 } 1200 1201 func newFakeGit(name string, files map[string]string, enforceLocalServer bool) (*fakeGit, error) { 1202 ctx, err := fakeContextWithFiles(files) 1203 if err != nil { 1204 return nil, err 1205 } 1206 defer ctx.Close() 1207 curdir, err := os.Getwd() 1208 if err != nil { 1209 return nil, err 1210 } 1211 defer os.Chdir(curdir) 1212 1213 if output, err := exec.Command("git", "init", ctx.Dir).CombinedOutput(); err != nil { 1214 return nil, fmt.Errorf("error trying to init repo: %s (%s)", err, output) 1215 } 1216 err = os.Chdir(ctx.Dir) 1217 if err != nil { 1218 return nil, err 1219 } 1220 if output, err := exec.Command("git", "config", "user.name", "Fake User").CombinedOutput(); err != nil { 1221 return nil, fmt.Errorf("error trying to set 'user.name': %s (%s)", err, output) 1222 } 1223 if output, err := exec.Command("git", "config", "user.email", "fake.user@example.com").CombinedOutput(); err != nil { 1224 return nil, fmt.Errorf("error trying to set 'user.email': %s (%s)", err, output) 1225 } 1226 if output, err := exec.Command("git", "add", "*").CombinedOutput(); err != nil { 1227 return nil, fmt.Errorf("error trying to add files to repo: %s (%s)", err, output) 1228 } 1229 if output, err := exec.Command("git", "commit", "-a", "-m", "Initial commit").CombinedOutput(); err != nil { 1230 return nil, fmt.Errorf("error trying to commit to repo: %s (%s)", err, output) 1231 } 1232 1233 root, err := ioutil.TempDir("", "docker-test-git-repo") 1234 if err != nil { 1235 return nil, err 1236 } 1237 repoPath := filepath.Join(root, name+".git") 1238 if output, err := exec.Command("git", "clone", "--bare", ctx.Dir, repoPath).CombinedOutput(); err != nil { 1239 os.RemoveAll(root) 1240 return nil, fmt.Errorf("error trying to clone --bare: %s (%s)", err, output) 1241 } 1242 err = os.Chdir(repoPath) 1243 if err != nil { 1244 os.RemoveAll(root) 1245 return nil, err 1246 } 1247 if output, err := exec.Command("git", "update-server-info").CombinedOutput(); err != nil { 1248 os.RemoveAll(root) 1249 return nil, fmt.Errorf("error trying to git update-server-info: %s (%s)", err, output) 1250 } 1251 err = os.Chdir(curdir) 1252 if err != nil { 1253 os.RemoveAll(root) 1254 return nil, err 1255 } 1256 1257 var server gitServer 1258 if !enforceLocalServer { 1259 // use fakeStorage server, which might be local or remote (at test daemon) 1260 server, err = fakeStorageWithContext(fakeContextFromDir(root)) 1261 if err != nil { 1262 return nil, fmt.Errorf("cannot start fake storage: %v", err) 1263 } 1264 } else { 1265 // always start a local http server on CLI test machin 1266 httpServer := httptest.NewServer(http.FileServer(http.Dir(root))) 1267 server = &localGitServer{httpServer} 1268 } 1269 return &fakeGit{ 1270 root: root, 1271 server: server, 1272 RepoURL: fmt.Sprintf("%s/%s.git", server.URL(), name), 1273 }, nil 1274 } 1275 1276 // Write `content` to the file at path `dst`, creating it if necessary, 1277 // as well as any missing directories. 1278 // The file is truncated if it already exists. 1279 // Call c.Fatal() at the first error. 1280 func writeFile(dst, content string, c *check.C) { 1281 // Create subdirectories if necessary 1282 if err := os.MkdirAll(path.Dir(dst), 0700); err != nil { 1283 c.Fatal(err) 1284 } 1285 f, err := os.OpenFile(dst, os.O_CREATE|os.O_RDWR|os.O_TRUNC, 0700) 1286 if err != nil { 1287 c.Fatal(err) 1288 } 1289 defer f.Close() 1290 // Write content (truncate if it exists) 1291 if _, err := io.Copy(f, strings.NewReader(content)); err != nil { 1292 c.Fatal(err) 1293 } 1294 } 1295 1296 // Return the contents of file at path `src`. 1297 // Call c.Fatal() at the first error (including if the file doesn't exist) 1298 func readFile(src string, c *check.C) (content string) { 1299 data, err := ioutil.ReadFile(src) 1300 if err != nil { 1301 c.Fatal(err) 1302 } 1303 1304 return string(data) 1305 } 1306 1307 func containerStorageFile(containerID, basename string) string { 1308 return filepath.Join(containerStoragePath, containerID, basename) 1309 } 1310 1311 // docker commands that use this function must be run with the '-d' switch. 1312 func runCommandAndReadContainerFile(filename string, cmd *exec.Cmd) ([]byte, error) { 1313 out, _, err := runCommandWithOutput(cmd) 1314 if err != nil { 1315 return nil, fmt.Errorf("%v: %q", err, out) 1316 } 1317 1318 contID := strings.TrimSpace(out) 1319 1320 if err := waitRun(contID); err != nil { 1321 return nil, fmt.Errorf("%v: %q", contID, err) 1322 } 1323 1324 return readContainerFile(contID, filename) 1325 } 1326 1327 func readContainerFile(containerID, filename string) ([]byte, error) { 1328 f, err := os.Open(containerStorageFile(containerID, filename)) 1329 if err != nil { 1330 return nil, err 1331 } 1332 defer f.Close() 1333 1334 content, err := ioutil.ReadAll(f) 1335 if err != nil { 1336 return nil, err 1337 } 1338 1339 return content, nil 1340 } 1341 1342 func readContainerFileWithExec(containerID, filename string) ([]byte, error) { 1343 out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "exec", containerID, "cat", filename)) 1344 return []byte(out), err 1345 } 1346 1347 // daemonTime provides the current time on the daemon host 1348 func daemonTime(c *check.C) time.Time { 1349 if isLocalDaemon { 1350 return time.Now() 1351 } 1352 1353 status, body, err := sockRequest("GET", "/info", nil) 1354 c.Assert(status, check.Equals, http.StatusOK) 1355 c.Assert(err, check.IsNil) 1356 1357 type infoJSON struct { 1358 SystemTime string 1359 } 1360 var info infoJSON 1361 if err = json.Unmarshal(body, &info); err != nil { 1362 c.Fatalf("unable to unmarshal /info response: %v", err) 1363 } 1364 1365 dt, err := time.Parse(time.RFC3339Nano, info.SystemTime) 1366 if err != nil { 1367 c.Fatal(err) 1368 } 1369 return dt 1370 } 1371 1372 func setupRegistry(c *check.C) *testRegistryV2 { 1373 testRequires(c, RegistryHosting) 1374 reg, err := newTestRegistryV2(c) 1375 if err != nil { 1376 c.Fatal(err) 1377 } 1378 1379 // Wait for registry to be ready to serve requests. 1380 for i := 0; i != 5; i++ { 1381 if err = reg.Ping(); err == nil { 1382 break 1383 } 1384 time.Sleep(100 * time.Millisecond) 1385 } 1386 1387 if err != nil { 1388 c.Fatal("Timeout waiting for test registry to become available") 1389 } 1390 return reg 1391 } 1392 1393 func setupNotary(c *check.C) *testNotary { 1394 testRequires(c, NotaryHosting) 1395 ts, err := newTestNotary(c) 1396 if err != nil { 1397 c.Fatal(err) 1398 } 1399 1400 return ts 1401 } 1402 1403 // appendBaseEnv appends the minimum set of environment variables to exec the 1404 // docker cli binary for testing with correct configuration to the given env 1405 // list. 1406 func appendBaseEnv(env []string) []string { 1407 preserveList := []string{ 1408 // preserve remote test host 1409 "DOCKER_HOST", 1410 1411 // windows: requires preserving SystemRoot, otherwise dial tcp fails 1412 // with "GetAddrInfoW: A non-recoverable error occurred during a database lookup." 1413 "SystemRoot", 1414 } 1415 1416 for _, key := range preserveList { 1417 if val := os.Getenv(key); val != "" { 1418 env = append(env, fmt.Sprintf("%s=%s", key, val)) 1419 } 1420 } 1421 return env 1422 } 1423 1424 func createTmpFile(c *check.C, content string) string { 1425 f, err := ioutil.TempFile("", "testfile") 1426 c.Assert(err, check.IsNil) 1427 1428 filename := f.Name() 1429 1430 err = ioutil.WriteFile(filename, []byte(content), 0644) 1431 c.Assert(err, check.IsNil) 1432 1433 return filename 1434 } 1435 1436 func buildImageWithOutInDamon(socket string, name, dockerfile string, useCache bool) (string, error) { 1437 args := []string{"--host", socket} 1438 buildCmd := buildImageCmdArgs(args, name, dockerfile, useCache) 1439 out, exitCode, err := runCommandWithOutput(buildCmd) 1440 if err != nil || exitCode != 0 { 1441 return out, fmt.Errorf("failed to build the image: %s, error: %v", out, err) 1442 } 1443 return out, nil 1444 } 1445 1446 func buildImageCmdArgs(args []string, name, dockerfile string, useCache bool) *exec.Cmd { 1447 args = append(args, []string{"-D", "build", "-t", name}...) 1448 if !useCache { 1449 args = append(args, "--no-cache") 1450 } 1451 args = append(args, "-") 1452 buildCmd := exec.Command(dockerBinary, args...) 1453 buildCmd.Stdin = strings.NewReader(dockerfile) 1454 return buildCmd 1455 1456 } 1457 1458 func waitForContainer(contID string, args ...string) error { 1459 args = append([]string{"run", "--name", contID}, args...) 1460 cmd := exec.Command(dockerBinary, args...) 1461 if _, err := runCommand(cmd); err != nil { 1462 return err 1463 } 1464 1465 if err := waitRun(contID); err != nil { 1466 return err 1467 } 1468 1469 return nil 1470 } 1471 1472 // waitRun will wait for the specified container to be running, maximum 5 seconds. 1473 func waitRun(contID string) error { 1474 return waitInspect(contID, "{{.State.Running}}", "true", 5*time.Second) 1475 } 1476 1477 // waitExited will wait for the specified container to state exit, subject 1478 // to a maximum time limit in seconds supplied by the caller 1479 func waitExited(contID string, duration time.Duration) error { 1480 return waitInspect(contID, "{{.State.Status}}", "exited", duration) 1481 } 1482 1483 // waitInspect will wait for the specified container to have the specified string 1484 // in the inspect output. It will wait until the specified timeout (in seconds) 1485 // is reached. 1486 func waitInspect(name, expr, expected string, timeout time.Duration) error { 1487 after := time.After(timeout) 1488 1489 for { 1490 cmd := exec.Command(dockerBinary, "inspect", "-f", expr, name) 1491 out, _, err := runCommandWithOutput(cmd) 1492 if err != nil { 1493 if !strings.Contains(out, "No such") { 1494 return fmt.Errorf("error executing docker inspect: %v\n%s", err, out) 1495 } 1496 select { 1497 case <-after: 1498 return err 1499 default: 1500 time.Sleep(10 * time.Millisecond) 1501 continue 1502 } 1503 } 1504 1505 out = strings.TrimSpace(out) 1506 if out == expected { 1507 break 1508 } 1509 1510 select { 1511 case <-after: 1512 return fmt.Errorf("condition \"%q == %q\" not true in time", out, expected) 1513 default: 1514 } 1515 1516 time.Sleep(100 * time.Millisecond) 1517 } 1518 return nil 1519 }