github.com/ph/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 }