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