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