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