github.com/guilhermebr/docker@v1.4.2-0.20150428121140-67da055cebca/integration-cli/docker_utils.go (about) 1 package main 2 3 import ( 4 "bytes" 5 "encoding/json" 6 "errors" 7 "fmt" 8 "io" 9 "io/ioutil" 10 "net" 11 "net/http" 12 "net/http/httptest" 13 "net/http/httputil" 14 "net/url" 15 "os" 16 "os/exec" 17 "path" 18 "path/filepath" 19 "strconv" 20 "strings" 21 "time" 22 23 "github.com/docker/docker/opts" 24 "github.com/docker/docker/pkg/ioutils" 25 "github.com/docker/docker/pkg/stringutils" 26 "github.com/go-check/check" 27 ) 28 29 // Daemon represents a Docker daemon for the testing framework. 30 type Daemon struct { 31 c *check.C 32 logFile *os.File 33 folder string 34 stdin io.WriteCloser 35 stdout, stderr io.ReadCloser 36 cmd *exec.Cmd 37 storageDriver string 38 execDriver string 39 wait chan error 40 } 41 42 // NewDaemon returns a Daemon instance to be used for testing. 43 // This will create a directory such as daemon123456789 in the folder specified by $DEST. 44 // The daemon will not automatically start. 45 func NewDaemon(c *check.C) *Daemon { 46 dest := os.Getenv("DEST") 47 if dest == "" { 48 c.Fatal("Please set the DEST environment variable") 49 } 50 51 dir := filepath.Join(dest, fmt.Sprintf("daemon%d", time.Now().UnixNano()%100000000)) 52 daemonFolder, err := filepath.Abs(dir) 53 if err != nil { 54 c.Fatalf("Could not make %q an absolute path: %v", dir, err) 55 } 56 57 if err := os.MkdirAll(filepath.Join(daemonFolder, "graph"), 0600); err != nil { 58 c.Fatalf("Could not create %s/graph directory", daemonFolder) 59 } 60 61 return &Daemon{ 62 c: c, 63 folder: daemonFolder, 64 storageDriver: os.Getenv("DOCKER_GRAPHDRIVER"), 65 execDriver: os.Getenv("DOCKER_EXECDRIVER"), 66 } 67 } 68 69 // Start will start the daemon and return once it is ready to receive requests. 70 // You can specify additional daemon flags. 71 func (d *Daemon) Start(arg ...string) error { 72 dockerBinary, err := exec.LookPath(dockerBinary) 73 if err != nil { 74 d.c.Fatalf("could not find docker binary in $PATH: %v", err) 75 } 76 77 args := []string{ 78 "--host", d.sock(), 79 "--daemon", 80 "--graph", fmt.Sprintf("%s/graph", d.folder), 81 "--pidfile", fmt.Sprintf("%s/docker.pid", d.folder), 82 } 83 84 // If we don't explicitly set the log-level or debug flag(-D) then 85 // turn on debug mode 86 foundIt := false 87 for _, a := range arg { 88 if strings.Contains(a, "--log-level") || strings.Contains(a, "-D") { 89 foundIt = true 90 } 91 } 92 if !foundIt { 93 args = append(args, "--debug") 94 } 95 96 if d.storageDriver != "" { 97 args = append(args, "--storage-driver", d.storageDriver) 98 } 99 if d.execDriver != "" { 100 args = append(args, "--exec-driver", d.execDriver) 101 } 102 103 args = append(args, arg...) 104 d.cmd = exec.Command(dockerBinary, args...) 105 106 d.logFile, err = os.OpenFile(filepath.Join(d.folder, "docker.log"), os.O_RDWR|os.O_CREATE|os.O_APPEND, 0600) 107 if err != nil { 108 d.c.Fatalf("Could not create %s/docker.log: %v", d.folder, err) 109 } 110 111 d.cmd.Stdout = d.logFile 112 d.cmd.Stderr = d.logFile 113 114 if err := d.cmd.Start(); err != nil { 115 return fmt.Errorf("could not start daemon container: %v", err) 116 } 117 118 wait := make(chan error) 119 120 go func() { 121 wait <- d.cmd.Wait() 122 d.c.Log("exiting daemon") 123 close(wait) 124 }() 125 126 d.wait = wait 127 128 tick := time.Tick(500 * time.Millisecond) 129 // make sure daemon is ready to receive requests 130 startTime := time.Now().Unix() 131 for { 132 d.c.Log("waiting for daemon to start") 133 if time.Now().Unix()-startTime > 5 { 134 // After 5 seconds, give up 135 return errors.New("Daemon exited and never started") 136 } 137 select { 138 case <-time.After(2 * time.Second): 139 return errors.New("timeout: daemon does not respond") 140 case <-tick: 141 c, err := net.Dial("unix", filepath.Join(d.folder, "docker.sock")) 142 if err != nil { 143 continue 144 } 145 146 client := httputil.NewClientConn(c, nil) 147 defer client.Close() 148 149 req, err := http.NewRequest("GET", "/_ping", nil) 150 if err != nil { 151 d.c.Fatalf("could not create new request: %v", err) 152 } 153 154 resp, err := client.Do(req) 155 if err != nil { 156 continue 157 } 158 if resp.StatusCode != http.StatusOK { 159 d.c.Logf("received status != 200 OK: %s", resp.Status) 160 } 161 162 d.c.Log("daemon started") 163 return nil 164 } 165 } 166 } 167 168 // StartWithBusybox will first start the daemon with Daemon.Start() 169 // then save the busybox image from the main daemon and load it into this Daemon instance. 170 func (d *Daemon) StartWithBusybox(arg ...string) error { 171 if err := d.Start(arg...); err != nil { 172 return err 173 } 174 bb := filepath.Join(d.folder, "busybox.tar") 175 if _, err := os.Stat(bb); err != nil { 176 if !os.IsNotExist(err) { 177 return fmt.Errorf("unexpected error on busybox.tar stat: %v", err) 178 } 179 // saving busybox image from main daemon 180 if err := exec.Command(dockerBinary, "save", "--output", bb, "busybox:latest").Run(); err != nil { 181 return fmt.Errorf("could not save busybox image: %v", err) 182 } 183 } 184 // loading busybox image to this daemon 185 if _, err := d.Cmd("load", "--input", bb); err != nil { 186 return fmt.Errorf("could not load busybox image: %v", err) 187 } 188 if err := os.Remove(bb); err != nil { 189 d.c.Logf("Could not remove %s: %v", bb, err) 190 } 191 return nil 192 } 193 194 // Stop will send a SIGINT every second and wait for the daemon to stop. 195 // If it timeouts, a SIGKILL is sent. 196 // Stop will not delete the daemon directory. If a purged daemon is needed, 197 // instantiate a new one with NewDaemon. 198 func (d *Daemon) Stop() error { 199 if d.cmd == nil || d.wait == nil { 200 return errors.New("daemon not started") 201 } 202 203 defer func() { 204 d.logFile.Close() 205 d.cmd = nil 206 }() 207 208 i := 1 209 tick := time.Tick(time.Second) 210 211 if err := d.cmd.Process.Signal(os.Interrupt); err != nil { 212 return fmt.Errorf("could not send signal: %v", err) 213 } 214 out1: 215 for { 216 select { 217 case err := <-d.wait: 218 return err 219 case <-time.After(15 * time.Second): 220 // time for stopping jobs and run onShutdown hooks 221 d.c.Log("timeout") 222 break out1 223 } 224 } 225 226 out2: 227 for { 228 select { 229 case err := <-d.wait: 230 return err 231 case <-tick: 232 i++ 233 if i > 4 { 234 d.c.Logf("tried to interrupt daemon for %d times, now try to kill it", i) 235 break out2 236 } 237 d.c.Logf("Attempt #%d: daemon is still running with pid %d", i, d.cmd.Process.Pid) 238 if err := d.cmd.Process.Signal(os.Interrupt); err != nil { 239 return fmt.Errorf("could not send signal: %v", err) 240 } 241 } 242 } 243 244 if err := d.cmd.Process.Kill(); err != nil { 245 d.c.Logf("Could not kill daemon: %v", err) 246 return err 247 } 248 249 return nil 250 } 251 252 // Restart will restart the daemon by first stopping it and then starting it. 253 func (d *Daemon) Restart(arg ...string) error { 254 d.Stop() 255 return d.Start(arg...) 256 } 257 258 func (d *Daemon) sock() string { 259 return fmt.Sprintf("unix://%s/docker.sock", d.folder) 260 } 261 262 // Cmd will execute a docker CLI command against this Daemon. 263 // Example: d.Cmd("version") will run docker -H unix://path/to/unix.sock version 264 func (d *Daemon) Cmd(name string, arg ...string) (string, error) { 265 args := []string{"--host", d.sock(), name} 266 args = append(args, arg...) 267 c := exec.Command(dockerBinary, args...) 268 b, err := c.CombinedOutput() 269 return string(b), err 270 } 271 272 func (d *Daemon) CmdWithArgs(daemonArgs []string, name string, arg ...string) (string, error) { 273 args := append(daemonArgs, name) 274 args = append(args, arg...) 275 c := exec.Command(dockerBinary, args...) 276 b, err := c.CombinedOutput() 277 return string(b), err 278 } 279 280 func (d *Daemon) LogfileName() string { 281 return d.logFile.Name() 282 } 283 284 func daemonHost() string { 285 daemonUrlStr := "unix://" + opts.DefaultUnixSocket 286 if daemonHostVar := os.Getenv("DOCKER_HOST"); daemonHostVar != "" { 287 daemonUrlStr = daemonHostVar 288 } 289 return daemonUrlStr 290 } 291 292 func sockConn(timeout time.Duration) (net.Conn, error) { 293 daemon := daemonHost() 294 daemonUrl, err := url.Parse(daemon) 295 if err != nil { 296 return nil, fmt.Errorf("could not parse url %q: %v", daemon, err) 297 } 298 299 var c net.Conn 300 switch daemonUrl.Scheme { 301 case "unix": 302 return net.DialTimeout(daemonUrl.Scheme, daemonUrl.Path, timeout) 303 case "tcp": 304 return net.DialTimeout(daemonUrl.Scheme, daemonUrl.Host, timeout) 305 default: 306 return c, fmt.Errorf("unknown scheme %v (%s)", daemonUrl.Scheme, daemon) 307 } 308 } 309 310 func sockRequest(method, endpoint string, data interface{}) (int, []byte, error) { 311 jsonData := bytes.NewBuffer(nil) 312 if err := json.NewEncoder(jsonData).Encode(data); err != nil { 313 return -1, nil, err 314 } 315 316 status, body, err := sockRequestRaw(method, endpoint, jsonData, "application/json") 317 if err != nil { 318 b, _ := ioutil.ReadAll(body) 319 return status, b, err 320 } 321 var b []byte 322 b, err = readBody(body) 323 return status, b, err 324 } 325 326 func sockRequestRaw(method, endpoint string, data io.Reader, ct string) (int, io.ReadCloser, error) { 327 c, err := sockConn(time.Duration(10 * time.Second)) 328 if err != nil { 329 return -1, nil, fmt.Errorf("could not dial docker daemon: %v", err) 330 } 331 332 client := httputil.NewClientConn(c, nil) 333 334 req, err := http.NewRequest(method, endpoint, data) 335 if err != nil { 336 client.Close() 337 return -1, nil, fmt.Errorf("could not create new request: %v", err) 338 } 339 340 if ct != "" { 341 req.Header.Set("Content-Type", ct) 342 } 343 344 resp, err := client.Do(req) 345 if err != nil { 346 client.Close() 347 return -1, nil, fmt.Errorf("could not perform request: %v", err) 348 } 349 body := ioutils.NewReadCloserWrapper(resp.Body, func() error { 350 defer client.Close() 351 return resp.Body.Close() 352 }) 353 354 return resp.StatusCode, body, err 355 } 356 357 func readBody(b io.ReadCloser) ([]byte, error) { 358 defer b.Close() 359 return ioutil.ReadAll(b) 360 } 361 362 func deleteContainer(container string) error { 363 container = strings.TrimSpace(strings.Replace(container, "\n", " ", -1)) 364 rmArgs := strings.Split(fmt.Sprintf("rm -fv %v", container), " ") 365 exitCode, err := runCommand(exec.Command(dockerBinary, rmArgs...)) 366 // set error manually if not set 367 if exitCode != 0 && err == nil { 368 err = fmt.Errorf("failed to remove container: `docker rm` exit is non-zero") 369 } 370 371 return err 372 } 373 374 func getAllContainers() (string, error) { 375 getContainersCmd := exec.Command(dockerBinary, "ps", "-q", "-a") 376 out, exitCode, err := runCommandWithOutput(getContainersCmd) 377 if exitCode != 0 && err == nil { 378 err = fmt.Errorf("failed to get a list of containers: %v\n", out) 379 } 380 381 return out, err 382 } 383 384 func deleteAllContainers() error { 385 containers, err := getAllContainers() 386 if err != nil { 387 fmt.Println(containers) 388 return err 389 } 390 391 if err = deleteContainer(containers); err != nil { 392 return err 393 } 394 return nil 395 } 396 397 var protectedImages = map[string]struct{}{} 398 399 func init() { 400 out, err := exec.Command(dockerBinary, "images").CombinedOutput() 401 if err != nil { 402 panic(err) 403 } 404 lines := strings.Split(string(out), "\n")[1:] 405 for _, l := range lines { 406 if l == "" { 407 continue 408 } 409 fields := strings.Fields(l) 410 imgTag := fields[0] + ":" + fields[1] 411 // just for case if we have dangling images in tested daemon 412 if imgTag != "<none>:<none>" { 413 protectedImages[imgTag] = struct{}{} 414 } 415 } 416 } 417 418 func deleteAllImages() error { 419 out, err := exec.Command(dockerBinary, "images").CombinedOutput() 420 if err != nil { 421 return err 422 } 423 lines := strings.Split(string(out), "\n")[1:] 424 var imgs []string 425 for _, l := range lines { 426 if l == "" { 427 continue 428 } 429 fields := strings.Fields(l) 430 imgTag := fields[0] + ":" + fields[1] 431 if _, ok := protectedImages[imgTag]; !ok { 432 if fields[0] == "<none>" { 433 imgs = append(imgs, fields[2]) 434 continue 435 } 436 imgs = append(imgs, imgTag) 437 } 438 } 439 if len(imgs) == 0 { 440 return nil 441 } 442 args := append([]string{"rmi", "-f"}, imgs...) 443 if err := exec.Command(dockerBinary, args...).Run(); err != nil { 444 return err 445 } 446 return nil 447 } 448 449 func getPausedContainers() (string, error) { 450 getPausedContainersCmd := exec.Command(dockerBinary, "ps", "-f", "status=paused", "-q", "-a") 451 out, exitCode, err := runCommandWithOutput(getPausedContainersCmd) 452 if exitCode != 0 && err == nil { 453 err = fmt.Errorf("failed to get a list of paused containers: %v\n", out) 454 } 455 456 return out, err 457 } 458 459 func getSliceOfPausedContainers() ([]string, error) { 460 out, err := getPausedContainers() 461 if err == nil { 462 if len(out) == 0 { 463 return nil, err 464 } 465 slice := strings.Split(strings.TrimSpace(out), "\n") 466 return slice, err 467 } 468 return []string{out}, err 469 } 470 471 func unpauseContainer(container string) error { 472 unpauseCmd := exec.Command(dockerBinary, "unpause", container) 473 exitCode, err := runCommand(unpauseCmd) 474 if exitCode != 0 && err == nil { 475 err = fmt.Errorf("failed to unpause container") 476 } 477 478 return nil 479 } 480 481 func unpauseAllContainers() error { 482 containers, err := getPausedContainers() 483 if err != nil { 484 fmt.Println(containers) 485 return err 486 } 487 488 containers = strings.Replace(containers, "\n", " ", -1) 489 containers = strings.Trim(containers, " ") 490 containerList := strings.Split(containers, " ") 491 492 for _, value := range containerList { 493 if err = unpauseContainer(value); err != nil { 494 return err 495 } 496 } 497 498 return nil 499 } 500 501 func deleteImages(images ...string) error { 502 args := []string{"rmi", "-f"} 503 args = append(args, images...) 504 rmiCmd := exec.Command(dockerBinary, args...) 505 exitCode, err := runCommand(rmiCmd) 506 // set error manually if not set 507 if exitCode != 0 && err == nil { 508 err = fmt.Errorf("failed to remove image: `docker rmi` exit is non-zero") 509 } 510 return err 511 } 512 513 func imageExists(image string) error { 514 inspectCmd := exec.Command(dockerBinary, "inspect", image) 515 exitCode, err := runCommand(inspectCmd) 516 if exitCode != 0 && err == nil { 517 err = fmt.Errorf("couldn't find image %q", image) 518 } 519 return err 520 } 521 522 func pullImageIfNotExist(image string) (err error) { 523 if err := imageExists(image); err != nil { 524 pullCmd := exec.Command(dockerBinary, "pull", image) 525 _, exitCode, err := runCommandWithOutput(pullCmd) 526 527 if err != nil || exitCode != 0 { 528 err = fmt.Errorf("image %q wasn't found locally and it couldn't be pulled: %s", image, err) 529 } 530 } 531 return 532 } 533 534 func dockerCmd(c *check.C, args ...string) (string, int) { 535 out, status, err := runCommandWithOutput(exec.Command(dockerBinary, args...)) 536 if err != nil { 537 c.Fatalf("%q failed with errors: %s, %v", strings.Join(args, " "), out, err) 538 } 539 return out, status 540 } 541 542 // execute a docker command with a timeout 543 func dockerCmdWithTimeout(timeout time.Duration, args ...string) (string, int, error) { 544 out, status, err := runCommandWithOutputAndTimeout(exec.Command(dockerBinary, args...), timeout) 545 if err != nil { 546 return out, status, fmt.Errorf("%q failed with errors: %v : %q)", strings.Join(args, " "), err, out) 547 } 548 return out, status, err 549 } 550 551 // execute a docker command in a directory 552 func dockerCmdInDir(c *check.C, path string, args ...string) (string, int, error) { 553 dockerCommand := exec.Command(dockerBinary, args...) 554 dockerCommand.Dir = path 555 out, status, err := runCommandWithOutput(dockerCommand) 556 if err != nil { 557 return out, status, fmt.Errorf("%q failed with errors: %v : %q)", strings.Join(args, " "), err, out) 558 } 559 return out, status, err 560 } 561 562 // execute a docker command in a directory with a timeout 563 func dockerCmdInDirWithTimeout(timeout time.Duration, path string, args ...string) (string, int, error) { 564 dockerCommand := exec.Command(dockerBinary, args...) 565 dockerCommand.Dir = path 566 out, status, err := runCommandWithOutputAndTimeout(dockerCommand, timeout) 567 if err != nil { 568 return out, status, fmt.Errorf("%q failed with errors: %v : %q)", strings.Join(args, " "), err, out) 569 } 570 return out, status, err 571 } 572 573 func findContainerIP(c *check.C, id string) string { 574 cmd := exec.Command(dockerBinary, "inspect", "--format='{{ .NetworkSettings.IPAddress }}'", id) 575 out, _, err := runCommandWithOutput(cmd) 576 if err != nil { 577 c.Fatal(err, out) 578 } 579 580 return strings.Trim(out, " \r\n'") 581 } 582 583 func getContainerCount() (int, error) { 584 const containers = "Containers:" 585 586 cmd := exec.Command(dockerBinary, "info") 587 out, _, err := runCommandWithOutput(cmd) 588 if err != nil { 589 return 0, err 590 } 591 592 lines := strings.Split(out, "\n") 593 for _, line := range lines { 594 if strings.Contains(line, containers) { 595 output := strings.TrimSpace(line) 596 output = strings.TrimLeft(output, containers) 597 output = strings.Trim(output, " ") 598 containerCount, err := strconv.Atoi(output) 599 if err != nil { 600 return 0, err 601 } 602 return containerCount, nil 603 } 604 } 605 return 0, fmt.Errorf("couldn't find the Container count in the output") 606 } 607 608 type FakeContext struct { 609 Dir string 610 } 611 612 func (f *FakeContext) Add(file, content string) error { 613 filepath := path.Join(f.Dir, file) 614 dirpath := path.Dir(filepath) 615 if dirpath != "." { 616 if err := os.MkdirAll(dirpath, 0755); err != nil { 617 return err 618 } 619 } 620 return ioutil.WriteFile(filepath, []byte(content), 0644) 621 } 622 623 func (f *FakeContext) Delete(file string) error { 624 filepath := path.Join(f.Dir, file) 625 return os.RemoveAll(filepath) 626 } 627 628 func (f *FakeContext) Close() error { 629 return os.RemoveAll(f.Dir) 630 } 631 632 func fakeContextFromDir(dir string) *FakeContext { 633 return &FakeContext{dir} 634 } 635 636 func fakeContextWithFiles(files map[string]string) (*FakeContext, error) { 637 tmp, err := ioutil.TempDir("", "fake-context") 638 if err != nil { 639 return nil, err 640 } 641 if err := os.Chmod(tmp, 0755); err != nil { 642 return nil, err 643 } 644 645 ctx := fakeContextFromDir(tmp) 646 for file, content := range files { 647 if err := ctx.Add(file, content); err != nil { 648 ctx.Close() 649 return nil, err 650 } 651 } 652 return ctx, nil 653 } 654 655 func fakeContextAddDockerfile(ctx *FakeContext, dockerfile string) error { 656 if err := ctx.Add("Dockerfile", dockerfile); err != nil { 657 ctx.Close() 658 return err 659 } 660 return nil 661 } 662 663 func fakeContext(dockerfile string, files map[string]string) (*FakeContext, error) { 664 ctx, err := fakeContextWithFiles(files) 665 if err != nil { 666 ctx.Close() 667 return nil, err 668 } 669 if err := fakeContextAddDockerfile(ctx, dockerfile); err != nil { 670 return nil, err 671 } 672 return ctx, nil 673 } 674 675 // FakeStorage is a static file server. It might be running locally or remotely 676 // on test host. 677 type FakeStorage interface { 678 Close() error 679 URL() string 680 CtxDir() string 681 } 682 683 // fakeStorage returns either a local or remote (at daemon machine) file server 684 func fakeStorage(files map[string]string) (FakeStorage, error) { 685 ctx, err := fakeContextWithFiles(files) 686 if err != nil { 687 return nil, err 688 } 689 return fakeStorageWithContext(ctx) 690 } 691 692 // fakeStorageWithContext returns either a local or remote (at daemon machine) file server 693 func fakeStorageWithContext(ctx *FakeContext) (FakeStorage, error) { 694 if isLocalDaemon { 695 return newLocalFakeStorage(ctx) 696 } 697 return newRemoteFileServer(ctx) 698 } 699 700 // localFileStorage is a file storage on the running machine 701 type localFileStorage struct { 702 *FakeContext 703 *httptest.Server 704 } 705 706 func (s *localFileStorage) URL() string { 707 return s.Server.URL 708 } 709 710 func (s *localFileStorage) CtxDir() string { 711 return s.FakeContext.Dir 712 } 713 714 func (s *localFileStorage) Close() error { 715 defer s.Server.Close() 716 return s.FakeContext.Close() 717 } 718 719 func newLocalFakeStorage(ctx *FakeContext) (*localFileStorage, error) { 720 handler := http.FileServer(http.Dir(ctx.Dir)) 721 server := httptest.NewServer(handler) 722 return &localFileStorage{ 723 FakeContext: ctx, 724 Server: server, 725 }, nil 726 } 727 728 // remoteFileServer is a containerized static file server started on the remote 729 // testing machine to be used in URL-accepting docker build functionality. 730 type remoteFileServer struct { 731 host string // hostname/port web server is listening to on docker host e.g. 0.0.0.0:43712 732 container string 733 image string 734 ctx *FakeContext 735 } 736 737 func (f *remoteFileServer) URL() string { 738 u := url.URL{ 739 Scheme: "http", 740 Host: f.host} 741 return u.String() 742 } 743 744 func (f *remoteFileServer) CtxDir() string { 745 return f.ctx.Dir 746 } 747 748 func (f *remoteFileServer) Close() error { 749 defer func() { 750 if f.ctx != nil { 751 f.ctx.Close() 752 } 753 if f.image != "" { 754 deleteImages(f.image) 755 } 756 }() 757 if f.container == "" { 758 return nil 759 } 760 return deleteContainer(f.container) 761 } 762 763 func newRemoteFileServer(ctx *FakeContext) (*remoteFileServer, error) { 764 var ( 765 image = fmt.Sprintf("fileserver-img-%s", strings.ToLower(stringutils.GenerateRandomAlphaOnlyString(10))) 766 container = fmt.Sprintf("fileserver-cnt-%s", strings.ToLower(stringutils.GenerateRandomAlphaOnlyString(10))) 767 ) 768 769 // Build the image 770 if err := fakeContextAddDockerfile(ctx, `FROM httpserver 771 COPY . /static`); err != nil { 772 return nil, fmt.Errorf("Cannot add Dockerfile to context: %v", err) 773 } 774 if _, err := buildImageFromContext(image, ctx, false); err != nil { 775 return nil, fmt.Errorf("failed building file storage container image: %v", err) 776 } 777 778 // Start the container 779 runCmd := exec.Command(dockerBinary, "run", "-d", "-P", "--name", container, image) 780 if out, ec, err := runCommandWithOutput(runCmd); err != nil { 781 return nil, fmt.Errorf("failed to start file storage container. ec=%v\nout=%s\nerr=%v", ec, out, err) 782 } 783 784 // Find out the system assigned port 785 out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "port", container, "80/tcp")) 786 if err != nil { 787 return nil, fmt.Errorf("failed to find container port: err=%v\nout=%s", err, out) 788 } 789 790 return &remoteFileServer{ 791 container: container, 792 image: image, 793 host: strings.Trim(out, "\n"), 794 ctx: ctx}, nil 795 } 796 797 func inspectFieldAndMarshall(name, field string, output interface{}) error { 798 str, err := inspectFieldJSON(name, field) 799 if err != nil { 800 return err 801 } 802 803 return json.Unmarshal([]byte(str), output) 804 } 805 806 func inspectFilter(name, filter string) (string, error) { 807 format := fmt.Sprintf("{{%s}}", filter) 808 inspectCmd := exec.Command(dockerBinary, "inspect", "-f", format, name) 809 out, exitCode, err := runCommandWithOutput(inspectCmd) 810 if err != nil || exitCode != 0 { 811 return "", fmt.Errorf("failed to inspect container %s: %s", name, out) 812 } 813 return strings.TrimSpace(out), nil 814 } 815 816 func inspectField(name, field string) (string, error) { 817 return inspectFilter(name, fmt.Sprintf(".%s", field)) 818 } 819 820 func inspectFieldJSON(name, field string) (string, error) { 821 return inspectFilter(name, fmt.Sprintf("json .%s", field)) 822 } 823 824 func inspectFieldMap(name, path, field string) (string, error) { 825 return inspectFilter(name, fmt.Sprintf("index .%s %q", path, field)) 826 } 827 828 func getIDByName(name string) (string, error) { 829 return inspectField(name, "Id") 830 } 831 832 // getContainerState returns the exit code of the container 833 // and true if it's running 834 // the exit code should be ignored if it's running 835 func getContainerState(c *check.C, id string) (int, bool, error) { 836 var ( 837 exitStatus int 838 running bool 839 ) 840 out, exitCode := dockerCmd(c, "inspect", "--format={{.State.Running}} {{.State.ExitCode}}", id) 841 if exitCode != 0 { 842 return 0, false, fmt.Errorf("%q doesn't exist: %s", id, out) 843 } 844 845 out = strings.Trim(out, "\n") 846 splitOutput := strings.Split(out, " ") 847 if len(splitOutput) != 2 { 848 return 0, false, fmt.Errorf("failed to get container state: output is broken") 849 } 850 if splitOutput[0] == "true" { 851 running = true 852 } 853 if n, err := strconv.Atoi(splitOutput[1]); err == nil { 854 exitStatus = n 855 } else { 856 return 0, false, fmt.Errorf("failed to get container state: couldn't parse integer") 857 } 858 859 return exitStatus, running, nil 860 } 861 862 func buildImageWithOut(name, dockerfile string, useCache bool) (string, string, error) { 863 args := []string{"build", "-t", name} 864 if !useCache { 865 args = append(args, "--no-cache") 866 } 867 args = append(args, "-") 868 buildCmd := exec.Command(dockerBinary, args...) 869 buildCmd.Stdin = strings.NewReader(dockerfile) 870 out, exitCode, err := runCommandWithOutput(buildCmd) 871 if err != nil || exitCode != 0 { 872 return "", out, fmt.Errorf("failed to build the image: %s", out) 873 } 874 id, err := getIDByName(name) 875 if err != nil { 876 return "", out, err 877 } 878 return id, out, nil 879 } 880 881 func buildImageWithStdoutStderr(name, dockerfile string, useCache bool) (string, string, string, error) { 882 args := []string{"build", "-t", name} 883 if !useCache { 884 args = append(args, "--no-cache") 885 } 886 args = append(args, "-") 887 buildCmd := exec.Command(dockerBinary, args...) 888 buildCmd.Stdin = strings.NewReader(dockerfile) 889 stdout, stderr, exitCode, err := runCommandWithStdoutStderr(buildCmd) 890 if err != nil || exitCode != 0 { 891 return "", stdout, stderr, fmt.Errorf("failed to build the image: %s", stdout) 892 } 893 id, err := getIDByName(name) 894 if err != nil { 895 return "", stdout, stderr, err 896 } 897 return id, stdout, stderr, nil 898 } 899 900 func buildImage(name, dockerfile string, useCache bool) (string, error) { 901 id, _, err := buildImageWithOut(name, dockerfile, useCache) 902 return id, err 903 } 904 905 func buildImageFromContext(name string, ctx *FakeContext, useCache bool) (string, error) { 906 args := []string{"build", "-t", name} 907 if !useCache { 908 args = append(args, "--no-cache") 909 } 910 args = append(args, ".") 911 buildCmd := exec.Command(dockerBinary, args...) 912 buildCmd.Dir = ctx.Dir 913 out, exitCode, err := runCommandWithOutput(buildCmd) 914 if err != nil || exitCode != 0 { 915 return "", fmt.Errorf("failed to build the image: %s", out) 916 } 917 return getIDByName(name) 918 } 919 920 func buildImageFromPath(name, path string, useCache bool) (string, error) { 921 args := []string{"build", "-t", name} 922 if !useCache { 923 args = append(args, "--no-cache") 924 } 925 args = append(args, path) 926 buildCmd := exec.Command(dockerBinary, args...) 927 out, exitCode, err := runCommandWithOutput(buildCmd) 928 if err != nil || exitCode != 0 { 929 return "", fmt.Errorf("failed to build the image: %s", out) 930 } 931 return getIDByName(name) 932 } 933 934 type GitServer interface { 935 URL() string 936 Close() error 937 } 938 939 type localGitServer struct { 940 *httptest.Server 941 } 942 943 func (r *localGitServer) Close() error { 944 r.Server.Close() 945 return nil 946 } 947 948 func (r *localGitServer) URL() string { 949 return r.Server.URL 950 } 951 952 type FakeGIT struct { 953 root string 954 server GitServer 955 RepoURL string 956 } 957 958 func (g *FakeGIT) Close() { 959 g.server.Close() 960 os.RemoveAll(g.root) 961 } 962 963 func fakeGIT(name string, files map[string]string, enforceLocalServer bool) (*FakeGIT, error) { 964 ctx, err := fakeContextWithFiles(files) 965 if err != nil { 966 return nil, err 967 } 968 defer ctx.Close() 969 curdir, err := os.Getwd() 970 if err != nil { 971 return nil, err 972 } 973 defer os.Chdir(curdir) 974 975 if output, err := exec.Command("git", "init", ctx.Dir).CombinedOutput(); err != nil { 976 return nil, fmt.Errorf("error trying to init repo: %s (%s)", err, output) 977 } 978 err = os.Chdir(ctx.Dir) 979 if err != nil { 980 return nil, err 981 } 982 if output, err := exec.Command("git", "config", "user.name", "Fake User").CombinedOutput(); err != nil { 983 return nil, fmt.Errorf("error trying to set 'user.name': %s (%s)", err, output) 984 } 985 if output, err := exec.Command("git", "config", "user.email", "fake.user@example.com").CombinedOutput(); err != nil { 986 return nil, fmt.Errorf("error trying to set 'user.email': %s (%s)", err, output) 987 } 988 if output, err := exec.Command("git", "add", "*").CombinedOutput(); err != nil { 989 return nil, fmt.Errorf("error trying to add files to repo: %s (%s)", err, output) 990 } 991 if output, err := exec.Command("git", "commit", "-a", "-m", "Initial commit").CombinedOutput(); err != nil { 992 return nil, fmt.Errorf("error trying to commit to repo: %s (%s)", err, output) 993 } 994 995 root, err := ioutil.TempDir("", "docker-test-git-repo") 996 if err != nil { 997 return nil, err 998 } 999 repoPath := filepath.Join(root, name+".git") 1000 if output, err := exec.Command("git", "clone", "--bare", ctx.Dir, repoPath).CombinedOutput(); err != nil { 1001 os.RemoveAll(root) 1002 return nil, fmt.Errorf("error trying to clone --bare: %s (%s)", err, output) 1003 } 1004 err = os.Chdir(repoPath) 1005 if err != nil { 1006 os.RemoveAll(root) 1007 return nil, err 1008 } 1009 if output, err := exec.Command("git", "update-server-info").CombinedOutput(); err != nil { 1010 os.RemoveAll(root) 1011 return nil, fmt.Errorf("error trying to git update-server-info: %s (%s)", err, output) 1012 } 1013 err = os.Chdir(curdir) 1014 if err != nil { 1015 os.RemoveAll(root) 1016 return nil, err 1017 } 1018 1019 var server GitServer 1020 if !enforceLocalServer { 1021 // use fakeStorage server, which might be local or remote (at test daemon) 1022 server, err = fakeStorageWithContext(fakeContextFromDir(root)) 1023 if err != nil { 1024 return nil, fmt.Errorf("cannot start fake storage: %v", err) 1025 } 1026 } else { 1027 // always start a local http server on CLI test machin 1028 httpServer := httptest.NewServer(http.FileServer(http.Dir(root))) 1029 server = &localGitServer{httpServer} 1030 } 1031 return &FakeGIT{ 1032 root: root, 1033 server: server, 1034 RepoURL: fmt.Sprintf("%s/%s.git", server.URL(), name), 1035 }, nil 1036 } 1037 1038 // Write `content` to the file at path `dst`, creating it if necessary, 1039 // as well as any missing directories. 1040 // The file is truncated if it already exists. 1041 // Call c.Fatal() at the first error. 1042 func writeFile(dst, content string, c *check.C) { 1043 // Create subdirectories if necessary 1044 if err := os.MkdirAll(path.Dir(dst), 0700); err != nil && !os.IsExist(err) { 1045 c.Fatal(err) 1046 } 1047 f, err := os.OpenFile(dst, os.O_CREATE|os.O_RDWR|os.O_TRUNC, 0700) 1048 if err != nil { 1049 c.Fatal(err) 1050 } 1051 // Write content (truncate if it exists) 1052 if _, err := io.Copy(f, strings.NewReader(content)); err != nil { 1053 c.Fatal(err) 1054 } 1055 } 1056 1057 // Return the contents of file at path `src`. 1058 // Call c.Fatal() at the first error (including if the file doesn't exist) 1059 func readFile(src string, c *check.C) (content string) { 1060 data, err := ioutil.ReadFile(src) 1061 if err != nil { 1062 c.Fatal(err) 1063 } 1064 1065 return string(data) 1066 } 1067 1068 func containerStorageFile(containerId, basename string) string { 1069 return filepath.Join("/var/lib/docker/containers", containerId, basename) 1070 } 1071 1072 // docker commands that use this function must be run with the '-d' switch. 1073 func runCommandAndReadContainerFile(filename string, cmd *exec.Cmd) ([]byte, error) { 1074 out, _, err := runCommandWithOutput(cmd) 1075 if err != nil { 1076 return nil, fmt.Errorf("%v: %q", err, out) 1077 } 1078 1079 time.Sleep(1 * time.Second) 1080 1081 contID := strings.TrimSpace(out) 1082 1083 return readContainerFile(contID, filename) 1084 } 1085 1086 func readContainerFile(containerId, filename string) ([]byte, error) { 1087 f, err := os.Open(containerStorageFile(containerId, filename)) 1088 if err != nil { 1089 return nil, err 1090 } 1091 defer f.Close() 1092 1093 content, err := ioutil.ReadAll(f) 1094 if err != nil { 1095 return nil, err 1096 } 1097 1098 return content, nil 1099 } 1100 1101 func readContainerFileWithExec(containerId, filename string) ([]byte, error) { 1102 out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "exec", containerId, "cat", filename)) 1103 return []byte(out), err 1104 } 1105 1106 // daemonTime provides the current time on the daemon host 1107 func daemonTime(c *check.C) time.Time { 1108 if isLocalDaemon { 1109 return time.Now() 1110 } 1111 1112 status, body, err := sockRequest("GET", "/info", nil) 1113 c.Assert(status, check.Equals, http.StatusOK) 1114 c.Assert(err, check.IsNil) 1115 1116 type infoJSON struct { 1117 SystemTime string 1118 } 1119 var info infoJSON 1120 if err = json.Unmarshal(body, &info); err != nil { 1121 c.Fatalf("unable to unmarshal /info response: %v", err) 1122 } 1123 1124 dt, err := time.Parse(time.RFC3339Nano, info.SystemTime) 1125 if err != nil { 1126 c.Fatal(err) 1127 } 1128 return dt 1129 } 1130 1131 func setupRegistry(c *check.C) *testRegistryV2 { 1132 testRequires(c, RegistryHosting) 1133 reg, err := newTestRegistryV2(c) 1134 if err != nil { 1135 c.Fatal(err) 1136 } 1137 1138 // Wait for registry to be ready to serve requests. 1139 for i := 0; i != 5; i++ { 1140 if err = reg.Ping(); err == nil { 1141 break 1142 } 1143 time.Sleep(100 * time.Millisecond) 1144 } 1145 1146 if err != nil { 1147 c.Fatal("Timeout waiting for test registry to become available") 1148 } 1149 return reg 1150 } 1151 1152 // appendBaseEnv appends the minimum set of environment variables to exec the 1153 // docker cli binary for testing with correct configuration to the given env 1154 // list. 1155 func appendBaseEnv(env []string) []string { 1156 preserveList := []string{ 1157 // preserve remote test host 1158 "DOCKER_HOST", 1159 1160 // windows: requires preserving SystemRoot, otherwise dial tcp fails 1161 // with "GetAddrInfoW: A non-recoverable error occurred during a database lookup." 1162 "SystemRoot", 1163 } 1164 1165 for _, key := range preserveList { 1166 if val := os.Getenv(key); val != "" { 1167 env = append(env, fmt.Sprintf("%s=%s", key, val)) 1168 } 1169 } 1170 return env 1171 }