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