2016-09-06 14:46:37 -04:00
package client
import (
"bytes"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net"
"net/http"
"net/url"
2016-09-21 17:04:44 -04:00
"os"
2016-09-06 14:46:37 -04:00
"strings"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/versions"
2016-09-08 18:37:45 -04:00
"github.com/pkg/errors"
2016-09-06 14:46:37 -04:00
"golang.org/x/net/context"
2016-09-08 23:44:25 -04:00
"golang.org/x/net/context/ctxhttp"
2016-09-06 14:46:37 -04:00
)
// serverResponse is a wrapper for http API responses.
type serverResponse struct {
body io . ReadCloser
header http . Header
statusCode int
2017-06-26 12:06:34 -04:00
reqURL * url . URL
2016-09-06 14:46:37 -04:00
}
// head sends an http request to the docker API using the method HEAD.
func ( cli * Client ) head ( ctx context . Context , path string , query url . Values , headers map [ string ] [ ] string ) ( serverResponse , error ) {
return cli . sendRequest ( ctx , "HEAD" , path , query , nil , headers )
}
2016-12-01 15:18:02 -05:00
// get sends an http request to the docker API using the method GET with a specific Go context.
2016-09-06 14:46:37 -04:00
func ( cli * Client ) get ( ctx context . Context , path string , query url . Values , headers map [ string ] [ ] string ) ( serverResponse , error ) {
return cli . sendRequest ( ctx , "GET" , path , query , nil , headers )
}
2016-12-01 15:18:02 -05:00
// post sends an http request to the docker API using the method POST with a specific Go context.
2016-09-06 14:46:37 -04:00
func ( cli * Client ) post ( ctx context . Context , path string , query url . Values , obj interface { } , headers map [ string ] [ ] string ) ( serverResponse , error ) {
2016-10-31 12:39:38 -04:00
body , headers , err := encodeBody ( obj , headers )
if err != nil {
return serverResponse { } , err
}
return cli . sendRequest ( ctx , "POST" , path , query , body , headers )
2016-09-06 14:46:37 -04:00
}
func ( cli * Client ) postRaw ( ctx context . Context , path string , query url . Values , body io . Reader , headers map [ string ] [ ] string ) ( serverResponse , error ) {
2016-10-31 12:39:38 -04:00
return cli . sendRequest ( ctx , "POST" , path , query , body , headers )
2016-09-06 14:46:37 -04:00
}
// put sends an http request to the docker API using the method PUT.
func ( cli * Client ) put ( ctx context . Context , path string , query url . Values , obj interface { } , headers map [ string ] [ ] string ) ( serverResponse , error ) {
2016-10-31 12:39:38 -04:00
body , headers , err := encodeBody ( obj , headers )
if err != nil {
return serverResponse { } , err
}
return cli . sendRequest ( ctx , "PUT" , path , query , body , headers )
2016-09-06 14:46:37 -04:00
}
2016-12-01 15:18:02 -05:00
// putRaw sends an http request to the docker API using the method PUT.
2016-09-06 14:46:37 -04:00
func ( cli * Client ) putRaw ( ctx context . Context , path string , query url . Values , body io . Reader , headers map [ string ] [ ] string ) ( serverResponse , error ) {
2016-10-31 12:39:38 -04:00
return cli . sendRequest ( ctx , "PUT" , path , query , body , headers )
2016-09-06 14:46:37 -04:00
}
// delete sends an http request to the docker API using the method DELETE.
func ( cli * Client ) delete ( ctx context . Context , path string , query url . Values , headers map [ string ] [ ] string ) ( serverResponse , error ) {
return cli . sendRequest ( ctx , "DELETE" , path , query , nil , headers )
}
2016-10-31 12:39:38 -04:00
type headers map [ string ] [ ] string
2016-09-06 14:46:37 -04:00
2016-10-31 12:39:38 -04:00
func encodeBody ( obj interface { } , headers headers ) ( io . Reader , headers , error ) {
if obj == nil {
return nil , headers , nil
2016-09-06 14:46:37 -04:00
}
2016-10-31 12:39:38 -04:00
body , err := encodeData ( obj )
if err != nil {
return nil , headers , err
2016-09-06 14:46:37 -04:00
}
2016-10-31 12:39:38 -04:00
if headers == nil {
headers = make ( map [ string ] [ ] string )
}
headers [ "Content-Type" ] = [ ] string { "application/json" }
return body , headers , nil
}
2016-09-06 14:46:37 -04:00
2016-10-31 12:39:38 -04:00
func ( cli * Client ) buildRequest ( method , path string , body io . Reader , headers headers ) ( * http . Request , error ) {
2016-09-06 14:46:37 -04:00
expectedPayload := ( method == "POST" || method == "PUT" )
if expectedPayload && body == nil {
body = bytes . NewReader ( [ ] byte { } )
}
2016-10-31 12:39:38 -04:00
req , err := http . NewRequest ( method , path , body )
2016-09-06 14:46:37 -04:00
if err != nil {
2016-10-31 12:39:38 -04:00
return nil , err
2016-09-06 14:46:37 -04:00
}
2016-10-31 12:39:38 -04:00
req = cli . addHeaders ( req , headers )
2016-09-06 14:46:37 -04:00
if cli . proto == "unix" || cli . proto == "npipe" {
// For local communications, it doesn't matter what the host is. We just
// need a valid and meaningful host name. (See #189)
req . Host = "docker"
}
2016-09-08 23:44:25 -04:00
2016-09-06 14:46:37 -04:00
req . URL . Host = cli . addr
2016-10-11 18:53:14 -04:00
req . URL . Scheme = cli . scheme
2016-09-06 14:46:37 -04:00
if expectedPayload && req . Header . Get ( "Content-Type" ) == "" {
req . Header . Set ( "Content-Type" , "text/plain" )
}
2016-10-31 12:39:38 -04:00
return req , nil
}
func ( cli * Client ) sendRequest ( ctx context . Context , method , path string , query url . Values , body io . Reader , headers headers ) ( serverResponse , error ) {
req , err := cli . buildRequest ( method , cli . getAPIPath ( path , query ) , body , headers )
if err != nil {
return serverResponse { } , err
}
2017-06-26 12:06:34 -04:00
resp , err := cli . doRequest ( ctx , req )
if err != nil {
return resp , err
}
if err := cli . checkResponseErr ( resp ) ; err != nil {
return resp , err
}
return resp , nil
2016-10-31 12:39:38 -04:00
}
func ( cli * Client ) doRequest ( ctx context . Context , req * http . Request ) ( serverResponse , error ) {
2017-06-26 12:06:34 -04:00
serverResp := serverResponse { statusCode : - 1 , reqURL : req . URL }
2016-09-06 14:46:37 -04:00
2016-09-08 23:44:25 -04:00
resp , err := ctxhttp . Do ( ctx , cli . client , req )
2016-09-06 14:46:37 -04:00
if err != nil {
2016-10-11 18:53:14 -04:00
if cli . scheme != "https" && strings . Contains ( err . Error ( ) , "malformed HTTP response" ) {
2016-09-06 14:46:37 -04:00
return serverResp , fmt . Errorf ( "%v.\n* Are you trying to connect to a TLS-enabled daemon without TLS?" , err )
}
2016-10-11 18:53:14 -04:00
if cli . scheme == "https" && strings . Contains ( err . Error ( ) , "bad certificate" ) {
2016-09-06 14:46:37 -04:00
return serverResp , fmt . Errorf ( "The server probably has client authentication (--tlsverify) enabled. Please check your TLS client certification settings: %v" , err )
}
// Don't decorate context sentinel errors; users may be comparing to
// them directly.
switch err {
case context . Canceled , context . DeadlineExceeded :
return serverResp , err
}
2016-09-21 17:04:44 -04:00
if nErr , ok := err . ( * url . Error ) ; ok {
if nErr , ok := nErr . Err . ( * net . OpError ) ; ok {
if os . IsPermission ( nErr . Err ) {
return serverResp , errors . Wrapf ( err , "Got permission denied while trying to connect to the Docker daemon socket at %v" , cli . host )
}
}
}
2016-09-06 14:46:37 -04:00
if err , ok := err . ( net . Error ) ; ok {
if err . Timeout ( ) {
return serverResp , ErrorConnectionFailed ( cli . host )
}
if ! err . Temporary ( ) {
if strings . Contains ( err . Error ( ) , "connection refused" ) || strings . Contains ( err . Error ( ) , "dial unix" ) {
return serverResp , ErrorConnectionFailed ( cli . host )
}
}
}
2016-09-08 18:37:45 -04:00
2016-10-14 13:14:43 -04:00
// Although there's not a strongly typed error for this in go-winio,
// lots of people are using the default configuration for the docker
// daemon on Windows where the daemon is listening on a named pipe
// `//./pipe/docker_engine, and the client must be running elevated.
// Give users a clue rather than the not-overly useful message
2016-11-11 14:51:26 -05:00
// such as `error during connect: Get http://%2F%2F.%2Fpipe%2Fdocker_engine/v1.26/info:
2016-10-14 13:14:43 -04:00
// open //./pipe/docker_engine: The system cannot find the file specified.`.
// Note we can't string compare "The system cannot find the file specified" as
// this is localised - for example in French the error would be
// `open //./pipe/docker_engine: Le fichier spécifié est introuvable.`
if strings . Contains ( err . Error ( ) , ` open //./pipe/docker_engine ` ) {
err = errors . New ( err . Error ( ) + " In the default daemon configuration on Windows, the docker client must be run elevated to connect. This error may also indicate that the docker daemon is not running." )
}
2016-09-08 18:37:45 -04:00
return serverResp , errors . Wrap ( err , "error during connect" )
2016-09-06 14:46:37 -04:00
}
if resp != nil {
serverResp . statusCode = resp . StatusCode
2017-06-26 12:06:34 -04:00
serverResp . body = resp . Body
serverResp . header = resp . Header
2016-09-06 14:46:37 -04:00
}
2017-06-26 12:06:34 -04:00
return serverResp , nil
}
2016-09-06 14:46:37 -04:00
2017-06-26 12:06:34 -04:00
func ( cli * Client ) checkResponseErr ( serverResp serverResponse ) error {
if serverResp . statusCode >= 200 && serverResp . statusCode < 400 {
return nil
}
2016-09-06 14:46:37 -04:00
2017-06-26 12:06:34 -04:00
body , err := ioutil . ReadAll ( serverResp . body )
if err != nil {
return err
}
if len ( body ) == 0 {
return fmt . Errorf ( "Error: request returned %s for API route and version %s, check if the server supports the requested API version" , http . StatusText ( serverResp . statusCode ) , serverResp . reqURL )
}
2016-09-06 14:46:37 -04:00
2017-06-26 12:06:34 -04:00
var ct string
if serverResp . header != nil {
ct = serverResp . header . Get ( "Content-Type" )
2016-09-06 14:46:37 -04:00
}
2017-06-26 12:06:34 -04:00
var errorMessage string
if ( cli . version == "" || versions . GreaterThan ( cli . version , "1.23" ) ) && ct == "application/json" {
var errorResponse types . ErrorResponse
if err := json . Unmarshal ( body , & errorResponse ) ; err != nil {
return fmt . Errorf ( "Error reading JSON: %v" , err )
}
errorMessage = errorResponse . Message
} else {
errorMessage = string ( body )
}
return fmt . Errorf ( "Error response from daemon: %s" , strings . TrimSpace ( errorMessage ) )
2016-09-06 14:46:37 -04:00
}
2016-10-31 12:39:38 -04:00
func ( cli * Client ) addHeaders ( req * http . Request , headers headers ) * http . Request {
2016-09-06 14:46:37 -04:00
// Add CLI Config's HTTP Headers BEFORE we set the Docker headers
// then the user can't change OUR headers
for k , v := range cli . customHTTPHeaders {
2016-11-02 20:43:32 -04:00
if versions . LessThan ( cli . version , "1.25" ) && k == "User-Agent" {
continue
}
2016-09-06 14:46:37 -04:00
req . Header . Set ( k , v )
}
if headers != nil {
for k , v := range headers {
req . Header [ k ] = v
}
}
2016-10-31 12:39:38 -04:00
return req
2016-09-06 14:46:37 -04:00
}
func encodeData ( data interface { } ) ( * bytes . Buffer , error ) {
params := bytes . NewBuffer ( nil )
if data != nil {
if err := json . NewEncoder ( params ) . Encode ( data ) ; err != nil {
return nil , err
}
}
return params , nil
}
func ensureReaderClosed ( response serverResponse ) {
2017-06-26 12:06:34 -04:00
if response . body != nil {
2016-09-06 14:46:37 -04:00
// Drain up to 512 bytes and close the body to let the Transport reuse the connection
2017-06-26 12:06:34 -04:00
io . CopyN ( ioutil . Discard , response . body , 512 )
2016-09-06 14:46:37 -04:00
response . body . Close ( )
}
}