@@ -3,26 +3,25 @@ package command
33import (
44 "context"
55 "io"
6- "net"
7- "net/http"
86 "os"
97 "path/filepath"
108 "runtime"
119 "strconv"
12- "time"
1310
1411 "github.com/docker/cli/cli"
1512 "github.com/docker/cli/cli/config"
1613 cliconfig "github.com/docker/cli/cli/config"
1714 "github.com/docker/cli/cli/config/configfile"
18- "github.com/docker/cli/cli/connhelper"
15+ dcontext "github.com/docker/cli/cli/context"
16+ "github.com/docker/cli/cli/context/docker"
17+ kubcontext "github.com/docker/cli/cli/context/kubernetes"
18+ "github.com/docker/cli/cli/context/store"
1919 cliflags "github.com/docker/cli/cli/flags"
2020 manifeststore "github.com/docker/cli/cli/manifest/store"
2121 registryclient "github.com/docker/cli/cli/registry/client"
2222 "github.com/docker/cli/cli/trust"
2323 dopts "github.com/docker/cli/opts"
2424 clitypes "github.com/docker/cli/types"
25- "github.com/docker/docker/api"
2625 "github.com/docker/docker/api/types"
2726 registrytypes "github.com/docker/docker/api/types/registry"
2827 "github.com/docker/docker/client"
@@ -34,6 +33,9 @@ import (
3433 "github.com/theupdateframework/notary/passphrase"
3534)
3635
36+ // ContextDockerHost is the reported context when DOCKER_HOST env var or -H flag is set
37+ const ContextDockerHost = "<DOCKER_HOST>"
38+
3739// Streams is an interface which exposes the standard input and output streams
3840type Streams interface {
3941 In () * InStream
@@ -57,6 +59,9 @@ type Cli interface {
5759 RegistryClient (bool ) registryclient.RegistryClient
5860 ContentTrustEnabled () bool
5961 NewContainerizedEngineClient (sockPath string ) (clitypes.ContainerizedClient , error )
62+ ContextStore () store.Store
63+ CurrentContext () string
64+ StackOrchestrator (flagValue string ) (Orchestrator , error )
6065}
6166
6267// DockerCli is an instance the docker command line client.
@@ -71,8 +76,16 @@ type DockerCli struct {
7176 clientInfo ClientInfo
7277 contentTrust bool
7378 newContainerizeClient func (string ) (clitypes.ContainerizedClient , error )
79+ contextStore store.Store
80+ currentContext string
7481}
7582
83+ var storeConfig = store .NewConfig (
84+ func () interface {} { return & DockerContext {} },
85+ store .EndpointTypeGetter (docker .DockerEndpoint , func () interface {} { return & docker.EndpointMeta {} }),
86+ store .EndpointTypeGetter (kubcontext .KubernetesEndpoint , func () interface {} { return & kubcontext.EndpointMeta {} }),
87+ )
88+
7689// DefaultVersion returns api.defaultVersion or DOCKER_API_VERSION if specified.
7790func (cli * DockerCli ) DefaultVersion () string {
7891 return cli .clientInfo .DefaultVersion
@@ -167,14 +180,23 @@ func (cli *DockerCli) RegistryClient(allowInsecure bool) registryclient.Registry
167180// line flags are parsed.
168181func (cli * DockerCli ) Initialize (opts * cliflags.ClientOptions ) error {
169182 cli .configFile = cliconfig .LoadDefaultConfigFile (cli .err )
170-
171183 var err error
172- cli .client , err = NewAPIClientFromFlags (opts .Common , cli .configFile )
184+ cli .contextStore = store .New (cliconfig .ContextStoreDir (), storeConfig )
185+ cli .currentContext , err = resolveContextName (opts .Common , cli .configFile )
186+ if err != nil {
187+ return err
188+ }
189+ endpoint , err := resolveDockerEndpoint (cli .contextStore , cli .currentContext , opts .Common )
190+ if err != nil {
191+ return errors .Wrap (err , "unable to resolve docker endpoint" )
192+ }
193+
194+ cli .client , err = newAPIClientFromEndpoint (endpoint , cli .configFile )
173195 if tlsconfig .IsErrEncryptedKey (err ) {
174196 passRetriever := passphrase .PromptRetrieverWithInOut (cli .In (), cli .Out (), nil )
175197 newClient := func (password string ) (client.APIClient , error ) {
176- opts . Common . TLSOptions . Passphrase = password
177- return NewAPIClientFromFlags ( opts . Common , cli .configFile )
198+ endpoint . TLSPassword = password
199+ return newAPIClientFromEndpoint ( endpoint , cli .configFile )
178200 }
179201 cli .client , err = getClientWithPassword (passRetriever , newClient )
180202 }
@@ -198,6 +220,75 @@ func (cli *DockerCli) Initialize(opts *cliflags.ClientOptions) error {
198220 return nil
199221}
200222
223+ // NewAPIClientFromFlags creates a new APIClient from command line flags
224+ func NewAPIClientFromFlags (opts * cliflags.CommonOptions , configFile * configfile.ConfigFile ) (client.APIClient , error ) {
225+ store := store .New (cliconfig .ContextStoreDir (), storeConfig )
226+ contextName , err := resolveContextName (opts , configFile )
227+ if err != nil {
228+ return nil , err
229+ }
230+ endpoint , err := resolveDockerEndpoint (store , contextName , opts )
231+ if err != nil {
232+ return nil , errors .Wrap (err , "unable to resolve docker endpoint" )
233+ }
234+ return newAPIClientFromEndpoint (endpoint , configFile )
235+ }
236+
237+ func newAPIClientFromEndpoint (ep docker.Endpoint , configFile * configfile.ConfigFile ) (client.APIClient , error ) {
238+ clientOpts , err := ep .ClientOpts ()
239+ if err != nil {
240+ return nil , err
241+ }
242+ customHeaders := configFile .HTTPHeaders
243+ if customHeaders == nil {
244+ customHeaders = map [string ]string {}
245+ }
246+ customHeaders ["User-Agent" ] = UserAgent ()
247+ clientOpts = append (clientOpts , client .WithHTTPHeaders (customHeaders ))
248+ return client .NewClientWithOpts (clientOpts ... )
249+ }
250+
251+ func resolveDockerEndpoint (s store.Store , contextName string , opts * cliflags.CommonOptions ) (docker.Endpoint , error ) {
252+ if contextName != ContextDockerHost {
253+ ctxMeta , err := s .GetContextMetadata (contextName )
254+ if err != nil {
255+ return docker.Endpoint {}, err
256+ }
257+ epMeta , err := docker .EndpointFromContext (ctxMeta )
258+ if err != nil {
259+ return docker.Endpoint {}, err
260+ }
261+ return epMeta .WithTLSData (s , contextName )
262+ }
263+ host , err := getServerHost (opts .Hosts , opts .TLSOptions )
264+ if err != nil {
265+ return docker.Endpoint {}, err
266+ }
267+
268+ var (
269+ skipTLSVerify bool
270+ tlsData * dcontext.TLSData
271+ )
272+
273+ if opts .TLSOptions != nil {
274+ skipTLSVerify = opts .TLSOptions .InsecureSkipVerify
275+ tlsData , err = dcontext .TLSDataFromFiles (opts .TLSOptions .CAFile , opts .TLSOptions .CertFile , opts .TLSOptions .KeyFile )
276+ if err != nil {
277+ return docker.Endpoint {}, err
278+ }
279+ }
280+
281+ return docker.Endpoint {
282+ EndpointMeta : docker.EndpointMeta {
283+ EndpointMetaBase : dcontext.EndpointMetaBase {
284+ Host : host ,
285+ SkipTLSVerify : skipTLSVerify ,
286+ },
287+ },
288+ TLSData : tlsData ,
289+ }, nil
290+ }
291+
201292func isEnabled (value string ) (bool , error ) {
202293 switch value {
203294 case "enabled" :
@@ -253,6 +344,51 @@ func (cli *DockerCli) NewContainerizedEngineClient(sockPath string) (clitypes.Co
253344 return cli .newContainerizeClient (sockPath )
254345}
255346
347+ // ContextStore returns the ContextStore
348+ func (cli * DockerCli ) ContextStore () store.Store {
349+ return cli .contextStore
350+ }
351+
352+ // CurrentContext returns the current context name
353+ func (cli * DockerCli ) CurrentContext () string {
354+ return cli .currentContext
355+ }
356+
357+ // StackOrchestrator resolves which stack orchestrator is in use
358+ func (cli * DockerCli ) StackOrchestrator (flagValue string ) (Orchestrator , error ) {
359+ var ctxOrchestrator string
360+
361+ configFile := cli .configFile
362+ if configFile == nil {
363+ configFile = cliconfig .LoadDefaultConfigFile (cli .Err ())
364+ }
365+
366+ currentContext := cli .CurrentContext ()
367+ if currentContext == "" {
368+ currentContext = configFile .CurrentContext
369+ }
370+ if currentContext == "" {
371+ currentContext = ContextDockerHost
372+ }
373+ if currentContext != ContextDockerHost {
374+ contextstore := cli .contextStore
375+ if contextstore == nil {
376+ contextstore = store .New (cliconfig .ContextStoreDir (), storeConfig )
377+ }
378+ ctxRaw , err := contextstore .GetContextMetadata (currentContext )
379+ if err != nil {
380+ return "" , err
381+ }
382+ ctxMeta , err := GetDockerContext (ctxRaw )
383+ if err != nil {
384+ return "" , err
385+ }
386+ ctxOrchestrator = string (ctxMeta .StackOrchestrator )
387+ }
388+
389+ return GetStackOrchestrator (flagValue , ctxOrchestrator , configFile .StackOrchestrator , cli .Err ())
390+ }
391+
256392// ServerInfo stores details about the supported features and platform of the
257393// server
258394type ServerInfo struct {
@@ -272,51 +408,6 @@ func NewDockerCli(in io.ReadCloser, out, err io.Writer, isTrusted bool, containe
272408 return & DockerCli {in : NewInStream (in ), out : NewOutStream (out ), err : err , contentTrust : isTrusted , newContainerizeClient : containerizedFn }
273409}
274410
275- // NewAPIClientFromFlags creates a new APIClient from command line flags
276- func NewAPIClientFromFlags (opts * cliflags.CommonOptions , configFile * configfile.ConfigFile ) (client.APIClient , error ) {
277- host , err := getServerHost (opts .Hosts , opts .TLSOptions )
278- if err != nil {
279- return & client.Client {}, err
280- }
281- var clientOpts []func (* client.Client ) error
282- helper , err := connhelper .GetConnectionHelper (host )
283- if err != nil {
284- return & client.Client {}, err
285- }
286- if helper == nil {
287- clientOpts = append (clientOpts , withHTTPClient (opts .TLSOptions ))
288- clientOpts = append (clientOpts , client .WithHost (host ))
289- } else {
290- clientOpts = append (clientOpts , func (c * client.Client ) error {
291- httpClient := & http.Client {
292- // No tls
293- // No proxy
294- Transport : & http.Transport {
295- DialContext : helper .Dialer ,
296- },
297- }
298- return client .WithHTTPClient (httpClient )(c )
299- })
300- clientOpts = append (clientOpts , client .WithHost (helper .Host ))
301- clientOpts = append (clientOpts , client .WithDialContext (helper .Dialer ))
302- }
303-
304- customHeaders := configFile .HTTPHeaders
305- if customHeaders == nil {
306- customHeaders = map [string ]string {}
307- }
308- customHeaders ["User-Agent" ] = UserAgent ()
309- clientOpts = append (clientOpts , client .WithHTTPHeaders (customHeaders ))
310-
311- verStr := api .DefaultVersion
312- if tmpStr := os .Getenv ("DOCKER_API_VERSION" ); tmpStr != "" {
313- verStr = tmpStr
314- }
315- clientOpts = append (clientOpts , client .WithVersion (verStr ))
316-
317- return client .NewClientWithOpts (clientOpts ... )
318- }
319-
320411func getServerHost (hosts []string , tlsOptions * tlsconfig.Options ) (string , error ) {
321412 var host string
322413 switch len (hosts ) {
@@ -331,35 +422,37 @@ func getServerHost(hosts []string, tlsOptions *tlsconfig.Options) (string, error
331422 return dopts .ParseHost (tlsOptions != nil , host )
332423}
333424
334- func withHTTPClient (tlsOpts * tlsconfig.Options ) func (* client.Client ) error {
335- return func (c * client.Client ) error {
336- if tlsOpts == nil {
337- // Use the default HTTPClient
338- return nil
339- }
340-
341- opts := * tlsOpts
342- opts .ExclusiveRootPools = true
343- tlsConfig , err := tlsconfig .Client (opts )
344- if err != nil {
345- return err
346- }
347-
348- httpClient := & http.Client {
349- Transport : & http.Transport {
350- TLSClientConfig : tlsConfig ,
351- DialContext : (& net.Dialer {
352- KeepAlive : 30 * time .Second ,
353- Timeout : 30 * time .Second ,
354- }).DialContext ,
355- },
356- CheckRedirect : client .CheckRedirect ,
357- }
358- return client .WithHTTPClient (httpClient )(c )
359- }
360- }
361-
362425// UserAgent returns the user agent string used for making API requests
363426func UserAgent () string {
364427 return "Docker-Client/" + cli .Version + " (" + runtime .GOOS + ")"
365428}
429+
430+ // resolveContextName resolves the current context name with the following rules:
431+ // - setting both --context and --host flags is ambiguous
432+ // - if --context is set, use this value
433+ // - if --host flag or DOCKER_HOST is set, fallbacks to use the same logic as before context-store was added
434+ // for backward compatibility with existing scripts
435+ // - if DOCKER_CONTEXT is set, use this value
436+ // - if Config file has a globally set "CurrentContext", use this value
437+ // - fallbacks to default HOST, uses TLS config from flags/env vars
438+ func resolveContextName (opts * cliflags.CommonOptions , config * configfile.ConfigFile ) (string , error ) {
439+ if opts .Context != "" && len (opts .Hosts ) > 0 {
440+ return "" , errors .New ("Conflicting options: either specify --host or --context, not bot" )
441+ }
442+ if opts .Context != "" {
443+ return opts .Context , nil
444+ }
445+ if len (opts .Hosts ) > 0 {
446+ return ContextDockerHost , nil
447+ }
448+ if _ , present := os .LookupEnv ("DOCKER_HOST" ); present {
449+ return ContextDockerHost , nil
450+ }
451+ if ctxName , ok := os .LookupEnv ("DOCKER_CONTEXT" ); ok {
452+ return ctxName , nil
453+ }
454+ if config != nil && config .CurrentContext != "" {
455+ return config .CurrentContext , nil
456+ }
457+ return ContextDockerHost , nil
458+ }
0 commit comments