client.go 13 KB

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