summaryrefslogtreecommitdiff
path: root/api/client/utils.go
diff options
context:
space:
mode:
Diffstat (limited to 'api/client/utils.go')
-rw-r--r--api/client/utils.go390
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
+}