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