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