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