@@ -107,8 +107,14 @@ func CheckRedirect(req *http.Request, via []*http.Request) error {
107107// Use DOCKER_API_VERSION to set the version of the API to reach, leave empty for latest.
108108// Use DOCKER_CERT_PATH to load the TLS certificates from.
109109// Use DOCKER_TLS_VERIFY to enable or disable TLS verification, off by default.
110+ // deprecated: use NewClientWithOpts(FromEnv)
110111func 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
112118 if dockerCertPath := os .Getenv ("DOCKER_CERT_PATH" ); dockerCertPath != "" {
113119 options := tlsconfig.Options {
114120 CAFile : filepath .Join (dockerCertPath , "ca.pem" ),
@@ -118,10 +124,10 @@ func NewEnvClient() (*Client, error) {
118124 }
119125 tlsc , err := tlsconfig .Client (options )
120126 if err != nil {
121- return nil , err
127+ return err
122128 }
123129
124- client = & http.Client {
130+ httpClient = & http.Client {
125131 Transport : & http.Transport {
126132 TLSClientConfig : tlsc ,
127133 },
@@ -130,74 +136,142 @@ func NewEnvClient() (*Client, error) {
130136 }
131137
132138 host := os .Getenv ("DOCKER_HOST" )
133- if host == "" {
134- host = DefaultDockerHost
139+ if host != "" {
140+ var err error
141+ if err := WithHost (host )(c ); err != nil {
142+ return err
143+ }
144+ httpClient , err = defaultHTTPClient (host )
145+ if err != nil {
146+ return err
147+ }
148+ }
149+ if httpClient != nil {
150+ if err := WithHTTPClient (httpClient )(c ); err != nil {
151+ return err
152+ }
135153 }
136154 version := os .Getenv ("DOCKER_API_VERSION" )
137- if version == "" {
138- version = api .DefaultVersion
155+ if version != "" {
156+ c .version = version
157+ c .manualOverride = true
139158 }
159+ return nil
160+ }
140161
141- cli , err := NewClient (host , version , client , nil )
142- if err != nil {
143- return cli , err
162+ // WithVersion overrides the client version with the specified one
163+ func WithVersion (version string ) func (* Client ) error {
164+ return func (c * Client ) error {
165+ c .version = version
166+ return nil
144167 }
145- if os .Getenv ("DOCKER_API_VERSION" ) != "" {
146- cli .manualOverride = true
168+ }
169+
170+ // WithHost overrides the client host with the specified one
171+ func WithHost (host string ) func (* Client ) error {
172+ return func (c * Client ) error {
173+ hostURL , err := ParseHostURL (host )
174+ if err != nil {
175+ return err
176+ }
177+ c .host = host
178+ c .proto = hostURL .Scheme
179+ c .addr = hostURL .Host
180+ c .basePath = hostURL .Path
181+ client , err := defaultHTTPClient (host )
182+ if err != nil {
183+ return err
184+ }
185+ return WithHTTPClient (client )(c )
147186 }
148- return cli , nil
149187}
150188
151- // NewClient initializes a new API client for the given host and API version.
152- // It uses the given http client as transport.
189+ // WithHTTPClient overrides the client http client with the specified one
190+ func WithHTTPClient (client * http.Client ) func (* Client ) error {
191+ return func (c * Client ) error {
192+ if client != nil {
193+ c .client = client
194+ }
195+ return nil
196+ }
197+ }
198+
199+ // WithHTTPHeaders overrides the client default http headers
200+ func WithHTTPHeaders (headers map [string ]string ) func (* Client ) error {
201+ return func (c * Client ) error {
202+ c .customHTTPHeaders = headers
203+ return nil
204+ }
205+ }
206+
207+ // NewClientWithOpts initializes a new API client with default values. It takes functors
208+ // to modify values when creating it, like `NewClientWithOpts(WithVersion(…))`
153209// It also initializes the custom http headers to add to each request.
154210//
155211// It won't send any version information if the version number is empty. It is
156212// highly recommended that you set a version or your client may break if the
157213// server is upgraded.
158- func NewClient ( host string , version string , client * http. Client , httpHeaders map [ string ] string ) (* Client , error ) {
159- hostURL , err := ParseHostURL ( host )
214+ func NewClientWithOpts ( ops ... func ( * Client ) error ) (* Client , error ) {
215+ client , err := defaultHTTPClient ( DefaultDockerHost )
160216 if err != nil {
161217 return nil , err
162218 }
219+ c := & Client {
220+ host : DefaultDockerHost ,
221+ version : api .DefaultVersion ,
222+ scheme : "http" ,
223+ client : client ,
224+ proto : defaultProto ,
225+ addr : defaultAddr ,
226+ }
163227
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 ,
228+ for _ , op := range ops {
229+ if err := op (c ); err != nil {
230+ return nil , err
174231 }
175232 }
176233
177- scheme := "http"
178- tlsConfig := resolveTLSConfig (client .Transport )
234+ if _ , ok := c .client .Transport .(http.RoundTripper ); ! ok {
235+ return nil , fmt .Errorf ("unable to verify TLS configuration, invalid transport %v" , c .client .Transport )
236+ }
237+ tlsConfig := resolveTLSConfig (c .client .Transport )
179238 if tlsConfig != nil {
180239 // TODO(stevvooe): This isn't really the right way to write clients in Go.
181240 // `NewClient` should probably only take an `*http.Client` and work from there.
182241 // Unfortunately, the model of having a host-ish/url-thingy as the connection
183242 // string has us confusing protocol and transport layers. We continue doing
184243 // this to avoid breaking existing clients but this should be addressed.
185- scheme = "https"
244+ c . scheme = "https"
186245 }
187246
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 ,
247+ return c , nil
248+ }
249+
250+ func defaultHTTPClient (host string ) (* http.Client , error ) {
251+ url , err := ParseHostURL (host )
252+ if err != nil {
253+ return nil , err
254+ }
255+ transport := new (http.Transport )
256+ sockets .ConfigureTransport (transport , url .Scheme , url .Host )
257+ return & http.Client {
258+ Transport : transport ,
259+ CheckRedirect : CheckRedirect ,
198260 }, nil
199261}
200262
263+ // NewClient initializes a new API client for the given host and API version.
264+ // It uses the given http client as transport.
265+ // It also initializes the custom http headers to add to each request.
266+ //
267+ // It won't send any version information if the version number is empty. It is
268+ // highly recommended that you set a version or your client may break if the
269+ // server is upgraded.
270+ // deprecated: use NewClientWithOpts
271+ func NewClient (host string , version string , client * http.Client , httpHeaders map [string ]string ) (* Client , error ) {
272+ return NewClientWithOpts (WithHost (host ), WithVersion (version ), WithHTTPClient (client ), WithHTTPHeaders (httpHeaders ))
273+ }
274+
201275// Close the transport used by the client
202276func (cli * Client ) Close () error {
203277 if t , ok := cli .client .Transport .(* http.Transport ); ok {
0 commit comments