github.com/lalyos/docker@v1.14.0-dev/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 res, body, err := sockRequestRaw(method, endpoint, jsonData, "application/json") 317 if err != nil { 318 b, _ := ioutil.ReadAll(body) 319 return -1, b, err 320 } 321 var b []byte 322 b, err = readBody(body) 323 return res.StatusCode, b, err 324 } 325 326 func sockRequestRaw(method, endpoint string, data io.Reader, ct string) (*http.Response, io.ReadCloser, error) { 327 c, err := sockConn(time.Duration(10 * time.Second)) 328 if err != nil { 329 return nil, 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 nil, 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 nil, 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, body, nil 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, vargs ...string) string { 574 args := append(vargs, "inspect", "--format='{{ .NetworkSettings.IPAddress }}'", id) 575 cmd := exec.Command(dockerBinary, args...) 576 out, _, err := runCommandWithOutput(cmd) 577 if err != nil { 578 c.Fatal(err, out) 579 } 580 581 return strings.Trim(out, " \r\n'") 582 } 583 584 func (d *Daemon) findContainerIP(id string) string { 585 return findContainerIP(d.c, id, "--host", d.sock()) 586 } 587 588 func getContainerCount() (int, error) { 589 const containers = "Containers:" 590 591 cmd := exec.Command(dockerBinary, "info") 592 out, _, err := runCommandWithOutput(cmd) 593 if err != nil { 594 return 0, err 595 } 596 597 lines := strings.Split(out, "\n") 598 for _, line := range lines { 599 if strings.Contains(line, containers) { 600 output := strings.TrimSpace(line) 601 output = strings.TrimLeft(output, containers) 602 output = strings.Trim(output, " ") 603 containerCount, err := strconv.Atoi(output) 604 if err != nil { 605 return 0, err 606 } 607 return containerCount, nil 608 } 609 } 610 return 0, fmt.Errorf("couldn't find the Container count in the output") 611 } 612 613 type FakeContext struct { 614 Dir string 615 } 616 617 func (f *FakeContext) Add(file, content string) error { 618 filepath := path.Join(f.Dir, file) 619 dirpath := path.Dir(filepath) 620 if dirpath != "." { 621 if err := os.MkdirAll(dirpath, 0755); err != nil { 622 return err 623 } 624 } 625 return ioutil.WriteFile(filepath, []byte(content), 0644) 626 } 627 628 func (f *FakeContext) Delete(file string) error { 629 filepath := path.Join(f.Dir, file) 630 return os.RemoveAll(filepath) 631 } 632 633 func (f *FakeContext) Close() error { 634 return os.RemoveAll(f.Dir) 635 } 636 637 func fakeContextFromDir(dir string) *FakeContext { 638 return &FakeContext{dir} 639 } 640 641 func fakeContextWithFiles(files map[string]string) (*FakeContext, error) { 642 tmp, err := ioutil.TempDir("", "fake-context") 643 if err != nil { 644 return nil, err 645 } 646 if err := os.Chmod(tmp, 0755); err != nil { 647 return nil, err 648 } 649 650 ctx := fakeContextFromDir(tmp) 651 for file, content := range files { 652 if err := ctx.Add(file, content); err != nil { 653 ctx.Close() 654 return nil, err 655 } 656 } 657 return ctx, nil 658 } 659 660 func fakeContextAddDockerfile(ctx *FakeContext, dockerfile string) error { 661 if err := ctx.Add("Dockerfile", dockerfile); err != nil { 662 ctx.Close() 663 return err 664 } 665 return nil 666 } 667 668 func fakeContext(dockerfile string, files map[string]string) (*FakeContext, error) { 669 ctx, err := fakeContextWithFiles(files) 670 if err != nil { 671 ctx.Close() 672 return nil, err 673 } 674 if err := fakeContextAddDockerfile(ctx, dockerfile); err != nil { 675 return nil, err 676 } 677 return ctx, nil 678 } 679 680 // FakeStorage is a static file server. It might be running locally or remotely 681 // on test host. 682 type FakeStorage interface { 683 Close() error 684 URL() string 685 CtxDir() string 686 } 687 688 // fakeStorage returns either a local or remote (at daemon machine) file server 689 func fakeStorage(files map[string]string) (FakeStorage, error) { 690 ctx, err := fakeContextWithFiles(files) 691 if err != nil { 692 return nil, err 693 } 694 return fakeStorageWithContext(ctx) 695 } 696 697 // fakeStorageWithContext returns either a local or remote (at daemon machine) file server 698 func fakeStorageWithContext(ctx *FakeContext) (FakeStorage, error) { 699 if isLocalDaemon { 700 return newLocalFakeStorage(ctx) 701 } 702 return newRemoteFileServer(ctx) 703 } 704 705 // localFileStorage is a file storage on the running machine 706 type localFileStorage struct { 707 *FakeContext 708 *httptest.Server 709 } 710 711 func (s *localFileStorage) URL() string { 712 return s.Server.URL 713 } 714 715 func (s *localFileStorage) CtxDir() string { 716 return s.FakeContext.Dir 717 } 718 719 func (s *localFileStorage) Close() error { 720 defer s.Server.Close() 721 return s.FakeContext.Close() 722 } 723 724 func newLocalFakeStorage(ctx *FakeContext) (*localFileStorage, error) { 725 handler := http.FileServer(http.Dir(ctx.Dir)) 726 server := httptest.NewServer(handler) 727 return &localFileStorage{ 728 FakeContext: ctx, 729 Server: server, 730 }, nil 731 } 732 733 // remoteFileServer is a containerized static file server started on the remote 734 // testing machine to be used in URL-accepting docker build functionality. 735 type remoteFileServer struct { 736 host string // hostname/port web server is listening to on docker host e.g. 0.0.0.0:43712 737 container string 738 image string 739 ctx *FakeContext 740 } 741 742 func (f *remoteFileServer) URL() string { 743 u := url.URL{ 744 Scheme: "http", 745 Host: f.host} 746 return u.String() 747 } 748 749 func (f *remoteFileServer) CtxDir() string { 750 return f.ctx.Dir 751 } 752 753 func (f *remoteFileServer) Close() error { 754 defer func() { 755 if f.ctx != nil { 756 f.ctx.Close() 757 } 758 if f.image != "" { 759 deleteImages(f.image) 760 } 761 }() 762 if f.container == "" { 763 return nil 764 } 765 return deleteContainer(f.container) 766 } 767 768 func newRemoteFileServer(ctx *FakeContext) (*remoteFileServer, error) { 769 var ( 770 image = fmt.Sprintf("fileserver-img-%s", strings.ToLower(stringutils.GenerateRandomAlphaOnlyString(10))) 771 container = fmt.Sprintf("fileserver-cnt-%s", strings.ToLower(stringutils.GenerateRandomAlphaOnlyString(10))) 772 ) 773 774 // Build the image 775 if err := fakeContextAddDockerfile(ctx, `FROM httpserver 776 COPY . /static`); err != nil { 777 return nil, fmt.Errorf("Cannot add Dockerfile to context: %v", err) 778 } 779 if _, err := buildImageFromContext(image, ctx, false); err != nil { 780 return nil, fmt.Errorf("failed building file storage container image: %v", err) 781 } 782 783 // Start the container 784 runCmd := exec.Command(dockerBinary, "run", "-d", "-P", "--name", container, image) 785 if out, ec, err := runCommandWithOutput(runCmd); err != nil { 786 return nil, fmt.Errorf("failed to start file storage container. ec=%v\nout=%s\nerr=%v", ec, out, err) 787 } 788 789 // Find out the system assigned port 790 out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "port", container, "80/tcp")) 791 if err != nil { 792 return nil, fmt.Errorf("failed to find container port: err=%v\nout=%s", err, out) 793 } 794 795 return &remoteFileServer{ 796 container: container, 797 image: image, 798 host: strings.Trim(out, "\n"), 799 ctx: ctx}, nil 800 } 801 802 func inspectFieldAndMarshall(name, field string, output interface{}) error { 803 str, err := inspectFieldJSON(name, field) 804 if err != nil { 805 return err 806 } 807 808 return json.Unmarshal([]byte(str), output) 809 } 810 811 func inspectFilter(name, filter string) (string, error) { 812 format := fmt.Sprintf("{{%s}}", filter) 813 inspectCmd := exec.Command(dockerBinary, "inspect", "-f", format, name) 814 out, exitCode, err := runCommandWithOutput(inspectCmd) 815 if err != nil || exitCode != 0 { 816 return "", fmt.Errorf("failed to inspect container %s: %s", name, out) 817 } 818 return strings.TrimSpace(out), nil 819 } 820 821 func inspectField(name, field string) (string, error) { 822 return inspectFilter(name, fmt.Sprintf(".%s", field)) 823 } 824 825 func inspectFieldJSON(name, field string) (string, error) { 826 return inspectFilter(name, fmt.Sprintf("json .%s", field)) 827 } 828 829 func inspectFieldMap(name, path, field string) (string, error) { 830 return inspectFilter(name, fmt.Sprintf("index .%s %q", path, field)) 831 } 832 833 func getIDByName(name string) (string, error) { 834 return inspectField(name, "Id") 835 } 836 837 // getContainerState returns the exit code of the container 838 // and true if it's running 839 // the exit code should be ignored if it's running 840 func getContainerState(c *check.C, id string) (int, bool, error) { 841 var ( 842 exitStatus int 843 running bool 844 ) 845 out, exitCode := dockerCmd(c, "inspect", "--format={{.State.Running}} {{.State.ExitCode}}", id) 846 if exitCode != 0 { 847 return 0, false, fmt.Errorf("%q doesn't exist: %s", id, out) 848 } 849 850 out = strings.Trim(out, "\n") 851 splitOutput := strings.Split(out, " ") 852 if len(splitOutput) != 2 { 853 return 0, false, fmt.Errorf("failed to get container state: output is broken") 854 } 855 if splitOutput[0] == "true" { 856 running = true 857 } 858 if n, err := strconv.Atoi(splitOutput[1]); err == nil { 859 exitStatus = n 860 } else { 861 return 0, false, fmt.Errorf("failed to get container state: couldn't parse integer") 862 } 863 864 return exitStatus, running, nil 865 } 866 867 func buildImageWithOut(name, dockerfile string, useCache bool) (string, string, error) { 868 args := []string{"build", "-t", name} 869 if !useCache { 870 args = append(args, "--no-cache") 871 } 872 args = append(args, "-") 873 buildCmd := exec.Command(dockerBinary, args...) 874 buildCmd.Stdin = strings.NewReader(dockerfile) 875 out, exitCode, err := runCommandWithOutput(buildCmd) 876 if err != nil || exitCode != 0 { 877 return "", out, fmt.Errorf("failed to build the image: %s", out) 878 } 879 id, err := getIDByName(name) 880 if err != nil { 881 return "", out, err 882 } 883 return id, out, nil 884 } 885 886 func buildImageWithStdoutStderr(name, dockerfile string, useCache bool) (string, string, string, error) { 887 args := []string{"build", "-t", name} 888 if !useCache { 889 args = append(args, "--no-cache") 890 } 891 args = append(args, "-") 892 buildCmd := exec.Command(dockerBinary, args...) 893 buildCmd.Stdin = strings.NewReader(dockerfile) 894 stdout, stderr, exitCode, err := runCommandWithStdoutStderr(buildCmd) 895 if err != nil || exitCode != 0 { 896 return "", stdout, stderr, fmt.Errorf("failed to build the image: %s", stdout) 897 } 898 id, err := getIDByName(name) 899 if err != nil { 900 return "", stdout, stderr, err 901 } 902 return id, stdout, stderr, nil 903 } 904 905 func buildImage(name, dockerfile string, useCache bool) (string, error) { 906 id, _, err := buildImageWithOut(name, dockerfile, useCache) 907 return id, err 908 } 909 910 func buildImageFromContext(name string, ctx *FakeContext, useCache bool) (string, error) { 911 args := []string{"build", "-t", name} 912 if !useCache { 913 args = append(args, "--no-cache") 914 } 915 args = append(args, ".") 916 buildCmd := exec.Command(dockerBinary, args...) 917 buildCmd.Dir = ctx.Dir 918 out, exitCode, err := runCommandWithOutput(buildCmd) 919 if err != nil || exitCode != 0 { 920 return "", fmt.Errorf("failed to build the image: %s", out) 921 } 922 return getIDByName(name) 923 } 924 925 func buildImageFromPath(name, path string, useCache bool) (string, error) { 926 args := []string{"build", "-t", name} 927 if !useCache { 928 args = append(args, "--no-cache") 929 } 930 args = append(args, path) 931 buildCmd := exec.Command(dockerBinary, args...) 932 out, exitCode, err := runCommandWithOutput(buildCmd) 933 if err != nil || exitCode != 0 { 934 return "", fmt.Errorf("failed to build the image: %s", out) 935 } 936 return getIDByName(name) 937 } 938 939 type GitServer interface { 940 URL() string 941 Close() error 942 } 943 944 type localGitServer struct { 945 *httptest.Server 946 } 947 948 func (r *localGitServer) Close() error { 949 r.Server.Close() 950 return nil 951 } 952 953 func (r *localGitServer) URL() string { 954 return r.Server.URL 955 } 956 957 type FakeGIT struct { 958 root string 959 server GitServer 960 RepoURL string 961 } 962 963 func (g *FakeGIT) Close() { 964 g.server.Close() 965 os.RemoveAll(g.root) 966 } 967 968 func fakeGIT(name string, files map[string]string, enforceLocalServer bool) (*FakeGIT, error) { 969 ctx, err := fakeContextWithFiles(files) 970 if err != nil { 971 return nil, err 972 } 973 defer ctx.Close() 974 curdir, err := os.Getwd() 975 if err != nil { 976 return nil, err 977 } 978 defer os.Chdir(curdir) 979 980 if output, err := exec.Command("git", "init", ctx.Dir).CombinedOutput(); err != nil { 981 return nil, fmt.Errorf("error trying to init repo: %s (%s)", err, output) 982 } 983 err = os.Chdir(ctx.Dir) 984 if err != nil { 985 return nil, err 986 } 987 if output, err := exec.Command("git", "config", "user.name", "Fake User").CombinedOutput(); err != nil { 988 return nil, fmt.Errorf("error trying to set 'user.name': %s (%s)", err, output) 989 } 990 if output, err := exec.Command("git", "config", "user.email", "fake.user@example.com").CombinedOutput(); err != nil { 991 return nil, fmt.Errorf("error trying to set 'user.email': %s (%s)", err, output) 992 } 993 if output, err := exec.Command("git", "add", "*").CombinedOutput(); err != nil { 994 return nil, fmt.Errorf("error trying to add files to repo: %s (%s)", err, output) 995 } 996 if output, err := exec.Command("git", "commit", "-a", "-m", "Initial commit").CombinedOutput(); err != nil { 997 return nil, fmt.Errorf("error trying to commit to repo: %s (%s)", err, output) 998 } 999 1000 root, err := ioutil.TempDir("", "docker-test-git-repo") 1001 if err != nil { 1002 return nil, err 1003 } 1004 repoPath := filepath.Join(root, name+".git") 1005 if output, err := exec.Command("git", "clone", "--bare", ctx.Dir, repoPath).CombinedOutput(); err != nil { 1006 os.RemoveAll(root) 1007 return nil, fmt.Errorf("error trying to clone --bare: %s (%s)", err, output) 1008 } 1009 err = os.Chdir(repoPath) 1010 if err != nil { 1011 os.RemoveAll(root) 1012 return nil, err 1013 } 1014 if output, err := exec.Command("git", "update-server-info").CombinedOutput(); err != nil { 1015 os.RemoveAll(root) 1016 return nil, fmt.Errorf("error trying to git update-server-info: %s (%s)", err, output) 1017 } 1018 err = os.Chdir(curdir) 1019 if err != nil { 1020 os.RemoveAll(root) 1021 return nil, err 1022 } 1023 1024 var server GitServer 1025 if !enforceLocalServer { 1026 // use fakeStorage server, which might be local or remote (at test daemon) 1027 server, err = fakeStorageWithContext(fakeContextFromDir(root)) 1028 if err != nil { 1029 return nil, fmt.Errorf("cannot start fake storage: %v", err) 1030 } 1031 } else { 1032 // always start a local http server on CLI test machin 1033 httpServer := httptest.NewServer(http.FileServer(http.Dir(root))) 1034 server = &localGitServer{httpServer} 1035 } 1036 return &FakeGIT{ 1037 root: root, 1038 server: server, 1039 RepoURL: fmt.Sprintf("%s/%s.git", server.URL(), name), 1040 }, nil 1041 } 1042 1043 // Write `content` to the file at path `dst`, creating it if necessary, 1044 // as well as any missing directories. 1045 // The file is truncated if it already exists. 1046 // Call c.Fatal() at the first error. 1047 func writeFile(dst, content string, c *check.C) { 1048 // Create subdirectories if necessary 1049 if err := os.MkdirAll(path.Dir(dst), 0700); err != nil && !os.IsExist(err) { 1050 c.Fatal(err) 1051 } 1052 f, err := os.OpenFile(dst, os.O_CREATE|os.O_RDWR|os.O_TRUNC, 0700) 1053 if err != nil { 1054 c.Fatal(err) 1055 } 1056 // Write content (truncate if it exists) 1057 if _, err := io.Copy(f, strings.NewReader(content)); err != nil { 1058 c.Fatal(err) 1059 } 1060 } 1061 1062 // Return the contents of file at path `src`. 1063 // Call c.Fatal() at the first error (including if the file doesn't exist) 1064 func readFile(src string, c *check.C) (content string) { 1065 data, err := ioutil.ReadFile(src) 1066 if err != nil { 1067 c.Fatal(err) 1068 } 1069 1070 return string(data) 1071 } 1072 1073 func containerStorageFile(containerId, basename string) string { 1074 return filepath.Join("/var/lib/docker/containers", containerId, basename) 1075 } 1076 1077 // docker commands that use this function must be run with the '-d' switch. 1078 func runCommandAndReadContainerFile(filename string, cmd *exec.Cmd) ([]byte, error) { 1079 out, _, err := runCommandWithOutput(cmd) 1080 if err != nil { 1081 return nil, fmt.Errorf("%v: %q", err, out) 1082 } 1083 1084 time.Sleep(1 * time.Second) 1085 1086 contID := strings.TrimSpace(out) 1087 1088 return readContainerFile(contID, filename) 1089 } 1090 1091 func readContainerFile(containerId, filename string) ([]byte, error) { 1092 f, err := os.Open(containerStorageFile(containerId, filename)) 1093 if err != nil { 1094 return nil, err 1095 } 1096 defer f.Close() 1097 1098 content, err := ioutil.ReadAll(f) 1099 if err != nil { 1100 return nil, err 1101 } 1102 1103 return content, nil 1104 } 1105 1106 func readContainerFileWithExec(containerId, filename string) ([]byte, error) { 1107 out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "exec", containerId, "cat", filename)) 1108 return []byte(out), err 1109 } 1110 1111 // daemonTime provides the current time on the daemon host 1112 func daemonTime(c *check.C) time.Time { 1113 if isLocalDaemon { 1114 return time.Now() 1115 } 1116 1117 status, body, err := sockRequest("GET", "/info", nil) 1118 c.Assert(status, check.Equals, http.StatusOK) 1119 c.Assert(err, check.IsNil) 1120 1121 type infoJSON struct { 1122 SystemTime string 1123 } 1124 var info infoJSON 1125 if err = json.Unmarshal(body, &info); err != nil { 1126 c.Fatalf("unable to unmarshal /info response: %v", err) 1127 } 1128 1129 dt, err := time.Parse(time.RFC3339Nano, info.SystemTime) 1130 if err != nil { 1131 c.Fatal(err) 1132 } 1133 return dt 1134 } 1135 1136 func setupRegistry(c *check.C) *testRegistryV2 { 1137 testRequires(c, RegistryHosting) 1138 reg, err := newTestRegistryV2(c) 1139 if err != nil { 1140 c.Fatal(err) 1141 } 1142 1143 // Wait for registry to be ready to serve requests. 1144 for i := 0; i != 5; i++ { 1145 if err = reg.Ping(); err == nil { 1146 break 1147 } 1148 time.Sleep(100 * time.Millisecond) 1149 } 1150 1151 if err != nil { 1152 c.Fatal("Timeout waiting for test registry to become available") 1153 } 1154 return reg 1155 } 1156 1157 // appendBaseEnv appends the minimum set of environment variables to exec the 1158 // docker cli binary for testing with correct configuration to the given env 1159 // list. 1160 func appendBaseEnv(env []string) []string { 1161 preserveList := []string{ 1162 // preserve remote test host 1163 "DOCKER_HOST", 1164 1165 // windows: requires preserving SystemRoot, otherwise dial tcp fails 1166 // with "GetAddrInfoW: A non-recoverable error occurred during a database lookup." 1167 "SystemRoot", 1168 } 1169 1170 for _, key := range preserveList { 1171 if val := os.Getenv(key); val != "" { 1172 env = append(env, fmt.Sprintf("%s=%s", key, val)) 1173 } 1174 } 1175 return env 1176 }