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 }