github.com/docker/docker@v299999999.0.0-20200612211812-aaf470eca7b5+incompatible/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  	"path"
    51  	"strings"
    52  
    53  	"github.com/docker/docker/api"
    54  	"github.com/docker/docker/api/types"
    55  	"github.com/docker/docker/api/types/versions"
    56  	"github.com/docker/go-connections/sockets"
    57  	"github.com/pkg/errors"
    58  )
    59  
    60  // ErrRedirect is the error returned by checkRedirect when the request is non-GET.
    61  var ErrRedirect = errors.New("unexpected redirect in response")
    62  
    63  // Client is the API client that performs all operations
    64  // against a docker server.
    65  type Client struct {
    66  	// scheme sets the scheme for the client
    67  	scheme string
    68  	// host holds the server address to connect to
    69  	host string
    70  	// proto holds the client protocol i.e. unix.
    71  	proto string
    72  	// addr holds the client address.
    73  	addr string
    74  	// basePath holds the path to prepend to the requests.
    75  	basePath string
    76  	// client used to send and receive http requests.
    77  	client *http.Client
    78  	// version of the server to talk to.
    79  	version string
    80  	// custom http headers configured by users.
    81  	customHTTPHeaders map[string]string
    82  	// manualOverride is set to true when the version was set by users.
    83  	manualOverride bool
    84  
    85  	// negotiateVersion indicates if the client should automatically negotiate
    86  	// the API version to use when making requests. API version negotiation is
    87  	// performed on the first request, after which negotiated is set to "true"
    88  	// so that subsequent requests do not re-negotiate.
    89  	negotiateVersion bool
    90  
    91  	// negotiated indicates that API version negotiation took place
    92  	negotiated bool
    93  }
    94  
    95  // CheckRedirect specifies the policy for dealing with redirect responses:
    96  // If the request is non-GET return `ErrRedirect`. Otherwise use the last response.
    97  //
    98  // Go 1.8 changes behavior for HTTP redirects (specifically 301, 307, and 308) in the client .
    99  // The Docker client (and by extension docker API client) can be made to send a request
   100  // like POST /containers//start where what would normally be in the name section of the URL is empty.
   101  // This triggers an HTTP 301 from the daemon.
   102  // In go 1.8 this 301 will be converted to a GET request, and ends up getting a 404 from the daemon.
   103  // This behavior change manifests in the client in that before the 301 was not followed and
   104  // the client did not generate an error, but now results in a message like Error response from daemon: page not found.
   105  func CheckRedirect(req *http.Request, via []*http.Request) error {
   106  	if via[0].Method == http.MethodGet {
   107  		return http.ErrUseLastResponse
   108  	}
   109  	return ErrRedirect
   110  }
   111  
   112  // NewClientWithOpts initializes a new API client with default values. It takes functors
   113  // to modify values when creating it, like `NewClientWithOpts(WithVersion(…))`
   114  // It also initializes the custom http headers to add to each request.
   115  //
   116  // It won't send any version information if the version number is empty. It is
   117  // highly recommended that you set a version or your client may break if the
   118  // server is upgraded.
   119  func NewClientWithOpts(ops ...Opt) (*Client, error) {
   120  	client, err := defaultHTTPClient(DefaultDockerHost)
   121  	if err != nil {
   122  		return nil, err
   123  	}
   124  	c := &Client{
   125  		host:    DefaultDockerHost,
   126  		version: api.DefaultVersion,
   127  		client:  client,
   128  		proto:   defaultProto,
   129  		addr:    defaultAddr,
   130  	}
   131  
   132  	for _, op := range ops {
   133  		if err := op(c); err != nil {
   134  			return nil, err
   135  		}
   136  	}
   137  
   138  	if _, ok := c.client.Transport.(http.RoundTripper); !ok {
   139  		return nil, fmt.Errorf("unable to verify TLS configuration, invalid transport %v", c.client.Transport)
   140  	}
   141  	if c.scheme == "" {
   142  		c.scheme = "http"
   143  
   144  		tlsConfig := resolveTLSConfig(c.client.Transport)
   145  		if tlsConfig != nil {
   146  			// TODO(stevvooe): This isn't really the right way to write clients in Go.
   147  			// `NewClient` should probably only take an `*http.Client` and work from there.
   148  			// Unfortunately, the model of having a host-ish/url-thingy as the connection
   149  			// string has us confusing protocol and transport layers. We continue doing
   150  			// this to avoid breaking existing clients but this should be addressed.
   151  			c.scheme = "https"
   152  		}
   153  	}
   154  
   155  	return c, nil
   156  }
   157  
   158  func defaultHTTPClient(host string) (*http.Client, error) {
   159  	url, err := ParseHostURL(host)
   160  	if err != nil {
   161  		return nil, err
   162  	}
   163  	transport := new(http.Transport)
   164  	sockets.ConfigureTransport(transport, url.Scheme, url.Host)
   165  	return &http.Client{
   166  		Transport:     transport,
   167  		CheckRedirect: CheckRedirect,
   168  	}, nil
   169  }
   170  
   171  // Close the transport used by the client
   172  func (cli *Client) Close() error {
   173  	if t, ok := cli.client.Transport.(*http.Transport); ok {
   174  		t.CloseIdleConnections()
   175  	}
   176  	return nil
   177  }
   178  
   179  // getAPIPath returns the versioned request path to call the api.
   180  // It appends the query parameters to the path if they are not empty.
   181  func (cli *Client) getAPIPath(ctx context.Context, p string, query url.Values) string {
   182  	var apiPath string
   183  	if cli.negotiateVersion && !cli.negotiated {
   184  		cli.NegotiateAPIVersion(ctx)
   185  	}
   186  	if cli.version != "" {
   187  		v := strings.TrimPrefix(cli.version, "v")
   188  		apiPath = path.Join(cli.basePath, "/v"+v, p)
   189  	} else {
   190  		apiPath = path.Join(cli.basePath, p)
   191  	}
   192  	return (&url.URL{Path: apiPath, RawQuery: query.Encode()}).String()
   193  }
   194  
   195  // ClientVersion returns the API version used by this client.
   196  func (cli *Client) ClientVersion() string {
   197  	return cli.version
   198  }
   199  
   200  // NegotiateAPIVersion queries the API and updates the version to match the
   201  // API version. Any errors are silently ignored. If a manual override is in place,
   202  // either through the `DOCKER_API_VERSION` environment variable, or if the client
   203  // was initialized with a fixed version (`opts.WithVersion(xx)`), no negotiation
   204  // will be performed.
   205  func (cli *Client) NegotiateAPIVersion(ctx context.Context) {
   206  	if !cli.manualOverride {
   207  		ping, _ := cli.Ping(ctx)
   208  		cli.negotiateAPIVersionPing(ping)
   209  	}
   210  }
   211  
   212  // NegotiateAPIVersionPing updates the client version to match the Ping.APIVersion
   213  // if the ping version is less than the default version.  If a manual override is
   214  // in place, either through the `DOCKER_API_VERSION` environment variable, or if
   215  // the client was initialized with a fixed version (`opts.WithVersion(xx)`), no
   216  // negotiation is performed.
   217  func (cli *Client) NegotiateAPIVersionPing(p types.Ping) {
   218  	if !cli.manualOverride {
   219  		cli.negotiateAPIVersionPing(p)
   220  	}
   221  }
   222  
   223  // negotiateAPIVersionPing queries the API and updates the version to match the
   224  // API version. Any errors are silently ignored.
   225  func (cli *Client) negotiateAPIVersionPing(p types.Ping) {
   226  	// try the latest version before versioning headers existed
   227  	if p.APIVersion == "" {
   228  		p.APIVersion = "1.24"
   229  	}
   230  
   231  	// if the client is not initialized with a version, start with the latest supported version
   232  	if cli.version == "" {
   233  		cli.version = api.DefaultVersion
   234  	}
   235  
   236  	// if server version is lower than the client version, downgrade
   237  	if versions.LessThan(p.APIVersion, cli.version) {
   238  		cli.version = p.APIVersion
   239  	}
   240  
   241  	// Store the results, so that automatic API version negotiation (if enabled)
   242  	// won't be performed on the next request.
   243  	if cli.negotiateVersion {
   244  		cli.negotiated = true
   245  	}
   246  }
   247  
   248  // DaemonHost returns the host address used by the client
   249  func (cli *Client) DaemonHost() string {
   250  	return cli.host
   251  }
   252  
   253  // HTTPClient returns a copy of the HTTP client bound to the server
   254  func (cli *Client) HTTPClient() *http.Client {
   255  	c := *cli.client
   256  	return &c
   257  }
   258  
   259  // ParseHostURL parses a url string, validates the string is a host url, and
   260  // returns the parsed URL
   261  func ParseHostURL(host string) (*url.URL, error) {
   262  	protoAddrParts := strings.SplitN(host, "://", 2)
   263  	if len(protoAddrParts) == 1 {
   264  		return nil, fmt.Errorf("unable to parse docker host `%s`", host)
   265  	}
   266  
   267  	var basePath string
   268  	proto, addr := protoAddrParts[0], protoAddrParts[1]
   269  	if proto == "tcp" {
   270  		parsed, err := url.Parse("tcp://" + addr)
   271  		if err != nil {
   272  			return nil, err
   273  		}
   274  		addr = parsed.Host
   275  		basePath = parsed.Path
   276  	}
   277  	return &url.URL{
   278  		Scheme: proto,
   279  		Host:   addr,
   280  		Path:   basePath,
   281  	}, nil
   282  }
   283  
   284  // CustomHTTPHeaders returns the custom http headers stored by the client.
   285  func (cli *Client) CustomHTTPHeaders() map[string]string {
   286  	m := make(map[string]string)
   287  	for k, v := range cli.customHTTPHeaders {
   288  		m[k] = v
   289  	}
   290  	return m
   291  }
   292  
   293  // SetCustomHTTPHeaders that will be set on every HTTP request made by the client.
   294  // Deprecated: use WithHTTPHeaders when creating the client.
   295  func (cli *Client) SetCustomHTTPHeaders(headers map[string]string) {
   296  	cli.customHTTPHeaders = headers
   297  }
   298  
   299  // Dialer returns a dialer for a raw stream connection, with HTTP/1.1 header, that can be used for proxying the daemon connection.
   300  // Used by `docker dial-stdio` (docker/cli#889).
   301  func (cli *Client) Dialer() func(context.Context) (net.Conn, error) {
   302  	return func(ctx context.Context) (net.Conn, error) {
   303  		if transport, ok := cli.client.Transport.(*http.Transport); ok {
   304  			if transport.DialContext != nil && transport.TLSClientConfig == nil {
   305  				return transport.DialContext(ctx, cli.proto, cli.addr)
   306  			}
   307  		}
   308  		return fallbackDial(cli.proto, cli.addr, resolveTLSConfig(cli.client.Transport))
   309  	}
   310  }