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 }