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