github.com/brahmaroutu/docker@v1.2.1-0.20160809185609-eb28dde01f16/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 input state 51 inState *term.State 52 // outState holds the terminal output state 53 outState *term.State 54 } 55 56 // Initialize calls the init function that will setup the configuration for the client 57 // such as the TLS, tcp and other parameters used to run the client. 58 func (cli *DockerCli) Initialize() error { 59 if cli.init == nil { 60 return nil 61 } 62 return cli.init() 63 } 64 65 // Client returns the APIClient 66 func (cli *DockerCli) Client() client.APIClient { 67 return cli.client 68 } 69 70 // Out returns the writer used for stdout 71 func (cli *DockerCli) Out() io.Writer { 72 return cli.out 73 } 74 75 // Err returns the writer used for stderr 76 func (cli *DockerCli) Err() io.Writer { 77 return cli.err 78 } 79 80 // In returns the reader used for stdin 81 func (cli *DockerCli) In() io.ReadCloser { 82 return cli.in 83 } 84 85 // ConfigFile returns the ConfigFile 86 func (cli *DockerCli) ConfigFile() *configfile.ConfigFile { 87 return cli.configFile 88 } 89 90 // IsTerminalIn returns true if the clients stdin is a TTY 91 func (cli *DockerCli) IsTerminalIn() bool { 92 return cli.isTerminalIn 93 } 94 95 // IsTerminalOut returns true if the clients stdout is a TTY 96 func (cli *DockerCli) IsTerminalOut() bool { 97 return cli.isTerminalOut 98 } 99 100 // OutFd returns the fd for the stdout stream 101 func (cli *DockerCli) OutFd() uintptr { 102 return cli.outFd 103 } 104 105 // CheckTtyInput checks if we are trying to attach to a container tty 106 // from a non-tty client input stream, and if so, returns an error. 107 func (cli *DockerCli) CheckTtyInput(attachStdin, ttyMode bool) error { 108 // In order to attach to a container tty, input stream for the client must 109 // be a tty itself: redirecting or piping the client standard input is 110 // incompatible with `docker run -t`, `docker exec -t` or `docker attach`. 111 if ttyMode && attachStdin && !cli.isTerminalIn { 112 eText := "the input device is not a TTY" 113 if runtime.GOOS == "windows" { 114 return errors.New(eText + ". If you are using mintty, try prefixing the command with 'winpty'") 115 } 116 return errors.New(eText) 117 } 118 return nil 119 } 120 121 func (cli *DockerCli) setRawTerminal() error { 122 if os.Getenv("NORAW") == "" { 123 if cli.isTerminalIn { 124 state, err := term.SetRawTerminal(cli.inFd) 125 if err != nil { 126 return err 127 } 128 cli.inState = state 129 } 130 if cli.isTerminalOut { 131 state, err := term.SetRawTerminalOutput(cli.outFd) 132 if err != nil { 133 return err 134 } 135 cli.outState = state 136 } 137 } 138 return nil 139 } 140 141 func (cli *DockerCli) restoreTerminal(in io.Closer) error { 142 if cli.inState != nil { 143 term.RestoreTerminal(cli.inFd, cli.inState) 144 } 145 if cli.outState != nil { 146 term.RestoreTerminal(cli.outFd, cli.outState) 147 } 148 // WARNING: DO NOT REMOVE THE OS CHECK !!! 149 // For some reason this Close call blocks on darwin.. 150 // As the client exists right after, simply discard the close 151 // until we find a better solution. 152 if in != nil && runtime.GOOS != "darwin" { 153 return in.Close() 154 } 155 return nil 156 } 157 158 // NewDockerCli returns a DockerCli instance with IO output and error streams set by in, out and err. 159 // The key file, protocol (i.e. unix) and address are passed in as strings, along with the tls.Config. If the tls.Config 160 // is set the client scheme will be set to https. 161 // The client will be given a 32-second timeout (see https://github.com/docker/docker/pull/8035). 162 func NewDockerCli(in io.ReadCloser, out, err io.Writer, clientFlags *cliflags.ClientFlags) *DockerCli { 163 cli := &DockerCli{ 164 in: in, 165 out: out, 166 err: err, 167 keyFile: clientFlags.Common.TrustKey, 168 } 169 170 cli.init = func() error { 171 clientFlags.PostParse() 172 cli.configFile = LoadDefaultConfigFile(err) 173 174 client, err := NewAPIClientFromFlags(clientFlags, cli.configFile) 175 if err != nil { 176 return err 177 } 178 179 cli.client = client 180 181 if cli.in != nil { 182 cli.inFd, cli.isTerminalIn = term.GetFdInfo(cli.in) 183 } 184 if cli.out != nil { 185 cli.outFd, cli.isTerminalOut = term.GetFdInfo(cli.out) 186 } 187 188 return nil 189 } 190 191 return cli 192 } 193 194 // LoadDefaultConfigFile attempts to load the default config file and returns 195 // an initialized ConfigFile struct if none is found. 196 func LoadDefaultConfigFile(err io.Writer) *configfile.ConfigFile { 197 configFile, e := cliconfig.Load(cliconfig.ConfigDir()) 198 if e != nil { 199 fmt.Fprintf(err, "WARNING: Error loading config file:%v\n", e) 200 } 201 if !configFile.ContainsAuth() { 202 credentials.DetectDefaultStore(configFile) 203 } 204 return configFile 205 } 206 207 // NewAPIClientFromFlags creates a new APIClient from command line flags 208 func NewAPIClientFromFlags(clientFlags *cliflags.ClientFlags, configFile *configfile.ConfigFile) (client.APIClient, error) { 209 host, err := getServerHost(clientFlags.Common.Hosts, clientFlags.Common.TLSOptions) 210 if err != nil { 211 return &client.Client{}, err 212 } 213 214 customHeaders := configFile.HTTPHeaders 215 if customHeaders == nil { 216 customHeaders = map[string]string{} 217 } 218 customHeaders["User-Agent"] = clientUserAgent() 219 220 verStr := api.DefaultVersion 221 if tmpStr := os.Getenv("DOCKER_API_VERSION"); tmpStr != "" { 222 verStr = tmpStr 223 } 224 225 httpClient, err := newHTTPClient(host, clientFlags.Common.TLSOptions) 226 if err != nil { 227 return &client.Client{}, err 228 } 229 230 return client.NewClient(host, verStr, httpClient, customHeaders) 231 } 232 233 func getServerHost(hosts []string, tlsOptions *tlsconfig.Options) (host string, err error) { 234 switch len(hosts) { 235 case 0: 236 host = os.Getenv("DOCKER_HOST") 237 case 1: 238 host = hosts[0] 239 default: 240 return "", errors.New("Please specify only one -H") 241 } 242 243 host, err = opts.ParseHost(tlsOptions != nil, host) 244 return 245 } 246 247 func newHTTPClient(host string, tlsOptions *tlsconfig.Options) (*http.Client, error) { 248 if tlsOptions == nil { 249 // let the api client configure the default transport. 250 return nil, nil 251 } 252 253 config, err := tlsconfig.Client(*tlsOptions) 254 if err != nil { 255 return nil, err 256 } 257 tr := &http.Transport{ 258 TLSClientConfig: config, 259 } 260 proto, addr, _, err := client.ParseHost(host) 261 if err != nil { 262 return nil, err 263 } 264 265 sockets.ConfigureTransport(tr, proto, addr) 266 267 return &http.Client{ 268 Transport: tr, 269 }, nil 270 } 271 272 func clientUserAgent() string { 273 return "Docker-Client/" + dockerversion.Version + " (" + runtime.GOOS + ")" 274 }