1// Copyright 2013 go-dockerclient authors. All rights reserved. 2// Use of this source code is governed by a BSD-style 3// license that can be found in the LICENSE file. 4 5// Package docker provides a client for the Docker remote API. 6// 7// See https://goo.gl/o2v3rk for more details on the remote API. 8package docker 9 10import ( 11 "bufio" 12 "bytes" 13 "context" 14 "crypto/tls" 15 "crypto/x509" 16 "encoding/json" 17 "errors" 18 "fmt" 19 "io" 20 "io/ioutil" 21 "net" 22 "net/http" 23 "net/http/httputil" 24 "net/url" 25 "os" 26 "path/filepath" 27 "reflect" 28 "runtime" 29 "strconv" 30 "strings" 31 "sync/atomic" 32 "time" 33 34 "github.com/ory/dockertest/docker/opts" 35 "github.com/ory/dockertest/docker/pkg/homedir" 36 "github.com/ory/dockertest/docker/pkg/jsonmessage" 37 "github.com/ory/dockertest/docker/pkg/stdcopy" 38) 39 40const ( 41 userAgent = "go-dockerclient" 42 43 unixProtocol = "unix" 44 namedPipeProtocol = "npipe" 45) 46 47var ( 48 // ErrInvalidEndpoint is returned when the endpoint is not a valid HTTP URL. 49 ErrInvalidEndpoint = errors.New("invalid endpoint") 50 51 // ErrConnectionRefused is returned when the client cannot connect to the given endpoint. 52 ErrConnectionRefused = errors.New("cannot connect to Docker endpoint") 53 54 // ErrInactivityTimeout is returned when a streamable call has been inactive for some time. 55 ErrInactivityTimeout = errors.New("inactivity time exceeded timeout") 56 57 apiVersion112, _ = NewAPIVersion("1.12") 58 apiVersion119, _ = NewAPIVersion("1.19") 59 apiVersion124, _ = NewAPIVersion("1.24") 60 apiVersion125, _ = NewAPIVersion("1.25") 61) 62 63// APIVersion is an internal representation of a version of the Remote API. 64type APIVersion []int 65 66// NewAPIVersion returns an instance of APIVersion for the given string. 67// 68// The given string must be in the form <major>.<minor>.<patch>, where <major>, 69// <minor> and <patch> are integer numbers. 70func NewAPIVersion(input string) (APIVersion, error) { 71 if !strings.Contains(input, ".") { 72 return nil, fmt.Errorf("Unable to parse version %q", input) 73 } 74 raw := strings.Split(input, "-") 75 arr := strings.Split(raw[0], ".") 76 ret := make(APIVersion, len(arr)) 77 var err error 78 for i, val := range arr { 79 ret[i], err = strconv.Atoi(val) 80 if err != nil { 81 return nil, fmt.Errorf("Unable to parse version %q: %q is not an integer", input, val) 82 } 83 } 84 return ret, nil 85} 86 87func (version APIVersion) String() string { 88 var str string 89 for i, val := range version { 90 str += strconv.Itoa(val) 91 if i < len(version)-1 { 92 str += "." 93 } 94 } 95 return str 96} 97 98// LessThan is a function for comparing APIVersion structs 99func (version APIVersion) LessThan(other APIVersion) bool { 100 return version.compare(other) < 0 101} 102 103// LessThanOrEqualTo is a function for comparing APIVersion structs 104func (version APIVersion) LessThanOrEqualTo(other APIVersion) bool { 105 return version.compare(other) <= 0 106} 107 108// GreaterThan is a function for comparing APIVersion structs 109func (version APIVersion) GreaterThan(other APIVersion) bool { 110 return version.compare(other) > 0 111} 112 113// GreaterThanOrEqualTo is a function for comparing APIVersion structs 114func (version APIVersion) GreaterThanOrEqualTo(other APIVersion) bool { 115 return version.compare(other) >= 0 116} 117 118func (version APIVersion) compare(other APIVersion) int { 119 for i, v := range version { 120 if i <= len(other)-1 { 121 otherVersion := other[i] 122 123 if v < otherVersion { 124 return -1 125 } else if v > otherVersion { 126 return 1 127 } 128 } 129 } 130 if len(version) > len(other) { 131 return 1 132 } 133 if len(version) < len(other) { 134 return -1 135 } 136 return 0 137} 138 139// Client is the basic type of this package. It provides methods for 140// interaction with the API. 141type Client struct { 142 SkipServerVersionCheck bool 143 HTTPClient *http.Client 144 TLSConfig *tls.Config 145 Dialer Dialer 146 147 endpoint string 148 endpointURL *url.URL 149 eventMonitor *eventMonitoringState 150 requestedAPIVersion APIVersion 151 serverAPIVersion APIVersion 152 expectedAPIVersion APIVersion 153} 154 155// Dialer is an interface that allows network connections to be dialed 156// (net.Dialer fulfills this interface) and named pipes (a shim using 157// winio.DialPipe) 158type Dialer interface { 159 Dial(network, address string) (net.Conn, error) 160} 161 162// NewClient returns a Client instance ready for communication with the given 163// server endpoint. It will use the latest remote API version available in the 164// server. 165func NewClient(endpoint string) (*Client, error) { 166 client, err := NewVersionedClient(endpoint, "") 167 if err != nil { 168 return nil, err 169 } 170 client.SkipServerVersionCheck = true 171 return client, nil 172} 173 174// NewTLSClient returns a Client instance ready for TLS communications with the givens 175// server endpoint, key and certificates . It will use the latest remote API version 176// available in the server. 177func NewTLSClient(endpoint string, cert, key, ca string) (*Client, error) { 178 client, err := NewVersionedTLSClient(endpoint, cert, key, ca, "") 179 if err != nil { 180 return nil, err 181 } 182 client.SkipServerVersionCheck = true 183 return client, nil 184} 185 186// NewTLSClientFromBytes returns a Client instance ready for TLS communications with the givens 187// server endpoint, key and certificates (passed inline to the function as opposed to being 188// read from a local file). It will use the latest remote API version available in the server. 189func NewTLSClientFromBytes(endpoint string, certPEMBlock, keyPEMBlock, caPEMCert []byte) (*Client, error) { 190 client, err := NewVersionedTLSClientFromBytes(endpoint, certPEMBlock, keyPEMBlock, caPEMCert, "") 191 if err != nil { 192 return nil, err 193 } 194 client.SkipServerVersionCheck = true 195 return client, nil 196} 197 198// NewVersionedClient returns a Client instance ready for communication with 199// the given server endpoint, using a specific remote API version. 200func NewVersionedClient(endpoint string, apiVersionString string) (*Client, error) { 201 u, err := parseEndpoint(endpoint, false) 202 if err != nil { 203 return nil, err 204 } 205 var requestedAPIVersion APIVersion 206 if strings.Contains(apiVersionString, ".") { 207 requestedAPIVersion, err = NewAPIVersion(apiVersionString) 208 if err != nil { 209 return nil, err 210 } 211 } 212 c := &Client{ 213 HTTPClient: defaultClient(), 214 Dialer: &net.Dialer{}, 215 endpoint: endpoint, 216 endpointURL: u, 217 eventMonitor: new(eventMonitoringState), 218 requestedAPIVersion: requestedAPIVersion, 219 } 220 c.initializeNativeClient(defaultTransport) 221 return c, nil 222} 223 224// WithTransport replaces underlying HTTP client of Docker Client by accepting 225// a function that returns pointer to a transport object. 226func (c *Client) WithTransport(trFunc func() *http.Transport) { 227 c.initializeNativeClient(trFunc) 228} 229 230// NewVersionnedTLSClient is like NewVersionedClient, but with ann extra n. 231// 232// Deprecated: Use NewVersionedTLSClient instead. 233func NewVersionnedTLSClient(endpoint string, cert, key, ca, apiVersionString string) (*Client, error) { 234 return NewVersionedTLSClient(endpoint, cert, key, ca, apiVersionString) 235} 236 237// NewVersionedTLSClient returns a Client instance ready for TLS communications with the givens 238// server endpoint, key and certificates, using a specific remote API version. 239func NewVersionedTLSClient(endpoint string, cert, key, ca, apiVersionString string) (*Client, error) { 240 var certPEMBlock []byte 241 var keyPEMBlock []byte 242 var caPEMCert []byte 243 if _, err := os.Stat(cert); !os.IsNotExist(err) { 244 certPEMBlock, err = ioutil.ReadFile(cert) 245 if err != nil { 246 return nil, err 247 } 248 } 249 if _, err := os.Stat(key); !os.IsNotExist(err) { 250 keyPEMBlock, err = ioutil.ReadFile(key) 251 if err != nil { 252 return nil, err 253 } 254 } 255 if _, err := os.Stat(ca); !os.IsNotExist(err) { 256 caPEMCert, err = ioutil.ReadFile(ca) 257 if err != nil { 258 return nil, err 259 } 260 } 261 return NewVersionedTLSClientFromBytes(endpoint, certPEMBlock, keyPEMBlock, caPEMCert, apiVersionString) 262} 263 264// NewClientFromEnv returns a Client instance ready for communication created from 265// Docker's default logic for the environment variables DOCKER_HOST, DOCKER_TLS_VERIFY, and DOCKER_CERT_PATH. 266// 267// See https://github.com/docker/docker/blob/1f963af697e8df3a78217f6fdbf67b8123a7db94/docker/docker.go#L68. 268// See https://github.com/docker/compose/blob/81707ef1ad94403789166d2fe042c8a718a4c748/compose/cli/docker_client.py#L7. 269func NewClientFromEnv() (*Client, error) { 270 client, err := NewVersionedClientFromEnv("") 271 if err != nil { 272 return nil, err 273 } 274 client.SkipServerVersionCheck = true 275 return client, nil 276} 277 278// NewVersionedClientFromEnv returns a Client instance ready for TLS communications created from 279// Docker's default logic for the environment variables DOCKER_HOST, DOCKER_TLS_VERIFY, and DOCKER_CERT_PATH, 280// and using a specific remote API version. 281// 282// See https://github.com/docker/docker/blob/1f963af697e8df3a78217f6fdbf67b8123a7db94/docker/docker.go#L68. 283// See https://github.com/docker/compose/blob/81707ef1ad94403789166d2fe042c8a718a4c748/compose/cli/docker_client.py#L7. 284func NewVersionedClientFromEnv(apiVersionString string) (*Client, error) { 285 dockerEnv, err := getDockerEnv() 286 if err != nil { 287 return nil, err 288 } 289 dockerHost := dockerEnv.dockerHost 290 if dockerEnv.dockerTLSVerify { 291 parts := strings.SplitN(dockerEnv.dockerHost, "://", 2) 292 if len(parts) != 2 { 293 return nil, fmt.Errorf("could not split %s into two parts by ://", dockerHost) 294 } 295 cert := filepath.Join(dockerEnv.dockerCertPath, "cert.pem") 296 key := filepath.Join(dockerEnv.dockerCertPath, "key.pem") 297 ca := filepath.Join(dockerEnv.dockerCertPath, "ca.pem") 298 return NewVersionedTLSClient(dockerEnv.dockerHost, cert, key, ca, apiVersionString) 299 } 300 return NewVersionedClient(dockerEnv.dockerHost, apiVersionString) 301} 302 303// NewVersionedTLSClientFromBytes returns a Client instance ready for TLS communications with the givens 304// server endpoint, key and certificates (passed inline to the function as opposed to being 305// read from a local file), using a specific remote API version. 306func NewVersionedTLSClientFromBytes(endpoint string, certPEMBlock, keyPEMBlock, caPEMCert []byte, apiVersionString string) (*Client, error) { 307 u, err := parseEndpoint(endpoint, true) 308 if err != nil { 309 return nil, err 310 } 311 var requestedAPIVersion APIVersion 312 if strings.Contains(apiVersionString, ".") { 313 requestedAPIVersion, err = NewAPIVersion(apiVersionString) 314 if err != nil { 315 return nil, err 316 } 317 } 318 tlsConfig := &tls.Config{} 319 if certPEMBlock != nil && keyPEMBlock != nil { 320 tlsCert, err := tls.X509KeyPair(certPEMBlock, keyPEMBlock) 321 if err != nil { 322 return nil, err 323 } 324 tlsConfig.Certificates = []tls.Certificate{tlsCert} 325 } 326 if caPEMCert == nil { 327 tlsConfig.InsecureSkipVerify = true 328 } else { 329 caPool := x509.NewCertPool() 330 if !caPool.AppendCertsFromPEM(caPEMCert) { 331 return nil, errors.New("Could not add RootCA pem") 332 } 333 tlsConfig.RootCAs = caPool 334 } 335 tr := defaultTransport() 336 tr.TLSClientConfig = tlsConfig 337 if err != nil { 338 return nil, err 339 } 340 c := &Client{ 341 HTTPClient: &http.Client{Transport: tr}, 342 TLSConfig: tlsConfig, 343 Dialer: &net.Dialer{}, 344 endpoint: endpoint, 345 endpointURL: u, 346 eventMonitor: new(eventMonitoringState), 347 requestedAPIVersion: requestedAPIVersion, 348 } 349 c.initializeNativeClient(defaultTransport) 350 return c, nil 351} 352 353// SetTimeout takes a timeout and applies it to the HTTPClient. It should not 354// be called concurrently with any other Client methods. 355func (c *Client) SetTimeout(t time.Duration) { 356 if c.HTTPClient != nil { 357 c.HTTPClient.Timeout = t 358 } 359} 360 361func (c *Client) checkAPIVersion() error { 362 serverAPIVersionString, err := c.getServerAPIVersionString() 363 if err != nil { 364 return err 365 } 366 c.serverAPIVersion, err = NewAPIVersion(serverAPIVersionString) 367 if err != nil { 368 return err 369 } 370 if c.requestedAPIVersion == nil { 371 c.expectedAPIVersion = c.serverAPIVersion 372 } else { 373 c.expectedAPIVersion = c.requestedAPIVersion 374 } 375 return nil 376} 377 378// Endpoint returns the current endpoint. It's useful for getting the endpoint 379// when using functions that get this data from the environment (like 380// NewClientFromEnv. 381func (c *Client) Endpoint() string { 382 return c.endpoint 383} 384 385// Ping pings the docker server 386// 387// See https://goo.gl/wYfgY1 for more details. 388func (c *Client) Ping() error { 389 return c.PingWithContext(nil) 390} 391 392// PingWithContext pings the docker server 393// The context object can be used to cancel the ping request. 394// 395// See https://goo.gl/wYfgY1 for more details. 396func (c *Client) PingWithContext(ctx context.Context) error { 397 path := "/_ping" 398 resp, err := c.do("GET", path, doOptions{context: ctx}) 399 if err != nil { 400 return err 401 } 402 if resp.StatusCode != http.StatusOK { 403 return newError(resp) 404 } 405 resp.Body.Close() 406 return nil 407} 408 409func (c *Client) getServerAPIVersionString() (version string, err error) { 410 resp, err := c.do("GET", "/version", doOptions{}) 411 if err != nil { 412 return "", err 413 } 414 defer resp.Body.Close() 415 if resp.StatusCode != http.StatusOK { 416 return "", fmt.Errorf("Received unexpected status %d while trying to retrieve the server version", resp.StatusCode) 417 } 418 var versionResponse map[string]interface{} 419 if err := json.NewDecoder(resp.Body).Decode(&versionResponse); err != nil { 420 return "", err 421 } 422 if version, ok := (versionResponse["ApiVersion"]).(string); ok { 423 return version, nil 424 } 425 return "", nil 426} 427 428type doOptions struct { 429 data interface{} 430 forceJSON bool 431 headers map[string]string 432 context context.Context 433} 434 435func (c *Client) do(method, path string, doOptions doOptions) (*http.Response, error) { 436 var params io.Reader 437 if doOptions.data != nil || doOptions.forceJSON { 438 buf, err := json.Marshal(doOptions.data) 439 if err != nil { 440 return nil, err 441 } 442 params = bytes.NewBuffer(buf) 443 } 444 if path != "/version" && !c.SkipServerVersionCheck && c.expectedAPIVersion == nil { 445 err := c.checkAPIVersion() 446 if err != nil { 447 return nil, err 448 } 449 } 450 protocol := c.endpointURL.Scheme 451 var u string 452 switch protocol { 453 case unixProtocol, namedPipeProtocol: 454 u = c.getFakeNativeURL(path) 455 default: 456 u = c.getURL(path) 457 } 458 459 req, err := http.NewRequest(method, u, params) 460 if err != nil { 461 return nil, err 462 } 463 req.Header.Set("User-Agent", userAgent) 464 if doOptions.data != nil { 465 req.Header.Set("Content-Type", "application/json") 466 } else if method == "POST" { 467 req.Header.Set("Content-Type", "plain/text") 468 } 469 470 for k, v := range doOptions.headers { 471 req.Header.Set(k, v) 472 } 473 474 ctx := doOptions.context 475 if ctx == nil { 476 ctx = context.Background() 477 } 478 479 resp, err := c.HTTPClient.Do(req.WithContext(ctx)) 480 if err != nil { 481 if strings.Contains(err.Error(), "connection refused") { 482 return nil, ErrConnectionRefused 483 } 484 485 return nil, chooseError(ctx, err) 486 } 487 if resp.StatusCode < 200 || resp.StatusCode >= 400 { 488 return nil, newError(resp) 489 } 490 return resp, nil 491} 492 493type streamOptions struct { 494 setRawTerminal bool 495 rawJSONStream bool 496 useJSONDecoder bool 497 headers map[string]string 498 in io.Reader 499 stdout io.Writer 500 stderr io.Writer 501 reqSent chan struct{} 502 // timeout is the initial connection timeout 503 timeout time.Duration 504 // Timeout with no data is received, it's reset every time new data 505 // arrives 506 inactivityTimeout time.Duration 507 context context.Context 508} 509 510// if error in context, return that instead of generic http error 511func chooseError(ctx context.Context, err error) error { 512 select { 513 case <-ctx.Done(): 514 return ctx.Err() 515 default: 516 return err 517 } 518} 519 520func (c *Client) stream(method, path string, streamOptions streamOptions) error { 521 if (method == "POST" || method == "PUT") && streamOptions.in == nil { 522 streamOptions.in = bytes.NewReader(nil) 523 } 524 if path != "/version" && !c.SkipServerVersionCheck && c.expectedAPIVersion == nil { 525 err := c.checkAPIVersion() 526 if err != nil { 527 return err 528 } 529 } 530 req, err := http.NewRequest(method, c.getURL(path), streamOptions.in) 531 if err != nil { 532 return err 533 } 534 req.Header.Set("User-Agent", userAgent) 535 if method == "POST" { 536 req.Header.Set("Content-Type", "plain/text") 537 } 538 for key, val := range streamOptions.headers { 539 req.Header.Set(key, val) 540 } 541 var resp *http.Response 542 protocol := c.endpointURL.Scheme 543 address := c.endpointURL.Path 544 if streamOptions.stdout == nil { 545 streamOptions.stdout = ioutil.Discard 546 } 547 if streamOptions.stderr == nil { 548 streamOptions.stderr = ioutil.Discard 549 } 550 551 // make a sub-context so that our active cancellation does not affect parent 552 ctx := streamOptions.context 553 if ctx == nil { 554 ctx = context.Background() 555 } 556 subCtx, cancelRequest := context.WithCancel(ctx) 557 defer cancelRequest() 558 559 if protocol == unixProtocol || protocol == namedPipeProtocol { 560 var dial net.Conn 561 dial, err = c.Dialer.Dial(protocol, address) 562 if err != nil { 563 return err 564 } 565 go func() { 566 <-subCtx.Done() 567 dial.Close() 568 }() 569 breader := bufio.NewReader(dial) 570 err = req.Write(dial) 571 if err != nil { 572 return chooseError(subCtx, err) 573 } 574 575 // ReadResponse may hang if server does not replay 576 if streamOptions.timeout > 0 { 577 dial.SetDeadline(time.Now().Add(streamOptions.timeout)) 578 } 579 580 if streamOptions.reqSent != nil { 581 close(streamOptions.reqSent) 582 } 583 if resp, err = http.ReadResponse(breader, req); err != nil { 584 // Cancel timeout for future I/O operations 585 if streamOptions.timeout > 0 { 586 dial.SetDeadline(time.Time{}) 587 } 588 if strings.Contains(err.Error(), "connection refused") { 589 return ErrConnectionRefused 590 } 591 592 return chooseError(subCtx, err) 593 } 594 } else { 595 if resp, err = c.HTTPClient.Do(req.WithContext(subCtx)); err != nil { 596 if strings.Contains(err.Error(), "connection refused") { 597 return ErrConnectionRefused 598 } 599 return chooseError(subCtx, err) 600 } 601 if streamOptions.reqSent != nil { 602 close(streamOptions.reqSent) 603 } 604 } 605 defer resp.Body.Close() 606 if resp.StatusCode < 200 || resp.StatusCode >= 400 { 607 return newError(resp) 608 } 609 var canceled uint32 610 if streamOptions.inactivityTimeout > 0 { 611 var ch chan<- struct{} 612 resp.Body, ch = handleInactivityTimeout(resp.Body, streamOptions.inactivityTimeout, cancelRequest, &canceled) 613 defer close(ch) 614 } 615 err = handleStreamResponse(resp, &streamOptions) 616 if err != nil { 617 if atomic.LoadUint32(&canceled) != 0 { 618 return ErrInactivityTimeout 619 } 620 return chooseError(subCtx, err) 621 } 622 return nil 623} 624 625func handleStreamResponse(resp *http.Response, streamOptions *streamOptions) error { 626 var err error 627 if !streamOptions.useJSONDecoder && resp.Header.Get("Content-Type") != "application/json" { 628 if streamOptions.setRawTerminal { 629 _, err = io.Copy(streamOptions.stdout, resp.Body) 630 } else { 631 _, err = stdcopy.StdCopy(streamOptions.stdout, streamOptions.stderr, resp.Body) 632 } 633 return err 634 } 635 // if we want to get raw json stream, just copy it back to output 636 // without decoding it 637 if streamOptions.rawJSONStream { 638 _, err = io.Copy(streamOptions.stdout, resp.Body) 639 return err 640 } 641 if st, ok := streamOptions.stdout.(interface { 642 io.Writer 643 FD() uintptr 644 IsTerminal() bool 645 }); ok { 646 err = jsonmessage.DisplayJSONMessagesToStream(resp.Body, st, nil) 647 } else { 648 err = jsonmessage.DisplayJSONMessagesStream(resp.Body, streamOptions.stdout, 0, false, nil) 649 } 650 return err 651} 652 653type proxyReader struct { 654 io.ReadCloser 655 calls uint64 656} 657 658func (p *proxyReader) callCount() uint64 { 659 return atomic.LoadUint64(&p.calls) 660} 661 662func (p *proxyReader) Read(data []byte) (int, error) { 663 atomic.AddUint64(&p.calls, 1) 664 return p.ReadCloser.Read(data) 665} 666 667func handleInactivityTimeout(reader io.ReadCloser, timeout time.Duration, cancelRequest func(), canceled *uint32) (io.ReadCloser, chan<- struct{}) { 668 done := make(chan struct{}) 669 proxyReader := &proxyReader{ReadCloser: reader} 670 go func() { 671 var lastCallCount uint64 672 for { 673 select { 674 case <-time.After(timeout): 675 case <-done: 676 return 677 } 678 curCallCount := proxyReader.callCount() 679 if curCallCount == lastCallCount { 680 atomic.AddUint32(canceled, 1) 681 cancelRequest() 682 return 683 } 684 lastCallCount = curCallCount 685 } 686 }() 687 return proxyReader, done 688} 689 690type hijackOptions struct { 691 success chan struct{} 692 setRawTerminal bool 693 in io.Reader 694 stdout io.Writer 695 stderr io.Writer 696 data interface{} 697} 698 699// CloseWaiter is an interface with methods for closing the underlying resource 700// and then waiting for it to finish processing. 701type CloseWaiter interface { 702 io.Closer 703 Wait() error 704} 705 706type waiterFunc func() error 707 708func (w waiterFunc) Wait() error { return w() } 709 710type closerFunc func() error 711 712func (c closerFunc) Close() error { return c() } 713 714func (c *Client) hijack(method, path string, hijackOptions hijackOptions) (CloseWaiter, error) { 715 if path != "/version" && !c.SkipServerVersionCheck && c.expectedAPIVersion == nil { 716 err := c.checkAPIVersion() 717 if err != nil { 718 return nil, err 719 } 720 } 721 var params io.Reader 722 if hijackOptions.data != nil { 723 buf, err := json.Marshal(hijackOptions.data) 724 if err != nil { 725 return nil, err 726 } 727 params = bytes.NewBuffer(buf) 728 } 729 req, err := http.NewRequest(method, c.getURL(path), params) 730 if err != nil { 731 return nil, err 732 } 733 req.Header.Set("Content-Type", "application/json") 734 req.Header.Set("Connection", "Upgrade") 735 req.Header.Set("Upgrade", "tcp") 736 protocol := c.endpointURL.Scheme 737 address := c.endpointURL.Path 738 if protocol != unixProtocol && protocol != namedPipeProtocol { 739 protocol = "tcp" 740 address = c.endpointURL.Host 741 } 742 var dial net.Conn 743 if c.TLSConfig != nil && protocol != unixProtocol && protocol != namedPipeProtocol { 744 netDialer, ok := c.Dialer.(*net.Dialer) 745 if !ok { 746 return nil, ErrTLSNotSupported 747 } 748 dial, err = tlsDialWithDialer(netDialer, protocol, address, c.TLSConfig) 749 if err != nil { 750 return nil, err 751 } 752 } else { 753 dial, err = c.Dialer.Dial(protocol, address) 754 if err != nil { 755 return nil, err 756 } 757 } 758 759 errs := make(chan error, 1) 760 quit := make(chan struct{}) 761 go func() { 762 clientconn := httputil.NewClientConn(dial, nil) 763 defer clientconn.Close() 764 clientconn.Do(req) 765 if hijackOptions.success != nil { 766 hijackOptions.success <- struct{}{} 767 <-hijackOptions.success 768 } 769 rwc, br := clientconn.Hijack() 770 defer rwc.Close() 771 772 errChanOut := make(chan error, 1) 773 errChanIn := make(chan error, 2) 774 if hijackOptions.stdout == nil && hijackOptions.stderr == nil { 775 close(errChanOut) 776 } else { 777 // Only copy if hijackOptions.stdout and/or hijackOptions.stderr is actually set. 778 // Otherwise, if the only stream you care about is stdin, your attach session 779 // will "hang" until the container terminates, even though you're not reading 780 // stdout/stderr 781 if hijackOptions.stdout == nil { 782 hijackOptions.stdout = ioutil.Discard 783 } 784 if hijackOptions.stderr == nil { 785 hijackOptions.stderr = ioutil.Discard 786 } 787 788 go func() { 789 defer func() { 790 if hijackOptions.in != nil { 791 if closer, ok := hijackOptions.in.(io.Closer); ok { 792 closer.Close() 793 } 794 errChanIn <- nil 795 } 796 }() 797 798 var err error 799 if hijackOptions.setRawTerminal { 800 _, err = io.Copy(hijackOptions.stdout, br) 801 } else { 802 _, err = stdcopy.StdCopy(hijackOptions.stdout, hijackOptions.stderr, br) 803 } 804 errChanOut <- err 805 }() 806 } 807 808 go func() { 809 var err error 810 if hijackOptions.in != nil { 811 _, err = io.Copy(rwc, hijackOptions.in) 812 } 813 errChanIn <- err 814 rwc.(interface { 815 CloseWrite() error 816 }).CloseWrite() 817 }() 818 819 var errIn error 820 select { 821 case errIn = <-errChanIn: 822 case <-quit: 823 } 824 825 var errOut error 826 select { 827 case errOut = <-errChanOut: 828 case <-quit: 829 } 830 831 if errIn != nil { 832 errs <- errIn 833 } else { 834 errs <- errOut 835 } 836 }() 837 838 return struct { 839 closerFunc 840 waiterFunc 841 }{ 842 closerFunc(func() error { close(quit); return nil }), 843 waiterFunc(func() error { return <-errs }), 844 }, nil 845} 846 847func (c *Client) getURL(path string) string { 848 urlStr := strings.TrimRight(c.endpointURL.String(), "/") 849 if c.endpointURL.Scheme == unixProtocol || c.endpointURL.Scheme == namedPipeProtocol { 850 urlStr = "" 851 } 852 if c.requestedAPIVersion != nil { 853 return fmt.Sprintf("%s/v%s%s", urlStr, c.requestedAPIVersion, path) 854 } 855 return fmt.Sprintf("%s%s", urlStr, path) 856} 857 858// getFakeNativeURL returns the URL needed to make an HTTP request over a UNIX 859// domain socket to the given path. 860func (c *Client) getFakeNativeURL(path string) string { 861 u := *c.endpointURL // Copy. 862 863 // Override URL so that net/http will not complain. 864 u.Scheme = "http" 865 u.Host = "unix.sock" // Doesn't matter what this is - it's not used. 866 u.Path = "" 867 urlStr := strings.TrimRight(u.String(), "/") 868 if c.requestedAPIVersion != nil { 869 return fmt.Sprintf("%s/v%s%s", urlStr, c.requestedAPIVersion, path) 870 } 871 return fmt.Sprintf("%s%s", urlStr, path) 872} 873 874type jsonMessage struct { 875 Status string `json:"status,omitempty"` 876 Progress string `json:"progress,omitempty"` 877 Error string `json:"error,omitempty"` 878 Stream string `json:"stream,omitempty"` 879} 880 881func queryString(opts interface{}) string { 882 if opts == nil { 883 return "" 884 } 885 value := reflect.ValueOf(opts) 886 if value.Kind() == reflect.Ptr { 887 value = value.Elem() 888 } 889 if value.Kind() != reflect.Struct { 890 return "" 891 } 892 items := url.Values(map[string][]string{}) 893 for i := 0; i < value.NumField(); i++ { 894 field := value.Type().Field(i) 895 if field.PkgPath != "" { 896 continue 897 } 898 key := field.Tag.Get("qs") 899 if key == "" { 900 key = strings.ToLower(field.Name) 901 } else if key == "-" { 902 continue 903 } 904 addQueryStringValue(items, key, value.Field(i)) 905 } 906 return items.Encode() 907} 908 909func addQueryStringValue(items url.Values, key string, v reflect.Value) { 910 switch v.Kind() { 911 case reflect.Bool: 912 if v.Bool() { 913 items.Add(key, "1") 914 } 915 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 916 if v.Int() > 0 { 917 items.Add(key, strconv.FormatInt(v.Int(), 10)) 918 } 919 case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: 920 if v.Uint() > 0 { 921 items.Add(key, strconv.FormatUint(v.Uint(), 10)) 922 } 923 case reflect.Float32, reflect.Float64: 924 if v.Float() > 0 { 925 items.Add(key, strconv.FormatFloat(v.Float(), 'f', -1, 64)) 926 } 927 case reflect.String: 928 if v.String() != "" { 929 items.Add(key, v.String()) 930 } 931 case reflect.Ptr: 932 if !v.IsNil() { 933 if b, err := json.Marshal(v.Interface()); err == nil { 934 items.Add(key, string(b)) 935 } 936 } 937 case reflect.Map: 938 if len(v.MapKeys()) > 0 { 939 if b, err := json.Marshal(v.Interface()); err == nil { 940 items.Add(key, string(b)) 941 } 942 } 943 case reflect.Array, reflect.Slice: 944 vLen := v.Len() 945 if vLen > 0 { 946 for i := 0; i < vLen; i++ { 947 addQueryStringValue(items, key, v.Index(i)) 948 } 949 } 950 } 951} 952 953// Error represents failures in the API. It represents a failure from the API. 954type Error struct { 955 Status int 956 Message string 957} 958 959func newError(resp *http.Response) *Error { 960 type ErrMsg struct { 961 Message string `json:"message"` 962 } 963 defer resp.Body.Close() 964 data, err := ioutil.ReadAll(resp.Body) 965 if err != nil { 966 return &Error{Status: resp.StatusCode, Message: fmt.Sprintf("cannot read body, err: %v", err)} 967 } 968 var emsg ErrMsg 969 err = json.Unmarshal(data, &emsg) 970 if err != nil { 971 return &Error{Status: resp.StatusCode, Message: string(data)} 972 } 973 return &Error{Status: resp.StatusCode, Message: emsg.Message} 974} 975 976func (e *Error) Error() string { 977 return fmt.Sprintf("API error (%d): %s", e.Status, e.Message) 978} 979 980func parseEndpoint(endpoint string, tls bool) (*url.URL, error) { 981 if endpoint != "" && !strings.Contains(endpoint, "://") { 982 endpoint = "tcp://" + endpoint 983 } 984 u, err := url.Parse(endpoint) 985 if err != nil { 986 return nil, ErrInvalidEndpoint 987 } 988 if tls && u.Scheme != "unix" { 989 u.Scheme = "https" 990 } 991 switch u.Scheme { 992 case unixProtocol, namedPipeProtocol: 993 return u, nil 994 case "http", "https", "tcp": 995 _, port, err := net.SplitHostPort(u.Host) 996 if err != nil { 997 if e, ok := err.(*net.AddrError); ok { 998 if e.Err == "missing port in address" { 999 return u, nil 1000 } 1001 } 1002 return nil, ErrInvalidEndpoint 1003 } 1004 number, err := strconv.ParseInt(port, 10, 64) 1005 if err == nil && number > 0 && number < 65536 { 1006 if u.Scheme == "tcp" { 1007 if tls { 1008 u.Scheme = "https" 1009 } else { 1010 u.Scheme = "http" 1011 } 1012 } 1013 return u, nil 1014 } 1015 return nil, ErrInvalidEndpoint 1016 default: 1017 return nil, ErrInvalidEndpoint 1018 } 1019} 1020 1021type dockerEnv struct { 1022 dockerHost string 1023 dockerTLSVerify bool 1024 dockerCertPath string 1025} 1026 1027func getDockerEnv() (*dockerEnv, error) { 1028 dockerHost := os.Getenv("DOCKER_HOST") 1029 var err error 1030 if dockerHost == "" { 1031 dockerHost = opts.DefaultHost 1032 } 1033 dockerTLSVerify := os.Getenv("DOCKER_TLS_VERIFY") != "" 1034 var dockerCertPath string 1035 if dockerTLSVerify { 1036 dockerCertPath = os.Getenv("DOCKER_CERT_PATH") 1037 if dockerCertPath == "" { 1038 home := homedir.Get() 1039 if home == "" { 1040 return nil, errors.New("environment variable HOME must be set if DOCKER_CERT_PATH is not set") 1041 } 1042 dockerCertPath = filepath.Join(home, ".docker") 1043 dockerCertPath, err = filepath.Abs(dockerCertPath) 1044 if err != nil { 1045 return nil, err 1046 } 1047 } 1048 } 1049 return &dockerEnv{ 1050 dockerHost: dockerHost, 1051 dockerTLSVerify: dockerTLSVerify, 1052 dockerCertPath: dockerCertPath, 1053 }, nil 1054} 1055 1056// defaultTransport returns a new http.Transport with similar default values to 1057// http.DefaultTransport, but with idle connections and keepalives disabled. 1058func defaultTransport() *http.Transport { 1059 transport := defaultPooledTransport() 1060 transport.DisableKeepAlives = true 1061 transport.MaxIdleConnsPerHost = -1 1062 return transport 1063} 1064 1065// defaultPooledTransport returns a new http.Transport with similar default 1066// values to http.DefaultTransport. Do not use this for transient transports as 1067// it can leak file descriptors over time. Only use this for transports that 1068// will be re-used for the same host(s). 1069func defaultPooledTransport() *http.Transport { 1070 transport := &http.Transport{ 1071 Proxy: http.ProxyFromEnvironment, 1072 DialContext: (&net.Dialer{ 1073 Timeout: 30 * time.Second, 1074 KeepAlive: 30 * time.Second, 1075 }).DialContext, 1076 MaxIdleConns: 100, 1077 IdleConnTimeout: 90 * time.Second, 1078 TLSHandshakeTimeout: 10 * time.Second, 1079 ExpectContinueTimeout: 1 * time.Second, 1080 MaxIdleConnsPerHost: runtime.GOMAXPROCS(0) + 1, 1081 } 1082 return transport 1083} 1084 1085// defaultClient returns a new http.Client with similar default values to 1086// http.Client, but with a non-shared Transport, idle connections disabled, and 1087// keepalives disabled. 1088func defaultClient() *http.Client { 1089 return &http.Client{ 1090 Transport: defaultTransport(), 1091 } 1092} 1093