github.com/kobeld/docker@v1.12.0-rc1/api/client/cli.go (about) 1 package client 2 3 import ( 4 "errors" 5 "fmt" 6 "io" 7 "net/http" 8 "os" 9 "runtime" 10 11 "github.com/docker/docker/api" 12 cliflags "github.com/docker/docker/cli/flags" 13 "github.com/docker/docker/cliconfig" 14 "github.com/docker/docker/cliconfig/configfile" 15 "github.com/docker/docker/cliconfig/credentials" 16 "github.com/docker/docker/dockerversion" 17 "github.com/docker/docker/opts" 18 "github.com/docker/docker/pkg/term" 19 "github.com/docker/engine-api/client" 20 "github.com/docker/go-connections/sockets" 21 "github.com/docker/go-connections/tlsconfig" 22 ) 23 24 // DockerCli represents the docker command line client. 25 // Instances of the client can be returned from NewDockerCli. 26 type DockerCli struct { 27 // initializing closure 28 init func() error 29 30 // configFile has the client configuration file 31 configFile *configfile.ConfigFile 32 // in holds the input stream and closer (io.ReadCloser) for the client. 33 in io.ReadCloser 34 // out holds the output stream (io.Writer) for the client. 35 out io.Writer 36 // err holds the error stream (io.Writer) for the client. 37 err io.Writer 38 // keyFile holds the key file as a string. 39 keyFile string 40 // inFd holds the file descriptor of the client's STDIN (if valid). 41 inFd uintptr 42 // outFd holds file descriptor of the client's STDOUT (if valid). 43 outFd uintptr 44 // isTerminalIn indicates whether the client's STDIN is a TTY 45 isTerminalIn bool 46 // isTerminalOut indicates whether the client's STDOUT is a TTY 47 isTerminalOut bool 48 // client is the http client that performs all API operations 49 client client.APIClient 50 // state holds the terminal state 51 state *term.State 52 } 53 54 // Initialize calls the init function that will setup the configuration for the client 55 // such as the TLS, tcp and other parameters used to run the client. 56 func (cli *DockerCli) Initialize() error { 57 if cli.init == nil { 58 return nil 59 } 60 return cli.init() 61 } 62 63 // Client returns the APIClient 64 func (cli *DockerCli) Client() client.APIClient { 65 return cli.client 66 } 67 68 // Out returns the writer used for stdout 69 func (cli *DockerCli) Out() io.Writer { 70 return cli.out 71 } 72 73 // Err returns the writer used for stderr 74 func (cli *DockerCli) Err() io.Writer { 75 return cli.err 76 } 77 78 // In returns the reader used for stdin 79 func (cli *DockerCli) In() io.ReadCloser { 80 return cli.in 81 } 82 83 // ConfigFile returns the ConfigFile 84 func (cli *DockerCli) ConfigFile() *configfile.ConfigFile { 85 return cli.configFile 86 } 87 88 // IsTerminalOut returns true if the clients stdin is a TTY 89 func (cli *DockerCli) IsTerminalOut() bool { 90 return cli.isTerminalOut 91 } 92 93 // OutFd returns the fd for the stdout stream 94 func (cli *DockerCli) OutFd() uintptr { 95 return cli.outFd 96 } 97 98 // CheckTtyInput checks if we are trying to attach to a container tty 99 // from a non-tty client input stream, and if so, returns an error. 100 func (cli *DockerCli) CheckTtyInput(attachStdin, ttyMode bool) error { 101 // In order to attach to a container tty, input stream for the client must 102 // be a tty itself: redirecting or piping the client standard input is 103 // incompatible with `docker run -t`, `docker exec -t` or `docker attach`. 104 if ttyMode && attachStdin && !cli.isTerminalIn { 105 eText := "the input device is not a TTY" 106 if runtime.GOOS == "windows" { 107 return errors.New(eText + ". If you are using mintty, try prefixing the command with 'winpty'") 108 } 109 return errors.New(eText) 110 } 111 return nil 112 } 113 114 // PsFormat returns the format string specified in the configuration. 115 // String contains columns and format specification, for example {{ID}}\t{{Name}}. 116 func (cli *DockerCli) PsFormat() string { 117 return cli.configFile.PsFormat 118 } 119 120 // ImagesFormat returns the format string specified in the configuration. 121 // String contains columns and format specification, for example {{ID}}\t{{Name}}. 122 func (cli *DockerCli) ImagesFormat() string { 123 return cli.configFile.ImagesFormat 124 } 125 126 func (cli *DockerCli) setRawTerminal() error { 127 if cli.isTerminalIn && os.Getenv("NORAW") == "" { 128 state, err := term.SetRawTerminal(cli.inFd) 129 if err != nil { 130 return err 131 } 132 cli.state = state 133 } 134 return nil 135 } 136 137 func (cli *DockerCli) restoreTerminal(in io.Closer) error { 138 if cli.state != nil { 139 term.RestoreTerminal(cli.inFd, cli.state) 140 } 141 // WARNING: DO NOT REMOVE THE OS CHECK !!! 142 // For some reason this Close call blocks on darwin.. 143 // As the client exists right after, simply discard the close 144 // until we find a better solution. 145 if in != nil && runtime.GOOS != "darwin" { 146 return in.Close() 147 } 148 return nil 149 } 150 151 // NewDockerCli returns a DockerCli instance with IO output and error streams set by in, out and err. 152 // The key file, protocol (i.e. unix) and address are passed in as strings, along with the tls.Config. If the tls.Config 153 // is set the client scheme will be set to https. 154 // The client will be given a 32-second timeout (see https://github.com/docker/docker/pull/8035). 155 func NewDockerCli(in io.ReadCloser, out, err io.Writer, clientFlags *cliflags.ClientFlags) *DockerCli { 156 cli := &DockerCli{ 157 in: in, 158 out: out, 159 err: err, 160 keyFile: clientFlags.Common.TrustKey, 161 } 162 163 cli.init = func() error { 164 clientFlags.PostParse() 165 cli.configFile = LoadDefaultConfigFile(err) 166 167 client, err := NewAPIClientFromFlags(clientFlags, cli.configFile) 168 if err != nil { 169 return err 170 } 171 172 cli.client = client 173 174 if cli.in != nil { 175 cli.inFd, cli.isTerminalIn = term.GetFdInfo(cli.in) 176 } 177 if cli.out != nil { 178 cli.outFd, cli.isTerminalOut = term.GetFdInfo(cli.out) 179 } 180 181 return nil 182 } 183 184 return cli 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.ConfigDir()) 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(clientFlags *cliflags.ClientFlags, configFile *configfile.ConfigFile) (client.APIClient, error) { 202 host, err := getServerHost(clientFlags.Common.Hosts, clientFlags.Common.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"] = clientUserAgent() 212 213 verStr := api.DefaultVersion 214 if tmpStr := os.Getenv("DOCKER_API_VERSION"); tmpStr != "" { 215 verStr = tmpStr 216 } 217 218 httpClient, err := newHTTPClient(host, clientFlags.Common.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 = opts.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 func clientUserAgent() string { 266 return "Docker-Client/" + dockerversion.Version + " (" + runtime.GOOS + ")" 267 }