| package client |
| |
| import ( |
| "bytes" |
| "encoding/base64" |
| "encoding/json" |
| "errors" |
| "fmt" |
| "io" |
| "io/ioutil" |
| "net" |
| "net/http" |
| "net/url" |
| "os" |
| gosignal "os/signal" |
| "strconv" |
| "strings" |
| "syscall" |
| "time" |
| |
| "github.com/docker/docker/api" |
| "github.com/docker/docker/dockerversion" |
| "github.com/docker/docker/engine" |
| "github.com/docker/docker/pkg/log" |
| "github.com/docker/docker/pkg/stdcopy" |
| "github.com/docker/docker/pkg/term" |
| "github.com/docker/docker/registry" |
| "github.com/docker/docker/utils" |
| ) |
| |
| var ( |
| ErrConnectionRefused = errors.New("Cannot connect to the Docker daemon. Is 'docker -d' running on this host?") |
| ) |
| |
| func (cli *DockerCli) HTTPClient() *http.Client { |
| tr := &http.Transport{ |
| TLSClientConfig: cli.tlsConfig, |
| Dial: func(network, addr string) (net.Conn, error) { |
| // Why 32? See issue 8035 |
| return net.DialTimeout(cli.proto, cli.addr, 32*time.Second) |
| }, |
| } |
| if cli.proto == "unix" { |
| // XXX workaround for net/http Transport which caches connections, but is |
| // intended for tcp connections, not unix sockets. |
| tr.DisableKeepAlives = true |
| |
| // no need in compressing for local communications |
| tr.DisableCompression = true |
| } |
| return &http.Client{Transport: tr} |
| } |
| |
| func (cli *DockerCli) encodeData(data interface{}) (*bytes.Buffer, error) { |
| params := bytes.NewBuffer(nil) |
| if data != nil { |
| if env, ok := data.(engine.Env); ok { |
| if err := env.Encode(params); err != nil { |
| return nil, err |
| } |
| } else { |
| buf, err := json.Marshal(data) |
| if err != nil { |
| return nil, err |
| } |
| if _, err := params.Write(buf); err != nil { |
| return nil, err |
| } |
| } |
| } |
| return params, nil |
| } |
| |
| func (cli *DockerCli) call(method, path string, data interface{}, passAuthInfo bool) (io.ReadCloser, int, error) { |
| params, err := cli.encodeData(data) |
| if err != nil { |
| return nil, -1, err |
| } |
| req, err := http.NewRequest(method, fmt.Sprintf("/v%s%s", api.APIVERSION, path), params) |
| if err != nil { |
| return nil, -1, err |
| } |
| if passAuthInfo { |
| cli.LoadConfigFile() |
| // Resolve the Auth config relevant for this server |
| authConfig := cli.configFile.ResolveAuthConfig(registry.IndexServerAddress()) |
| getHeaders := func(authConfig registry.AuthConfig) (map[string][]string, error) { |
| buf, err := json.Marshal(authConfig) |
| if err != nil { |
| return nil, err |
| } |
| registryAuthHeader := []string{ |
| base64.URLEncoding.EncodeToString(buf), |
| } |
| return map[string][]string{"X-Registry-Auth": registryAuthHeader}, nil |
| } |
| if headers, err := getHeaders(authConfig); err == nil && headers != nil { |
| for k, v := range headers { |
| req.Header[k] = v |
| } |
| } |
| } |
| req.Header.Set("User-Agent", "Docker-Client/"+dockerversion.VERSION) |
| req.URL.Host = cli.addr |
| req.URL.Scheme = cli.scheme |
| if data != nil { |
| req.Header.Set("Content-Type", "application/json") |
| } else if method == "POST" { |
| req.Header.Set("Content-Type", "plain/text") |
| } |
| resp, err := cli.HTTPClient().Do(req) |
| if err != nil { |
| if strings.Contains(err.Error(), "connection refused") { |
| return nil, -1, ErrConnectionRefused |
| } |
| return nil, -1, err |
| } |
| |
| if resp.StatusCode < 200 || resp.StatusCode >= 400 { |
| body, err := ioutil.ReadAll(resp.Body) |
| if err != nil { |
| return nil, -1, err |
| } |
| if len(body) == 0 { |
| return nil, resp.StatusCode, fmt.Errorf("Error: request returned %s for API route and version %s, check if the server supports the requested API version", http.StatusText(resp.StatusCode), req.URL) |
| } |
| return nil, resp.StatusCode, fmt.Errorf("Error response from daemon: %s", bytes.TrimSpace(body)) |
| } |
| |
| return resp.Body, resp.StatusCode, nil |
| } |
| |
| func (cli *DockerCli) stream(method, path string, in io.Reader, out io.Writer, headers map[string][]string) error { |
| return cli.streamHelper(method, path, true, in, out, nil, headers) |
| } |
| |
| func (cli *DockerCli) streamHelper(method, path string, setRawTerminal bool, in io.Reader, stdout, stderr io.Writer, headers map[string][]string) error { |
| if (method == "POST" || method == "PUT") && in == nil { |
| in = bytes.NewReader([]byte{}) |
| } |
| |
| req, err := http.NewRequest(method, fmt.Sprintf("/v%s%s", api.APIVERSION, path), in) |
| if err != nil { |
| return err |
| } |
| req.Header.Set("User-Agent", "Docker-Client/"+dockerversion.VERSION) |
| req.URL.Host = cli.addr |
| req.URL.Scheme = cli.scheme |
| if method == "POST" { |
| req.Header.Set("Content-Type", "plain/text") |
| } |
| |
| if headers != nil { |
| for k, v := range headers { |
| req.Header[k] = v |
| } |
| } |
| resp, err := cli.HTTPClient().Do(req) |
| if err != nil { |
| if strings.Contains(err.Error(), "connection refused") { |
| return fmt.Errorf("Cannot connect to the Docker daemon. Is 'docker -d' running on this host?") |
| } |
| return err |
| } |
| defer resp.Body.Close() |
| |
| if resp.StatusCode < 200 || resp.StatusCode >= 400 { |
| body, err := ioutil.ReadAll(resp.Body) |
| if err != nil { |
| return err |
| } |
| if len(body) == 0 { |
| return fmt.Errorf("Error :%s", http.StatusText(resp.StatusCode)) |
| } |
| return fmt.Errorf("Error: %s", bytes.TrimSpace(body)) |
| } |
| |
| if api.MatchesContentType(resp.Header.Get("Content-Type"), "application/json") { |
| return utils.DisplayJSONMessagesStream(resp.Body, stdout, cli.outFd, cli.isTerminalOut) |
| } |
| if stdout != nil || stderr != nil { |
| // When TTY is ON, use regular copy |
| if setRawTerminal { |
| _, err = io.Copy(stdout, resp.Body) |
| } else { |
| _, err = stdcopy.StdCopy(stdout, stderr, resp.Body) |
| } |
| log.Debugf("[stream] End of stdout") |
| return err |
| } |
| return nil |
| } |
| |
| func (cli *DockerCli) resizeTty(id string, isExec bool) { |
| height, width := cli.getTtySize() |
| if height == 0 && width == 0 { |
| return |
| } |
| v := url.Values{} |
| v.Set("h", strconv.Itoa(height)) |
| v.Set("w", strconv.Itoa(width)) |
| |
| path := "" |
| if !isExec { |
| path = "/containers/" + id + "/resize?" |
| } else { |
| path = "/exec/" + id + "/resize?" |
| } |
| |
| if _, _, err := readBody(cli.call("POST", path+v.Encode(), nil, false)); err != nil { |
| log.Debugf("Error resize: %s", err) |
| } |
| } |
| |
| func waitForExit(cli *DockerCli, containerId string) (int, error) { |
| stream, _, err := cli.call("POST", "/containers/"+containerId+"/wait", nil, false) |
| if err != nil { |
| return -1, err |
| } |
| |
| var out engine.Env |
| if err := out.Decode(stream); err != nil { |
| return -1, err |
| } |
| return out.GetInt("StatusCode"), nil |
| } |
| |
| // getExitCode perform an inspect on the container. It returns |
| // the running state and the exit code. |
| func getExitCode(cli *DockerCli, containerId string) (bool, int, error) { |
| steam, _, err := cli.call("GET", "/containers/"+containerId+"/json", nil, false) |
| if err != nil { |
| // If we can't connect, then the daemon probably died. |
| if err != ErrConnectionRefused { |
| return false, -1, err |
| } |
| return false, -1, nil |
| } |
| |
| var result engine.Env |
| if err := result.Decode(steam); err != nil { |
| return false, -1, err |
| } |
| |
| state := result.GetSubEnv("State") |
| return state.GetBool("Running"), state.GetInt("ExitCode"), nil |
| } |
| |
| func (cli *DockerCli) monitorTtySize(id string, isExec bool) error { |
| cli.resizeTty(id, isExec) |
| |
| sigchan := make(chan os.Signal, 1) |
| gosignal.Notify(sigchan, syscall.SIGWINCH) |
| go func() { |
| for _ = range sigchan { |
| cli.resizeTty(id, isExec) |
| } |
| }() |
| return nil |
| } |
| |
| func (cli *DockerCli) getTtySize() (int, int) { |
| if !cli.isTerminalOut { |
| return 0, 0 |
| } |
| ws, err := term.GetWinsize(cli.outFd) |
| if err != nil { |
| log.Debugf("Error getting size: %s", err) |
| if ws == nil { |
| return 0, 0 |
| } |
| } |
| return int(ws.Height), int(ws.Width) |
| } |
| |
| func readBody(stream io.ReadCloser, statusCode int, err error) ([]byte, int, error) { |
| if stream != nil { |
| defer stream.Close() |
| } |
| if err != nil { |
| return nil, statusCode, err |
| } |
| body, err := ioutil.ReadAll(stream) |
| if err != nil { |
| return nil, -1, err |
| } |
| return body, statusCode, nil |
| } |