github.com/LazyboyChen7/engine@v17.12.1-ce-rc2+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.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 */ 42 package client 43 44 import ( 45 "errors" 46 "fmt" 47 "net/http" 48 "net/url" 49 "os" 50 "path" 51 "path/filepath" 52 "strings" 53 54 "github.com/docker/docker/api" 55 "github.com/docker/docker/api/types" 56 "github.com/docker/docker/api/types/versions" 57 "github.com/docker/go-connections/sockets" 58 "github.com/docker/go-connections/tlsconfig" 59 "golang.org/x/net/context" 60 ) 61 62 // ErrRedirect is the error returned by checkRedirect when the request is non-GET. 63 var ErrRedirect = errors.New("unexpected redirect in response") 64 65 // Client is the API client that performs all operations 66 // against a docker server. 67 type Client struct { 68 // scheme sets the scheme for the client 69 scheme string 70 // host holds the server address to connect to 71 host string 72 // proto holds the client protocol i.e. unix. 73 proto string 74 // addr holds the client address. 75 addr string 76 // basePath holds the path to prepend to the requests. 77 basePath string 78 // client used to send and receive http requests. 79 client *http.Client 80 // version of the server to talk to. 81 version string 82 // custom http headers configured by users. 83 customHTTPHeaders map[string]string 84 // manualOverride is set to true when the version was set by users. 85 manualOverride bool 86 } 87 88 // CheckRedirect specifies the policy for dealing with redirect responses: 89 // If the request is non-GET return `ErrRedirect`. Otherwise use the last response. 90 // 91 // Go 1.8 changes behavior for HTTP redirects (specifically 301, 307, and 308) in the client . 92 // The Docker client (and by extension docker API client) can be made to to send a request 93 // like POST /containers//start where what would normally be in the name section of the URL is empty. 94 // This triggers an HTTP 301 from the daemon. 95 // In go 1.8 this 301 will be converted to a GET request, and ends up getting a 404 from the daemon. 96 // This behavior change manifests in the client in that before the 301 was not followed and 97 // the client did not generate an error, but now results in a message like Error response from daemon: page not found. 98 func CheckRedirect(req *http.Request, via []*http.Request) error { 99 if via[0].Method == http.MethodGet { 100 return http.ErrUseLastResponse 101 } 102 return ErrRedirect 103 } 104 105 // NewEnvClient initializes a new API client based on environment variables. 106 // Use DOCKER_HOST to set the url to the docker server. 107 // Use DOCKER_API_VERSION to set the version of the API to reach, leave empty for latest. 108 // Use DOCKER_CERT_PATH to load the TLS certificates from. 109 // Use DOCKER_TLS_VERIFY to enable or disable TLS verification, off by default. 110 func NewEnvClient() (*Client, error) { 111 var client *http.Client 112 if dockerCertPath := os.Getenv("DOCKER_CERT_PATH"); dockerCertPath != "" { 113 options := tlsconfig.Options{ 114 CAFile: filepath.Join(dockerCertPath, "ca.pem"), 115 CertFile: filepath.Join(dockerCertPath, "cert.pem"), 116 KeyFile: filepath.Join(dockerCertPath, "key.pem"), 117 InsecureSkipVerify: os.Getenv("DOCKER_TLS_VERIFY") == "", 118 } 119 tlsc, err := tlsconfig.Client(options) 120 if err != nil { 121 return nil, err 122 } 123 124 client = &http.Client{ 125 Transport: &http.Transport{ 126 TLSClientConfig: tlsc, 127 }, 128 CheckRedirect: CheckRedirect, 129 } 130 } 131 132 host := os.Getenv("DOCKER_HOST") 133 if host == "" { 134 host = DefaultDockerHost 135 } 136 version := os.Getenv("DOCKER_API_VERSION") 137 if version == "" { 138 version = api.DefaultVersion 139 } 140 141 cli, err := NewClient(host, version, client, nil) 142 if err != nil { 143 return cli, err 144 } 145 if os.Getenv("DOCKER_API_VERSION") != "" { 146 cli.manualOverride = true 147 } 148 return cli, nil 149 } 150 151 // NewClient initializes a new API client for the given host and API version. 152 // It uses the given http client as transport. 153 // It also initializes the custom http headers to add to each request. 154 // 155 // It won't send any version information if the version number is empty. It is 156 // highly recommended that you set a version or your client may break if the 157 // server is upgraded. 158 func NewClient(host string, version string, client *http.Client, httpHeaders map[string]string) (*Client, error) { 159 hostURL, err := ParseHostURL(host) 160 if err != nil { 161 return nil, err 162 } 163 164 if client != nil { 165 if _, ok := client.Transport.(http.RoundTripper); !ok { 166 return nil, fmt.Errorf("unable to verify TLS configuration, invalid transport %v", client.Transport) 167 } 168 } else { 169 transport := new(http.Transport) 170 sockets.ConfigureTransport(transport, hostURL.Scheme, hostURL.Host) 171 client = &http.Client{ 172 Transport: transport, 173 CheckRedirect: CheckRedirect, 174 } 175 } 176 177 scheme := "http" 178 tlsConfig := resolveTLSConfig(client.Transport) 179 if tlsConfig != nil { 180 // TODO(stevvooe): This isn't really the right way to write clients in Go. 181 // `NewClient` should probably only take an `*http.Client` and work from there. 182 // Unfortunately, the model of having a host-ish/url-thingy as the connection 183 // string has us confusing protocol and transport layers. We continue doing 184 // this to avoid breaking existing clients but this should be addressed. 185 scheme = "https" 186 } 187 188 // TODO: store URL instead of proto/addr/basePath 189 return &Client{ 190 scheme: scheme, 191 host: host, 192 proto: hostURL.Scheme, 193 addr: hostURL.Host, 194 basePath: hostURL.Path, 195 client: client, 196 version: version, 197 customHTTPHeaders: httpHeaders, 198 }, nil 199 } 200 201 // Close the transport used by the client 202 func (cli *Client) Close() error { 203 if t, ok := cli.client.Transport.(*http.Transport); ok { 204 t.CloseIdleConnections() 205 } 206 return nil 207 } 208 209 // getAPIPath returns the versioned request path to call the api. 210 // It appends the query parameters to the path if they are not empty. 211 func (cli *Client) getAPIPath(p string, query url.Values) string { 212 var apiPath string 213 if cli.version != "" { 214 v := strings.TrimPrefix(cli.version, "v") 215 apiPath = path.Join(cli.basePath, "/v"+v, p) 216 } else { 217 apiPath = path.Join(cli.basePath, p) 218 } 219 return (&url.URL{Path: apiPath, RawQuery: query.Encode()}).String() 220 } 221 222 // ClientVersion returns the API version used by this client. 223 func (cli *Client) ClientVersion() string { 224 return cli.version 225 } 226 227 // NegotiateAPIVersion queries the API and updates the version to match the 228 // API version. Any errors are silently ignored. 229 func (cli *Client) NegotiateAPIVersion(ctx context.Context) { 230 ping, _ := cli.Ping(ctx) 231 cli.NegotiateAPIVersionPing(ping) 232 } 233 234 // NegotiateAPIVersionPing updates the client version to match the Ping.APIVersion 235 // if the ping version is less than the default version. 236 func (cli *Client) NegotiateAPIVersionPing(p types.Ping) { 237 if cli.manualOverride { 238 return 239 } 240 241 // try the latest version before versioning headers existed 242 if p.APIVersion == "" { 243 p.APIVersion = "1.24" 244 } 245 246 // if the client is not initialized with a version, start with the latest supported version 247 if cli.version == "" { 248 cli.version = api.DefaultVersion 249 } 250 251 // if server version is lower than the client version, downgrade 252 if versions.LessThan(p.APIVersion, cli.version) { 253 cli.version = p.APIVersion 254 } 255 } 256 257 // DaemonHost returns the host address used by the client 258 func (cli *Client) DaemonHost() string { 259 return cli.host 260 } 261 262 // ParseHost parses a url string, validates the strings is a host url, and returns 263 // the parsed host as: protocol, address, and base path 264 // Deprecated: use ParseHostURL 265 func ParseHost(host string) (string, string, string, error) { 266 hostURL, err := ParseHostURL(host) 267 if err != nil { 268 return "", "", "", err 269 } 270 return hostURL.Scheme, hostURL.Host, hostURL.Path, nil 271 } 272 273 // ParseHostURL parses a url string, validates the string is a host url, and 274 // returns the parsed URL 275 func ParseHostURL(host string) (*url.URL, error) { 276 protoAddrParts := strings.SplitN(host, "://", 2) 277 if len(protoAddrParts) == 1 { 278 return nil, fmt.Errorf("unable to parse docker host `%s`", host) 279 } 280 281 var basePath string 282 proto, addr := protoAddrParts[0], protoAddrParts[1] 283 if proto == "tcp" { 284 parsed, err := url.Parse("tcp://" + addr) 285 if err != nil { 286 return nil, err 287 } 288 addr = parsed.Host 289 basePath = parsed.Path 290 } 291 return &url.URL{ 292 Scheme: proto, 293 Host: addr, 294 Path: basePath, 295 }, nil 296 } 297 298 // CustomHTTPHeaders returns the custom http headers stored by the client. 299 func (cli *Client) CustomHTTPHeaders() map[string]string { 300 m := make(map[string]string) 301 for k, v := range cli.customHTTPHeaders { 302 m[k] = v 303 } 304 return m 305 } 306 307 // SetCustomHTTPHeaders that will be set on every HTTP request made by the client. 308 func (cli *Client) SetCustomHTTPHeaders(headers map[string]string) { 309 cli.customHTTPHeaders = headers 310 }