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