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 }