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 }