github.com/jwhonce/docker@v0.6.7-0.20190327063223-da823cf3a5a3/internal/test/request/request.go (about)

     1  package request // import "github.com/docker/docker/internal/test/request"
     2  
     3  import (
     4  	"context"
     5  	"crypto/tls"
     6  	"fmt"
     7  	"io"
     8  	"io/ioutil"
     9  	"net"
    10  	"net/http"
    11  	"net/url"
    12  	"os"
    13  	"path/filepath"
    14  	"time"
    15  
    16  	"github.com/docker/docker/client"
    17  	"github.com/docker/docker/internal/test"
    18  	"github.com/docker/docker/internal/test/environment"
    19  	"github.com/docker/docker/opts"
    20  	"github.com/docker/docker/pkg/ioutils"
    21  	"github.com/docker/go-connections/sockets"
    22  	"github.com/docker/go-connections/tlsconfig"
    23  	"github.com/pkg/errors"
    24  	"gotest.tools/assert"
    25  )
    26  
    27  // NewAPIClient returns a docker API client configured from environment variables
    28  func NewAPIClient(t assert.TestingT, ops ...func(*client.Client) error) client.APIClient {
    29  	if ht, ok := t.(test.HelperT); ok {
    30  		ht.Helper()
    31  	}
    32  	ops = append([]func(*client.Client) error{client.FromEnv}, ops...)
    33  	clt, err := client.NewClientWithOpts(ops...)
    34  	assert.NilError(t, err)
    35  	return clt
    36  }
    37  
    38  // DaemonTime provides the current time on the daemon host
    39  func DaemonTime(ctx context.Context, t assert.TestingT, client client.APIClient, testEnv *environment.Execution) time.Time {
    40  	if ht, ok := t.(test.HelperT); ok {
    41  		ht.Helper()
    42  	}
    43  	if testEnv.IsLocalDaemon() {
    44  		return time.Now()
    45  	}
    46  
    47  	info, err := client.Info(ctx)
    48  	assert.NilError(t, err)
    49  
    50  	dt, err := time.Parse(time.RFC3339Nano, info.SystemTime)
    51  	assert.NilError(t, err, "invalid time format in GET /info response")
    52  	return dt
    53  }
    54  
    55  // DaemonUnixTime returns the current time on the daemon host with nanoseconds precision.
    56  // It return the time formatted how the client sends timestamps to the server.
    57  func DaemonUnixTime(ctx context.Context, t assert.TestingT, client client.APIClient, testEnv *environment.Execution) string {
    58  	if ht, ok := t.(test.HelperT); ok {
    59  		ht.Helper()
    60  	}
    61  	dt := DaemonTime(ctx, t, client, testEnv)
    62  	return fmt.Sprintf("%d.%09d", dt.Unix(), int64(dt.Nanosecond()))
    63  }
    64  
    65  // Post creates and execute a POST request on the specified host and endpoint, with the specified request modifiers
    66  func Post(endpoint string, modifiers ...func(*Options)) (*http.Response, io.ReadCloser, error) {
    67  	return Do(endpoint, append(modifiers, Method(http.MethodPost))...)
    68  }
    69  
    70  // Delete creates and execute a DELETE request on the specified host and endpoint, with the specified request modifiers
    71  func Delete(endpoint string, modifiers ...func(*Options)) (*http.Response, io.ReadCloser, error) {
    72  	return Do(endpoint, append(modifiers, Method(http.MethodDelete))...)
    73  }
    74  
    75  // Get creates and execute a GET request on the specified host and endpoint, with the specified request modifiers
    76  func Get(endpoint string, modifiers ...func(*Options)) (*http.Response, io.ReadCloser, error) {
    77  	return Do(endpoint, modifiers...)
    78  }
    79  
    80  // Head creates and execute a HEAD request on the specified host and endpoint, with the specified request modifiers
    81  func Head(endpoint string, modifiers ...func(*Options)) (*http.Response, io.ReadCloser, error) {
    82  	return Do(endpoint, append(modifiers, Method(http.MethodHead))...)
    83  }
    84  
    85  // Do creates and execute a request on the specified endpoint, with the specified request modifiers
    86  func Do(endpoint string, modifiers ...func(*Options)) (*http.Response, io.ReadCloser, error) {
    87  	opts := &Options{
    88  		host: DaemonHost(),
    89  	}
    90  	for _, mod := range modifiers {
    91  		mod(opts)
    92  	}
    93  	req, err := newRequest(endpoint, opts)
    94  	if err != nil {
    95  		return nil, nil, err
    96  	}
    97  	client, err := newHTTPClient(opts.host)
    98  	if err != nil {
    99  		return nil, nil, err
   100  	}
   101  	resp, err := client.Do(req)
   102  	var body io.ReadCloser
   103  	if resp != nil {
   104  		body = ioutils.NewReadCloserWrapper(resp.Body, func() error {
   105  			defer resp.Body.Close()
   106  			return nil
   107  		})
   108  	}
   109  	return resp, body, err
   110  }
   111  
   112  // ReadBody read the specified ReadCloser content and returns it
   113  func ReadBody(b io.ReadCloser) ([]byte, error) {
   114  	defer b.Close()
   115  	return ioutil.ReadAll(b)
   116  }
   117  
   118  // newRequest creates a new http Request to the specified host and endpoint, with the specified request modifiers
   119  func newRequest(endpoint string, opts *Options) (*http.Request, error) {
   120  	hostURL, err := client.ParseHostURL(opts.host)
   121  	if err != nil {
   122  		return nil, errors.Wrapf(err, "failed parsing url %q", opts.host)
   123  	}
   124  	req, err := http.NewRequest("GET", endpoint, nil)
   125  	if err != nil {
   126  		return nil, errors.Wrap(err, "failed to create request")
   127  	}
   128  
   129  	if os.Getenv("DOCKER_TLS_VERIFY") != "" {
   130  		req.URL.Scheme = "https"
   131  	} else {
   132  		req.URL.Scheme = "http"
   133  	}
   134  	req.URL.Host = hostURL.Host
   135  
   136  	for _, config := range opts.requestModifiers {
   137  		if err := config(req); err != nil {
   138  			return nil, err
   139  		}
   140  	}
   141  
   142  	return req, nil
   143  }
   144  
   145  // newHTTPClient creates an http client for the specific host
   146  // TODO: Share more code with client.defaultHTTPClient
   147  func newHTTPClient(host string) (*http.Client, error) {
   148  	// FIXME(vdemeester) 10*time.Second timeout of SockRequest… ?
   149  	hostURL, err := client.ParseHostURL(host)
   150  	if err != nil {
   151  		return nil, err
   152  	}
   153  	transport := new(http.Transport)
   154  	if hostURL.Scheme == "tcp" && os.Getenv("DOCKER_TLS_VERIFY") != "" {
   155  		// Setup the socket TLS configuration.
   156  		tlsConfig, err := getTLSConfig()
   157  		if err != nil {
   158  			return nil, err
   159  		}
   160  		transport = &http.Transport{TLSClientConfig: tlsConfig}
   161  	}
   162  	transport.DisableKeepAlives = true
   163  	err = sockets.ConfigureTransport(transport, hostURL.Scheme, hostURL.Host)
   164  	return &http.Client{Transport: transport}, err
   165  }
   166  
   167  func getTLSConfig() (*tls.Config, error) {
   168  	dockerCertPath := os.Getenv("DOCKER_CERT_PATH")
   169  
   170  	if dockerCertPath == "" {
   171  		return nil, errors.New("DOCKER_TLS_VERIFY specified, but no DOCKER_CERT_PATH environment variable")
   172  	}
   173  
   174  	option := &tlsconfig.Options{
   175  		CAFile:   filepath.Join(dockerCertPath, "ca.pem"),
   176  		CertFile: filepath.Join(dockerCertPath, "cert.pem"),
   177  		KeyFile:  filepath.Join(dockerCertPath, "key.pem"),
   178  	}
   179  	tlsConfig, err := tlsconfig.Client(*option)
   180  	if err != nil {
   181  		return nil, err
   182  	}
   183  
   184  	return tlsConfig, nil
   185  }
   186  
   187  // DaemonHost return the daemon host string for this test execution
   188  func DaemonHost() string {
   189  	daemonURLStr := "unix://" + opts.DefaultUnixSocket
   190  	if daemonHostVar := os.Getenv("DOCKER_HOST"); daemonHostVar != "" {
   191  		daemonURLStr = daemonHostVar
   192  	}
   193  	return daemonURLStr
   194  }
   195  
   196  // SockConn opens a connection on the specified socket
   197  func SockConn(timeout time.Duration, daemon string) (net.Conn, error) {
   198  	daemonURL, err := url.Parse(daemon)
   199  	if err != nil {
   200  		return nil, errors.Wrapf(err, "could not parse url %q", daemon)
   201  	}
   202  
   203  	var c net.Conn
   204  	switch daemonURL.Scheme {
   205  	case "npipe":
   206  		return npipeDial(daemonURL.Path, timeout)
   207  	case "unix":
   208  		return net.DialTimeout(daemonURL.Scheme, daemonURL.Path, timeout)
   209  	case "tcp":
   210  		if os.Getenv("DOCKER_TLS_VERIFY") != "" {
   211  			// Setup the socket TLS configuration.
   212  			tlsConfig, err := getTLSConfig()
   213  			if err != nil {
   214  				return nil, err
   215  			}
   216  			dialer := &net.Dialer{Timeout: timeout}
   217  			return tls.DialWithDialer(dialer, daemonURL.Scheme, daemonURL.Host, tlsConfig)
   218  		}
   219  		return net.DialTimeout(daemonURL.Scheme, daemonURL.Host, timeout)
   220  	default:
   221  		return c, errors.Errorf("unknown scheme %v (%s)", daemonURL.Scheme, daemon)
   222  	}
   223  }