1/*
2Package client is a Go client for the Docker Engine API.
3
4For more information about the Engine API, see the documentation:
5https://docs.docker.com/engine/reference/api/
6
7Usage
8
9You use the library by creating a client object and calling methods on it. The
10client can be created either from environment variables with NewEnvClient, or
11configured manually with NewClient.
12
13For 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.NewEnvClient()
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*/
42package client // import "github.com/docker/docker/client"
43
44import (
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.
64var ErrRedirect = errors.New("unexpected redirect in response")
65
66// Client is the API client that performs all operations
67// against a docker server.
68type 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 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.
99func 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)
110func 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.
121func 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.
154func 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.
176func WithDialer(dialer *net.Dialer) func(*Client) error {
177	return func(c *Client) error {
178		if transport, ok := c.client.Transport.(*http.Transport); ok {
179			transport.DialContext = dialer.DialContext
180			return nil
181		}
182		return errors.Errorf("cannot apply dialer to transport: %T", c.client.Transport)
183	}
184}
185
186// WithVersion overrides the client version with the specified one
187func WithVersion(version string) func(*Client) error {
188	return func(c *Client) error {
189		c.version = version
190		return nil
191	}
192}
193
194// WithHost overrides the client host with the specified one.
195func WithHost(host string) func(*Client) error {
196	return func(c *Client) error {
197		hostURL, err := ParseHostURL(host)
198		if err != nil {
199			return err
200		}
201		c.host = host
202		c.proto = hostURL.Scheme
203		c.addr = hostURL.Host
204		c.basePath = hostURL.Path
205		if transport, ok := c.client.Transport.(*http.Transport); ok {
206			return sockets.ConfigureTransport(transport, c.proto, c.addr)
207		}
208		return errors.Errorf("cannot apply host to transport: %T", c.client.Transport)
209	}
210}
211
212// WithHTTPClient overrides the client http client with the specified one
213func WithHTTPClient(client *http.Client) func(*Client) error {
214	return func(c *Client) error {
215		if client != nil {
216			c.client = client
217		}
218		return nil
219	}
220}
221
222// WithHTTPHeaders overrides the client default http headers
223func WithHTTPHeaders(headers map[string]string) func(*Client) error {
224	return func(c *Client) error {
225		c.customHTTPHeaders = headers
226		return nil
227	}
228}
229
230// NewClientWithOpts initializes a new API client with default values. It takes functors
231// to modify values when creating it, like `NewClientWithOpts(WithVersion(…))`
232// It also initializes the custom http headers to add to each request.
233//
234// It won't send any version information if the version number is empty. It is
235// highly recommended that you set a version or your client may break if the
236// server is upgraded.
237func NewClientWithOpts(ops ...func(*Client) error) (*Client, error) {
238	client, err := defaultHTTPClient(DefaultDockerHost)
239	if err != nil {
240		return nil, err
241	}
242	c := &Client{
243		host:    DefaultDockerHost,
244		version: api.DefaultVersion,
245		scheme:  "http",
246		client:  client,
247		proto:   defaultProto,
248		addr:    defaultAddr,
249	}
250
251	for _, op := range ops {
252		if err := op(c); err != nil {
253			return nil, err
254		}
255	}
256
257	if _, ok := c.client.Transport.(http.RoundTripper); !ok {
258		return nil, fmt.Errorf("unable to verify TLS configuration, invalid transport %v", c.client.Transport)
259	}
260	tlsConfig := resolveTLSConfig(c.client.Transport)
261	if tlsConfig != nil {
262		// TODO(stevvooe): This isn't really the right way to write clients in Go.
263		// `NewClient` should probably only take an `*http.Client` and work from there.
264		// Unfortunately, the model of having a host-ish/url-thingy as the connection
265		// string has us confusing protocol and transport layers. We continue doing
266		// this to avoid breaking existing clients but this should be addressed.
267		c.scheme = "https"
268	}
269
270	return c, nil
271}
272
273func defaultHTTPClient(host string) (*http.Client, error) {
274	url, err := ParseHostURL(host)
275	if err != nil {
276		return nil, err
277	}
278	transport := new(http.Transport)
279	sockets.ConfigureTransport(transport, url.Scheme, url.Host)
280	return &http.Client{
281		Transport:     transport,
282		CheckRedirect: CheckRedirect,
283	}, nil
284}
285
286// NewClient initializes a new API client for the given host and API version.
287// It uses the given http client as transport.
288// It also initializes the custom http headers to add to each request.
289//
290// It won't send any version information if the version number is empty. It is
291// highly recommended that you set a version or your client may break if the
292// server is upgraded.
293// Deprecated: use NewClientWithOpts
294func NewClient(host string, version string, client *http.Client, httpHeaders map[string]string) (*Client, error) {
295	return NewClientWithOpts(WithHost(host), WithVersion(version), WithHTTPClient(client), WithHTTPHeaders(httpHeaders))
296}
297
298// Close the transport used by the client
299func (cli *Client) Close() error {
300	if t, ok := cli.client.Transport.(*http.Transport); ok {
301		t.CloseIdleConnections()
302	}
303	return nil
304}
305
306// getAPIPath returns the versioned request path to call the api.
307// It appends the query parameters to the path if they are not empty.
308func (cli *Client) getAPIPath(p string, query url.Values) string {
309	var apiPath string
310	if cli.version != "" {
311		v := strings.TrimPrefix(cli.version, "v")
312		apiPath = path.Join(cli.basePath, "/v"+v, p)
313	} else {
314		apiPath = path.Join(cli.basePath, p)
315	}
316	return (&url.URL{Path: apiPath, RawQuery: query.Encode()}).String()
317}
318
319// ClientVersion returns the API version used by this client.
320func (cli *Client) ClientVersion() string {
321	return cli.version
322}
323
324// NegotiateAPIVersion queries the API and updates the version to match the
325// API version. Any errors are silently ignored.
326func (cli *Client) NegotiateAPIVersion(ctx context.Context) {
327	ping, _ := cli.Ping(ctx)
328	cli.NegotiateAPIVersionPing(ping)
329}
330
331// NegotiateAPIVersionPing updates the client version to match the Ping.APIVersion
332// if the ping version is less than the default version.
333func (cli *Client) NegotiateAPIVersionPing(p types.Ping) {
334	if cli.manualOverride {
335		return
336	}
337
338	// try the latest version before versioning headers existed
339	if p.APIVersion == "" {
340		p.APIVersion = "1.24"
341	}
342
343	// if the client is not initialized with a version, start with the latest supported version
344	if cli.version == "" {
345		cli.version = api.DefaultVersion
346	}
347
348	// if server version is lower than the client version, downgrade
349	if versions.LessThan(p.APIVersion, cli.version) {
350		cli.version = p.APIVersion
351	}
352}
353
354// DaemonHost returns the host address used by the client
355func (cli *Client) DaemonHost() string {
356	return cli.host
357}
358
359// HTTPClient returns a copy of the HTTP client bound to the server
360func (cli *Client) HTTPClient() *http.Client {
361	return &*cli.client
362}
363
364// ParseHostURL parses a url string, validates the string is a host url, and
365// returns the parsed URL
366func ParseHostURL(host string) (*url.URL, error) {
367	protoAddrParts := strings.SplitN(host, "://", 2)
368	if len(protoAddrParts) == 1 {
369		return nil, fmt.Errorf("unable to parse docker host `%s`", host)
370	}
371
372	var basePath string
373	proto, addr := protoAddrParts[0], protoAddrParts[1]
374	if proto == "tcp" {
375		parsed, err := url.Parse("tcp://" + addr)
376		if err != nil {
377			return nil, err
378		}
379		addr = parsed.Host
380		basePath = parsed.Path
381	}
382	return &url.URL{
383		Scheme: proto,
384		Host:   addr,
385		Path:   basePath,
386	}, nil
387}
388
389// CustomHTTPHeaders returns the custom http headers stored by the client.
390func (cli *Client) CustomHTTPHeaders() map[string]string {
391	m := make(map[string]string)
392	for k, v := range cli.customHTTPHeaders {
393		m[k] = v
394	}
395	return m
396}
397
398// SetCustomHTTPHeaders that will be set on every HTTP request made by the client.
399// Deprecated: use WithHTTPHeaders when creating the client.
400func (cli *Client) SetCustomHTTPHeaders(headers map[string]string) {
401	cli.customHTTPHeaders = headers
402}
403