diff options
Diffstat (limited to 'api/client/utils.go')
-rw-r--r-- | api/client/utils.go | 390 |
1 files changed, 390 insertions, 0 deletions
diff --git a/api/client/utils.go b/api/client/utils.go new file mode 100644 index 0000000000..4ef09ba783 --- /dev/null +++ b/api/client/utils.go @@ -0,0 +1,390 @@ +package client + +import ( + "bytes" + "crypto/tls" + "encoding/base64" + "encoding/json" + "errors" + "fmt" + "io" + "io/ioutil" + "net" + "net/http" + "net/http/httputil" + "net/url" + "os" + gosignal "os/signal" + "regexp" + goruntime "runtime" + "strconv" + "strings" + "syscall" + + "github.com/dotcloud/docker/api" + "github.com/dotcloud/docker/dockerversion" + "github.com/dotcloud/docker/engine" + "github.com/dotcloud/docker/pkg/term" + "github.com/dotcloud/docker/registry" + "github.com/dotcloud/docker/utils" +) + +var ( + ErrConnectionRefused = errors.New("Cannot connect to the Docker daemon. Is 'docker -d' running on this host?") +) + +func (cli *DockerCli) dial() (net.Conn, error) { + if cli.tlsConfig != nil && cli.proto != "unix" { + return tls.Dial(cli.proto, cli.addr, cli.tlsConfig) + } + return net.Dial(cli.proto, cli.addr) +} + +func (cli *DockerCli) call(method, path string, data interface{}, passAuthInfo bool) (io.ReadCloser, int, error) { + params := bytes.NewBuffer(nil) + if data != nil { + if env, ok := data.(engine.Env); ok { + if err := env.Encode(params); err != nil { + return nil, -1, err + } + } else { + buf, err := json.Marshal(data) + if err != nil { + return nil, -1, err + } + if _, err := params.Write(buf); err != nil { + return nil, -1, err + } + } + } + // fixme: refactor client to support redirect + re := regexp.MustCompile("/+") + path = re.ReplaceAllString(path, "/") + + 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.Host = cli.addr + if data != nil { + req.Header.Set("Content-Type", "application/json") + } else if method == "POST" { + req.Header.Set("Content-Type", "plain/text") + } + dial, err := cli.dial() + if err != nil { + if strings.Contains(err.Error(), "connection refused") { + return nil, -1, ErrConnectionRefused + } + return nil, -1, err + } + clientconn := httputil.NewClientConn(dial, nil) + resp, err := clientconn.Do(req) + if err != nil { + clientconn.Close() + 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: %s", bytes.TrimSpace(body)) + } + + wrapper := utils.NewReadCloserWrapper(resp.Body, func() error { + if resp != nil && resp.Body != nil { + resp.Body.Close() + } + return clientconn.Close() + }) + return wrapper, resp.StatusCode, nil +} + +func (cli *DockerCli) stream(method, path string, in io.Reader, out io.Writer, headers map[string][]string) error { + if (method == "POST" || method == "PUT") && in == nil { + in = bytes.NewReader([]byte{}) + } + + // fixme: refactor client to support redirect + re := regexp.MustCompile("/+") + path = re.ReplaceAllString(path, "/") + + 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.Host = cli.addr + if method == "POST" { + req.Header.Set("Content-Type", "plain/text") + } + + if headers != nil { + for k, v := range headers { + req.Header[k] = v + } + } + + dial, err := cli.dial() + 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 + } + clientconn := httputil.NewClientConn(dial, nil) + resp, err := clientconn.Do(req) + defer clientconn.Close() + 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, out, cli.terminalFd, cli.isTerminal) + } + if _, err := io.Copy(out, resp.Body); err != nil { + return err + } + return nil +} + +func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in io.ReadCloser, stdout, stderr io.Writer, started chan io.Closer) error { + defer func() { + if started != nil { + close(started) + } + }() + // fixme: refactor client to support redirect + re := regexp.MustCompile("/+") + path = re.ReplaceAllString(path, "/") + + req, err := http.NewRequest(method, fmt.Sprintf("/v%s%s", api.APIVERSION, path), nil) + if err != nil { + return err + } + req.Header.Set("User-Agent", "Docker-Client/"+dockerversion.VERSION) + req.Header.Set("Content-Type", "plain/text") + req.Host = cli.addr + + dial, err := cli.dial() + 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 + } + clientconn := httputil.NewClientConn(dial, nil) + defer clientconn.Close() + + // Server hijacks the connection, error 'connection closed' expected + clientconn.Do(req) + + rwc, br := clientconn.Hijack() + defer rwc.Close() + + if started != nil { + started <- rwc + } + + var receiveStdout chan error + + var oldState *term.State + + if in != nil && setRawTerminal && cli.isTerminal && os.Getenv("NORAW") == "" { + oldState, err = term.SetRawTerminal(cli.terminalFd) + if err != nil { + return err + } + defer term.RestoreTerminal(cli.terminalFd, oldState) + } + + if stdout != nil || stderr != nil { + receiveStdout = utils.Go(func() (err error) { + defer func() { + if in != nil { + if setRawTerminal && cli.isTerminal { + term.RestoreTerminal(cli.terminalFd, oldState) + } + // For some reason this Close call blocks on darwin.. + // As the client exists right after, simply discard the close + // until we find a better solution. + if goruntime.GOOS != "darwin" { + in.Close() + } + } + }() + + // When TTY is ON, use regular copy + if setRawTerminal { + _, err = io.Copy(stdout, br) + } else { + _, err = utils.StdCopy(stdout, stderr, br) + } + utils.Debugf("[hijack] End of stdout") + return err + }) + } + + sendStdin := utils.Go(func() error { + if in != nil { + io.Copy(rwc, in) + utils.Debugf("[hijack] End of stdin") + } + if tcpc, ok := rwc.(*net.TCPConn); ok { + if err := tcpc.CloseWrite(); err != nil { + utils.Debugf("Couldn't send EOF: %s\n", err) + } + } else if unixc, ok := rwc.(*net.UnixConn); ok { + if err := unixc.CloseWrite(); err != nil { + utils.Debugf("Couldn't send EOF: %s\n", err) + } + } + // Discard errors due to pipe interruption + return nil + }) + + if stdout != nil || stderr != nil { + if err := <-receiveStdout; err != nil { + utils.Debugf("Error receiveStdout: %s", err) + return err + } + } + + if !cli.isTerminal { + if err := <-sendStdin; err != nil { + utils.Debugf("Error sendStdin: %s", err) + return err + } + } + return nil + +} + +func (cli *DockerCli) resizeTty(id string) { + 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)) + if _, _, err := readBody(cli.call("POST", "/containers/"+id+"/resize?"+v.Encode(), nil, false)); err != nil { + utils.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) { + body, _, err := readBody(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 + } + c := &api.Container{} + if err := json.Unmarshal(body, c); err != nil { + return false, -1, err + } + return c.State.Running, c.State.ExitCode, nil +} + +func (cli *DockerCli) monitorTtySize(id string) error { + cli.resizeTty(id) + + sigchan := make(chan os.Signal, 1) + gosignal.Notify(sigchan, syscall.SIGWINCH) + go func() { + for _ = range sigchan { + cli.resizeTty(id) + } + }() + return nil +} + +func (cli *DockerCli) getTtySize() (int, int) { + if !cli.isTerminal { + return 0, 0 + } + ws, err := term.GetWinsize(cli.terminalFd) + if err != nil { + utils.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 +} |