github.com/fabiokung/docker@v0.11.2-0.20170222101415-4534dcd49497/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/docker/api"
    57  	"github.com/docker/go-connections/sockets"
    58  	"github.com/docker/go-connections/tlsconfig"
    59  )
    60  
    61  // Client is the API client that performs all operations
    62  // against a docker server.
    63  type Client struct {
    64  	// scheme sets the scheme for the client
    65  	scheme string
    66  	// host holds the server address to connect to
    67  	host string
    68  	// proto holds the client protocol i.e. unix.
    69  	proto string
    70  	// addr holds the client address.
    71  	addr string
    72  	// basePath holds the path to prepend to the requests.
    73  	basePath string
    74  	// client used to send and receive http requests.
    75  	client *http.Client
    76  	// version of the server to talk to.
    77  	version string
    78  	// custom http headers configured by users.
    79  	customHTTPHeaders map[string]string
    80  	// manualOverride is set to true when the version was set by users.
    81  	manualOverride bool
    82  }
    83  
    84  // NewEnvClient initializes a new API client based on environment variables.
    85  // Use DOCKER_HOST to set the url to the docker server.
    86  // Use DOCKER_API_VERSION to set the version of the API to reach, leave empty for latest.
    87  // Use DOCKER_CERT_PATH to load the TLS certificates from.
    88  // Use DOCKER_TLS_VERIFY to enable or disable TLS verification, off by default.
    89  func NewEnvClient() (*Client, error) {
    90  	var client *http.Client
    91  	if dockerCertPath := os.Getenv("DOCKER_CERT_PATH"); dockerCertPath != "" {
    92  		options := tlsconfig.Options{
    93  			CAFile:             filepath.Join(dockerCertPath, "ca.pem"),
    94  			CertFile:           filepath.Join(dockerCertPath, "cert.pem"),
    95  			KeyFile:            filepath.Join(dockerCertPath, "key.pem"),
    96  			InsecureSkipVerify: os.Getenv("DOCKER_TLS_VERIFY") == "",
    97  		}
    98  		tlsc, err := tlsconfig.Client(options)
    99  		if err != nil {
   100  			return nil, err
   101  		}
   102  
   103  		client = &http.Client{
   104  			Transport: &http.Transport{
   105  				TLSClientConfig: tlsc,
   106  			},
   107  		}
   108  	}
   109  
   110  	host := os.Getenv("DOCKER_HOST")
   111  	if host == "" {
   112  		host = DefaultDockerHost
   113  	}
   114  	version := os.Getenv("DOCKER_API_VERSION")
   115  	if version == "" {
   116  		version = api.DefaultVersion
   117  	}
   118  
   119  	cli, err := NewClient(host, version, client, nil)
   120  	if err != nil {
   121  		return cli, err
   122  	}
   123  	if os.Getenv("DOCKER_API_VERSION") != "" {
   124  		cli.manualOverride = true
   125  	}
   126  	return cli, nil
   127  }
   128  
   129  // NewClient initializes a new API client for the given host and API version.
   130  // It uses the given http client as transport.
   131  // It also initializes the custom http headers to add to each request.
   132  //
   133  // It won't send any version information if the version number is empty. It is
   134  // highly recommended that you set a version or your client may break if the
   135  // server is upgraded.
   136  func NewClient(host string, version string, client *http.Client, httpHeaders map[string]string) (*Client, error) {
   137  	proto, addr, basePath, err := ParseHost(host)
   138  	if err != nil {
   139  		return nil, err
   140  	}
   141  
   142  	if client != nil {
   143  		if _, ok := client.Transport.(*http.Transport); !ok {
   144  			return nil, fmt.Errorf("unable to verify TLS configuration, invalid transport %v", client.Transport)
   145  		}
   146  	} else {
   147  		transport := new(http.Transport)
   148  		sockets.ConfigureTransport(transport, proto, addr)
   149  		client = &http.Client{
   150  			Transport: transport,
   151  		}
   152  	}
   153  
   154  	scheme := "http"
   155  	tlsConfig := resolveTLSConfig(client.Transport)
   156  	if tlsConfig != nil {
   157  		// TODO(stevvooe): This isn't really the right way to write clients in Go.
   158  		// `NewClient` should probably only take an `*http.Client` and work from there.
   159  		// Unfortunately, the model of having a host-ish/url-thingy as the connection
   160  		// string has us confusing protocol and transport layers. We continue doing
   161  		// this to avoid breaking existing clients but this should be addressed.
   162  		scheme = "https"
   163  	}
   164  
   165  	return &Client{
   166  		scheme:            scheme,
   167  		host:              host,
   168  		proto:             proto,
   169  		addr:              addr,
   170  		basePath:          basePath,
   171  		client:            client,
   172  		version:           version,
   173  		customHTTPHeaders: httpHeaders,
   174  	}, nil
   175  }
   176  
   177  // Close ensures that transport.Client is closed
   178  // especially needed while using NewClient with *http.Client = nil
   179  // for example
   180  // client.NewClient("unix:///var/run/docker.sock", nil, "v1.18", map[string]string{"User-Agent": "engine-api-cli-1.0"})
   181  func (cli *Client) Close() error {
   182  
   183  	if t, ok := cli.client.Transport.(*http.Transport); ok {
   184  		t.CloseIdleConnections()
   185  	}
   186  
   187  	return nil
   188  }
   189  
   190  // getAPIPath returns the versioned request path to call the api.
   191  // It appends the query parameters to the path if they are not empty.
   192  func (cli *Client) getAPIPath(p string, query url.Values) string {
   193  	var apiPath string
   194  	if cli.version != "" {
   195  		v := strings.TrimPrefix(cli.version, "v")
   196  		apiPath = fmt.Sprintf("%s/v%s%s", cli.basePath, v, p)
   197  	} else {
   198  		apiPath = fmt.Sprintf("%s%s", cli.basePath, p)
   199  	}
   200  
   201  	u := &url.URL{
   202  		Path: apiPath,
   203  	}
   204  	if len(query) > 0 {
   205  		u.RawQuery = query.Encode()
   206  	}
   207  	return u.String()
   208  }
   209  
   210  // ClientVersion returns the version string associated with this
   211  // instance of the Client. Note that this value can be changed
   212  // via the DOCKER_API_VERSION env var.
   213  // This operation doesn't acquire a mutex.
   214  func (cli *Client) ClientVersion() string {
   215  	return cli.version
   216  }
   217  
   218  // UpdateClientVersion updates the version string associated with this
   219  // instance of the Client. This operation doesn't acquire a mutex.
   220  func (cli *Client) UpdateClientVersion(v string) {
   221  	if !cli.manualOverride {
   222  		cli.version = v
   223  	}
   224  
   225  }
   226  
   227  // ParseHost verifies that the given host strings is valid.
   228  func ParseHost(host string) (string, string, string, error) {
   229  	protoAddrParts := strings.SplitN(host, "://", 2)
   230  	if len(protoAddrParts) == 1 {
   231  		return "", "", "", fmt.Errorf("unable to parse docker host `%s`", host)
   232  	}
   233  
   234  	var basePath string
   235  	proto, addr := protoAddrParts[0], protoAddrParts[1]
   236  	if proto == "tcp" {
   237  		parsed, err := url.Parse("tcp://" + addr)
   238  		if err != nil {
   239  			return "", "", "", err
   240  		}
   241  		addr = parsed.Host
   242  		basePath = parsed.Path
   243  	}
   244  	return proto, addr, basePath, nil
   245  }
   246  
   247  // CustomHTTPHeaders returns the custom http headers associated with this
   248  // instance of the Client. This operation doesn't acquire a mutex.
   249  func (cli *Client) CustomHTTPHeaders() map[string]string {
   250  	m := make(map[string]string)
   251  	for k, v := range cli.customHTTPHeaders {
   252  		m[k] = v
   253  	}
   254  	return m
   255  }
   256  
   257  // SetCustomHTTPHeaders updates the custom http headers associated with this
   258  // instance of the Client. This operation doesn't acquire a mutex.
   259  func (cli *Client) SetCustomHTTPHeaders(headers map[string]string) {
   260  	cli.customHTTPHeaders = headers
   261  }