github.com/tompao/docker@v1.9.1/api/client/utils.go (about) 1 package client 2 3 import ( 4 "bytes" 5 "encoding/base64" 6 "encoding/json" 7 "errors" 8 "fmt" 9 "io" 10 "io/ioutil" 11 "net/http" 12 "net/url" 13 "os" 14 gosignal "os/signal" 15 "runtime" 16 "strconv" 17 "strings" 18 "time" 19 20 "github.com/Sirupsen/logrus" 21 "github.com/docker/docker/api" 22 "github.com/docker/docker/api/types" 23 "github.com/docker/docker/autogen/dockerversion" 24 "github.com/docker/docker/cliconfig" 25 "github.com/docker/docker/pkg/jsonmessage" 26 "github.com/docker/docker/pkg/signal" 27 "github.com/docker/docker/pkg/stdcopy" 28 "github.com/docker/docker/pkg/term" 29 "github.com/docker/docker/registry" 30 "github.com/docker/docker/utils" 31 ) 32 33 var ( 34 errConnectionFailed = errors.New("Cannot connect to the Docker daemon. Is the docker daemon running on this host?") 35 ) 36 37 type serverResponse struct { 38 body io.ReadCloser 39 header http.Header 40 statusCode int 41 } 42 43 // HTTPClient creates a new HTTP client with the cli's client transport instance. 44 func (cli *DockerCli) HTTPClient() *http.Client { 45 return &http.Client{Transport: cli.transport} 46 } 47 48 func (cli *DockerCli) encodeData(data interface{}) (*bytes.Buffer, error) { 49 params := bytes.NewBuffer(nil) 50 if data != nil { 51 if err := json.NewEncoder(params).Encode(data); err != nil { 52 return nil, err 53 } 54 } 55 return params, nil 56 } 57 58 func (cli *DockerCli) clientRequest(method, path string, in io.Reader, headers map[string][]string) (*serverResponse, error) { 59 60 serverResp := &serverResponse{ 61 body: nil, 62 statusCode: -1, 63 } 64 65 expectedPayload := (method == "POST" || method == "PUT") 66 if expectedPayload && in == nil { 67 in = bytes.NewReader([]byte{}) 68 } 69 req, err := http.NewRequest(method, fmt.Sprintf("%s/v%s%s", cli.basePath, api.Version, path), in) 70 if err != nil { 71 return serverResp, err 72 } 73 74 // Add CLI Config's HTTP Headers BEFORE we set the Docker headers 75 // then the user can't change OUR headers 76 for k, v := range cli.configFile.HTTPHeaders { 77 req.Header.Set(k, v) 78 } 79 80 req.Header.Set("User-Agent", "Docker-Client/"+dockerversion.VERSION+" ("+runtime.GOOS+")") 81 req.URL.Host = cli.addr 82 req.URL.Scheme = cli.scheme 83 84 if headers != nil { 85 for k, v := range headers { 86 req.Header[k] = v 87 } 88 } 89 90 if expectedPayload && req.Header.Get("Content-Type") == "" { 91 req.Header.Set("Content-Type", "text/plain") 92 } 93 94 resp, err := cli.HTTPClient().Do(req) 95 if resp != nil { 96 serverResp.statusCode = resp.StatusCode 97 } 98 99 if err != nil { 100 if utils.IsTimeout(err) || strings.Contains(err.Error(), "connection refused") || strings.Contains(err.Error(), "dial unix") { 101 return serverResp, errConnectionFailed 102 } 103 104 if cli.tlsConfig == nil && strings.Contains(err.Error(), "malformed HTTP response") { 105 return serverResp, fmt.Errorf("%v.\n* Are you trying to connect to a TLS-enabled daemon without TLS?", err) 106 } 107 if cli.tlsConfig != nil && strings.Contains(err.Error(), "remote error: bad certificate") { 108 return serverResp, fmt.Errorf("The server probably has client authentication (--tlsverify) enabled. Please check your TLS client certification settings: %v", err) 109 } 110 111 return serverResp, fmt.Errorf("An error occurred trying to connect: %v", err) 112 } 113 114 if serverResp.statusCode < 200 || serverResp.statusCode >= 400 { 115 body, err := ioutil.ReadAll(resp.Body) 116 if err != nil { 117 return serverResp, err 118 } 119 if len(body) == 0 { 120 return serverResp, fmt.Errorf("Error: request returned %s for API route and version %s, check if the server supports the requested API version", http.StatusText(serverResp.statusCode), req.URL) 121 } 122 return serverResp, fmt.Errorf("Error response from daemon: %s", bytes.TrimSpace(body)) 123 } 124 125 serverResp.body = resp.Body 126 serverResp.header = resp.Header 127 return serverResp, nil 128 } 129 130 // cmdAttempt builds the corresponding registry Auth Header from the given 131 // authConfig. It returns the servers body, status, error response 132 func (cli *DockerCli) cmdAttempt(authConfig cliconfig.AuthConfig, method, path string, in io.Reader, out io.Writer) (io.ReadCloser, int, error) { 133 buf, err := json.Marshal(authConfig) 134 if err != nil { 135 return nil, -1, err 136 } 137 registryAuthHeader := []string{ 138 base64.URLEncoding.EncodeToString(buf), 139 } 140 141 // begin the request 142 serverResp, err := cli.clientRequest(method, path, in, map[string][]string{ 143 "X-Registry-Auth": registryAuthHeader, 144 }) 145 if err == nil && out != nil { 146 // If we are streaming output, complete the stream since 147 // errors may not appear until later. 148 err = cli.streamBody(serverResp.body, serverResp.header.Get("Content-Type"), true, out, nil) 149 } 150 if err != nil { 151 // Since errors in a stream appear after status 200 has been written, 152 // we may need to change the status code. 153 if strings.Contains(err.Error(), "Authentication is required") || 154 strings.Contains(err.Error(), "Status 401") || 155 strings.Contains(err.Error(), "401 Unauthorized") || 156 strings.Contains(err.Error(), "status code 401") { 157 serverResp.statusCode = http.StatusUnauthorized 158 } 159 } 160 return serverResp.body, serverResp.statusCode, err 161 } 162 163 func (cli *DockerCli) clientRequestAttemptLogin(method, path string, in io.Reader, out io.Writer, index *registry.IndexInfo, cmdName string) (io.ReadCloser, int, error) { 164 165 // Resolve the Auth config relevant for this server 166 authConfig := registry.ResolveAuthConfig(cli.configFile, index) 167 body, statusCode, err := cli.cmdAttempt(authConfig, method, path, in, out) 168 if statusCode == http.StatusUnauthorized { 169 fmt.Fprintf(cli.out, "\nPlease login prior to %s:\n", cmdName) 170 if err = cli.CmdLogin(index.GetAuthConfigKey()); err != nil { 171 return nil, -1, err 172 } 173 authConfig = registry.ResolveAuthConfig(cli.configFile, index) 174 return cli.cmdAttempt(authConfig, method, path, in, out) 175 } 176 return body, statusCode, err 177 } 178 179 func (cli *DockerCli) callWrapper(method, path string, data interface{}, headers map[string][]string) (io.ReadCloser, http.Header, int, error) { 180 sr, err := cli.call(method, path, data, headers) 181 return sr.body, sr.header, sr.statusCode, err 182 } 183 184 func (cli *DockerCli) call(method, path string, data interface{}, headers map[string][]string) (*serverResponse, error) { 185 params, err := cli.encodeData(data) 186 if err != nil { 187 sr := &serverResponse{ 188 body: nil, 189 header: nil, 190 statusCode: -1, 191 } 192 return sr, nil 193 } 194 195 if data != nil { 196 if headers == nil { 197 headers = make(map[string][]string) 198 } 199 headers["Content-Type"] = []string{"application/json"} 200 } 201 202 serverResp, err := cli.clientRequest(method, path, params, headers) 203 return serverResp, err 204 } 205 206 type streamOpts struct { 207 rawTerminal bool 208 in io.Reader 209 out io.Writer 210 err io.Writer 211 headers map[string][]string 212 } 213 214 func (cli *DockerCli) stream(method, path string, opts *streamOpts) (*serverResponse, error) { 215 serverResp, err := cli.clientRequest(method, path, opts.in, opts.headers) 216 if err != nil { 217 return serverResp, err 218 } 219 return serverResp, cli.streamBody(serverResp.body, serverResp.header.Get("Content-Type"), opts.rawTerminal, opts.out, opts.err) 220 } 221 222 func (cli *DockerCli) streamBody(body io.ReadCloser, contentType string, rawTerminal bool, stdout, stderr io.Writer) error { 223 defer body.Close() 224 225 if api.MatchesContentType(contentType, "application/json") { 226 return jsonmessage.DisplayJSONMessagesStream(body, stdout, cli.outFd, cli.isTerminalOut) 227 } 228 if stdout != nil || stderr != nil { 229 // When TTY is ON, use regular copy 230 var err error 231 if rawTerminal { 232 _, err = io.Copy(stdout, body) 233 } else { 234 _, err = stdcopy.StdCopy(stdout, stderr, body) 235 } 236 logrus.Debugf("[stream] End of stdout") 237 return err 238 } 239 return nil 240 } 241 242 func (cli *DockerCli) resizeTty(id string, isExec bool) { 243 height, width := cli.getTtySize() 244 if height == 0 && width == 0 { 245 return 246 } 247 v := url.Values{} 248 v.Set("h", strconv.Itoa(height)) 249 v.Set("w", strconv.Itoa(width)) 250 251 path := "" 252 if !isExec { 253 path = "/containers/" + id + "/resize?" 254 } else { 255 path = "/exec/" + id + "/resize?" 256 } 257 258 if _, _, err := readBody(cli.call("POST", path+v.Encode(), nil, nil)); err != nil { 259 logrus.Debugf("Error resize: %s", err) 260 } 261 } 262 263 func waitForExit(cli *DockerCli, containerID string) (int, error) { 264 serverResp, err := cli.call("POST", "/containers/"+containerID+"/wait", nil, nil) 265 if err != nil { 266 return -1, err 267 } 268 269 defer serverResp.body.Close() 270 271 var res types.ContainerWaitResponse 272 if err := json.NewDecoder(serverResp.body).Decode(&res); err != nil { 273 return -1, err 274 } 275 276 return res.StatusCode, nil 277 } 278 279 // getExitCode perform an inspect on the container. It returns 280 // the running state and the exit code. 281 func getExitCode(cli *DockerCli, containerID string) (bool, int, error) { 282 serverResp, err := cli.call("GET", "/containers/"+containerID+"/json", nil, nil) 283 if err != nil { 284 // If we can't connect, then the daemon probably died. 285 if err != errConnectionFailed { 286 return false, -1, err 287 } 288 return false, -1, nil 289 } 290 291 defer serverResp.body.Close() 292 293 var c types.ContainerJSON 294 if err := json.NewDecoder(serverResp.body).Decode(&c); err != nil { 295 return false, -1, err 296 } 297 298 return c.State.Running, c.State.ExitCode, nil 299 } 300 301 // getExecExitCode perform an inspect on the exec command. It returns 302 // the running state and the exit code. 303 func getExecExitCode(cli *DockerCli, execID string) (bool, int, error) { 304 serverResp, err := cli.call("GET", "/exec/"+execID+"/json", nil, nil) 305 if err != nil { 306 // If we can't connect, then the daemon probably died. 307 if err != errConnectionFailed { 308 return false, -1, err 309 } 310 return false, -1, nil 311 } 312 313 defer serverResp.body.Close() 314 315 //TODO: Should we reconsider having a type in api/types? 316 //this is a response to exex/id/json not container 317 var c struct { 318 Running bool 319 ExitCode int 320 } 321 322 if err := json.NewDecoder(serverResp.body).Decode(&c); err != nil { 323 return false, -1, err 324 } 325 326 return c.Running, c.ExitCode, nil 327 } 328 329 func (cli *DockerCli) monitorTtySize(id string, isExec bool) error { 330 cli.resizeTty(id, isExec) 331 332 if runtime.GOOS == "windows" { 333 go func() { 334 prevH, prevW := cli.getTtySize() 335 for { 336 time.Sleep(time.Millisecond * 250) 337 h, w := cli.getTtySize() 338 339 if prevW != w || prevH != h { 340 cli.resizeTty(id, isExec) 341 } 342 prevH = h 343 prevW = w 344 } 345 }() 346 } else { 347 sigchan := make(chan os.Signal, 1) 348 gosignal.Notify(sigchan, signal.SIGWINCH) 349 go func() { 350 for range sigchan { 351 cli.resizeTty(id, isExec) 352 } 353 }() 354 } 355 return nil 356 } 357 358 func (cli *DockerCli) getTtySize() (int, int) { 359 if !cli.isTerminalOut { 360 return 0, 0 361 } 362 ws, err := term.GetWinsize(cli.outFd) 363 if err != nil { 364 logrus.Debugf("Error getting size: %s", err) 365 if ws == nil { 366 return 0, 0 367 } 368 } 369 return int(ws.Height), int(ws.Width) 370 } 371 372 func readBody(serverResp *serverResponse, err error) ([]byte, int, error) { 373 if serverResp.body != nil { 374 defer serverResp.body.Close() 375 } 376 if err != nil { 377 return nil, serverResp.statusCode, err 378 } 379 body, err := ioutil.ReadAll(serverResp.body) 380 if err != nil { 381 return nil, -1, err 382 } 383 return body, serverResp.statusCode, nil 384 }