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