github.com/kunnos/engine@v1.13.1/cli/command/cli.go (about) 1 package command 2 3 import ( 4 "errors" 5 "fmt" 6 "io" 7 "net/http" 8 "os" 9 "path/filepath" 10 "runtime" 11 12 "github.com/docker/docker/api" 13 "github.com/docker/docker/api/types" 14 "github.com/docker/docker/api/types/versions" 15 cliflags "github.com/docker/docker/cli/flags" 16 "github.com/docker/docker/cliconfig" 17 "github.com/docker/docker/cliconfig/configfile" 18 "github.com/docker/docker/cliconfig/credentials" 19 "github.com/docker/docker/client" 20 "github.com/docker/docker/dockerversion" 21 dopts "github.com/docker/docker/opts" 22 "github.com/docker/go-connections/sockets" 23 "github.com/docker/go-connections/tlsconfig" 24 "github.com/spf13/cobra" 25 "golang.org/x/net/context" 26 ) 27 28 // Streams is an interface which exposes the standard input and output streams 29 type Streams interface { 30 In() *InStream 31 Out() *OutStream 32 Err() io.Writer 33 } 34 35 // DockerCli represents the docker command line client. 36 // Instances of the client can be returned from NewDockerCli. 37 type DockerCli struct { 38 configFile *configfile.ConfigFile 39 in *InStream 40 out *OutStream 41 err io.Writer 42 keyFile string 43 client client.APIClient 44 hasExperimental bool 45 defaultVersion string 46 } 47 48 // HasExperimental returns true if experimental features are accessible. 49 func (cli *DockerCli) HasExperimental() bool { 50 return cli.hasExperimental 51 } 52 53 // DefaultVersion returns api.defaultVersion of DOCKER_API_VERSION if specified. 54 func (cli *DockerCli) DefaultVersion() string { 55 return cli.defaultVersion 56 } 57 58 // Client returns the APIClient 59 func (cli *DockerCli) Client() client.APIClient { 60 return cli.client 61 } 62 63 // Out returns the writer used for stdout 64 func (cli *DockerCli) Out() *OutStream { 65 return cli.out 66 } 67 68 // Err returns the writer used for stderr 69 func (cli *DockerCli) Err() io.Writer { 70 return cli.err 71 } 72 73 // In returns the reader used for stdin 74 func (cli *DockerCli) In() *InStream { 75 return cli.in 76 } 77 78 // ShowHelp shows the command help. 79 func (cli *DockerCli) ShowHelp(cmd *cobra.Command, args []string) error { 80 cmd.SetOutput(cli.err) 81 cmd.HelpFunc()(cmd, args) 82 return nil 83 } 84 85 // ConfigFile returns the ConfigFile 86 func (cli *DockerCli) ConfigFile() *configfile.ConfigFile { 87 return cli.configFile 88 } 89 90 // GetAllCredentials returns all of the credentials stored in all of the 91 // configured credential stores. 92 func (cli *DockerCli) GetAllCredentials() (map[string]types.AuthConfig, error) { 93 auths := make(map[string]types.AuthConfig) 94 for registry := range cli.configFile.CredentialHelpers { 95 helper := cli.CredentialsStore(registry) 96 newAuths, err := helper.GetAll() 97 if err != nil { 98 return nil, err 99 } 100 addAll(auths, newAuths) 101 } 102 defaultStore := cli.CredentialsStore("") 103 newAuths, err := defaultStore.GetAll() 104 if err != nil { 105 return nil, err 106 } 107 addAll(auths, newAuths) 108 return auths, nil 109 } 110 111 func addAll(to, from map[string]types.AuthConfig) { 112 for reg, ac := range from { 113 to[reg] = ac 114 } 115 } 116 117 // CredentialsStore returns a new credentials store based 118 // on the settings provided in the configuration file. Empty string returns 119 // the default credential store. 120 func (cli *DockerCli) CredentialsStore(serverAddress string) credentials.Store { 121 if helper := getConfiguredCredentialStore(cli.configFile, serverAddress); helper != "" { 122 return credentials.NewNativeStore(cli.configFile, helper) 123 } 124 return credentials.NewFileStore(cli.configFile) 125 } 126 127 // getConfiguredCredentialStore returns the credential helper configured for the 128 // given registry, the default credsStore, or the empty string if neither are 129 // configured. 130 func getConfiguredCredentialStore(c *configfile.ConfigFile, serverAddress string) string { 131 if c.CredentialHelpers != nil && serverAddress != "" { 132 if helper, exists := c.CredentialHelpers[serverAddress]; exists { 133 return helper 134 } 135 } 136 return c.CredentialsStore 137 } 138 139 // Initialize the dockerCli runs initialization that must happen after command 140 // line flags are parsed. 141 func (cli *DockerCli) Initialize(opts *cliflags.ClientOptions) error { 142 cli.configFile = LoadDefaultConfigFile(cli.err) 143 144 var err error 145 cli.client, err = NewAPIClientFromFlags(opts.Common, cli.configFile) 146 if err != nil { 147 return err 148 } 149 150 cli.defaultVersion = cli.client.ClientVersion() 151 152 if opts.Common.TrustKey == "" { 153 cli.keyFile = filepath.Join(cliconfig.ConfigDir(), cliflags.DefaultTrustKeyFile) 154 } else { 155 cli.keyFile = opts.Common.TrustKey 156 } 157 158 if ping, err := cli.client.Ping(context.Background()); err == nil { 159 cli.hasExperimental = ping.Experimental 160 161 // since the new header was added in 1.25, assume server is 1.24 if header is not present. 162 if ping.APIVersion == "" { 163 ping.APIVersion = "1.24" 164 } 165 166 // if server version is lower than the current cli, downgrade 167 if versions.LessThan(ping.APIVersion, cli.client.ClientVersion()) { 168 cli.client.UpdateClientVersion(ping.APIVersion) 169 } 170 } 171 return nil 172 } 173 174 // NewDockerCli returns a DockerCli instance with IO output and error streams set by in, out and err. 175 func NewDockerCli(in io.ReadCloser, out, err io.Writer) *DockerCli { 176 return &DockerCli{in: NewInStream(in), out: NewOutStream(out), err: err} 177 } 178 179 // LoadDefaultConfigFile attempts to load the default config file and returns 180 // an initialized ConfigFile struct if none is found. 181 func LoadDefaultConfigFile(err io.Writer) *configfile.ConfigFile { 182 configFile, e := cliconfig.Load(cliconfig.ConfigDir()) 183 if e != nil { 184 fmt.Fprintf(err, "WARNING: Error loading config file:%v\n", e) 185 } 186 if !configFile.ContainsAuth() { 187 credentials.DetectDefaultStore(configFile) 188 } 189 return configFile 190 } 191 192 // NewAPIClientFromFlags creates a new APIClient from command line flags 193 func NewAPIClientFromFlags(opts *cliflags.CommonOptions, configFile *configfile.ConfigFile) (client.APIClient, error) { 194 host, err := getServerHost(opts.Hosts, opts.TLSOptions) 195 if err != nil { 196 return &client.Client{}, err 197 } 198 199 customHeaders := configFile.HTTPHeaders 200 if customHeaders == nil { 201 customHeaders = map[string]string{} 202 } 203 customHeaders["User-Agent"] = UserAgent() 204 205 verStr := api.DefaultVersion 206 if tmpStr := os.Getenv("DOCKER_API_VERSION"); tmpStr != "" { 207 verStr = tmpStr 208 } 209 210 httpClient, err := newHTTPClient(host, opts.TLSOptions) 211 if err != nil { 212 return &client.Client{}, err 213 } 214 215 return client.NewClient(host, verStr, httpClient, customHeaders) 216 } 217 218 func getServerHost(hosts []string, tlsOptions *tlsconfig.Options) (host string, err error) { 219 switch len(hosts) { 220 case 0: 221 host = os.Getenv("DOCKER_HOST") 222 case 1: 223 host = hosts[0] 224 default: 225 return "", errors.New("Please specify only one -H") 226 } 227 228 host, err = dopts.ParseHost(tlsOptions != nil, host) 229 return 230 } 231 232 func newHTTPClient(host string, tlsOptions *tlsconfig.Options) (*http.Client, error) { 233 if tlsOptions == nil { 234 // let the api client configure the default transport. 235 return nil, nil 236 } 237 238 config, err := tlsconfig.Client(*tlsOptions) 239 if err != nil { 240 return nil, err 241 } 242 tr := &http.Transport{ 243 TLSClientConfig: config, 244 } 245 proto, addr, _, err := client.ParseHost(host) 246 if err != nil { 247 return nil, err 248 } 249 250 sockets.ConfigureTransport(tr, proto, addr) 251 252 return &http.Client{ 253 Transport: tr, 254 }, nil 255 } 256 257 // UserAgent returns the user agent string used for making API requests 258 func UserAgent() string { 259 return "Docker-Client/" + dockerversion.Version + " (" + runtime.GOOS + ")" 260 }