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