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