github.com/lazyboychen7/engine@v17.12.1-ce-rc2+incompatible/integration-cli/request/request.go (about)

     1  package request
     2  
     3  import (
     4  	"bufio"
     5  	"bytes"
     6  	"crypto/tls"
     7  	"encoding/json"
     8  	"fmt"
     9  	"io"
    10  	"io/ioutil"
    11  	"net"
    12  	"net/http"
    13  	"net/http/httputil"
    14  	"net/url"
    15  	"os"
    16  	"path/filepath"
    17  	"strings"
    18  	"time"
    19  
    20  	"github.com/docker/docker/api"
    21  	dclient "github.com/docker/docker/client"
    22  	"github.com/docker/docker/opts"
    23  	"github.com/docker/docker/pkg/ioutils"
    24  	"github.com/docker/go-connections/sockets"
    25  	"github.com/docker/go-connections/tlsconfig"
    26  	"github.com/pkg/errors"
    27  )
    28  
    29  // Method creates a modifier that sets the specified string as the request method
    30  func Method(method string) func(*http.Request) error {
    31  	return func(req *http.Request) error {
    32  		req.Method = method
    33  		return nil
    34  	}
    35  }
    36  
    37  // RawString sets the specified string as body for the request
    38  func RawString(content string) func(*http.Request) error {
    39  	return RawContent(ioutil.NopCloser(strings.NewReader(content)))
    40  }
    41  
    42  // RawContent sets the specified reader as body for the request
    43  func RawContent(reader io.ReadCloser) func(*http.Request) error {
    44  	return func(req *http.Request) error {
    45  		req.Body = reader
    46  		return nil
    47  	}
    48  }
    49  
    50  // ContentType sets the specified Content-Type request header
    51  func ContentType(contentType string) func(*http.Request) error {
    52  	return func(req *http.Request) error {
    53  		req.Header.Set("Content-Type", contentType)
    54  		return nil
    55  	}
    56  }
    57  
    58  // JSON sets the Content-Type request header to json
    59  func JSON(req *http.Request) error {
    60  	return ContentType("application/json")(req)
    61  }
    62  
    63  // JSONBody creates a modifier that encodes the specified data to a JSON string and set it as request body. It also sets
    64  // the Content-Type header of the request.
    65  func JSONBody(data interface{}) func(*http.Request) error {
    66  	return func(req *http.Request) error {
    67  		jsonData := bytes.NewBuffer(nil)
    68  		if err := json.NewEncoder(jsonData).Encode(data); err != nil {
    69  			return err
    70  		}
    71  		req.Body = ioutil.NopCloser(jsonData)
    72  		req.Header.Set("Content-Type", "application/json")
    73  		return nil
    74  	}
    75  }
    76  
    77  // Post creates and execute a POST request on the specified host and endpoint, with the specified request modifiers
    78  func Post(endpoint string, modifiers ...func(*http.Request) error) (*http.Response, io.ReadCloser, error) {
    79  	return Do(endpoint, append(modifiers, Method(http.MethodPost))...)
    80  }
    81  
    82  // Delete creates and execute a DELETE request on the specified host and endpoint, with the specified request modifiers
    83  func Delete(endpoint string, modifiers ...func(*http.Request) error) (*http.Response, io.ReadCloser, error) {
    84  	return Do(endpoint, append(modifiers, Method(http.MethodDelete))...)
    85  }
    86  
    87  // Get creates and execute a GET request on the specified host and endpoint, with the specified request modifiers
    88  func Get(endpoint string, modifiers ...func(*http.Request) error) (*http.Response, io.ReadCloser, error) {
    89  	return Do(endpoint, modifiers...)
    90  }
    91  
    92  // Do creates and execute a request on the specified endpoint, with the specified request modifiers
    93  func Do(endpoint string, modifiers ...func(*http.Request) error) (*http.Response, io.ReadCloser, error) {
    94  	return DoOnHost(DaemonHost(), endpoint, modifiers...)
    95  }
    96  
    97  // DoOnHost creates and execute a request on the specified host and endpoint, with the specified request modifiers
    98  func DoOnHost(host, endpoint string, modifiers ...func(*http.Request) error) (*http.Response, io.ReadCloser, error) {
    99  	req, err := New(host, endpoint, modifiers...)
   100  	if err != nil {
   101  		return nil, nil, err
   102  	}
   103  	client, err := NewHTTPClient(host)
   104  	if err != nil {
   105  		return nil, nil, err
   106  	}
   107  	resp, err := client.Do(req)
   108  	var body io.ReadCloser
   109  	if resp != nil {
   110  		body = ioutils.NewReadCloserWrapper(resp.Body, func() error {
   111  			defer resp.Body.Close()
   112  			return nil
   113  		})
   114  	}
   115  	return resp, body, err
   116  }
   117  
   118  // New creates a new http Request to the specified host and endpoint, with the specified request modifiers
   119  func New(host, endpoint string, modifiers ...func(*http.Request) error) (*http.Request, error) {
   120  	_, addr, _, err := dclient.ParseHost(host)
   121  	if err != nil {
   122  		return nil, err
   123  	}
   124  	if err != nil {
   125  		return nil, errors.Wrapf(err, "could not parse url %q", host)
   126  	}
   127  	req, err := http.NewRequest("GET", endpoint, nil)
   128  	if err != nil {
   129  		return nil, fmt.Errorf("could not create new request: %v", err)
   130  	}
   131  
   132  	if os.Getenv("DOCKER_TLS_VERIFY") != "" {
   133  		req.URL.Scheme = "https"
   134  	} else {
   135  		req.URL.Scheme = "http"
   136  	}
   137  	req.URL.Host = addr
   138  
   139  	for _, config := range modifiers {
   140  		if err := config(req); err != nil {
   141  			return nil, err
   142  		}
   143  	}
   144  	return req, nil
   145  }
   146  
   147  // NewHTTPClient creates an http client for the specific host
   148  func NewHTTPClient(host string) (*http.Client, error) {
   149  	// FIXME(vdemeester) 10*time.Second timeout of SockRequest… ?
   150  	proto, addr, _, err := dclient.ParseHost(host)
   151  	if err != nil {
   152  		return nil, err
   153  	}
   154  	transport := new(http.Transport)
   155  	if proto == "tcp" && os.Getenv("DOCKER_TLS_VERIFY") != "" {
   156  		// Setup the socket TLS configuration.
   157  		tlsConfig, err := getTLSConfig()
   158  		if err != nil {
   159  			return nil, err
   160  		}
   161  		transport = &http.Transport{TLSClientConfig: tlsConfig}
   162  	}
   163  	transport.DisableKeepAlives = true
   164  	err = sockets.ConfigureTransport(transport, proto, addr)
   165  	return &http.Client{
   166  		Transport: transport,
   167  	}, err
   168  }
   169  
   170  // NewClient returns a new Docker API client
   171  func NewClient() (dclient.APIClient, error) {
   172  	return NewClientForHost(DaemonHost())
   173  }
   174  
   175  // NewClientForHost returns a Docker API client for the host
   176  func NewClientForHost(host string) (dclient.APIClient, error) {
   177  	httpClient, err := NewHTTPClient(host)
   178  	if err != nil {
   179  		return nil, err
   180  	}
   181  	return dclient.NewClient(host, api.DefaultVersion, httpClient, nil)
   182  }
   183  
   184  // FIXME(vdemeester) httputil.ClientConn is deprecated, use http.Client instead (closer to actual client)
   185  // Deprecated: Use New instead of NewRequestClient
   186  // Deprecated: use request.Do (or Get, Delete, Post) instead
   187  func newRequestClient(method, endpoint string, data io.Reader, ct, daemon string, modifiers ...func(*http.Request)) (*http.Request, *httputil.ClientConn, error) {
   188  	c, err := SockConn(time.Duration(10*time.Second), daemon)
   189  	if err != nil {
   190  		return nil, nil, fmt.Errorf("could not dial docker daemon: %v", err)
   191  	}
   192  
   193  	client := httputil.NewClientConn(c, nil)
   194  
   195  	req, err := http.NewRequest(method, endpoint, data)
   196  	if err != nil {
   197  		client.Close()
   198  		return nil, nil, fmt.Errorf("could not create new request: %v", err)
   199  	}
   200  
   201  	for _, opt := range modifiers {
   202  		opt(req)
   203  	}
   204  
   205  	if ct != "" {
   206  		req.Header.Set("Content-Type", ct)
   207  	}
   208  	return req, client, nil
   209  }
   210  
   211  // SockRequest create a request against the specified host (with method, endpoint and other request modifier) and
   212  // returns the status code, and the content as an byte slice
   213  // Deprecated: use request.Do instead
   214  func SockRequest(method, endpoint string, data interface{}, daemon string, modifiers ...func(*http.Request)) (int, []byte, error) {
   215  	jsonData := bytes.NewBuffer(nil)
   216  	if err := json.NewEncoder(jsonData).Encode(data); err != nil {
   217  		return -1, nil, err
   218  	}
   219  
   220  	res, body, err := SockRequestRaw(method, endpoint, jsonData, "application/json", daemon, modifiers...)
   221  	if err != nil {
   222  		return -1, nil, err
   223  	}
   224  	b, err := ReadBody(body)
   225  	return res.StatusCode, b, err
   226  }
   227  
   228  // ReadBody read the specified ReadCloser content and returns it
   229  func ReadBody(b io.ReadCloser) ([]byte, error) {
   230  	defer b.Close()
   231  	return ioutil.ReadAll(b)
   232  }
   233  
   234  // SockRequestRaw create a request against the specified host (with method, endpoint and other request modifier) and
   235  // returns the http response, the output as a io.ReadCloser
   236  // Deprecated: use request.Do (or Get, Delete, Post) instead
   237  func SockRequestRaw(method, endpoint string, data io.Reader, ct, daemon string, modifiers ...func(*http.Request)) (*http.Response, io.ReadCloser, error) {
   238  	req, client, err := newRequestClient(method, endpoint, data, ct, daemon, modifiers...)
   239  	if err != nil {
   240  		return nil, nil, err
   241  	}
   242  
   243  	resp, err := client.Do(req)
   244  	if err != nil {
   245  		client.Close()
   246  		return resp, nil, err
   247  	}
   248  	body := ioutils.NewReadCloserWrapper(resp.Body, func() error {
   249  		defer resp.Body.Close()
   250  		return client.Close()
   251  	})
   252  
   253  	return resp, body, err
   254  }
   255  
   256  // SockRequestHijack creates a connection to specified host (with method, contenttype, …) and returns a hijacked connection
   257  // and the output as a `bufio.Reader`
   258  func SockRequestHijack(method, endpoint string, data io.Reader, ct string, daemon string, modifiers ...func(*http.Request)) (net.Conn, *bufio.Reader, error) {
   259  	req, client, err := newRequestClient(method, endpoint, data, ct, daemon, modifiers...)
   260  	if err != nil {
   261  		return nil, nil, err
   262  	}
   263  
   264  	client.Do(req)
   265  	conn, br := client.Hijack()
   266  	return conn, br, nil
   267  }
   268  
   269  // SockConn opens a connection on the specified socket
   270  func SockConn(timeout time.Duration, daemon string) (net.Conn, error) {
   271  	daemonURL, err := url.Parse(daemon)
   272  	if err != nil {
   273  		return nil, errors.Wrapf(err, "could not parse url %q", daemon)
   274  	}
   275  
   276  	var c net.Conn
   277  	switch daemonURL.Scheme {
   278  	case "npipe":
   279  		return npipeDial(daemonURL.Path, timeout)
   280  	case "unix":
   281  		return net.DialTimeout(daemonURL.Scheme, daemonURL.Path, timeout)
   282  	case "tcp":
   283  		if os.Getenv("DOCKER_TLS_VERIFY") != "" {
   284  			// Setup the socket TLS configuration.
   285  			tlsConfig, err := getTLSConfig()
   286  			if err != nil {
   287  				return nil, err
   288  			}
   289  			dialer := &net.Dialer{Timeout: timeout}
   290  			return tls.DialWithDialer(dialer, daemonURL.Scheme, daemonURL.Host, tlsConfig)
   291  		}
   292  		return net.DialTimeout(daemonURL.Scheme, daemonURL.Host, timeout)
   293  	default:
   294  		return c, errors.Errorf("unknown scheme %v (%s)", daemonURL.Scheme, daemon)
   295  	}
   296  }
   297  
   298  func getTLSConfig() (*tls.Config, error) {
   299  	dockerCertPath := os.Getenv("DOCKER_CERT_PATH")
   300  
   301  	if dockerCertPath == "" {
   302  		return nil, errors.New("DOCKER_TLS_VERIFY specified, but no DOCKER_CERT_PATH environment variable")
   303  	}
   304  
   305  	option := &tlsconfig.Options{
   306  		CAFile:   filepath.Join(dockerCertPath, "ca.pem"),
   307  		CertFile: filepath.Join(dockerCertPath, "cert.pem"),
   308  		KeyFile:  filepath.Join(dockerCertPath, "key.pem"),
   309  	}
   310  	tlsConfig, err := tlsconfig.Client(*option)
   311  	if err != nil {
   312  		return nil, err
   313  	}
   314  
   315  	return tlsConfig, nil
   316  }
   317  
   318  // DaemonHost return the daemon host string for this test execution
   319  func DaemonHost() string {
   320  	daemonURLStr := "unix://" + opts.DefaultUnixSocket
   321  	if daemonHostVar := os.Getenv("DOCKER_HOST"); daemonHostVar != "" {
   322  		daemonURLStr = daemonHostVar
   323  	}
   324  	return daemonURLStr
   325  }
   326  
   327  // NewEnvClientWithVersion returns a docker client with a specified version.
   328  // See: github.com/docker/docker/client `NewEnvClient()`
   329  func NewEnvClientWithVersion(version string) (*dclient.Client, error) {
   330  	if version == "" {
   331  		return nil, errors.New("version not specified")
   332  	}
   333  
   334  	var httpClient *http.Client
   335  	if os.Getenv("DOCKER_CERT_PATH") != "" {
   336  		tlsConfig, err := getTLSConfig()
   337  		if err != nil {
   338  			return nil, err
   339  		}
   340  		httpClient = &http.Client{
   341  			Transport: &http.Transport{
   342  				TLSClientConfig: tlsConfig,
   343  			},
   344  		}
   345  	}
   346  
   347  	host := os.Getenv("DOCKER_HOST")
   348  	if host == "" {
   349  		host = dclient.DefaultDockerHost
   350  	}
   351  
   352  	cli, err := dclient.NewClient(host, version, httpClient, nil)
   353  	if err != nil {
   354  		return cli, err
   355  	}
   356  	return cli, nil
   357  }