options.go 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233
  1. package client
  2. import (
  3. "context"
  4. "net"
  5. "net/http"
  6. "os"
  7. "path/filepath"
  8. "time"
  9. "github.com/docker/go-connections/sockets"
  10. "github.com/docker/go-connections/tlsconfig"
  11. "github.com/pkg/errors"
  12. "go.opentelemetry.io/otel/trace"
  13. )
  14. // Opt is a configuration option to initialize a [Client].
  15. type Opt func(*Client) error
  16. // FromEnv configures the client with values from environment variables. It
  17. // is the equivalent of using the [WithTLSClientConfigFromEnv], [WithHostFromEnv],
  18. // and [WithVersionFromEnv] options.
  19. //
  20. // FromEnv uses the following environment variables:
  21. //
  22. // - DOCKER_HOST ([EnvOverrideHost]) to set the URL to the docker server.
  23. // - DOCKER_API_VERSION ([EnvOverrideAPIVersion]) to set the version of the
  24. // API to use, leave empty for latest.
  25. // - DOCKER_CERT_PATH ([EnvOverrideCertPath]) to specify the directory from
  26. // which to load the TLS certificates ("ca.pem", "cert.pem", "key.pem').
  27. // - DOCKER_TLS_VERIFY ([EnvTLSVerify]) to enable or disable TLS verification
  28. // (off by default).
  29. func FromEnv(c *Client) error {
  30. ops := []Opt{
  31. WithTLSClientConfigFromEnv(),
  32. WithHostFromEnv(),
  33. WithVersionFromEnv(),
  34. }
  35. for _, op := range ops {
  36. if err := op(c); err != nil {
  37. return err
  38. }
  39. }
  40. return nil
  41. }
  42. // WithDialContext applies the dialer to the client transport. This can be
  43. // used to set the Timeout and KeepAlive settings of the client. It returns
  44. // an error if the client does not have a [http.Transport] configured.
  45. func WithDialContext(dialContext func(ctx context.Context, network, addr string) (net.Conn, error)) Opt {
  46. return func(c *Client) error {
  47. if transport, ok := c.client.Transport.(*http.Transport); ok {
  48. transport.DialContext = dialContext
  49. return nil
  50. }
  51. return errors.Errorf("cannot apply dialer to transport: %T", c.client.Transport)
  52. }
  53. }
  54. // WithHost overrides the client host with the specified one.
  55. func WithHost(host string) Opt {
  56. return func(c *Client) error {
  57. hostURL, err := ParseHostURL(host)
  58. if err != nil {
  59. return err
  60. }
  61. c.host = host
  62. c.proto = hostURL.Scheme
  63. c.addr = hostURL.Host
  64. c.basePath = hostURL.Path
  65. if transport, ok := c.client.Transport.(*http.Transport); ok {
  66. return sockets.ConfigureTransport(transport, c.proto, c.addr)
  67. }
  68. return errors.Errorf("cannot apply host to transport: %T", c.client.Transport)
  69. }
  70. }
  71. // WithHostFromEnv overrides the client host with the host specified in the
  72. // DOCKER_HOST ([EnvOverrideHost]) environment variable. If DOCKER_HOST is not set,
  73. // or set to an empty value, the host is not modified.
  74. func WithHostFromEnv() Opt {
  75. return func(c *Client) error {
  76. if host := os.Getenv(EnvOverrideHost); host != "" {
  77. return WithHost(host)(c)
  78. }
  79. return nil
  80. }
  81. }
  82. // WithHTTPClient overrides the client's HTTP client with the specified one.
  83. func WithHTTPClient(client *http.Client) Opt {
  84. return func(c *Client) error {
  85. if client != nil {
  86. c.client = client
  87. }
  88. return nil
  89. }
  90. }
  91. // WithTimeout configures the time limit for requests made by the HTTP client.
  92. func WithTimeout(timeout time.Duration) Opt {
  93. return func(c *Client) error {
  94. c.client.Timeout = timeout
  95. return nil
  96. }
  97. }
  98. // WithUserAgent configures the User-Agent header to use for HTTP requests.
  99. // It overrides any User-Agent set in headers. When set to an empty string,
  100. // the User-Agent header is removed, and no header is sent.
  101. func WithUserAgent(ua string) Opt {
  102. return func(c *Client) error {
  103. c.userAgent = &ua
  104. return nil
  105. }
  106. }
  107. // WithHTTPHeaders appends custom HTTP headers to the client's default headers.
  108. // It does not allow for built-in headers (such as "User-Agent", if set) to
  109. // be overridden. Also see [WithUserAgent].
  110. func WithHTTPHeaders(headers map[string]string) Opt {
  111. return func(c *Client) error {
  112. c.customHTTPHeaders = headers
  113. return nil
  114. }
  115. }
  116. // WithScheme overrides the client scheme with the specified one.
  117. func WithScheme(scheme string) Opt {
  118. return func(c *Client) error {
  119. c.scheme = scheme
  120. return nil
  121. }
  122. }
  123. // WithTLSClientConfig applies a TLS config to the client transport.
  124. func WithTLSClientConfig(cacertPath, certPath, keyPath string) Opt {
  125. return func(c *Client) error {
  126. transport, ok := c.client.Transport.(*http.Transport)
  127. if !ok {
  128. return errors.Errorf("cannot apply tls config to transport: %T", c.client.Transport)
  129. }
  130. config, err := tlsconfig.Client(tlsconfig.Options{
  131. CAFile: cacertPath,
  132. CertFile: certPath,
  133. KeyFile: keyPath,
  134. ExclusiveRootPools: true,
  135. })
  136. if err != nil {
  137. return errors.Wrap(err, "failed to create tls config")
  138. }
  139. transport.TLSClientConfig = config
  140. return nil
  141. }
  142. }
  143. // WithTLSClientConfigFromEnv configures the client's TLS settings with the
  144. // settings in the DOCKER_CERT_PATH ([EnvOverrideCertPath]) and DOCKER_TLS_VERIFY
  145. // ([EnvTLSVerify]) environment variables. If DOCKER_CERT_PATH is not set or empty,
  146. // TLS configuration is not modified.
  147. //
  148. // WithTLSClientConfigFromEnv uses the following environment variables:
  149. //
  150. // - DOCKER_CERT_PATH ([EnvOverrideCertPath]) to specify the directory from
  151. // which to load the TLS certificates ("ca.pem", "cert.pem", "key.pem").
  152. // - DOCKER_TLS_VERIFY ([EnvTLSVerify]) to enable or disable TLS verification
  153. // (off by default).
  154. func WithTLSClientConfigFromEnv() Opt {
  155. return func(c *Client) error {
  156. dockerCertPath := os.Getenv(EnvOverrideCertPath)
  157. if dockerCertPath == "" {
  158. return nil
  159. }
  160. tlsc, err := tlsconfig.Client(tlsconfig.Options{
  161. CAFile: filepath.Join(dockerCertPath, "ca.pem"),
  162. CertFile: filepath.Join(dockerCertPath, "cert.pem"),
  163. KeyFile: filepath.Join(dockerCertPath, "key.pem"),
  164. InsecureSkipVerify: os.Getenv(EnvTLSVerify) == "",
  165. })
  166. if err != nil {
  167. return err
  168. }
  169. c.client = &http.Client{
  170. Transport: &http.Transport{TLSClientConfig: tlsc},
  171. CheckRedirect: CheckRedirect,
  172. }
  173. return nil
  174. }
  175. }
  176. // WithVersion overrides the client version with the specified one. If an empty
  177. // version is provided, the value is ignored to allow version negotiation
  178. // (see [WithAPIVersionNegotiation]).
  179. func WithVersion(version string) Opt {
  180. return func(c *Client) error {
  181. if version != "" {
  182. c.version = version
  183. c.manualOverride = true
  184. }
  185. return nil
  186. }
  187. }
  188. // WithVersionFromEnv overrides the client version with the version specified in
  189. // the DOCKER_API_VERSION ([EnvOverrideAPIVersion]) environment variable.
  190. // If DOCKER_API_VERSION is not set, or set to an empty value, the version
  191. // is not modified.
  192. func WithVersionFromEnv() Opt {
  193. return func(c *Client) error {
  194. return WithVersion(os.Getenv(EnvOverrideAPIVersion))(c)
  195. }
  196. }
  197. // WithAPIVersionNegotiation enables automatic API version negotiation for the client.
  198. // With this option enabled, the client automatically negotiates the API version
  199. // to use when making requests. API version negotiation is performed on the first
  200. // request; subsequent requests do not re-negotiate.
  201. func WithAPIVersionNegotiation() Opt {
  202. return func(c *Client) error {
  203. c.negotiateVersion = true
  204. return nil
  205. }
  206. }
  207. // WithTraceProvider sets the trace provider for the client.
  208. // If this is not set then the global trace provider will be used.
  209. func WithTraceProvider(provider trace.TracerProvider) Opt {
  210. return func(c *Client) error {
  211. c.tp = provider
  212. return nil
  213. }
  214. }