github.com/tompao/docker@v1.9.1/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  	"github.com/docker/docker/utils"
    31  )
    32  
    33  var (
    34  	errConnectionFailed = errors.New("Cannot connect to the Docker daemon. Is the docker daemon running on this host?")
    35  )
    36  
    37  type serverResponse struct {
    38  	body       io.ReadCloser
    39  	header     http.Header
    40  	statusCode int
    41  }
    42  
    43  // HTTPClient creates a new HTTP client with the cli's client transport instance.
    44  func (cli *DockerCli) HTTPClient() *http.Client {
    45  	return &http.Client{Transport: cli.transport}
    46  }
    47  
    48  func (cli *DockerCli) encodeData(data interface{}) (*bytes.Buffer, error) {
    49  	params := bytes.NewBuffer(nil)
    50  	if data != nil {
    51  		if err := json.NewEncoder(params).Encode(data); err != nil {
    52  			return nil, err
    53  		}
    54  	}
    55  	return params, nil
    56  }
    57  
    58  func (cli *DockerCli) clientRequest(method, path string, in io.Reader, headers map[string][]string) (*serverResponse, error) {
    59  
    60  	serverResp := &serverResponse{
    61  		body:       nil,
    62  		statusCode: -1,
    63  	}
    64  
    65  	expectedPayload := (method == "POST" || method == "PUT")
    66  	if expectedPayload && in == nil {
    67  		in = bytes.NewReader([]byte{})
    68  	}
    69  	req, err := http.NewRequest(method, fmt.Sprintf("%s/v%s%s", cli.basePath, api.Version, path), in)
    70  	if err != nil {
    71  		return serverResp, err
    72  	}
    73  
    74  	// Add CLI Config's HTTP Headers BEFORE we set the Docker headers
    75  	// then the user can't change OUR headers
    76  	for k, v := range cli.configFile.HTTPHeaders {
    77  		req.Header.Set(k, v)
    78  	}
    79  
    80  	req.Header.Set("User-Agent", "Docker-Client/"+dockerversion.VERSION+" ("+runtime.GOOS+")")
    81  	req.URL.Host = cli.addr
    82  	req.URL.Scheme = cli.scheme
    83  
    84  	if headers != nil {
    85  		for k, v := range headers {
    86  			req.Header[k] = v
    87  		}
    88  	}
    89  
    90  	if expectedPayload && req.Header.Get("Content-Type") == "" {
    91  		req.Header.Set("Content-Type", "text/plain")
    92  	}
    93  
    94  	resp, err := cli.HTTPClient().Do(req)
    95  	if resp != nil {
    96  		serverResp.statusCode = resp.StatusCode
    97  	}
    98  
    99  	if err != nil {
   100  		if utils.IsTimeout(err) || strings.Contains(err.Error(), "connection refused") || strings.Contains(err.Error(), "dial unix") {
   101  			return serverResp, errConnectionFailed
   102  		}
   103  
   104  		if cli.tlsConfig == nil && strings.Contains(err.Error(), "malformed HTTP response") {
   105  			return serverResp, fmt.Errorf("%v.\n* Are you trying to connect to a TLS-enabled daemon without TLS?", err)
   106  		}
   107  		if cli.tlsConfig != nil && strings.Contains(err.Error(), "remote error: bad certificate") {
   108  			return serverResp, fmt.Errorf("The server probably has client authentication (--tlsverify) enabled. Please check your TLS client certification settings: %v", err)
   109  		}
   110  
   111  		return serverResp, fmt.Errorf("An error occurred trying to connect: %v", err)
   112  	}
   113  
   114  	if serverResp.statusCode < 200 || serverResp.statusCode >= 400 {
   115  		body, err := ioutil.ReadAll(resp.Body)
   116  		if err != nil {
   117  			return serverResp, err
   118  		}
   119  		if len(body) == 0 {
   120  			return serverResp, fmt.Errorf("Error: request returned %s for API route and version %s, check if the server supports the requested API version", http.StatusText(serverResp.statusCode), req.URL)
   121  		}
   122  		return serverResp, fmt.Errorf("Error response from daemon: %s", bytes.TrimSpace(body))
   123  	}
   124  
   125  	serverResp.body = resp.Body
   126  	serverResp.header = resp.Header
   127  	return serverResp, nil
   128  }
   129  
   130  // cmdAttempt builds the corresponding registry Auth Header from the given
   131  // authConfig. It returns the servers body, status, error response
   132  func (cli *DockerCli) cmdAttempt(authConfig cliconfig.AuthConfig, method, path string, in io.Reader, out io.Writer) (io.ReadCloser, int, error) {
   133  	buf, err := json.Marshal(authConfig)
   134  	if err != nil {
   135  		return nil, -1, err
   136  	}
   137  	registryAuthHeader := []string{
   138  		base64.URLEncoding.EncodeToString(buf),
   139  	}
   140  
   141  	// begin the request
   142  	serverResp, err := cli.clientRequest(method, path, in, map[string][]string{
   143  		"X-Registry-Auth": registryAuthHeader,
   144  	})
   145  	if err == nil && out != nil {
   146  		// If we are streaming output, complete the stream since
   147  		// errors may not appear until later.
   148  		err = cli.streamBody(serverResp.body, serverResp.header.Get("Content-Type"), true, out, nil)
   149  	}
   150  	if err != nil {
   151  		// Since errors in a stream appear after status 200 has been written,
   152  		// we may need to change the status code.
   153  		if strings.Contains(err.Error(), "Authentication is required") ||
   154  			strings.Contains(err.Error(), "Status 401") ||
   155  			strings.Contains(err.Error(), "401 Unauthorized") ||
   156  			strings.Contains(err.Error(), "status code 401") {
   157  			serverResp.statusCode = http.StatusUnauthorized
   158  		}
   159  	}
   160  	return serverResp.body, serverResp.statusCode, err
   161  }
   162  
   163  func (cli *DockerCli) clientRequestAttemptLogin(method, path string, in io.Reader, out io.Writer, index *registry.IndexInfo, cmdName string) (io.ReadCloser, int, error) {
   164  
   165  	// Resolve the Auth config relevant for this server
   166  	authConfig := registry.ResolveAuthConfig(cli.configFile, index)
   167  	body, statusCode, err := cli.cmdAttempt(authConfig, method, path, in, out)
   168  	if statusCode == http.StatusUnauthorized {
   169  		fmt.Fprintf(cli.out, "\nPlease login prior to %s:\n", cmdName)
   170  		if err = cli.CmdLogin(index.GetAuthConfigKey()); err != nil {
   171  			return nil, -1, err
   172  		}
   173  		authConfig = registry.ResolveAuthConfig(cli.configFile, index)
   174  		return cli.cmdAttempt(authConfig, method, path, in, out)
   175  	}
   176  	return body, statusCode, err
   177  }
   178  
   179  func (cli *DockerCli) callWrapper(method, path string, data interface{}, headers map[string][]string) (io.ReadCloser, http.Header, int, error) {
   180  	sr, err := cli.call(method, path, data, headers)
   181  	return sr.body, sr.header, sr.statusCode, err
   182  }
   183  
   184  func (cli *DockerCli) call(method, path string, data interface{}, headers map[string][]string) (*serverResponse, error) {
   185  	params, err := cli.encodeData(data)
   186  	if err != nil {
   187  		sr := &serverResponse{
   188  			body:       nil,
   189  			header:     nil,
   190  			statusCode: -1,
   191  		}
   192  		return sr, nil
   193  	}
   194  
   195  	if data != nil {
   196  		if headers == nil {
   197  			headers = make(map[string][]string)
   198  		}
   199  		headers["Content-Type"] = []string{"application/json"}
   200  	}
   201  
   202  	serverResp, err := cli.clientRequest(method, path, params, headers)
   203  	return serverResp, err
   204  }
   205  
   206  type streamOpts struct {
   207  	rawTerminal bool
   208  	in          io.Reader
   209  	out         io.Writer
   210  	err         io.Writer
   211  	headers     map[string][]string
   212  }
   213  
   214  func (cli *DockerCli) stream(method, path string, opts *streamOpts) (*serverResponse, error) {
   215  	serverResp, err := cli.clientRequest(method, path, opts.in, opts.headers)
   216  	if err != nil {
   217  		return serverResp, err
   218  	}
   219  	return serverResp, cli.streamBody(serverResp.body, serverResp.header.Get("Content-Type"), opts.rawTerminal, opts.out, opts.err)
   220  }
   221  
   222  func (cli *DockerCli) streamBody(body io.ReadCloser, contentType string, rawTerminal bool, stdout, stderr io.Writer) error {
   223  	defer body.Close()
   224  
   225  	if api.MatchesContentType(contentType, "application/json") {
   226  		return jsonmessage.DisplayJSONMessagesStream(body, stdout, cli.outFd, cli.isTerminalOut)
   227  	}
   228  	if stdout != nil || stderr != nil {
   229  		// When TTY is ON, use regular copy
   230  		var err error
   231  		if rawTerminal {
   232  			_, err = io.Copy(stdout, body)
   233  		} else {
   234  			_, err = stdcopy.StdCopy(stdout, stderr, body)
   235  		}
   236  		logrus.Debugf("[stream] End of stdout")
   237  		return err
   238  	}
   239  	return nil
   240  }
   241  
   242  func (cli *DockerCli) resizeTty(id string, isExec bool) {
   243  	height, width := cli.getTtySize()
   244  	if height == 0 && width == 0 {
   245  		return
   246  	}
   247  	v := url.Values{}
   248  	v.Set("h", strconv.Itoa(height))
   249  	v.Set("w", strconv.Itoa(width))
   250  
   251  	path := ""
   252  	if !isExec {
   253  		path = "/containers/" + id + "/resize?"
   254  	} else {
   255  		path = "/exec/" + id + "/resize?"
   256  	}
   257  
   258  	if _, _, err := readBody(cli.call("POST", path+v.Encode(), nil, nil)); err != nil {
   259  		logrus.Debugf("Error resize: %s", err)
   260  	}
   261  }
   262  
   263  func waitForExit(cli *DockerCli, containerID string) (int, error) {
   264  	serverResp, err := cli.call("POST", "/containers/"+containerID+"/wait", nil, nil)
   265  	if err != nil {
   266  		return -1, err
   267  	}
   268  
   269  	defer serverResp.body.Close()
   270  
   271  	var res types.ContainerWaitResponse
   272  	if err := json.NewDecoder(serverResp.body).Decode(&res); err != nil {
   273  		return -1, err
   274  	}
   275  
   276  	return res.StatusCode, nil
   277  }
   278  
   279  // getExitCode perform an inspect on the container. It returns
   280  // the running state and the exit code.
   281  func getExitCode(cli *DockerCli, containerID string) (bool, int, error) {
   282  	serverResp, err := cli.call("GET", "/containers/"+containerID+"/json", nil, nil)
   283  	if err != nil {
   284  		// If we can't connect, then the daemon probably died.
   285  		if err != errConnectionFailed {
   286  			return false, -1, err
   287  		}
   288  		return false, -1, nil
   289  	}
   290  
   291  	defer serverResp.body.Close()
   292  
   293  	var c types.ContainerJSON
   294  	if err := json.NewDecoder(serverResp.body).Decode(&c); err != nil {
   295  		return false, -1, err
   296  	}
   297  
   298  	return c.State.Running, c.State.ExitCode, nil
   299  }
   300  
   301  // getExecExitCode perform an inspect on the exec command. It returns
   302  // the running state and the exit code.
   303  func getExecExitCode(cli *DockerCli, execID string) (bool, int, error) {
   304  	serverResp, err := cli.call("GET", "/exec/"+execID+"/json", nil, nil)
   305  	if err != nil {
   306  		// If we can't connect, then the daemon probably died.
   307  		if err != errConnectionFailed {
   308  			return false, -1, err
   309  		}
   310  		return false, -1, nil
   311  	}
   312  
   313  	defer serverResp.body.Close()
   314  
   315  	//TODO: Should we reconsider having a type in api/types?
   316  	//this is a response to exex/id/json not container
   317  	var c struct {
   318  		Running  bool
   319  		ExitCode int
   320  	}
   321  
   322  	if err := json.NewDecoder(serverResp.body).Decode(&c); err != nil {
   323  		return false, -1, err
   324  	}
   325  
   326  	return c.Running, c.ExitCode, nil
   327  }
   328  
   329  func (cli *DockerCli) monitorTtySize(id string, isExec bool) error {
   330  	cli.resizeTty(id, isExec)
   331  
   332  	if runtime.GOOS == "windows" {
   333  		go func() {
   334  			prevH, prevW := cli.getTtySize()
   335  			for {
   336  				time.Sleep(time.Millisecond * 250)
   337  				h, w := cli.getTtySize()
   338  
   339  				if prevW != w || prevH != h {
   340  					cli.resizeTty(id, isExec)
   341  				}
   342  				prevH = h
   343  				prevW = w
   344  			}
   345  		}()
   346  	} else {
   347  		sigchan := make(chan os.Signal, 1)
   348  		gosignal.Notify(sigchan, signal.SIGWINCH)
   349  		go func() {
   350  			for range sigchan {
   351  				cli.resizeTty(id, isExec)
   352  			}
   353  		}()
   354  	}
   355  	return nil
   356  }
   357  
   358  func (cli *DockerCli) getTtySize() (int, int) {
   359  	if !cli.isTerminalOut {
   360  		return 0, 0
   361  	}
   362  	ws, err := term.GetWinsize(cli.outFd)
   363  	if err != nil {
   364  		logrus.Debugf("Error getting size: %s", err)
   365  		if ws == nil {
   366  			return 0, 0
   367  		}
   368  	}
   369  	return int(ws.Height), int(ws.Width)
   370  }
   371  
   372  func readBody(serverResp *serverResponse, err error) ([]byte, int, error) {
   373  	if serverResp.body != nil {
   374  		defer serverResp.body.Close()
   375  	}
   376  	if err != nil {
   377  		return nil, serverResp.statusCode, err
   378  	}
   379  	body, err := ioutil.ReadAll(serverResp.body)
   380  	if err != nil {
   381  		return nil, -1, err
   382  	}
   383  	return body, serverResp.statusCode, nil
   384  }