github.com/eatbyte/docker@v1.6.0/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 log "github.com/Sirupsen/logrus" 21 "github.com/docker/docker/api" 22 "github.com/docker/docker/autogen/dockerversion" 23 "github.com/docker/docker/engine" 24 "github.com/docker/docker/pkg/signal" 25 "github.com/docker/docker/pkg/stdcopy" 26 "github.com/docker/docker/pkg/term" 27 "github.com/docker/docker/registry" 28 "github.com/docker/docker/utils" 29 ) 30 31 var ( 32 ErrConnectionRefused = errors.New("Cannot connect to the Docker daemon. Is 'docker -d' running on this host?") 33 ) 34 35 func (cli *DockerCli) HTTPClient() *http.Client { 36 return &http.Client{Transport: cli.transport} 37 } 38 39 func (cli *DockerCli) encodeData(data interface{}) (*bytes.Buffer, error) { 40 params := bytes.NewBuffer(nil) 41 if data != nil { 42 if env, ok := data.(engine.Env); ok { 43 if err := env.Encode(params); err != nil { 44 return nil, err 45 } 46 } else { 47 buf, err := json.Marshal(data) 48 if err != nil { 49 return nil, err 50 } 51 if _, err := params.Write(buf); err != nil { 52 return nil, err 53 } 54 } 55 } 56 return params, nil 57 } 58 59 func (cli *DockerCli) clientRequest(method, path string, in io.Reader, headers map[string][]string) (io.ReadCloser, string, int, error) { 60 expectedPayload := (method == "POST" || method == "PUT") 61 if expectedPayload && in == nil { 62 in = bytes.NewReader([]byte{}) 63 } 64 req, err := http.NewRequest(method, fmt.Sprintf("/v%s%s", api.APIVERSION, path), in) 65 if err != nil { 66 return nil, "", -1, err 67 } 68 req.Header.Set("User-Agent", "Docker-Client/"+dockerversion.VERSION) 69 req.URL.Host = cli.addr 70 req.URL.Scheme = cli.scheme 71 if headers != nil { 72 for k, v := range headers { 73 req.Header[k] = v 74 } 75 } 76 if expectedPayload && req.Header.Get("Content-Type") == "" { 77 req.Header.Set("Content-Type", "text/plain") 78 } 79 80 resp, err := cli.HTTPClient().Do(req) 81 statusCode := -1 82 if resp != nil { 83 statusCode = resp.StatusCode 84 } 85 if err != nil { 86 if strings.Contains(err.Error(), "connection refused") { 87 return nil, "", statusCode, ErrConnectionRefused 88 } 89 90 if cli.tlsConfig == nil { 91 return nil, "", statusCode, fmt.Errorf("%v. Are you trying to connect to a TLS-enabled daemon without TLS?", err) 92 } 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 registry.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 := cli.configFile.ResolveAuthConfig(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 = cli.configFile.ResolveAuthConfig(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 func (cli *DockerCli) stream(method, path string, in io.Reader, out io.Writer, headers map[string][]string) error { 173 return cli.streamHelper(method, path, true, in, out, nil, headers) 174 } 175 176 func (cli *DockerCli) streamHelper(method, path string, setRawTerminal bool, in io.Reader, stdout, stderr io.Writer, headers map[string][]string) error { 177 body, contentType, _, err := cli.clientRequest(method, path, in, headers) 178 if err != nil { 179 return err 180 } 181 return cli.streamBody(body, contentType, setRawTerminal, stdout, stderr) 182 } 183 184 func (cli *DockerCli) streamBody(body io.ReadCloser, contentType string, setRawTerminal bool, stdout, stderr io.Writer) error { 185 defer body.Close() 186 187 if api.MatchesContentType(contentType, "application/json") { 188 return utils.DisplayJSONMessagesStream(body, stdout, cli.outFd, cli.isTerminalOut) 189 } 190 if stdout != nil || stderr != nil { 191 // When TTY is ON, use regular copy 192 var err error 193 if setRawTerminal { 194 _, err = io.Copy(stdout, body) 195 } else { 196 _, err = stdcopy.StdCopy(stdout, stderr, body) 197 } 198 log.Debugf("[stream] End of stdout") 199 return err 200 } 201 return nil 202 } 203 204 func (cli *DockerCli) resizeTty(id string, isExec bool) { 205 height, width := cli.getTtySize() 206 if height == 0 && width == 0 { 207 return 208 } 209 v := url.Values{} 210 v.Set("h", strconv.Itoa(height)) 211 v.Set("w", strconv.Itoa(width)) 212 213 path := "" 214 if !isExec { 215 path = "/containers/" + id + "/resize?" 216 } else { 217 path = "/exec/" + id + "/resize?" 218 } 219 220 if _, _, err := readBody(cli.call("POST", path+v.Encode(), nil, nil)); err != nil { 221 log.Debugf("Error resize: %s", err) 222 } 223 } 224 225 func waitForExit(cli *DockerCli, containerID string) (int, error) { 226 stream, _, err := cli.call("POST", "/containers/"+containerID+"/wait", nil, nil) 227 if err != nil { 228 return -1, err 229 } 230 231 var out engine.Env 232 if err := out.Decode(stream); err != nil { 233 return -1, err 234 } 235 return out.GetInt("StatusCode"), nil 236 } 237 238 // getExitCode perform an inspect on the container. It returns 239 // the running state and the exit code. 240 func getExitCode(cli *DockerCli, containerID string) (bool, int, error) { 241 stream, _, err := cli.call("GET", "/containers/"+containerID+"/json", nil, nil) 242 if err != nil { 243 // If we can't connect, then the daemon probably died. 244 if err != ErrConnectionRefused { 245 return false, -1, err 246 } 247 return false, -1, nil 248 } 249 250 var result engine.Env 251 if err := result.Decode(stream); err != nil { 252 return false, -1, err 253 } 254 255 state := result.GetSubEnv("State") 256 return state.GetBool("Running"), state.GetInt("ExitCode"), nil 257 } 258 259 // getExecExitCode perform an inspect on the exec command. It returns 260 // the running state and the exit code. 261 func getExecExitCode(cli *DockerCli, execID string) (bool, int, error) { 262 stream, _, err := cli.call("GET", "/exec/"+execID+"/json", nil, nil) 263 if err != nil { 264 // If we can't connect, then the daemon probably died. 265 if err != ErrConnectionRefused { 266 return false, -1, err 267 } 268 return false, -1, nil 269 } 270 271 var result engine.Env 272 if err := result.Decode(stream); err != nil { 273 return false, -1, err 274 } 275 276 return result.GetBool("Running"), result.GetInt("ExitCode"), nil 277 } 278 279 func (cli *DockerCli) monitorTtySize(id string, isExec bool) error { 280 cli.resizeTty(id, isExec) 281 282 if runtime.GOOS == "windows" { 283 go func() { 284 prevH, prevW := cli.getTtySize() 285 for { 286 time.Sleep(time.Millisecond * 250) 287 h, w := cli.getTtySize() 288 289 if prevW != w || prevH != h { 290 cli.resizeTty(id, isExec) 291 } 292 prevH = h 293 prevW = w 294 } 295 }() 296 } else { 297 sigchan := make(chan os.Signal, 1) 298 gosignal.Notify(sigchan, signal.SIGWINCH) 299 go func() { 300 for _ = range sigchan { 301 cli.resizeTty(id, isExec) 302 } 303 }() 304 } 305 return nil 306 } 307 308 func (cli *DockerCli) getTtySize() (int, int) { 309 if !cli.isTerminalOut { 310 return 0, 0 311 } 312 ws, err := term.GetWinsize(cli.outFd) 313 if err != nil { 314 log.Debugf("Error getting size: %s", err) 315 if ws == nil { 316 return 0, 0 317 } 318 } 319 return int(ws.Height), int(ws.Width) 320 } 321 322 func readBody(stream io.ReadCloser, statusCode int, err error) ([]byte, int, error) { 323 if stream != nil { 324 defer stream.Close() 325 } 326 if err != nil { 327 return nil, statusCode, err 328 } 329 body, err := ioutil.ReadAll(stream) 330 if err != nil { 331 return nil, -1, err 332 } 333 return body, statusCode, nil 334 }