client.go 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295
  1. /*
  2. Package client is a Go client for the Docker Engine API.
  3. For more information about the Engine API, see the documentation:
  4. https://docs.docker.com/engine/api/
  5. Usage
  6. You use the library by creating a client object and calling methods on it. The
  7. client can be created either from environment variables with NewClientWithOpts(client.FromEnv),
  8. or configured manually with NewClient().
  9. For example, to list running containers (the equivalent of "docker ps"):
  10. package main
  11. import (
  12. "context"
  13. "fmt"
  14. "github.com/docker/docker/api/types"
  15. "github.com/docker/docker/client"
  16. )
  17. func main() {
  18. cli, err := client.NewClientWithOpts(client.FromEnv)
  19. if err != nil {
  20. panic(err)
  21. }
  22. containers, err := cli.ContainerList(context.Background(), types.ContainerListOptions{})
  23. if err != nil {
  24. panic(err)
  25. }
  26. for _, container := range containers {
  27. fmt.Printf("%s %s\n", container.ID[:10], container.Image)
  28. }
  29. }
  30. */
  31. package client // import "github.com/docker/docker/client"
  32. import (
  33. "context"
  34. "fmt"
  35. "net"
  36. "net/http"
  37. "net/url"
  38. "path"
  39. "strings"
  40. "github.com/docker/docker/api"
  41. "github.com/docker/docker/api/types"
  42. "github.com/docker/docker/api/types/versions"
  43. "github.com/docker/go-connections/sockets"
  44. "github.com/pkg/errors"
  45. )
  46. // ErrRedirect is the error returned by checkRedirect when the request is non-GET.
  47. var ErrRedirect = errors.New("unexpected redirect in response")
  48. // Client is the API client that performs all operations
  49. // against a docker server.
  50. type Client struct {
  51. // scheme sets the scheme for the client
  52. scheme string
  53. // host holds the server address to connect to
  54. host string
  55. // proto holds the client protocol i.e. unix.
  56. proto string
  57. // addr holds the client address.
  58. addr string
  59. // basePath holds the path to prepend to the requests.
  60. basePath string
  61. // client used to send and receive http requests.
  62. client *http.Client
  63. // version of the server to talk to.
  64. version string
  65. // custom http headers configured by users.
  66. customHTTPHeaders map[string]string
  67. // manualOverride is set to true when the version was set by users.
  68. manualOverride bool
  69. // negotiateVersion indicates if the client should automatically negotiate
  70. // the API version to use when making requests. API version negotiation is
  71. // performed on the first request, after which negotiated is set to "true"
  72. // so that subsequent requests do not re-negotiate.
  73. negotiateVersion bool
  74. // negotiated indicates that API version negotiation took place
  75. negotiated bool
  76. }
  77. // CheckRedirect specifies the policy for dealing with redirect responses:
  78. // If the request is non-GET return `ErrRedirect`. Otherwise use the last response.
  79. //
  80. // Go 1.8 changes behavior for HTTP redirects (specifically 301, 307, and 308) in the client .
  81. // The Docker client (and by extension docker API client) can be made to send a request
  82. // like POST /containers//start where what would normally be in the name section of the URL is empty.
  83. // This triggers an HTTP 301 from the daemon.
  84. // In go 1.8 this 301 will be converted to a GET request, and ends up getting a 404 from the daemon.
  85. // This behavior change manifests in the client in that before the 301 was not followed and
  86. // the client did not generate an error, but now results in a message like Error response from daemon: page not found.
  87. func CheckRedirect(req *http.Request, via []*http.Request) error {
  88. if via[0].Method == http.MethodGet {
  89. return http.ErrUseLastResponse
  90. }
  91. return ErrRedirect
  92. }
  93. // NewClientWithOpts initializes a new API client with default values. It takes functors
  94. // to modify values when creating it, like `NewClientWithOpts(WithVersion(…))`
  95. // It also initializes the custom http headers to add to each request.
  96. //
  97. // It won't send any version information if the version number is empty. It is
  98. // highly recommended that you set a version or your client may break if the
  99. // server is upgraded.
  100. func NewClientWithOpts(ops ...Opt) (*Client, error) {
  101. client, err := defaultHTTPClient(DefaultDockerHost)
  102. if err != nil {
  103. return nil, err
  104. }
  105. c := &Client{
  106. host: DefaultDockerHost,
  107. version: api.DefaultVersion,
  108. client: client,
  109. proto: defaultProto,
  110. addr: defaultAddr,
  111. }
  112. for _, op := range ops {
  113. if err := op(c); err != nil {
  114. return nil, err
  115. }
  116. }
  117. if _, ok := c.client.Transport.(http.RoundTripper); !ok {
  118. return nil, fmt.Errorf("unable to verify TLS configuration, invalid transport %v", c.client.Transport)
  119. }
  120. if c.scheme == "" {
  121. c.scheme = "http"
  122. tlsConfig := resolveTLSConfig(c.client.Transport)
  123. if tlsConfig != nil {
  124. // TODO(stevvooe): This isn't really the right way to write clients in Go.
  125. // `NewClient` should probably only take an `*http.Client` and work from there.
  126. // Unfortunately, the model of having a host-ish/url-thingy as the connection
  127. // string has us confusing protocol and transport layers. We continue doing
  128. // this to avoid breaking existing clients but this should be addressed.
  129. c.scheme = "https"
  130. }
  131. }
  132. return c, nil
  133. }
  134. func defaultHTTPClient(host string) (*http.Client, error) {
  135. url, err := ParseHostURL(host)
  136. if err != nil {
  137. return nil, err
  138. }
  139. transport := new(http.Transport)
  140. sockets.ConfigureTransport(transport, url.Scheme, url.Host)
  141. return &http.Client{
  142. Transport: transport,
  143. CheckRedirect: CheckRedirect,
  144. }, nil
  145. }
  146. // Close the transport used by the client
  147. func (cli *Client) Close() error {
  148. if t, ok := cli.client.Transport.(*http.Transport); ok {
  149. t.CloseIdleConnections()
  150. }
  151. return nil
  152. }
  153. // getAPIPath returns the versioned request path to call the api.
  154. // It appends the query parameters to the path if they are not empty.
  155. func (cli *Client) getAPIPath(ctx context.Context, p string, query url.Values) string {
  156. var apiPath string
  157. if cli.negotiateVersion && !cli.negotiated {
  158. cli.NegotiateAPIVersion(ctx)
  159. }
  160. if cli.version != "" {
  161. v := strings.TrimPrefix(cli.version, "v")
  162. apiPath = path.Join(cli.basePath, "/v"+v, p)
  163. } else {
  164. apiPath = path.Join(cli.basePath, p)
  165. }
  166. return (&url.URL{Path: apiPath, RawQuery: query.Encode()}).String()
  167. }
  168. // ClientVersion returns the API version used by this client.
  169. func (cli *Client) ClientVersion() string {
  170. return cli.version
  171. }
  172. // NegotiateAPIVersion queries the API and updates the version to match the
  173. // API version. Any errors are silently ignored. If a manual override is in place,
  174. // either through the `DOCKER_API_VERSION` environment variable, or if the client
  175. // was initialized with a fixed version (`opts.WithVersion(xx)`), no negotiation
  176. // will be performed.
  177. func (cli *Client) NegotiateAPIVersion(ctx context.Context) {
  178. if !cli.manualOverride {
  179. ping, _ := cli.Ping(ctx)
  180. cli.negotiateAPIVersionPing(ping)
  181. }
  182. }
  183. // NegotiateAPIVersionPing updates the client version to match the Ping.APIVersion
  184. // if the ping version is less than the default version. If a manual override is
  185. // in place, either through the `DOCKER_API_VERSION` environment variable, or if
  186. // the client was initialized with a fixed version (`opts.WithVersion(xx)`), no
  187. // negotiation is performed.
  188. func (cli *Client) NegotiateAPIVersionPing(p types.Ping) {
  189. if !cli.manualOverride {
  190. cli.negotiateAPIVersionPing(p)
  191. }
  192. }
  193. // negotiateAPIVersionPing queries the API and updates the version to match the
  194. // API version. Any errors are silently ignored.
  195. func (cli *Client) negotiateAPIVersionPing(p types.Ping) {
  196. // try the latest version before versioning headers existed
  197. if p.APIVersion == "" {
  198. p.APIVersion = "1.24"
  199. }
  200. // if the client is not initialized with a version, start with the latest supported version
  201. if cli.version == "" {
  202. cli.version = api.DefaultVersion
  203. }
  204. // if server version is lower than the client version, downgrade
  205. if versions.LessThan(p.APIVersion, cli.version) {
  206. cli.version = p.APIVersion
  207. }
  208. // Store the results, so that automatic API version negotiation (if enabled)
  209. // won't be performed on the next request.
  210. if cli.negotiateVersion {
  211. cli.negotiated = true
  212. }
  213. }
  214. // DaemonHost returns the host address used by the client
  215. func (cli *Client) DaemonHost() string {
  216. return cli.host
  217. }
  218. // HTTPClient returns a copy of the HTTP client bound to the server
  219. func (cli *Client) HTTPClient() *http.Client {
  220. c := *cli.client
  221. return &c
  222. }
  223. // ParseHostURL parses a url string, validates the string is a host url, and
  224. // returns the parsed URL
  225. func ParseHostURL(host string) (*url.URL, error) {
  226. protoAddrParts := strings.SplitN(host, "://", 2)
  227. if len(protoAddrParts) == 1 {
  228. return nil, fmt.Errorf("unable to parse docker host `%s`", host)
  229. }
  230. var basePath string
  231. proto, addr := protoAddrParts[0], protoAddrParts[1]
  232. if proto == "tcp" {
  233. parsed, err := url.Parse("tcp://" + addr)
  234. if err != nil {
  235. return nil, err
  236. }
  237. addr = parsed.Host
  238. basePath = parsed.Path
  239. }
  240. return &url.URL{
  241. Scheme: proto,
  242. Host: addr,
  243. Path: basePath,
  244. }, nil
  245. }
  246. // Dialer returns a dialer for a raw stream connection, with HTTP/1.1 header, that can be used for proxying the daemon connection.
  247. // Used by `docker dial-stdio` (docker/cli#889).
  248. func (cli *Client) Dialer() func(context.Context) (net.Conn, error) {
  249. return func(ctx context.Context) (net.Conn, error) {
  250. if transport, ok := cli.client.Transport.(*http.Transport); ok {
  251. if transport.DialContext != nil && transport.TLSClientConfig == nil {
  252. return transport.DialContext(ctx, cli.proto, cli.addr)
  253. }
  254. }
  255. return fallbackDial(cli.proto, cli.addr, resolveTLSConfig(cli.client.Transport))
  256. }
  257. }