github.com/endophage/docker@v1.4.2-0.20161027011718-242853499895/client/client.go (about)

     1  /*
     2  Package client is a Go client for the Docker Remote 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 Remote API, see the documentation:
     9  https://docs.docker.com/engine/reference/api/docker_remote_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.23"
    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  }
    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  
   115  	version := os.Getenv("DOCKER_API_VERSION")
   116  	if version == "" {
   117  		version = DefaultVersion
   118  	}
   119  
   120  	return NewClient(host, version, client, nil)
   121  }
   122  
   123  // NewClient initializes a new API client for the given host and API version.
   124  // It uses the given http client as transport.
   125  // It also initializes the custom http headers to add to each request.
   126  //
   127  // It won't send any version information if the version number is empty. It is
   128  // highly recommended that you set a version or your client may break if the
   129  // server is upgraded.
   130  func NewClient(host string, version string, client *http.Client, httpHeaders map[string]string) (*Client, error) {
   131  	proto, addr, basePath, err := ParseHost(host)
   132  	if err != nil {
   133  		return nil, err
   134  	}
   135  
   136  	if client != nil {
   137  		if _, ok := client.Transport.(*http.Transport); !ok {
   138  			return nil, fmt.Errorf("unable to verify TLS configuration, invalid transport %v", client.Transport)
   139  		}
   140  	} else {
   141  		transport := new(http.Transport)
   142  		sockets.ConfigureTransport(transport, proto, addr)
   143  		client = &http.Client{
   144  			Transport: transport,
   145  		}
   146  	}
   147  
   148  	scheme := "http"
   149  	tlsConfig := resolveTLSConfig(client.Transport)
   150  	if tlsConfig != nil {
   151  		// TODO(stevvooe): This isn't really the right way to write clients in Go.
   152  		// `NewClient` should probably only take an `*http.Client` and work from there.
   153  		// Unfortunately, the model of having a host-ish/url-thingy as the connection
   154  		// string has us confusing protocol and transport layers. We continue doing
   155  		// this to avoid breaking existing clients but this should be addressed.
   156  		scheme = "https"
   157  	}
   158  
   159  	return &Client{
   160  		scheme:            scheme,
   161  		host:              host,
   162  		proto:             proto,
   163  		addr:              addr,
   164  		basePath:          basePath,
   165  		client:            client,
   166  		version:           version,
   167  		customHTTPHeaders: httpHeaders,
   168  	}, nil
   169  }
   170  
   171  // Close ensures that transport.Client is closed
   172  // especially needed while using NewClient with *http.Client = nil
   173  // for example
   174  // client.NewClient("unix:///var/run/docker.sock", nil, "v1.18", map[string]string{"User-Agent": "engine-api-cli-1.0"})
   175  func (cli *Client) Close() error {
   176  
   177  	if t, ok := cli.client.Transport.(*http.Transport); ok {
   178  		t.CloseIdleConnections()
   179  	}
   180  
   181  	return nil
   182  }
   183  
   184  // getAPIPath returns the versioned request path to call the api.
   185  // It appends the query parameters to the path if they are not empty.
   186  func (cli *Client) getAPIPath(p string, query url.Values) string {
   187  	var apiPath string
   188  	if cli.version != "" {
   189  		v := strings.TrimPrefix(cli.version, "v")
   190  		apiPath = fmt.Sprintf("%s/v%s%s", cli.basePath, v, p)
   191  	} else {
   192  		apiPath = fmt.Sprintf("%s%s", cli.basePath, p)
   193  	}
   194  
   195  	u := &url.URL{
   196  		Path: apiPath,
   197  	}
   198  	if len(query) > 0 {
   199  		u.RawQuery = query.Encode()
   200  	}
   201  	return u.String()
   202  }
   203  
   204  // ClientVersion returns the version string associated with this
   205  // instance of the Client. Note that this value can be changed
   206  // via the DOCKER_API_VERSION env var.
   207  func (cli *Client) ClientVersion() string {
   208  	return cli.version
   209  }
   210  
   211  // UpdateClientVersion updates the version string associated with this
   212  // instance of the Client.
   213  func (cli *Client) UpdateClientVersion(v string) {
   214  	cli.version = v
   215  }
   216  
   217  // ParseHost verifies that the given host strings is valid.
   218  func ParseHost(host string) (string, string, string, error) {
   219  	protoAddrParts := strings.SplitN(host, "://", 2)
   220  	if len(protoAddrParts) == 1 {
   221  		return "", "", "", fmt.Errorf("unable to parse docker host `%s`", host)
   222  	}
   223  
   224  	var basePath string
   225  	proto, addr := protoAddrParts[0], protoAddrParts[1]
   226  	if proto == "tcp" {
   227  		parsed, err := url.Parse("tcp://" + addr)
   228  		if err != nil {
   229  			return "", "", "", err
   230  		}
   231  		addr = parsed.Host
   232  		basePath = parsed.Path
   233  	}
   234  	return proto, addr, basePath, nil
   235  }