github.com/zhouyu0/docker-note@v0.0.0-20190722021225-b8d3825084db/client/client.go (about)

     1  /*
     2  Package client is a Go client for the Docker Engine API.
     3  
     4  For more information about the Engine API, see the documentation:
     5  https://docs.docker.com/engine/reference/api/
     6  
     7  Usage
     8  
     9  You use the library by creating a client object and calling methods on it. The
    10  client can be created either from environment variables with NewEnvClient, or
    11  configured manually with NewClient.
    12  
    13  For example, to list running containers (the equivalent of "docker ps"):
    14  
    15  	package main
    16  
    17  	import (
    18  		"context"
    19  		"fmt"
    20  
    21  		"github.com/docker/docker/api/types"
    22  		"github.com/docker/docker/client"
    23  	)
    24  
    25  	func main() {
    26  		cli, err := client.NewClientWithOpts(client.FromEnv)
    27  		if err != nil {
    28  			panic(err)
    29  		}
    30  
    31  		containers, err := cli.ContainerList(context.Background(), types.ContainerListOptions{})
    32  		if err != nil {
    33  			panic(err)
    34  		}
    35  
    36  		for _, container := range containers {
    37  			fmt.Printf("%s %s\n", container.ID[:10], container.Image)
    38  		}
    39  	}
    40  
    41  */
    42  package client // import "github.com/docker/docker/client"
    43  
    44  import (
    45  	"context"
    46  	"fmt"
    47  	"net"
    48  	"net/http"
    49  	"net/url"
    50  	"os"
    51  	"path"
    52  	"path/filepath"
    53  	"strings"
    54  
    55  	"github.com/docker/docker/api"
    56  	"github.com/docker/docker/api/types"
    57  	"github.com/docker/docker/api/types/versions"
    58  	"github.com/docker/go-connections/sockets"
    59  	"github.com/docker/go-connections/tlsconfig"
    60  	"github.com/pkg/errors"
    61  )
    62  
    63  // ErrRedirect is the error returned by checkRedirect when the request is non-GET.
    64  var ErrRedirect = errors.New("unexpected redirect in response")
    65  
    66  // Client is the API client that performs all operations
    67  // against a docker server.
    68  type Client struct {
    69  	// scheme sets the scheme for the client
    70  	scheme string
    71  	// host holds the server address to connect to
    72  	host string
    73  	// proto holds the client protocol i.e. unix.
    74  	proto string
    75  	// addr holds the client address.
    76  	addr string
    77  	// basePath holds the path to prepend to the requests.
    78  	basePath string
    79  	// client used to send and receive http requests.
    80  	client *http.Client
    81  	// version of the server to talk to.
    82  	version string
    83  	// custom http headers configured by users.
    84  	customHTTPHeaders map[string]string
    85  	// manualOverride is set to true when the version was set by users.
    86  	manualOverride bool
    87  }
    88  
    89  // CheckRedirect specifies the policy for dealing with redirect responses:
    90  // If the request is non-GET return `ErrRedirect`. Otherwise use the last response.
    91  //
    92  // Go 1.8 changes behavior for HTTP redirects (specifically 301, 307, and 308) in the client .
    93  // The Docker client (and by extension docker API client) can be made to send a request
    94  // like POST /containers//start where what would normally be in the name section of the URL is empty.
    95  // This triggers an HTTP 301 from the daemon.
    96  // In go 1.8 this 301 will be converted to a GET request, and ends up getting a 404 from the daemon.
    97  // This behavior change manifests in the client in that before the 301 was not followed and
    98  // the client did not generate an error, but now results in a message like Error response from daemon: page not found.
    99  func CheckRedirect(req *http.Request, via []*http.Request) error {
   100  	if via[0].Method == http.MethodGet {
   101  		return http.ErrUseLastResponse
   102  	}
   103  	return ErrRedirect
   104  }
   105  
   106  // NewEnvClient initializes a new API client based on environment variables.
   107  // See FromEnv for a list of support environment variables.
   108  //
   109  // Deprecated: use NewClientWithOpts(FromEnv)
   110  func NewEnvClient() (*Client, error) {
   111  	return NewClientWithOpts(FromEnv)
   112  }
   113  
   114  // FromEnv configures the client with values from environment variables.
   115  //
   116  // Supported environment variables:
   117  // DOCKER_HOST to set the url to the docker server.
   118  // DOCKER_API_VERSION to set the version of the API to reach, leave empty for latest.
   119  // DOCKER_CERT_PATH to load the TLS certificates from.
   120  // DOCKER_TLS_VERIFY to enable or disable TLS verification, off by default.
   121  func FromEnv(c *Client) error {
   122  	if dockerCertPath := os.Getenv("DOCKER_CERT_PATH"); dockerCertPath != "" {
   123  		options := tlsconfig.Options{
   124  			CAFile:             filepath.Join(dockerCertPath, "ca.pem"),
   125  			CertFile:           filepath.Join(dockerCertPath, "cert.pem"),
   126  			KeyFile:            filepath.Join(dockerCertPath, "key.pem"),
   127  			InsecureSkipVerify: os.Getenv("DOCKER_TLS_VERIFY") == "",
   128  		}
   129  		tlsc, err := tlsconfig.Client(options)
   130  		if err != nil {
   131  			return err
   132  		}
   133  
   134  		c.client = &http.Client{
   135  			Transport:     &http.Transport{TLSClientConfig: tlsc},
   136  			CheckRedirect: CheckRedirect,
   137  		}
   138  	}
   139  
   140  	if host := os.Getenv("DOCKER_HOST"); host != "" {
   141  		if err := WithHost(host)(c); err != nil {
   142  			return err
   143  		}
   144  	}
   145  
   146  	if version := os.Getenv("DOCKER_API_VERSION"); version != "" {
   147  		c.version = version
   148  		c.manualOverride = true
   149  	}
   150  	return nil
   151  }
   152  
   153  // WithTLSClientConfig applies a tls config to the client transport.
   154  func WithTLSClientConfig(cacertPath, certPath, keyPath string) func(*Client) error {
   155  	return func(c *Client) error {
   156  		opts := tlsconfig.Options{
   157  			CAFile:             cacertPath,
   158  			CertFile:           certPath,
   159  			KeyFile:            keyPath,
   160  			ExclusiveRootPools: true,
   161  		}
   162  		config, err := tlsconfig.Client(opts)
   163  		if err != nil {
   164  			return errors.Wrap(err, "failed to create tls config")
   165  		}
   166  		if transport, ok := c.client.Transport.(*http.Transport); ok {
   167  			transport.TLSClientConfig = config
   168  			return nil
   169  		}
   170  		return errors.Errorf("cannot apply tls config to transport: %T", c.client.Transport)
   171  	}
   172  }
   173  
   174  // WithDialer applies the dialer.DialContext to the client transport. This can be
   175  // used to set the Timeout and KeepAlive settings of the client.
   176  // Deprecated: use WithDialContext
   177  func WithDialer(dialer *net.Dialer) func(*Client) error {
   178  	return WithDialContext(dialer.DialContext)
   179  }
   180  
   181  // WithDialContext applies the dialer to the client transport. This can be
   182  // used to set the Timeout and KeepAlive settings of the client.
   183  func WithDialContext(dialContext func(ctx context.Context, network, addr string) (net.Conn, error)) func(*Client) error {
   184  	return func(c *Client) error {
   185  		if transport, ok := c.client.Transport.(*http.Transport); ok {
   186  			transport.DialContext = dialContext
   187  			return nil
   188  		}
   189  		return errors.Errorf("cannot apply dialer to transport: %T", c.client.Transport)
   190  	}
   191  }
   192  
   193  // WithVersion overrides the client version with the specified one
   194  func WithVersion(version string) func(*Client) error {
   195  	return func(c *Client) error {
   196  		c.version = version
   197  		return nil
   198  	}
   199  }
   200  
   201  // WithHost overrides the client host with the specified one.
   202  func WithHost(host string) func(*Client) error {
   203  	return func(c *Client) error {
   204  		hostURL, err := ParseHostURL(host)
   205  		if err != nil {
   206  			return err
   207  		}
   208  		c.host = host
   209  		c.proto = hostURL.Scheme
   210  		c.addr = hostURL.Host
   211  		c.basePath = hostURL.Path
   212  		if transport, ok := c.client.Transport.(*http.Transport); ok {
   213  			return sockets.ConfigureTransport(transport, c.proto, c.addr)
   214  		}
   215  		return errors.Errorf("cannot apply host to transport: %T", c.client.Transport)
   216  	}
   217  }
   218  
   219  // WithHTTPClient overrides the client http client with the specified one
   220  func WithHTTPClient(client *http.Client) func(*Client) error {
   221  	return func(c *Client) error {
   222  		if client != nil {
   223  			c.client = client
   224  		}
   225  		return nil
   226  	}
   227  }
   228  
   229  // WithHTTPHeaders overrides the client default http headers
   230  func WithHTTPHeaders(headers map[string]string) func(*Client) error {
   231  	return func(c *Client) error {
   232  		c.customHTTPHeaders = headers
   233  		return nil
   234  	}
   235  }
   236  
   237  // NewClientWithOpts initializes a new API client with default values. It takes functors
   238  // to modify values when creating it, like `NewClientWithOpts(WithVersion(…))`
   239  // It also initializes the custom http headers to add to each request.
   240  //
   241  // It won't send any version information if the version number is empty. It is
   242  // highly recommended that you set a version or your client may break if the
   243  // server is upgraded.
   244  func NewClientWithOpts(ops ...func(*Client) error) (*Client, error) {
   245  	client, err := defaultHTTPClient(DefaultDockerHost)
   246  	if err != nil {
   247  		return nil, err
   248  	}
   249  	c := &Client{
   250  		host:    DefaultDockerHost,
   251  		version: api.DefaultVersion,
   252  		scheme:  "http",
   253  		client:  client,
   254  		proto:   defaultProto,
   255  		addr:    defaultAddr,
   256  	}
   257  
   258  	for _, op := range ops {
   259  		if err := op(c); err != nil {
   260  			return nil, err
   261  		}
   262  	}
   263  
   264  	if _, ok := c.client.Transport.(http.RoundTripper); !ok {
   265  		return nil, fmt.Errorf("unable to verify TLS configuration, invalid transport %v", c.client.Transport)
   266  	}
   267  	tlsConfig := resolveTLSConfig(c.client.Transport)
   268  	if tlsConfig != nil {
   269  		// TODO(stevvooe): This isn't really the right way to write clients in Go.
   270  		// `NewClient` should probably only take an `*http.Client` and work from there.
   271  		// Unfortunately, the model of having a host-ish/url-thingy as the connection
   272  		// string has us confusing protocol and transport layers. We continue doing
   273  		// this to avoid breaking existing clients but this should be addressed.
   274  		c.scheme = "https"
   275  	}
   276  
   277  	return c, nil
   278  }
   279  
   280  func defaultHTTPClient(host string) (*http.Client, error) {
   281  	url, err := ParseHostURL(host)
   282  	if err != nil {
   283  		return nil, err
   284  	}
   285  	transport := new(http.Transport)
   286  	sockets.ConfigureTransport(transport, url.Scheme, url.Host)
   287  	return &http.Client{
   288  		Transport:     transport,
   289  		CheckRedirect: CheckRedirect,
   290  	}, nil
   291  }
   292  
   293  // NewClient initializes a new API client for the given host and API version.
   294  // It uses the given http client as transport.
   295  // It also initializes the custom http headers to add to each request.
   296  //
   297  // It won't send any version information if the version number is empty. It is
   298  // highly recommended that you set a version or your client may break if the
   299  // server is upgraded.
   300  // Deprecated: use NewClientWithOpts
   301  func NewClient(host string, version string, client *http.Client, httpHeaders map[string]string) (*Client, error) {
   302  	return NewClientWithOpts(WithHost(host), WithVersion(version), WithHTTPClient(client), WithHTTPHeaders(httpHeaders))
   303  }
   304  
   305  // Close the transport used by the client
   306  func (cli *Client) Close() error {
   307  	if t, ok := cli.client.Transport.(*http.Transport); ok {
   308  		t.CloseIdleConnections()
   309  	}
   310  	return nil
   311  }
   312  
   313  // getAPIPath returns the versioned request path to call the api.
   314  // It appends the query parameters to the path if they are not empty.
   315  func (cli *Client) getAPIPath(p string, query url.Values) string {
   316  	var apiPath string
   317  	if cli.version != "" {
   318  		v := strings.TrimPrefix(cli.version, "v")
   319  		apiPath = path.Join(cli.basePath, "/v"+v, p)
   320  	} else {
   321  		apiPath = path.Join(cli.basePath, p)
   322  	}
   323  	return (&url.URL{Path: apiPath, RawQuery: query.Encode()}).String()
   324  }
   325  
   326  // ClientVersion returns the API version used by this client.
   327  func (cli *Client) ClientVersion() string {
   328  	return cli.version
   329  }
   330  
   331  // NegotiateAPIVersion queries the API and updates the version to match the
   332  // API version. Any errors are silently ignored.
   333  func (cli *Client) NegotiateAPIVersion(ctx context.Context) {
   334  	ping, _ := cli.Ping(ctx)
   335  	cli.NegotiateAPIVersionPing(ping)
   336  }
   337  
   338  // NegotiateAPIVersionPing updates the client version to match the Ping.APIVersion
   339  // if the ping version is less than the default version.
   340  func (cli *Client) NegotiateAPIVersionPing(p types.Ping) {
   341  	if cli.manualOverride {
   342  		return
   343  	}
   344  
   345  	// try the latest version before versioning headers existed
   346  	if p.APIVersion == "" {
   347  		p.APIVersion = "1.24"
   348  	}
   349  
   350  	// if the client is not initialized with a version, start with the latest supported version
   351  	if cli.version == "" {
   352  		cli.version = api.DefaultVersion
   353  	}
   354  
   355  	// if server version is lower than the client version, downgrade
   356  	if versions.LessThan(p.APIVersion, cli.version) {
   357  		cli.version = p.APIVersion
   358  	}
   359  }
   360  
   361  // DaemonHost returns the host address used by the client
   362  func (cli *Client) DaemonHost() string {
   363  	return cli.host
   364  }
   365  
   366  // HTTPClient returns a copy of the HTTP client bound to the server
   367  func (cli *Client) HTTPClient() *http.Client {
   368  	return &*cli.client
   369  }
   370  
   371  // ParseHostURL parses a url string, validates the string is a host url, and
   372  // returns the parsed URL
   373  func ParseHostURL(host string) (*url.URL, error) {
   374  	protoAddrParts := strings.SplitN(host, "://", 2)
   375  	if len(protoAddrParts) == 1 {
   376  		return nil, fmt.Errorf("unable to parse docker host `%s`", host)
   377  	}
   378  
   379  	var basePath string
   380  	proto, addr := protoAddrParts[0], protoAddrParts[1]
   381  	if proto == "tcp" {
   382  		parsed, err := url.Parse("tcp://" + addr)
   383  		if err != nil {
   384  			return nil, err
   385  		}
   386  		addr = parsed.Host
   387  		basePath = parsed.Path
   388  	}
   389  	return &url.URL{
   390  		Scheme: proto,
   391  		Host:   addr,
   392  		Path:   basePath,
   393  	}, nil
   394  }
   395  
   396  // CustomHTTPHeaders returns the custom http headers stored by the client.
   397  func (cli *Client) CustomHTTPHeaders() map[string]string {
   398  	m := make(map[string]string)
   399  	for k, v := range cli.customHTTPHeaders {
   400  		m[k] = v
   401  	}
   402  	return m
   403  }
   404  
   405  // SetCustomHTTPHeaders that will be set on every HTTP request made by the client.
   406  // Deprecated: use WithHTTPHeaders when creating the client.
   407  func (cli *Client) SetCustomHTTPHeaders(headers map[string]string) {
   408  	cli.customHTTPHeaders = headers
   409  }
   410  
   411  // Dialer returns a dialer for a raw stream connection, with HTTP/1.1 header, that can be used for proxying the daemon connection.
   412  // Used by `docker dial-stdio` (docker/cli#889).
   413  func (cli *Client) Dialer() func(context.Context) (net.Conn, error) {
   414  	return func(ctx context.Context) (net.Conn, error) {
   415  		if transport, ok := cli.client.Transport.(*http.Transport); ok {
   416  			if transport.DialContext != nil && transport.TLSClientConfig == nil {
   417  				return transport.DialContext(ctx, cli.proto, cli.addr)
   418  			}
   419  		}
   420  		return fallbackDial(cli.proto, cli.addr, resolveTLSConfig(cli.client.Transport))
   421  	}
   422  }