github.com/daaku/docker@v1.5.0/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  	"strconv"
    16  	"strings"
    17  
    18  	log "github.com/Sirupsen/logrus"
    19  	"github.com/docker/docker/api"
    20  	"github.com/docker/docker/dockerversion"
    21  	"github.com/docker/docker/engine"
    22  	"github.com/docker/docker/pkg/signal"
    23  	"github.com/docker/docker/pkg/stdcopy"
    24  	"github.com/docker/docker/pkg/term"
    25  	"github.com/docker/docker/registry"
    26  	"github.com/docker/docker/utils"
    27  )
    28  
    29  var (
    30  	ErrConnectionRefused = errors.New("Cannot connect to the Docker daemon. Is 'docker -d' running on this host?")
    31  )
    32  
    33  func (cli *DockerCli) HTTPClient() *http.Client {
    34  	return &http.Client{Transport: cli.transport}
    35  }
    36  
    37  func (cli *DockerCli) encodeData(data interface{}) (*bytes.Buffer, error) {
    38  	params := bytes.NewBuffer(nil)
    39  	if data != nil {
    40  		if env, ok := data.(engine.Env); ok {
    41  			if err := env.Encode(params); err != nil {
    42  				return nil, err
    43  			}
    44  		} else {
    45  			buf, err := json.Marshal(data)
    46  			if err != nil {
    47  				return nil, err
    48  			}
    49  			if _, err := params.Write(buf); err != nil {
    50  				return nil, err
    51  			}
    52  		}
    53  	}
    54  	return params, nil
    55  }
    56  
    57  func (cli *DockerCli) call(method, path string, data interface{}, passAuthInfo bool) (io.ReadCloser, int, error) {
    58  	params, err := cli.encodeData(data)
    59  	if err != nil {
    60  		return nil, -1, err
    61  	}
    62  	req, err := http.NewRequest(method, fmt.Sprintf("/v%s%s", api.APIVERSION, path), params)
    63  	if err != nil {
    64  		return nil, -1, err
    65  	}
    66  	if passAuthInfo {
    67  		cli.LoadConfigFile()
    68  		// Resolve the Auth config relevant for this server
    69  		authConfig := cli.configFile.Configs[registry.IndexServerAddress()]
    70  		getHeaders := func(authConfig registry.AuthConfig) (map[string][]string, error) {
    71  			buf, err := json.Marshal(authConfig)
    72  			if err != nil {
    73  				return nil, err
    74  			}
    75  			registryAuthHeader := []string{
    76  				base64.URLEncoding.EncodeToString(buf),
    77  			}
    78  			return map[string][]string{"X-Registry-Auth": registryAuthHeader}, nil
    79  		}
    80  		if headers, err := getHeaders(authConfig); err == nil && headers != nil {
    81  			for k, v := range headers {
    82  				req.Header[k] = v
    83  			}
    84  		}
    85  	}
    86  	req.Header.Set("User-Agent", "Docker-Client/"+dockerversion.VERSION)
    87  	req.URL.Host = cli.addr
    88  	req.URL.Scheme = cli.scheme
    89  	if data != nil {
    90  		req.Header.Set("Content-Type", "application/json")
    91  	} else if method == "POST" {
    92  		req.Header.Set("Content-Type", "text/plain")
    93  	}
    94  	resp, err := cli.HTTPClient().Do(req)
    95  	if err != nil {
    96  		if strings.Contains(err.Error(), "connection refused") {
    97  			return nil, -1, ErrConnectionRefused
    98  		}
    99  
   100  		if cli.tlsConfig == nil {
   101  			return nil, -1, fmt.Errorf("%v. Are you trying to connect to a TLS-enabled daemon without TLS?", err)
   102  		}
   103  		return nil, -1, fmt.Errorf("An error occurred trying to connect: %v", err)
   104  
   105  	}
   106  
   107  	if resp.StatusCode < 200 || resp.StatusCode >= 400 {
   108  		body, err := ioutil.ReadAll(resp.Body)
   109  		if err != nil {
   110  			return nil, -1, err
   111  		}
   112  		if len(body) == 0 {
   113  			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)
   114  		}
   115  		return nil, resp.StatusCode, fmt.Errorf("Error response from daemon: %s", bytes.TrimSpace(body))
   116  	}
   117  
   118  	return resp.Body, resp.StatusCode, nil
   119  }
   120  
   121  func (cli *DockerCli) stream(method, path string, in io.Reader, out io.Writer, headers map[string][]string) error {
   122  	return cli.streamHelper(method, path, true, in, out, nil, headers)
   123  }
   124  
   125  func (cli *DockerCli) streamHelper(method, path string, setRawTerminal bool, in io.Reader, stdout, stderr io.Writer, headers map[string][]string) error {
   126  	if (method == "POST" || method == "PUT") && in == nil {
   127  		in = bytes.NewReader([]byte{})
   128  	}
   129  
   130  	req, err := http.NewRequest(method, fmt.Sprintf("/v%s%s", api.APIVERSION, path), in)
   131  	if err != nil {
   132  		return err
   133  	}
   134  	req.Header.Set("User-Agent", "Docker-Client/"+dockerversion.VERSION)
   135  	req.URL.Host = cli.addr
   136  	req.URL.Scheme = cli.scheme
   137  	if method == "POST" {
   138  		req.Header.Set("Content-Type", "text/plain")
   139  	}
   140  
   141  	if headers != nil {
   142  		for k, v := range headers {
   143  			req.Header[k] = v
   144  		}
   145  	}
   146  	resp, err := cli.HTTPClient().Do(req)
   147  	if err != nil {
   148  		if strings.Contains(err.Error(), "connection refused") {
   149  			return fmt.Errorf("Cannot connect to the Docker daemon. Is 'docker -d' running on this host?")
   150  		}
   151  		return err
   152  	}
   153  	defer resp.Body.Close()
   154  
   155  	if resp.StatusCode < 200 || resp.StatusCode >= 400 {
   156  		body, err := ioutil.ReadAll(resp.Body)
   157  		if err != nil {
   158  			return err
   159  		}
   160  		if len(body) == 0 {
   161  			return fmt.Errorf("Error :%s", http.StatusText(resp.StatusCode))
   162  		}
   163  		return fmt.Errorf("Error: %s", bytes.TrimSpace(body))
   164  	}
   165  
   166  	if api.MatchesContentType(resp.Header.Get("Content-Type"), "application/json") {
   167  		return utils.DisplayJSONMessagesStream(resp.Body, stdout, cli.outFd, cli.isTerminalOut)
   168  	}
   169  	if stdout != nil || stderr != nil {
   170  		// When TTY is ON, use regular copy
   171  		if setRawTerminal {
   172  			_, err = io.Copy(stdout, resp.Body)
   173  		} else {
   174  			_, err = stdcopy.StdCopy(stdout, stderr, resp.Body)
   175  		}
   176  		log.Debugf("[stream] End of stdout")
   177  		return err
   178  	}
   179  	return nil
   180  }
   181  
   182  func (cli *DockerCli) resizeTty(id string, isExec bool) {
   183  	height, width := cli.getTtySize()
   184  	if height == 0 && width == 0 {
   185  		return
   186  	}
   187  	v := url.Values{}
   188  	v.Set("h", strconv.Itoa(height))
   189  	v.Set("w", strconv.Itoa(width))
   190  
   191  	path := ""
   192  	if !isExec {
   193  		path = "/containers/" + id + "/resize?"
   194  	} else {
   195  		path = "/exec/" + id + "/resize?"
   196  	}
   197  
   198  	if _, _, err := readBody(cli.call("POST", path+v.Encode(), nil, false)); err != nil {
   199  		log.Debugf("Error resize: %s", err)
   200  	}
   201  }
   202  
   203  func waitForExit(cli *DockerCli, containerId string) (int, error) {
   204  	stream, _, err := cli.call("POST", "/containers/"+containerId+"/wait", nil, false)
   205  	if err != nil {
   206  		return -1, err
   207  	}
   208  
   209  	var out engine.Env
   210  	if err := out.Decode(stream); err != nil {
   211  		return -1, err
   212  	}
   213  	return out.GetInt("StatusCode"), nil
   214  }
   215  
   216  // getExitCode perform an inspect on the container. It returns
   217  // the running state and the exit code.
   218  func getExitCode(cli *DockerCli, containerId string) (bool, int, error) {
   219  	stream, _, err := cli.call("GET", "/containers/"+containerId+"/json", nil, false)
   220  	if err != nil {
   221  		// If we can't connect, then the daemon probably died.
   222  		if err != ErrConnectionRefused {
   223  			return false, -1, err
   224  		}
   225  		return false, -1, nil
   226  	}
   227  
   228  	var result engine.Env
   229  	if err := result.Decode(stream); err != nil {
   230  		return false, -1, err
   231  	}
   232  
   233  	state := result.GetSubEnv("State")
   234  	return state.GetBool("Running"), state.GetInt("ExitCode"), nil
   235  }
   236  
   237  // getExecExitCode perform an inspect on the exec command. It returns
   238  // the running state and the exit code.
   239  func getExecExitCode(cli *DockerCli, execId string) (bool, int, error) {
   240  	stream, _, err := cli.call("GET", "/exec/"+execId+"/json", nil, false)
   241  	if err != nil {
   242  		// If we can't connect, then the daemon probably died.
   243  		if err != ErrConnectionRefused {
   244  			return false, -1, err
   245  		}
   246  		return false, -1, nil
   247  	}
   248  
   249  	var result engine.Env
   250  	if err := result.Decode(stream); err != nil {
   251  		return false, -1, err
   252  	}
   253  
   254  	return result.GetBool("Running"), result.GetInt("ExitCode"), nil
   255  }
   256  
   257  func (cli *DockerCli) monitorTtySize(id string, isExec bool) error {
   258  	cli.resizeTty(id, isExec)
   259  
   260  	sigchan := make(chan os.Signal, 1)
   261  	gosignal.Notify(sigchan, signal.SIGWINCH)
   262  	go func() {
   263  		for _ = range sigchan {
   264  			cli.resizeTty(id, isExec)
   265  		}
   266  	}()
   267  	return nil
   268  }
   269  
   270  func (cli *DockerCli) getTtySize() (int, int) {
   271  	if !cli.isTerminalOut {
   272  		return 0, 0
   273  	}
   274  	ws, err := term.GetWinsize(cli.outFd)
   275  	if err != nil {
   276  		log.Debugf("Error getting size: %s", err)
   277  		if ws == nil {
   278  			return 0, 0
   279  		}
   280  	}
   281  	return int(ws.Height), int(ws.Width)
   282  }
   283  
   284  func readBody(stream io.ReadCloser, statusCode int, err error) ([]byte, int, error) {
   285  	if stream != nil {
   286  		defer stream.Close()
   287  	}
   288  	if err != nil {
   289  		return nil, statusCode, err
   290  	}
   291  	body, err := ioutil.ReadAll(stream)
   292  	if err != nil {
   293  		return nil, -1, err
   294  	}
   295  	return body, statusCode, nil
   296  }