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