1package client
2
3import (
4	"context"
5	"net"
6	"net/http"
7	"os"
8	"path/filepath"
9	"time"
10
11	"github.com/docker/go-connections/sockets"
12	"github.com/docker/go-connections/tlsconfig"
13	"github.com/pkg/errors"
14)
15
16// Opt is a configuration option to initialize a client
17type Opt func(*Client) error
18
19// FromEnv configures the client with values from environment variables.
20//
21// Supported environment variables:
22// DOCKER_HOST to set the url to the docker server.
23// DOCKER_API_VERSION to set the version of the API to reach, leave empty for latest.
24// DOCKER_CERT_PATH to load the TLS certificates from.
25// DOCKER_TLS_VERIFY to enable or disable TLS verification, off by default.
26func FromEnv(c *Client) error {
27	if dockerCertPath := os.Getenv("DOCKER_CERT_PATH"); dockerCertPath != "" {
28		options := tlsconfig.Options{
29			CAFile:             filepath.Join(dockerCertPath, "ca.pem"),
30			CertFile:           filepath.Join(dockerCertPath, "cert.pem"),
31			KeyFile:            filepath.Join(dockerCertPath, "key.pem"),
32			InsecureSkipVerify: os.Getenv("DOCKER_TLS_VERIFY") == "",
33		}
34		tlsc, err := tlsconfig.Client(options)
35		if err != nil {
36			return err
37		}
38
39		c.client = &http.Client{
40			Transport:     &http.Transport{TLSClientConfig: tlsc},
41			CheckRedirect: CheckRedirect,
42		}
43	}
44
45	if host := os.Getenv("DOCKER_HOST"); host != "" {
46		if err := WithHost(host)(c); err != nil {
47			return err
48		}
49	}
50
51	if version := os.Getenv("DOCKER_API_VERSION"); version != "" {
52		if err := WithVersion(version)(c); err != nil {
53			return err
54		}
55	}
56	return nil
57}
58
59// WithDialer applies the dialer.DialContext to the client transport. This can be
60// used to set the Timeout and KeepAlive settings of the client.
61// Deprecated: use WithDialContext
62func WithDialer(dialer *net.Dialer) Opt {
63	return WithDialContext(dialer.DialContext)
64}
65
66// WithDialContext applies the dialer to the client transport. This can be
67// used to set the Timeout and KeepAlive settings of the client.
68func WithDialContext(dialContext func(ctx context.Context, network, addr string) (net.Conn, error)) Opt {
69	return func(c *Client) error {
70		if transport, ok := c.client.Transport.(*http.Transport); ok {
71			transport.DialContext = dialContext
72			return nil
73		}
74		return errors.Errorf("cannot apply dialer to transport: %T", c.client.Transport)
75	}
76}
77
78// WithHost overrides the client host with the specified one.
79func WithHost(host string) Opt {
80	return func(c *Client) error {
81		hostURL, err := ParseHostURL(host)
82		if err != nil {
83			return err
84		}
85		c.host = host
86		c.proto = hostURL.Scheme
87		c.addr = hostURL.Host
88		c.basePath = hostURL.Path
89		if transport, ok := c.client.Transport.(*http.Transport); ok {
90			return sockets.ConfigureTransport(transport, c.proto, c.addr)
91		}
92		return errors.Errorf("cannot apply host to transport: %T", c.client.Transport)
93	}
94}
95
96// WithHTTPClient overrides the client http client with the specified one
97func WithHTTPClient(client *http.Client) Opt {
98	return func(c *Client) error {
99		if client != nil {
100			c.client = client
101		}
102		return nil
103	}
104}
105
106// WithTimeout configures the time limit for requests made by the HTTP client
107func WithTimeout(timeout time.Duration) Opt {
108	return func(c *Client) error {
109		c.client.Timeout = timeout
110		return nil
111	}
112}
113
114// WithHTTPHeaders overrides the client default http headers
115func WithHTTPHeaders(headers map[string]string) Opt {
116	return func(c *Client) error {
117		c.customHTTPHeaders = headers
118		return nil
119	}
120}
121
122// WithScheme overrides the client scheme with the specified one
123func WithScheme(scheme string) Opt {
124	return func(c *Client) error {
125		c.scheme = scheme
126		return nil
127	}
128}
129
130// WithTLSClientConfig applies a tls config to the client transport.
131func WithTLSClientConfig(cacertPath, certPath, keyPath string) Opt {
132	return func(c *Client) error {
133		opts := tlsconfig.Options{
134			CAFile:             cacertPath,
135			CertFile:           certPath,
136			KeyFile:            keyPath,
137			ExclusiveRootPools: true,
138		}
139		config, err := tlsconfig.Client(opts)
140		if err != nil {
141			return errors.Wrap(err, "failed to create tls config")
142		}
143		if transport, ok := c.client.Transport.(*http.Transport); ok {
144			transport.TLSClientConfig = config
145			return nil
146		}
147		return errors.Errorf("cannot apply tls config to transport: %T", c.client.Transport)
148	}
149}
150
151// WithVersion overrides the client version with the specified one. If an empty
152// version is specified, the value will be ignored to allow version negotiation.
153func WithVersion(version string) Opt {
154	return func(c *Client) error {
155		if version != "" {
156			c.version = version
157			c.manualOverride = true
158		}
159		return nil
160	}
161}
162
163// WithAPIVersionNegotiation enables automatic API version negotiation for the client.
164// With this option enabled, the client automatically negotiates the API version
165// to use when making requests. API version negotiation is performed on the first
166// request; subsequent requests will not re-negotiate.
167func WithAPIVersionNegotiation() Opt {
168	return func(c *Client) error {
169		c.negotiateVersion = true
170		return nil
171	}
172}
173