github.com/demonoid81/moby@v0.0.0-20200517203328-62dd8e17c460/testutil/request/request.go (about)

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