github.com/hms58/moby@v1.13.1/client/client.go (about)

     1  /*
     2  Package client is a Go client for the Docker Engine API.
     3  
     4  The "docker" command uses this package to communicate with the daemon. It can also
     5  be used by your own Go applications to do anything the command-line interface does
     6  – running containers, pulling images, managing swarms, etc.
     7  
     8  For more information about the Engine API, see the documentation:
     9  https://docs.docker.com/engine/reference/api/
    10  
    11  Usage
    12  
    13  You use the library by creating a client object and calling methods on it. The
    14  client can be created either from environment variables with NewEnvClient, or
    15  configured manually with NewClient.
    16  
    17  For example, to list running containers (the equivalent of "docker ps"):
    18  
    19  	package main
    20  
    21  	import (
    22  		"context"
    23  		"fmt"
    24  
    25  		"github.com/docker/docker/api/types"
    26  		"github.com/docker/docker/client"
    27  	)
    28  
    29  	func main() {
    30  		cli, err := client.NewEnvClient()
    31  		if err != nil {
    32  			panic(err)
    33  		}
    34  
    35  		containers, err := cli.ContainerList(context.Background(), types.ContainerListOptions{})
    36  		if err != nil {
    37  			panic(err)
    38  		}
    39  
    40  		for _, container := range containers {
    41  			fmt.Printf("%s %s\n", container.ID[:10], container.Image)
    42  		}
    43  	}
    44  
    45  */
    46  package client
    47  
    48  import (
    49  	"fmt"
    50  	"net/http"
    51  	"net/url"
    52  	"os"
    53  	"path/filepath"
    54  	"strings"
    55  
    56  	"github.com/docker/go-connections/sockets"
    57  	"github.com/docker/go-connections/tlsconfig"
    58  )
    59  
    60  // DefaultVersion is the version of the current stable API
    61  const DefaultVersion string = "1.25"
    62  
    63  // Client is the API client that performs all operations
    64  // against a docker server.
    65  type Client struct {
    66  	// scheme sets the scheme for the client
    67  	scheme string
    68  	// host holds the server address to connect to
    69  	host string
    70  	// proto holds the client protocol i.e. unix.
    71  	proto string
    72  	// addr holds the client address.
    73  	addr string
    74  	// basePath holds the path to prepend to the requests.
    75  	basePath string
    76  	// client used to send and receive http requests.
    77  	client *http.Client
    78  	// version of the server to talk to.
    79  	version string
    80  	// custom http headers configured by users.
    81  	customHTTPHeaders map[string]string
    82  	// manualOverride is set to true when the version was set by users.
    83  	manualOverride bool
    84  }
    85  
    86  // NewEnvClient initializes a new API client based on environment variables.
    87  // Use DOCKER_HOST to set the url to the docker server.
    88  // Use DOCKER_API_VERSION to set the version of the API to reach, leave empty for latest.
    89  // Use DOCKER_CERT_PATH to load the tls certificates from.
    90  // Use DOCKER_TLS_VERIFY to enable or disable TLS verification, off by default.
    91  func NewEnvClient() (*Client, error) {
    92  	var client *http.Client
    93  	if dockerCertPath := os.Getenv("DOCKER_CERT_PATH"); dockerCertPath != "" {
    94  		options := tlsconfig.Options{
    95  			CAFile:             filepath.Join(dockerCertPath, "ca.pem"),
    96  			CertFile:           filepath.Join(dockerCertPath, "cert.pem"),
    97  			KeyFile:            filepath.Join(dockerCertPath, "key.pem"),
    98  			InsecureSkipVerify: os.Getenv("DOCKER_TLS_VERIFY") == "",
    99  		}
   100  		tlsc, err := tlsconfig.Client(options)
   101  		if err != nil {
   102  			return nil, err
   103  		}
   104  
   105  		client = &http.Client{
   106  			Transport: &http.Transport{
   107  				TLSClientConfig: tlsc,
   108  			},
   109  		}
   110  	}
   111  
   112  	host := os.Getenv("DOCKER_HOST")
   113  	if host == "" {
   114  		host = DefaultDockerHost
   115  	}
   116  	version := os.Getenv("DOCKER_API_VERSION")
   117  	if version == "" {
   118  		version = DefaultVersion
   119  	}
   120  
   121  	cli, err := NewClient(host, version, client, nil)
   122  	if err != nil {
   123  		return cli, err
   124  	}
   125  	if os.Getenv("DOCKER_API_VERSION") != "" {
   126  		cli.manualOverride = true
   127  	}
   128  	return cli, nil
   129  }
   130  
   131  // NewClient initializes a new API client for the given host and API version.
   132  // It uses the given http client as transport.
   133  // It also initializes the custom http headers to add to each request.
   134  //
   135  // It won't send any version information if the version number is empty. It is
   136  // highly recommended that you set a version or your client may break if the
   137  // server is upgraded.
   138  func NewClient(host string, version string, client *http.Client, httpHeaders map[string]string) (*Client, error) {
   139  	proto, addr, basePath, err := ParseHost(host)
   140  	if err != nil {
   141  		return nil, err
   142  	}
   143  
   144  	if client != nil {
   145  		if _, ok := client.Transport.(*http.Transport); !ok {
   146  			return nil, fmt.Errorf("unable to verify TLS configuration, invalid transport %v", client.Transport)
   147  		}
   148  	} else {
   149  		transport := new(http.Transport)
   150  		sockets.ConfigureTransport(transport, proto, addr)
   151  		client = &http.Client{
   152  			Transport: transport,
   153  		}
   154  	}
   155  
   156  	scheme := "http"
   157  	tlsConfig := resolveTLSConfig(client.Transport)
   158  	if tlsConfig != nil {
   159  		// TODO(stevvooe): This isn't really the right way to write clients in Go.
   160  		// `NewClient` should probably only take an `*http.Client` and work from there.
   161  		// Unfortunately, the model of having a host-ish/url-thingy as the connection
   162  		// string has us confusing protocol and transport layers. We continue doing
   163  		// this to avoid breaking existing clients but this should be addressed.
   164  		scheme = "https"
   165  	}
   166  
   167  	return &Client{
   168  		scheme:            scheme,
   169  		host:              host,
   170  		proto:             proto,
   171  		addr:              addr,
   172  		basePath:          basePath,
   173  		client:            client,
   174  		version:           version,
   175  		customHTTPHeaders: httpHeaders,
   176  	}, nil
   177  }
   178  
   179  // Close ensures that transport.Client is closed
   180  // especially needed while using NewClient with *http.Client = nil
   181  // for example
   182  // client.NewClient("unix:///var/run/docker.sock", nil, "v1.18", map[string]string{"User-Agent": "engine-api-cli-1.0"})
   183  func (cli *Client) Close() error {
   184  
   185  	if t, ok := cli.client.Transport.(*http.Transport); ok {
   186  		t.CloseIdleConnections()
   187  	}
   188  
   189  	return nil
   190  }
   191  
   192  // getAPIPath returns the versioned request path to call the api.
   193  // It appends the query parameters to the path if they are not empty.
   194  func (cli *Client) getAPIPath(p string, query url.Values) string {
   195  	var apiPath string
   196  	if cli.version != "" {
   197  		v := strings.TrimPrefix(cli.version, "v")
   198  		apiPath = fmt.Sprintf("%s/v%s%s", cli.basePath, v, p)
   199  	} else {
   200  		apiPath = fmt.Sprintf("%s%s", cli.basePath, p)
   201  	}
   202  
   203  	u := &url.URL{
   204  		Path: apiPath,
   205  	}
   206  	if len(query) > 0 {
   207  		u.RawQuery = query.Encode()
   208  	}
   209  	return u.String()
   210  }
   211  
   212  // ClientVersion returns the version string associated with this
   213  // instance of the Client. Note that this value can be changed
   214  // via the DOCKER_API_VERSION env var.
   215  func (cli *Client) ClientVersion() string {
   216  	return cli.version
   217  }
   218  
   219  // UpdateClientVersion updates the version string associated with this
   220  // instance of the Client.
   221  func (cli *Client) UpdateClientVersion(v string) {
   222  	if !cli.manualOverride {
   223  		cli.version = v
   224  	}
   225  
   226  }
   227  
   228  // ParseHost verifies that the given host strings is valid.
   229  func ParseHost(host string) (string, string, string, error) {
   230  	protoAddrParts := strings.SplitN(host, "://", 2)
   231  	if len(protoAddrParts) == 1 {
   232  		return "", "", "", fmt.Errorf("unable to parse docker host `%s`", host)
   233  	}
   234  
   235  	var basePath string
   236  	proto, addr := protoAddrParts[0], protoAddrParts[1]
   237  	if proto == "tcp" {
   238  		parsed, err := url.Parse("tcp://" + addr)
   239  		if err != nil {
   240  			return "", "", "", err
   241  		}
   242  		addr = parsed.Host
   243  		basePath = parsed.Path
   244  	}
   245  	return proto, addr, basePath, nil
   246  }