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 }