github.com/squaremo/docker@v1.3.2-0.20150516120342-42cfc9554972/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  }