This allows to create a client with default values and override those
using functors. As an example, `NewEnvClient()` becomes
`NewClientWithOpts(FromEnv)` ; and if you want a different api version
for this client : `NewClientWithOpts(FromEnv, WithVersion("1.35"))`
Signed-off-by: Vincent Demeester <vincent@sbr.pm>
| ... | ... |
@@ -107,8 +107,14 @@ func CheckRedirect(req *http.Request, via []*http.Request) error {
|
| 107 | 107 |
// Use DOCKER_API_VERSION to set the version of the API to reach, leave empty for latest. |
| 108 | 108 |
// Use DOCKER_CERT_PATH to load the TLS certificates from. |
| 109 | 109 |
// Use DOCKER_TLS_VERIFY to enable or disable TLS verification, off by default. |
| 110 |
+// deprecated: use NewClientWithOpts(FromEnv) |
|
| 110 | 111 |
func NewEnvClient() (*Client, error) {
|
| 111 |
- var client *http.Client |
|
| 112 |
+ return NewClientWithOpts(FromEnv) |
|
| 113 |
+} |
|
| 114 |
+ |
|
| 115 |
+// FromEnv enhance the default client with values from environment variables |
|
| 116 |
+func FromEnv(c *Client) error {
|
|
| 117 |
+ var httpClient *http.Client |
|
| 112 | 118 |
if dockerCertPath := os.Getenv("DOCKER_CERT_PATH"); dockerCertPath != "" {
|
| 113 | 119 |
options := tlsconfig.Options{
|
| 114 | 120 |
CAFile: filepath.Join(dockerCertPath, "ca.pem"), |
| ... | ... |
@@ -118,10 +124,10 @@ func NewEnvClient() (*Client, error) {
|
| 118 | 118 |
} |
| 119 | 119 |
tlsc, err := tlsconfig.Client(options) |
| 120 | 120 |
if err != nil {
|
| 121 |
- return nil, err |
|
| 121 |
+ return err |
|
| 122 | 122 |
} |
| 123 | 123 |
|
| 124 |
- client = &http.Client{
|
|
| 124 |
+ httpClient = &http.Client{
|
|
| 125 | 125 |
Transport: &http.Transport{
|
| 126 | 126 |
TLSClientConfig: tlsc, |
| 127 | 127 |
}, |
| ... | ... |
@@ -130,74 +136,142 @@ func NewEnvClient() (*Client, error) {
|
| 130 | 130 |
} |
| 131 | 131 |
|
| 132 | 132 |
host := os.Getenv("DOCKER_HOST")
|
| 133 |
- if host == "" {
|
|
| 134 |
- host = DefaultDockerHost |
|
| 133 |
+ if host != "" {
|
|
| 134 |
+ var err error |
|
| 135 |
+ if err := WithHost(host)(c); err != nil {
|
|
| 136 |
+ return err |
|
| 137 |
+ } |
|
| 138 |
+ httpClient, err = defaultHTTPClient(host) |
|
| 139 |
+ if err != nil {
|
|
| 140 |
+ return err |
|
| 141 |
+ } |
|
| 142 |
+ } |
|
| 143 |
+ if httpClient != nil {
|
|
| 144 |
+ if err := WithHTTPClient(httpClient)(c); err != nil {
|
|
| 145 |
+ return err |
|
| 146 |
+ } |
|
| 135 | 147 |
} |
| 136 | 148 |
version := os.Getenv("DOCKER_API_VERSION")
|
| 137 |
- if version == "" {
|
|
| 138 |
- version = api.DefaultVersion |
|
| 149 |
+ if version != "" {
|
|
| 150 |
+ c.version = version |
|
| 151 |
+ c.manualOverride = true |
|
| 139 | 152 |
} |
| 153 |
+ return nil |
|
| 154 |
+} |
|
| 140 | 155 |
|
| 141 |
- cli, err := NewClient(host, version, client, nil) |
|
| 142 |
- if err != nil {
|
|
| 143 |
- return cli, err |
|
| 156 |
+// WithVersion overrides the client version with the specified one |
|
| 157 |
+func WithVersion(version string) func(*Client) error {
|
|
| 158 |
+ return func(c *Client) error {
|
|
| 159 |
+ c.version = version |
|
| 160 |
+ return nil |
|
| 144 | 161 |
} |
| 145 |
- if os.Getenv("DOCKER_API_VERSION") != "" {
|
|
| 146 |
- cli.manualOverride = true |
|
| 162 |
+} |
|
| 163 |
+ |
|
| 164 |
+// WithHost overrides the client host with the specified one |
|
| 165 |
+func WithHost(host string) func(*Client) error {
|
|
| 166 |
+ return func(c *Client) error {
|
|
| 167 |
+ hostURL, err := ParseHostURL(host) |
|
| 168 |
+ if err != nil {
|
|
| 169 |
+ return err |
|
| 170 |
+ } |
|
| 171 |
+ c.host = host |
|
| 172 |
+ c.proto = hostURL.Scheme |
|
| 173 |
+ c.addr = hostURL.Host |
|
| 174 |
+ c.basePath = hostURL.Path |
|
| 175 |
+ client, err := defaultHTTPClient(host) |
|
| 176 |
+ if err != nil {
|
|
| 177 |
+ return err |
|
| 178 |
+ } |
|
| 179 |
+ return WithHTTPClient(client)(c) |
|
| 147 | 180 |
} |
| 148 |
- return cli, nil |
|
| 149 | 181 |
} |
| 150 | 182 |
|
| 151 |
-// NewClient initializes a new API client for the given host and API version. |
|
| 152 |
-// It uses the given http client as transport. |
|
| 183 |
+// WithHTTPClient overrides the client http client with the specified one |
|
| 184 |
+func WithHTTPClient(client *http.Client) func(*Client) error {
|
|
| 185 |
+ return func(c *Client) error {
|
|
| 186 |
+ if client != nil {
|
|
| 187 |
+ c.client = client |
|
| 188 |
+ } |
|
| 189 |
+ return nil |
|
| 190 |
+ } |
|
| 191 |
+} |
|
| 192 |
+ |
|
| 193 |
+// WithHTTPHeaders overrides the client default http headers |
|
| 194 |
+func WithHTTPHeaders(headers map[string]string) func(*Client) error {
|
|
| 195 |
+ return func(c *Client) error {
|
|
| 196 |
+ c.customHTTPHeaders = headers |
|
| 197 |
+ return nil |
|
| 198 |
+ } |
|
| 199 |
+} |
|
| 200 |
+ |
|
| 201 |
+// NewClientWithOpts initializes a new API client with default values. It takes functors |
|
| 202 |
+// to modify values when creating it, like `NewClientWithOpts(WithVersion(…))` |
|
| 153 | 203 |
// It also initializes the custom http headers to add to each request. |
| 154 | 204 |
// |
| 155 | 205 |
// It won't send any version information if the version number is empty. It is |
| 156 | 206 |
// highly recommended that you set a version or your client may break if the |
| 157 | 207 |
// server is upgraded. |
| 158 |
-func NewClient(host string, version string, client *http.Client, httpHeaders map[string]string) (*Client, error) {
|
|
| 159 |
- hostURL, err := ParseHostURL(host) |
|
| 208 |
+func NewClientWithOpts(ops ...func(*Client) error) (*Client, error) {
|
|
| 209 |
+ client, err := defaultHTTPClient(DefaultDockerHost) |
|
| 160 | 210 |
if err != nil {
|
| 161 | 211 |
return nil, err |
| 162 | 212 |
} |
| 213 |
+ c := &Client{
|
|
| 214 |
+ host: DefaultDockerHost, |
|
| 215 |
+ version: api.DefaultVersion, |
|
| 216 |
+ scheme: "http", |
|
| 217 |
+ client: client, |
|
| 218 |
+ proto: defaultProto, |
|
| 219 |
+ addr: defaultAddr, |
|
| 220 |
+ } |
|
| 163 | 221 |
|
| 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, |
|
| 222 |
+ for _, op := range ops {
|
|
| 223 |
+ if err := op(c); err != nil {
|
|
| 224 |
+ return nil, err |
|
| 174 | 225 |
} |
| 175 | 226 |
} |
| 176 | 227 |
|
| 177 |
- scheme := "http" |
|
| 178 |
- tlsConfig := resolveTLSConfig(client.Transport) |
|
| 228 |
+ if _, ok := c.client.Transport.(http.RoundTripper); !ok {
|
|
| 229 |
+ return nil, fmt.Errorf("unable to verify TLS configuration, invalid transport %v", c.client.Transport)
|
|
| 230 |
+ } |
|
| 231 |
+ tlsConfig := resolveTLSConfig(c.client.Transport) |
|
| 179 | 232 |
if tlsConfig != nil {
|
| 180 | 233 |
// TODO(stevvooe): This isn't really the right way to write clients in Go. |
| 181 | 234 |
// `NewClient` should probably only take an `*http.Client` and work from there. |
| 182 | 235 |
// Unfortunately, the model of having a host-ish/url-thingy as the connection |
| 183 | 236 |
// string has us confusing protocol and transport layers. We continue doing |
| 184 | 237 |
// this to avoid breaking existing clients but this should be addressed. |
| 185 |
- scheme = "https" |
|
| 238 |
+ c.scheme = "https" |
|
| 186 | 239 |
} |
| 187 | 240 |
|
| 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, |
|
| 241 |
+ return c, nil |
|
| 242 |
+} |
|
| 243 |
+ |
|
| 244 |
+func defaultHTTPClient(host string) (*http.Client, error) {
|
|
| 245 |
+ url, err := ParseHostURL(host) |
|
| 246 |
+ if err != nil {
|
|
| 247 |
+ return nil, err |
|
| 248 |
+ } |
|
| 249 |
+ transport := new(http.Transport) |
|
| 250 |
+ sockets.ConfigureTransport(transport, url.Scheme, url.Host) |
|
| 251 |
+ return &http.Client{
|
|
| 252 |
+ Transport: transport, |
|
| 253 |
+ CheckRedirect: CheckRedirect, |
|
| 198 | 254 |
}, nil |
| 199 | 255 |
} |
| 200 | 256 |
|
| 257 |
+// NewClient initializes a new API client for the given host and API version. |
|
| 258 |
+// It uses the given http client as transport. |
|
| 259 |
+// It also initializes the custom http headers to add to each request. |
|
| 260 |
+// |
|
| 261 |
+// It won't send any version information if the version number is empty. It is |
|
| 262 |
+// highly recommended that you set a version or your client may break if the |
|
| 263 |
+// server is upgraded. |
|
| 264 |
+// deprecated: use NewClientWithOpts |
|
| 265 |
+func NewClient(host string, version string, client *http.Client, httpHeaders map[string]string) (*Client, error) {
|
|
| 266 |
+ return NewClientWithOpts(WithHost(host), WithVersion(version), WithHTTPClient(client), WithHTTPHeaders(httpHeaders)) |
|
| 267 |
+} |
|
| 268 |
+ |
|
| 201 | 269 |
// Close the transport used by the client |
| 202 | 270 |
func (cli *Client) Close() error {
|
| 203 | 271 |
if t, ok := cli.client.Transport.(*http.Transport); ok {
|
| ... | ... |
@@ -4,3 +4,6 @@ package client // import "github.com/docker/docker/client" |
| 4 | 4 |
|
| 5 | 5 |
// DefaultDockerHost defines os specific default if DOCKER_HOST is unset |
| 6 | 6 |
const DefaultDockerHost = "unix:///var/run/docker.sock" |
| 7 |
+ |
|
| 8 |
+const defaultProto = "unix" |
|
| 9 |
+const defaultAddr = "/var/run/docker.sock" |
| ... | ... |
@@ -2,3 +2,6 @@ package client // import "github.com/docker/docker/client" |
| 2 | 2 |
|
| 3 | 3 |
// DefaultDockerHost defines os specific default if DOCKER_HOST is unset |
| 4 | 4 |
const DefaultDockerHost = "npipe:////./pipe/docker_engine" |
| 5 |
+ |
|
| 6 |
+const defaultProto = "npipe" |
|
| 7 |
+const defaultAddr = "//./pipe/docker_engine" |