github.com/lazyboychen7/engine@v17.12.1-ce-rc2+incompatible/integration-cli/request/request.go (about) 1 package request 2 3 import ( 4 "bufio" 5 "bytes" 6 "crypto/tls" 7 "encoding/json" 8 "fmt" 9 "io" 10 "io/ioutil" 11 "net" 12 "net/http" 13 "net/http/httputil" 14 "net/url" 15 "os" 16 "path/filepath" 17 "strings" 18 "time" 19 20 "github.com/docker/docker/api" 21 dclient "github.com/docker/docker/client" 22 "github.com/docker/docker/opts" 23 "github.com/docker/docker/pkg/ioutils" 24 "github.com/docker/go-connections/sockets" 25 "github.com/docker/go-connections/tlsconfig" 26 "github.com/pkg/errors" 27 ) 28 29 // Method creates a modifier that sets the specified string as the request method 30 func Method(method string) func(*http.Request) error { 31 return func(req *http.Request) error { 32 req.Method = method 33 return nil 34 } 35 } 36 37 // RawString sets the specified string as body for the request 38 func RawString(content string) func(*http.Request) error { 39 return RawContent(ioutil.NopCloser(strings.NewReader(content))) 40 } 41 42 // RawContent sets the specified reader as body for the request 43 func RawContent(reader io.ReadCloser) func(*http.Request) error { 44 return func(req *http.Request) error { 45 req.Body = reader 46 return nil 47 } 48 } 49 50 // ContentType sets the specified Content-Type request header 51 func ContentType(contentType string) func(*http.Request) error { 52 return func(req *http.Request) error { 53 req.Header.Set("Content-Type", contentType) 54 return nil 55 } 56 } 57 58 // JSON sets the Content-Type request header to json 59 func JSON(req *http.Request) error { 60 return ContentType("application/json")(req) 61 } 62 63 // JSONBody creates a modifier that encodes the specified data to a JSON string and set it as request body. It also sets 64 // the Content-Type header of the request. 65 func JSONBody(data interface{}) func(*http.Request) error { 66 return func(req *http.Request) error { 67 jsonData := bytes.NewBuffer(nil) 68 if err := json.NewEncoder(jsonData).Encode(data); err != nil { 69 return err 70 } 71 req.Body = ioutil.NopCloser(jsonData) 72 req.Header.Set("Content-Type", "application/json") 73 return nil 74 } 75 } 76 77 // Post creates and execute a POST request on the specified host and endpoint, with the specified request modifiers 78 func Post(endpoint string, modifiers ...func(*http.Request) error) (*http.Response, io.ReadCloser, error) { 79 return Do(endpoint, append(modifiers, Method(http.MethodPost))...) 80 } 81 82 // Delete creates and execute a DELETE request on the specified host and endpoint, with the specified request modifiers 83 func Delete(endpoint string, modifiers ...func(*http.Request) error) (*http.Response, io.ReadCloser, error) { 84 return Do(endpoint, append(modifiers, Method(http.MethodDelete))...) 85 } 86 87 // Get creates and execute a GET request on the specified host and endpoint, with the specified request modifiers 88 func Get(endpoint string, modifiers ...func(*http.Request) error) (*http.Response, io.ReadCloser, error) { 89 return Do(endpoint, modifiers...) 90 } 91 92 // Do creates and execute a request on the specified endpoint, with the specified request modifiers 93 func Do(endpoint string, modifiers ...func(*http.Request) error) (*http.Response, io.ReadCloser, error) { 94 return DoOnHost(DaemonHost(), endpoint, modifiers...) 95 } 96 97 // DoOnHost creates and execute a request on the specified host and endpoint, with the specified request modifiers 98 func DoOnHost(host, endpoint string, modifiers ...func(*http.Request) error) (*http.Response, io.ReadCloser, error) { 99 req, err := New(host, endpoint, modifiers...) 100 if err != nil { 101 return nil, nil, err 102 } 103 client, err := NewHTTPClient(host) 104 if err != nil { 105 return nil, nil, err 106 } 107 resp, err := client.Do(req) 108 var body io.ReadCloser 109 if resp != nil { 110 body = ioutils.NewReadCloserWrapper(resp.Body, func() error { 111 defer resp.Body.Close() 112 return nil 113 }) 114 } 115 return resp, body, err 116 } 117 118 // New creates a new http Request to the specified host and endpoint, with the specified request modifiers 119 func New(host, endpoint string, modifiers ...func(*http.Request) error) (*http.Request, error) { 120 _, addr, _, err := dclient.ParseHost(host) 121 if err != nil { 122 return nil, err 123 } 124 if err != nil { 125 return nil, errors.Wrapf(err, "could not parse url %q", host) 126 } 127 req, err := http.NewRequest("GET", endpoint, nil) 128 if err != nil { 129 return nil, fmt.Errorf("could not create new request: %v", err) 130 } 131 132 if os.Getenv("DOCKER_TLS_VERIFY") != "" { 133 req.URL.Scheme = "https" 134 } else { 135 req.URL.Scheme = "http" 136 } 137 req.URL.Host = addr 138 139 for _, config := range modifiers { 140 if err := config(req); err != nil { 141 return nil, err 142 } 143 } 144 return req, nil 145 } 146 147 // NewHTTPClient creates an http client for the specific host 148 func NewHTTPClient(host string) (*http.Client, error) { 149 // FIXME(vdemeester) 10*time.Second timeout of SockRequest… ? 150 proto, addr, _, err := dclient.ParseHost(host) 151 if err != nil { 152 return nil, err 153 } 154 transport := new(http.Transport) 155 if proto == "tcp" && os.Getenv("DOCKER_TLS_VERIFY") != "" { 156 // Setup the socket TLS configuration. 157 tlsConfig, err := getTLSConfig() 158 if err != nil { 159 return nil, err 160 } 161 transport = &http.Transport{TLSClientConfig: tlsConfig} 162 } 163 transport.DisableKeepAlives = true 164 err = sockets.ConfigureTransport(transport, proto, addr) 165 return &http.Client{ 166 Transport: transport, 167 }, err 168 } 169 170 // NewClient returns a new Docker API client 171 func NewClient() (dclient.APIClient, error) { 172 return NewClientForHost(DaemonHost()) 173 } 174 175 // NewClientForHost returns a Docker API client for the host 176 func NewClientForHost(host string) (dclient.APIClient, error) { 177 httpClient, err := NewHTTPClient(host) 178 if err != nil { 179 return nil, err 180 } 181 return dclient.NewClient(host, api.DefaultVersion, httpClient, nil) 182 } 183 184 // FIXME(vdemeester) httputil.ClientConn is deprecated, use http.Client instead (closer to actual client) 185 // Deprecated: Use New instead of NewRequestClient 186 // Deprecated: use request.Do (or Get, Delete, Post) instead 187 func newRequestClient(method, endpoint string, data io.Reader, ct, daemon string, modifiers ...func(*http.Request)) (*http.Request, *httputil.ClientConn, error) { 188 c, err := SockConn(time.Duration(10*time.Second), daemon) 189 if err != nil { 190 return nil, nil, fmt.Errorf("could not dial docker daemon: %v", err) 191 } 192 193 client := httputil.NewClientConn(c, nil) 194 195 req, err := http.NewRequest(method, endpoint, data) 196 if err != nil { 197 client.Close() 198 return nil, nil, fmt.Errorf("could not create new request: %v", err) 199 } 200 201 for _, opt := range modifiers { 202 opt(req) 203 } 204 205 if ct != "" { 206 req.Header.Set("Content-Type", ct) 207 } 208 return req, client, nil 209 } 210 211 // SockRequest create a request against the specified host (with method, endpoint and other request modifier) and 212 // returns the status code, and the content as an byte slice 213 // Deprecated: use request.Do instead 214 func SockRequest(method, endpoint string, data interface{}, daemon string, modifiers ...func(*http.Request)) (int, []byte, error) { 215 jsonData := bytes.NewBuffer(nil) 216 if err := json.NewEncoder(jsonData).Encode(data); err != nil { 217 return -1, nil, err 218 } 219 220 res, body, err := SockRequestRaw(method, endpoint, jsonData, "application/json", daemon, modifiers...) 221 if err != nil { 222 return -1, nil, err 223 } 224 b, err := ReadBody(body) 225 return res.StatusCode, b, err 226 } 227 228 // ReadBody read the specified ReadCloser content and returns it 229 func ReadBody(b io.ReadCloser) ([]byte, error) { 230 defer b.Close() 231 return ioutil.ReadAll(b) 232 } 233 234 // SockRequestRaw create a request against the specified host (with method, endpoint and other request modifier) and 235 // returns the http response, the output as a io.ReadCloser 236 // Deprecated: use request.Do (or Get, Delete, Post) instead 237 func SockRequestRaw(method, endpoint string, data io.Reader, ct, daemon string, modifiers ...func(*http.Request)) (*http.Response, io.ReadCloser, error) { 238 req, client, err := newRequestClient(method, endpoint, data, ct, daemon, modifiers...) 239 if err != nil { 240 return nil, nil, err 241 } 242 243 resp, err := client.Do(req) 244 if err != nil { 245 client.Close() 246 return resp, nil, err 247 } 248 body := ioutils.NewReadCloserWrapper(resp.Body, func() error { 249 defer resp.Body.Close() 250 return client.Close() 251 }) 252 253 return resp, body, err 254 } 255 256 // SockRequestHijack creates a connection to specified host (with method, contenttype, …) and returns a hijacked connection 257 // and the output as a `bufio.Reader` 258 func SockRequestHijack(method, endpoint string, data io.Reader, ct string, daemon string, modifiers ...func(*http.Request)) (net.Conn, *bufio.Reader, error) { 259 req, client, err := newRequestClient(method, endpoint, data, ct, daemon, modifiers...) 260 if err != nil { 261 return nil, nil, err 262 } 263 264 client.Do(req) 265 conn, br := client.Hijack() 266 return conn, br, nil 267 } 268 269 // SockConn opens a connection on the specified socket 270 func SockConn(timeout time.Duration, daemon string) (net.Conn, error) { 271 daemonURL, err := url.Parse(daemon) 272 if err != nil { 273 return nil, errors.Wrapf(err, "could not parse url %q", daemon) 274 } 275 276 var c net.Conn 277 switch daemonURL.Scheme { 278 case "npipe": 279 return npipeDial(daemonURL.Path, timeout) 280 case "unix": 281 return net.DialTimeout(daemonURL.Scheme, daemonURL.Path, timeout) 282 case "tcp": 283 if os.Getenv("DOCKER_TLS_VERIFY") != "" { 284 // Setup the socket TLS configuration. 285 tlsConfig, err := getTLSConfig() 286 if err != nil { 287 return nil, err 288 } 289 dialer := &net.Dialer{Timeout: timeout} 290 return tls.DialWithDialer(dialer, daemonURL.Scheme, daemonURL.Host, tlsConfig) 291 } 292 return net.DialTimeout(daemonURL.Scheme, daemonURL.Host, timeout) 293 default: 294 return c, errors.Errorf("unknown scheme %v (%s)", daemonURL.Scheme, daemon) 295 } 296 } 297 298 func getTLSConfig() (*tls.Config, error) { 299 dockerCertPath := os.Getenv("DOCKER_CERT_PATH") 300 301 if dockerCertPath == "" { 302 return nil, errors.New("DOCKER_TLS_VERIFY specified, but no DOCKER_CERT_PATH environment variable") 303 } 304 305 option := &tlsconfig.Options{ 306 CAFile: filepath.Join(dockerCertPath, "ca.pem"), 307 CertFile: filepath.Join(dockerCertPath, "cert.pem"), 308 KeyFile: filepath.Join(dockerCertPath, "key.pem"), 309 } 310 tlsConfig, err := tlsconfig.Client(*option) 311 if err != nil { 312 return nil, err 313 } 314 315 return tlsConfig, nil 316 } 317 318 // DaemonHost return the daemon host string for this test execution 319 func DaemonHost() string { 320 daemonURLStr := "unix://" + opts.DefaultUnixSocket 321 if daemonHostVar := os.Getenv("DOCKER_HOST"); daemonHostVar != "" { 322 daemonURLStr = daemonHostVar 323 } 324 return daemonURLStr 325 } 326 327 // NewEnvClientWithVersion returns a docker client with a specified version. 328 // See: github.com/docker/docker/client `NewEnvClient()` 329 func NewEnvClientWithVersion(version string) (*dclient.Client, error) { 330 if version == "" { 331 return nil, errors.New("version not specified") 332 } 333 334 var httpClient *http.Client 335 if os.Getenv("DOCKER_CERT_PATH") != "" { 336 tlsConfig, err := getTLSConfig() 337 if err != nil { 338 return nil, err 339 } 340 httpClient = &http.Client{ 341 Transport: &http.Transport{ 342 TLSClientConfig: tlsConfig, 343 }, 344 } 345 } 346 347 host := os.Getenv("DOCKER_HOST") 348 if host == "" { 349 host = dclient.DefaultDockerHost 350 } 351 352 cli, err := dclient.NewClient(host, version, httpClient, nil) 353 if err != nil { 354 return cli, err 355 } 356 return cli, nil 357 }