client.go 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290
  1. /*
  2. Package client is a Go client for the Docker Engine API.
  3. The "docker" command uses this package to communicate with the daemon. It can also
  4. be used by your own Go applications to do anything the command-line interface does
  5. - running containers, pulling images, managing swarms, etc.
  6. For more information about the Engine API, see the documentation:
  7. https://docs.docker.com/engine/reference/api/
  8. Usage
  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. For example, to list running containers (the equivalent of "docker ps"):
  13. package main
  14. import (
  15. "context"
  16. "fmt"
  17. "github.com/docker/docker/api/types"
  18. "github.com/docker/docker/client"
  19. )
  20. func main() {
  21. cli, err := client.NewEnvClient()
  22. if err != nil {
  23. panic(err)
  24. }
  25. containers, err := cli.ContainerList(context.Background(), types.ContainerListOptions{})
  26. if err != nil {
  27. panic(err)
  28. }
  29. for _, container := range containers {
  30. fmt.Printf("%s %s\n", container.ID[:10], container.Image)
  31. }
  32. }
  33. */
  34. package client
  35. import (
  36. "errors"
  37. "fmt"
  38. "net/http"
  39. "net/url"
  40. "os"
  41. "path/filepath"
  42. "strings"
  43. "github.com/docker/docker/api"
  44. "github.com/docker/go-connections/sockets"
  45. "github.com/docker/go-connections/tlsconfig"
  46. )
  47. // ErrRedirect is the error returned by checkRedirect when the request is non-GET.
  48. var ErrRedirect = errors.New("unexpected redirect in response")
  49. // Client is the API client that performs all operations
  50. // against a docker server.
  51. type Client struct {
  52. // scheme sets the scheme for the client
  53. scheme string
  54. // host holds the server address to connect to
  55. host string
  56. // proto holds the client protocol i.e. unix.
  57. proto string
  58. // addr holds the client address.
  59. addr string
  60. // basePath holds the path to prepend to the requests.
  61. basePath string
  62. // client used to send and receive http requests.
  63. client *http.Client
  64. // version of the server to talk to.
  65. version string
  66. // custom http headers configured by users.
  67. customHTTPHeaders map[string]string
  68. // manualOverride is set to true when the version was set by users.
  69. manualOverride bool
  70. }
  71. // CheckRedirect specifies the policy for dealing with redirect responses:
  72. // If the request is non-GET return `ErrRedirect`. Otherwise use the last response.
  73. //
  74. // Go 1.8 changes behavior for HTTP redirects (specificlaly 301, 307, and 308) in the client .
  75. // The Docker client (and by extension docker API client) can be made to to send a request
  76. // like POST /containers//start where what would normally be in the name section of the URL is empty.
  77. // This triggers an HTTP 301 from the daemon.
  78. // In go 1.8 this 301 will be converted to a GET request, and ends up getting a 404 from the daemon.
  79. // This behavior change manifests in the client in that before the 301 was not followed and
  80. // the client did not generate an error, but now results in a message like Error response from daemon: page not found.
  81. func CheckRedirect(req *http.Request, via []*http.Request) error {
  82. if via[0].Method == http.MethodGet {
  83. return http.ErrUseLastResponse
  84. }
  85. return ErrRedirect
  86. }
  87. // NewEnvClient initializes a new API client based on environment variables.
  88. // Use DOCKER_HOST to set the url to the docker server.
  89. // Use DOCKER_API_VERSION to set the version of the API to reach, leave empty for latest.
  90. // Use DOCKER_CERT_PATH to load the TLS certificates from.
  91. // Use DOCKER_TLS_VERIFY to enable or disable TLS verification, off by default.
  92. func NewEnvClient() (*Client, error) {
  93. var client *http.Client
  94. if dockerCertPath := os.Getenv("DOCKER_CERT_PATH"); dockerCertPath != "" {
  95. options := tlsconfig.Options{
  96. CAFile: filepath.Join(dockerCertPath, "ca.pem"),
  97. CertFile: filepath.Join(dockerCertPath, "cert.pem"),
  98. KeyFile: filepath.Join(dockerCertPath, "key.pem"),
  99. InsecureSkipVerify: os.Getenv("DOCKER_TLS_VERIFY") == "",
  100. }
  101. tlsc, err := tlsconfig.Client(options)
  102. if err != nil {
  103. return nil, err
  104. }
  105. client = &http.Client{
  106. Transport: &http.Transport{
  107. TLSClientConfig: tlsc,
  108. },
  109. CheckRedirect: CheckRedirect,
  110. }
  111. }
  112. host := os.Getenv("DOCKER_HOST")
  113. if host == "" {
  114. host = DefaultDockerHost
  115. }
  116. version := os.Getenv("DOCKER_API_VERSION")
  117. if version == "" {
  118. version = api.DefaultVersion
  119. }
  120. cli, err := NewClient(host, version, client, nil)
  121. if err != nil {
  122. return cli, err
  123. }
  124. if os.Getenv("DOCKER_API_VERSION") != "" {
  125. cli.manualOverride = true
  126. }
  127. return cli, nil
  128. }
  129. // NewClient initializes a new API client for the given host and API version.
  130. // It uses the given http client as transport.
  131. // It also initializes the custom http headers to add to each request.
  132. //
  133. // It won't send any version information if the version number is empty. It is
  134. // highly recommended that you set a version or your client may break if the
  135. // server is upgraded.
  136. func NewClient(host string, version string, client *http.Client, httpHeaders map[string]string) (*Client, error) {
  137. proto, addr, basePath, err := ParseHost(host)
  138. if err != nil {
  139. return nil, err
  140. }
  141. if client != nil {
  142. if _, ok := client.Transport.(*http.Transport); !ok {
  143. return nil, fmt.Errorf("unable to verify TLS configuration, invalid transport %v", client.Transport)
  144. }
  145. } else {
  146. transport := new(http.Transport)
  147. sockets.ConfigureTransport(transport, proto, addr)
  148. client = &http.Client{
  149. Transport: transport,
  150. CheckRedirect: CheckRedirect,
  151. }
  152. }
  153. scheme := "http"
  154. tlsConfig := resolveTLSConfig(client.Transport)
  155. if tlsConfig != nil {
  156. // TODO(stevvooe): This isn't really the right way to write clients in Go.
  157. // `NewClient` should probably only take an `*http.Client` and work from there.
  158. // Unfortunately, the model of having a host-ish/url-thingy as the connection
  159. // string has us confusing protocol and transport layers. We continue doing
  160. // this to avoid breaking existing clients but this should be addressed.
  161. scheme = "https"
  162. }
  163. return &Client{
  164. scheme: scheme,
  165. host: host,
  166. proto: proto,
  167. addr: addr,
  168. basePath: basePath,
  169. client: client,
  170. version: version,
  171. customHTTPHeaders: httpHeaders,
  172. }, nil
  173. }
  174. // Close ensures that transport.Client is closed
  175. // especially needed while using NewClient with *http.Client = nil
  176. // for example
  177. // client.NewClient("unix:///var/run/docker.sock", nil, "v1.18", map[string]string{"User-Agent": "engine-api-cli-1.0"})
  178. func (cli *Client) Close() error {
  179. if t, ok := cli.client.Transport.(*http.Transport); ok {
  180. t.CloseIdleConnections()
  181. }
  182. return nil
  183. }
  184. // getAPIPath returns the versioned request path to call the api.
  185. // It appends the query parameters to the path if they are not empty.
  186. func (cli *Client) getAPIPath(p string, query url.Values) string {
  187. var apiPath string
  188. if cli.version != "" {
  189. v := strings.TrimPrefix(cli.version, "v")
  190. apiPath = fmt.Sprintf("%s/v%s%s", cli.basePath, v, p)
  191. } else {
  192. apiPath = fmt.Sprintf("%s%s", cli.basePath, p)
  193. }
  194. u := &url.URL{
  195. Path: apiPath,
  196. }
  197. if len(query) > 0 {
  198. u.RawQuery = query.Encode()
  199. }
  200. return u.String()
  201. }
  202. // ClientVersion returns the version string associated with this
  203. // instance of the Client. Note that this value can be changed
  204. // via the DOCKER_API_VERSION env var.
  205. // This operation doesn't acquire a mutex.
  206. func (cli *Client) ClientVersion() string {
  207. return cli.version
  208. }
  209. // UpdateClientVersion updates the version string associated with this
  210. // instance of the Client. This operation doesn't acquire a mutex.
  211. func (cli *Client) UpdateClientVersion(v string) {
  212. if !cli.manualOverride {
  213. cli.version = v
  214. }
  215. }
  216. // DaemonHost returns the host associated with this instance of the Client.
  217. // This operation doesn't acquire a mutex.
  218. func (cli *Client) DaemonHost() string {
  219. return cli.host
  220. }
  221. // ParseHost verifies that the given host strings is valid.
  222. func ParseHost(host string) (string, string, string, error) {
  223. protoAddrParts := strings.SplitN(host, "://", 2)
  224. if len(protoAddrParts) == 1 {
  225. return "", "", "", fmt.Errorf("unable to parse docker host `%s`", host)
  226. }
  227. var basePath string
  228. proto, addr := protoAddrParts[0], protoAddrParts[1]
  229. if proto == "tcp" {
  230. parsed, err := url.Parse("tcp://" + addr)
  231. if err != nil {
  232. return "", "", "", err
  233. }
  234. addr = parsed.Host
  235. basePath = parsed.Path
  236. }
  237. return proto, addr, basePath, nil
  238. }
  239. // CustomHTTPHeaders returns the custom http headers associated with this
  240. // instance of the Client. This operation doesn't acquire a mutex.
  241. func (cli *Client) CustomHTTPHeaders() map[string]string {
  242. m := make(map[string]string)
  243. for k, v := range cli.customHTTPHeaders {
  244. m[k] = v
  245. }
  246. return m
  247. }
  248. // SetCustomHTTPHeaders updates the custom http headers associated with this
  249. // instance of the Client. This operation doesn't acquire a mutex.
  250. func (cli *Client) SetCustomHTTPHeaders(headers map[string]string) {
  251. cli.customHTTPHeaders = headers
  252. }