github.com/lazyboychen7/engine@v17.12.1-ce-rc2+incompatible/client/client.go (about)

     1  /*
     2  Package client is a Go client for the Docker Engine API.
     3  
     4  For more information about the Engine API, see the documentation:
     5  https://docs.docker.com/engine/reference/api/
     6  
     7  Usage
     8  
     9  You use the library by creating a client object and calling methods on it. The
    10  client can be created either from environment variables with NewEnvClient, or
    11  configured manually with NewClient.
    12  
    13  For example, to list running containers (the equivalent of "docker ps"):
    14  
    15  	package main
    16  
    17  	import (
    18  		"context"
    19  		"fmt"
    20  
    21  		"github.com/docker/docker/api/types"
    22  		"github.com/docker/docker/client"
    23  	)
    24  
    25  	func main() {
    26  		cli, err := client.NewEnvClient()
    27  		if err != nil {
    28  			panic(err)
    29  		}
    30  
    31  		containers, err := cli.ContainerList(context.Background(), types.ContainerListOptions{})
    32  		if err != nil {
    33  			panic(err)
    34  		}
    35  
    36  		for _, container := range containers {
    37  			fmt.Printf("%s %s\n", container.ID[:10], container.Image)
    38  		}
    39  	}
    40  
    41  */
    42  package client
    43  
    44  import (
    45  	"errors"
    46  	"fmt"
    47  	"net/http"
    48  	"net/url"
    49  	"os"
    50  	"path"
    51  	"path/filepath"
    52  	"strings"
    53  
    54  	"github.com/docker/docker/api"
    55  	"github.com/docker/docker/api/types"
    56  	"github.com/docker/docker/api/types/versions"
    57  	"github.com/docker/go-connections/sockets"
    58  	"github.com/docker/go-connections/tlsconfig"
    59  	"golang.org/x/net/context"
    60  )
    61  
    62  // ErrRedirect is the error returned by checkRedirect when the request is non-GET.
    63  var ErrRedirect = errors.New("unexpected redirect in response")
    64  
    65  // Client is the API client that performs all operations
    66  // against a docker server.
    67  type Client struct {
    68  	// scheme sets the scheme for the client
    69  	scheme string
    70  	// host holds the server address to connect to
    71  	host string
    72  	// proto holds the client protocol i.e. unix.
    73  	proto string
    74  	// addr holds the client address.
    75  	addr string
    76  	// basePath holds the path to prepend to the requests.
    77  	basePath string
    78  	// client used to send and receive http requests.
    79  	client *http.Client
    80  	// version of the server to talk to.
    81  	version string
    82  	// custom http headers configured by users.
    83  	customHTTPHeaders map[string]string
    84  	// manualOverride is set to true when the version was set by users.
    85  	manualOverride bool
    86  }
    87  
    88  // CheckRedirect specifies the policy for dealing with redirect responses:
    89  // If the request is non-GET return `ErrRedirect`. Otherwise use the last response.
    90  //
    91  // Go 1.8 changes behavior for HTTP redirects (specifically 301, 307, and 308) in the client .
    92  // The Docker client (and by extension docker API client) can be made to to send a request
    93  // like POST /containers//start where what would normally be in the name section of the URL is empty.
    94  // This triggers an HTTP 301 from the daemon.
    95  // In go 1.8 this 301 will be converted to a GET request, and ends up getting a 404 from the daemon.
    96  // This behavior change manifests in the client in that before the 301 was not followed and
    97  // the client did not generate an error, but now results in a message like Error response from daemon: page not found.
    98  func CheckRedirect(req *http.Request, via []*http.Request) error {
    99  	if via[0].Method == http.MethodGet {
   100  		return http.ErrUseLastResponse
   101  	}
   102  	return ErrRedirect
   103  }
   104  
   105  // NewEnvClient initializes a new API client based on environment variables.
   106  // Use DOCKER_HOST to set the url to the docker server.
   107  // Use DOCKER_API_VERSION to set the version of the API to reach, leave empty for latest.
   108  // Use DOCKER_CERT_PATH to load the TLS certificates from.
   109  // Use DOCKER_TLS_VERIFY to enable or disable TLS verification, off by default.
   110  func NewEnvClient() (*Client, error) {
   111  	var client *http.Client
   112  	if dockerCertPath := os.Getenv("DOCKER_CERT_PATH"); dockerCertPath != "" {
   113  		options := tlsconfig.Options{
   114  			CAFile:             filepath.Join(dockerCertPath, "ca.pem"),
   115  			CertFile:           filepath.Join(dockerCertPath, "cert.pem"),
   116  			KeyFile:            filepath.Join(dockerCertPath, "key.pem"),
   117  			InsecureSkipVerify: os.Getenv("DOCKER_TLS_VERIFY") == "",
   118  		}
   119  		tlsc, err := tlsconfig.Client(options)
   120  		if err != nil {
   121  			return nil, err
   122  		}
   123  
   124  		client = &http.Client{
   125  			Transport: &http.Transport{
   126  				TLSClientConfig: tlsc,
   127  			},
   128  			CheckRedirect: CheckRedirect,
   129  		}
   130  	}
   131  
   132  	host := os.Getenv("DOCKER_HOST")
   133  	if host == "" {
   134  		host = DefaultDockerHost
   135  	}
   136  	version := os.Getenv("DOCKER_API_VERSION")
   137  	if version == "" {
   138  		version = api.DefaultVersion
   139  	}
   140  
   141  	cli, err := NewClient(host, version, client, nil)
   142  	if err != nil {
   143  		return cli, err
   144  	}
   145  	if os.Getenv("DOCKER_API_VERSION") != "" {
   146  		cli.manualOverride = true
   147  	}
   148  	return cli, nil
   149  }
   150  
   151  // NewClient initializes a new API client for the given host and API version.
   152  // It uses the given http client as transport.
   153  // It also initializes the custom http headers to add to each request.
   154  //
   155  // It won't send any version information if the version number is empty. It is
   156  // highly recommended that you set a version or your client may break if the
   157  // server is upgraded.
   158  func NewClient(host string, version string, client *http.Client, httpHeaders map[string]string) (*Client, error) {
   159  	hostURL, err := ParseHostURL(host)
   160  	if err != nil {
   161  		return nil, err
   162  	}
   163  
   164  	if client != nil {
   165  		if _, ok := client.Transport.(http.RoundTripper); !ok {
   166  			return nil, fmt.Errorf("unable to verify TLS configuration, invalid transport %v", client.Transport)
   167  		}
   168  	} else {
   169  		transport := new(http.Transport)
   170  		sockets.ConfigureTransport(transport, hostURL.Scheme, hostURL.Host)
   171  		client = &http.Client{
   172  			Transport:     transport,
   173  			CheckRedirect: CheckRedirect,
   174  		}
   175  	}
   176  
   177  	scheme := "http"
   178  	tlsConfig := resolveTLSConfig(client.Transport)
   179  	if tlsConfig != nil {
   180  		// TODO(stevvooe): This isn't really the right way to write clients in Go.
   181  		// `NewClient` should probably only take an `*http.Client` and work from there.
   182  		// Unfortunately, the model of having a host-ish/url-thingy as the connection
   183  		// string has us confusing protocol and transport layers. We continue doing
   184  		// this to avoid breaking existing clients but this should be addressed.
   185  		scheme = "https"
   186  	}
   187  
   188  	// TODO: store URL instead of proto/addr/basePath
   189  	return &Client{
   190  		scheme:            scheme,
   191  		host:              host,
   192  		proto:             hostURL.Scheme,
   193  		addr:              hostURL.Host,
   194  		basePath:          hostURL.Path,
   195  		client:            client,
   196  		version:           version,
   197  		customHTTPHeaders: httpHeaders,
   198  	}, nil
   199  }
   200  
   201  // Close the transport used by the client
   202  func (cli *Client) Close() error {
   203  	if t, ok := cli.client.Transport.(*http.Transport); ok {
   204  		t.CloseIdleConnections()
   205  	}
   206  	return nil
   207  }
   208  
   209  // getAPIPath returns the versioned request path to call the api.
   210  // It appends the query parameters to the path if they are not empty.
   211  func (cli *Client) getAPIPath(p string, query url.Values) string {
   212  	var apiPath string
   213  	if cli.version != "" {
   214  		v := strings.TrimPrefix(cli.version, "v")
   215  		apiPath = path.Join(cli.basePath, "/v"+v, p)
   216  	} else {
   217  		apiPath = path.Join(cli.basePath, p)
   218  	}
   219  	return (&url.URL{Path: apiPath, RawQuery: query.Encode()}).String()
   220  }
   221  
   222  // ClientVersion returns the API version used by this client.
   223  func (cli *Client) ClientVersion() string {
   224  	return cli.version
   225  }
   226  
   227  // NegotiateAPIVersion queries the API and updates the version to match the
   228  // API version. Any errors are silently ignored.
   229  func (cli *Client) NegotiateAPIVersion(ctx context.Context) {
   230  	ping, _ := cli.Ping(ctx)
   231  	cli.NegotiateAPIVersionPing(ping)
   232  }
   233  
   234  // NegotiateAPIVersionPing updates the client version to match the Ping.APIVersion
   235  // if the ping version is less than the default version.
   236  func (cli *Client) NegotiateAPIVersionPing(p types.Ping) {
   237  	if cli.manualOverride {
   238  		return
   239  	}
   240  
   241  	// try the latest version before versioning headers existed
   242  	if p.APIVersion == "" {
   243  		p.APIVersion = "1.24"
   244  	}
   245  
   246  	// if the client is not initialized with a version, start with the latest supported version
   247  	if cli.version == "" {
   248  		cli.version = api.DefaultVersion
   249  	}
   250  
   251  	// if server version is lower than the client version, downgrade
   252  	if versions.LessThan(p.APIVersion, cli.version) {
   253  		cli.version = p.APIVersion
   254  	}
   255  }
   256  
   257  // DaemonHost returns the host address used by the client
   258  func (cli *Client) DaemonHost() string {
   259  	return cli.host
   260  }
   261  
   262  // ParseHost parses a url string, validates the strings is a host url, and returns
   263  // the parsed host as: protocol, address, and base path
   264  // Deprecated: use ParseHostURL
   265  func ParseHost(host string) (string, string, string, error) {
   266  	hostURL, err := ParseHostURL(host)
   267  	if err != nil {
   268  		return "", "", "", err
   269  	}
   270  	return hostURL.Scheme, hostURL.Host, hostURL.Path, nil
   271  }
   272  
   273  // ParseHostURL parses a url string, validates the string is a host url, and
   274  // returns the parsed URL
   275  func ParseHostURL(host string) (*url.URL, error) {
   276  	protoAddrParts := strings.SplitN(host, "://", 2)
   277  	if len(protoAddrParts) == 1 {
   278  		return nil, fmt.Errorf("unable to parse docker host `%s`", host)
   279  	}
   280  
   281  	var basePath string
   282  	proto, addr := protoAddrParts[0], protoAddrParts[1]
   283  	if proto == "tcp" {
   284  		parsed, err := url.Parse("tcp://" + addr)
   285  		if err != nil {
   286  			return nil, err
   287  		}
   288  		addr = parsed.Host
   289  		basePath = parsed.Path
   290  	}
   291  	return &url.URL{
   292  		Scheme: proto,
   293  		Host:   addr,
   294  		Path:   basePath,
   295  	}, nil
   296  }
   297  
   298  // CustomHTTPHeaders returns the custom http headers stored by the client.
   299  func (cli *Client) CustomHTTPHeaders() map[string]string {
   300  	m := make(map[string]string)
   301  	for k, v := range cli.customHTTPHeaders {
   302  		m[k] = v
   303  	}
   304  	return m
   305  }
   306  
   307  // SetCustomHTTPHeaders that will be set on every HTTP request made by the client.
   308  func (cli *Client) SetCustomHTTPHeaders(headers map[string]string) {
   309  	cli.customHTTPHeaders = headers
   310  }