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