2016-10-08 13:34:37 +00:00
|
|
|
/*
|
2016-11-15 19:45:20 +00:00
|
|
|
Package client is a Go client for the Docker Engine API.
|
2016-10-08 13:34:37 +00:00
|
|
|
|
2016-11-15 19:45:20 +00:00
|
|
|
For more information about the Engine API, see the documentation:
|
2021-02-25 11:11:50 +00:00
|
|
|
https://docs.docker.com/engine/api/
|
2016-10-08 13:34:37 +00:00
|
|
|
|
|
|
|
Usage
|
|
|
|
|
|
|
|
You use the library by creating a client object and calling methods on it. The
|
2020-10-03 13:07:07 +00:00
|
|
|
client can be created either from environment variables with NewClientWithOpts(client.FromEnv),
|
|
|
|
or configured manually with NewClient().
|
2016-10-08 13:34:37 +00:00
|
|
|
|
|
|
|
For example, to list running containers (the equivalent of "docker ps"):
|
|
|
|
|
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"fmt"
|
|
|
|
|
|
|
|
"github.com/docker/docker/api/types"
|
|
|
|
"github.com/docker/docker/client"
|
|
|
|
)
|
|
|
|
|
|
|
|
func main() {
|
2019-01-03 21:49:00 +00:00
|
|
|
cli, err := client.NewClientWithOpts(client.FromEnv)
|
2016-10-08 13:34:37 +00:00
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
containers, err := cli.ContainerList(context.Background(), types.ContainerListOptions{})
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, container := range containers {
|
|
|
|
fmt.Printf("%s %s\n", container.ID[:10], container.Image)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
*/
|
2018-02-05 21:05:59 +00:00
|
|
|
package client // import "github.com/docker/docker/client"
|
2016-09-06 18:46:37 +00:00
|
|
|
|
|
|
|
import (
|
2018-04-19 22:30:59 +00:00
|
|
|
"context"
|
2018-02-16 18:24:57 +00:00
|
|
|
"net"
|
2016-09-06 18:46:37 +00:00
|
|
|
"net/http"
|
|
|
|
"net/url"
|
2017-08-11 17:47:02 +00:00
|
|
|
"path"
|
2016-09-06 18:46:37 +00:00
|
|
|
"strings"
|
|
|
|
|
2017-02-19 08:43:08 +00:00
|
|
|
"github.com/docker/docker/api"
|
2017-06-21 05:58:16 +00:00
|
|
|
"github.com/docker/docker/api/types"
|
|
|
|
"github.com/docker/docker/api/types/versions"
|
2016-09-09 03:44:25 +00:00
|
|
|
"github.com/docker/go-connections/sockets"
|
2018-02-16 18:24:57 +00:00
|
|
|
"github.com/pkg/errors"
|
2016-09-06 18:46:37 +00:00
|
|
|
)
|
|
|
|
|
2017-03-27 09:05:35 +00:00
|
|
|
// ErrRedirect is the error returned by checkRedirect when the request is non-GET.
|
|
|
|
var ErrRedirect = errors.New("unexpected redirect in response")
|
|
|
|
|
2016-09-06 18:46:37 +00:00
|
|
|
// Client is the API client that performs all operations
|
|
|
|
// against a docker server.
|
|
|
|
type Client struct {
|
2016-10-11 22:53:14 +00:00
|
|
|
// scheme sets the scheme for the client
|
|
|
|
scheme string
|
2016-09-06 18:46:37 +00:00
|
|
|
// host holds the server address to connect to
|
|
|
|
host string
|
|
|
|
// proto holds the client protocol i.e. unix.
|
|
|
|
proto string
|
|
|
|
// addr holds the client address.
|
|
|
|
addr string
|
|
|
|
// basePath holds the path to prepend to the requests.
|
|
|
|
basePath string
|
2016-09-09 03:44:25 +00:00
|
|
|
// client used to send and receive http requests.
|
|
|
|
client *http.Client
|
2016-09-06 18:46:37 +00:00
|
|
|
// version of the server to talk to.
|
|
|
|
version string
|
|
|
|
// custom http headers configured by users.
|
|
|
|
customHTTPHeaders map[string]string
|
2016-11-03 00:43:32 +00:00
|
|
|
// manualOverride is set to true when the version was set by users.
|
|
|
|
manualOverride bool
|
2019-04-08 12:14:07 +00:00
|
|
|
|
|
|
|
// negotiateVersion indicates if the client should automatically negotiate
|
|
|
|
// the API version to use when making requests. API version negotiation is
|
|
|
|
// performed on the first request, after which negotiated is set to "true"
|
|
|
|
// so that subsequent requests do not re-negotiate.
|
|
|
|
negotiateVersion bool
|
|
|
|
|
|
|
|
// negotiated indicates that API version negotiation took place
|
|
|
|
negotiated bool
|
2016-09-06 18:46:37 +00:00
|
|
|
}
|
|
|
|
|
2017-03-27 09:05:35 +00:00
|
|
|
// CheckRedirect specifies the policy for dealing with redirect responses:
|
2022-03-07 10:29:38 +00:00
|
|
|
// If the request is non-GET return ErrRedirect, otherwise use the last response.
|
2017-03-27 09:05:35 +00:00
|
|
|
//
|
2022-03-07 10:29:38 +00:00
|
|
|
// Go 1.8 changes behavior for HTTP redirects (specifically 301, 307, and 308)
|
|
|
|
// in the client. The Docker client (and by extension docker API client) can be
|
|
|
|
// made to send a request like POST /containers//start where what would normally
|
|
|
|
// be in the name section of the URL is empty. This triggers an HTTP 301 from
|
|
|
|
// the daemon.
|
|
|
|
//
|
|
|
|
// In go 1.8 this 301 will be converted to a GET request, and ends up getting
|
|
|
|
// a 404 from the daemon. This behavior change manifests in the client in that
|
|
|
|
// before, the 301 was not followed and the client did not generate an error,
|
|
|
|
// but now results in a message like Error response from daemon: page not found.
|
2017-03-27 09:05:35 +00:00
|
|
|
func CheckRedirect(req *http.Request, via []*http.Request) error {
|
|
|
|
if via[0].Method == http.MethodGet {
|
|
|
|
return http.ErrUseLastResponse
|
|
|
|
}
|
|
|
|
return ErrRedirect
|
|
|
|
}
|
|
|
|
|
2022-03-07 10:29:38 +00:00
|
|
|
// NewClientWithOpts initializes a new API client with a default HTTPClient, and
|
|
|
|
// default API host and version. It also initializes the custom HTTP headers to
|
|
|
|
// add to each request.
|
|
|
|
//
|
|
|
|
// It takes an optional list of Opt functional arguments, which are applied in
|
|
|
|
// the order they're provided, which allows modifying the defaults when creating
|
|
|
|
// the client. For example, the following initializes a client that configures
|
|
|
|
// itself with values from environment variables (client.FromEnv), and has
|
|
|
|
// automatic API version negotiation enabled (client.WithAPIVersionNegotiation()).
|
|
|
|
//
|
|
|
|
//
|
|
|
|
// cli, err := client.NewClientWithOpts(
|
|
|
|
// client.FromEnv,
|
|
|
|
// client.WithAPIVersionNegotiation(),
|
|
|
|
// )
|
2016-09-06 18:46:37 +00:00
|
|
|
//
|
2019-04-09 23:11:53 +00:00
|
|
|
func NewClientWithOpts(ops ...Opt) (*Client, error) {
|
2018-01-31 19:28:33 +00:00
|
|
|
client, err := defaultHTTPClient(DefaultDockerHost)
|
2016-09-06 18:46:37 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2018-01-31 19:28:33 +00:00
|
|
|
c := &Client{
|
|
|
|
host: DefaultDockerHost,
|
|
|
|
version: api.DefaultVersion,
|
|
|
|
client: client,
|
|
|
|
proto: defaultProto,
|
|
|
|
addr: defaultAddr,
|
|
|
|
}
|
2016-09-06 18:46:37 +00:00
|
|
|
|
2018-01-31 19:28:33 +00:00
|
|
|
for _, op := range ops {
|
|
|
|
if err := op(c); err != nil {
|
|
|
|
return nil, err
|
2016-09-22 02:16:44 +00:00
|
|
|
}
|
2016-09-06 18:46:37 +00:00
|
|
|
}
|
|
|
|
|
2018-11-01 17:40:37 +00:00
|
|
|
if c.scheme == "" {
|
|
|
|
c.scheme = "http"
|
|
|
|
|
|
|
|
tlsConfig := resolveTLSConfig(c.client.Transport)
|
|
|
|
if tlsConfig != nil {
|
|
|
|
// TODO(stevvooe): This isn't really the right way to write clients in Go.
|
|
|
|
// `NewClient` should probably only take an `*http.Client` and work from there.
|
|
|
|
// Unfortunately, the model of having a host-ish/url-thingy as the connection
|
|
|
|
// string has us confusing protocol and transport layers. We continue doing
|
|
|
|
// this to avoid breaking existing clients but this should be addressed.
|
|
|
|
c.scheme = "https"
|
|
|
|
}
|
2018-01-31 19:28:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return c, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func defaultHTTPClient(host string) (*http.Client, error) {
|
2022-03-07 10:29:38 +00:00
|
|
|
hostURL, err := ParseHostURL(host)
|
2018-01-31 19:28:33 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2022-03-07 10:29:38 +00:00
|
|
|
transport := &http.Transport{}
|
|
|
|
_ = sockets.ConfigureTransport(transport, hostURL.Scheme, hostURL.Host)
|
2018-01-31 19:28:33 +00:00
|
|
|
return &http.Client{
|
|
|
|
Transport: transport,
|
|
|
|
CheckRedirect: CheckRedirect,
|
2016-09-06 18:46:37 +00:00
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
2017-09-07 16:22:11 +00:00
|
|
|
// Close the transport used by the client
|
2016-09-08 01:57:54 +00:00
|
|
|
func (cli *Client) Close() error {
|
|
|
|
if t, ok := cli.client.Transport.(*http.Transport); ok {
|
|
|
|
t.CloseIdleConnections()
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2016-09-06 18:46:37 +00:00
|
|
|
// getAPIPath returns the versioned request path to call the api.
|
|
|
|
// It appends the query parameters to the path if they are not empty.
|
2019-04-08 12:14:07 +00:00
|
|
|
func (cli *Client) getAPIPath(ctx context.Context, p string, query url.Values) string {
|
2016-09-06 18:46:37 +00:00
|
|
|
var apiPath string
|
2019-04-08 12:14:07 +00:00
|
|
|
if cli.negotiateVersion && !cli.negotiated {
|
|
|
|
cli.NegotiateAPIVersion(ctx)
|
|
|
|
}
|
2016-09-06 18:46:37 +00:00
|
|
|
if cli.version != "" {
|
|
|
|
v := strings.TrimPrefix(cli.version, "v")
|
2017-09-07 17:46:23 +00:00
|
|
|
apiPath = path.Join(cli.basePath, "/v"+v, p)
|
2016-09-06 18:46:37 +00:00
|
|
|
} else {
|
2017-08-11 17:47:02 +00:00
|
|
|
apiPath = path.Join(cli.basePath, p)
|
2016-09-06 18:46:37 +00:00
|
|
|
}
|
2017-09-07 17:46:23 +00:00
|
|
|
return (&url.URL{Path: apiPath, RawQuery: query.Encode()}).String()
|
2016-09-06 18:46:37 +00:00
|
|
|
}
|
|
|
|
|
2017-09-07 16:22:11 +00:00
|
|
|
// ClientVersion returns the API version used by this client.
|
2016-09-06 18:46:37 +00:00
|
|
|
func (cli *Client) ClientVersion() string {
|
|
|
|
return cli.version
|
|
|
|
}
|
|
|
|
|
2022-03-07 10:29:38 +00:00
|
|
|
// NegotiateAPIVersion queries the API and updates the version to match the API
|
|
|
|
// version. NegotiateAPIVersion downgrades the client's API version to match the
|
|
|
|
// APIVersion if the ping version is lower than the default version. If the API
|
|
|
|
// version reported by the server is higher than the maximum version supported
|
|
|
|
// by the client, it uses the client's maximum version.
|
|
|
|
//
|
|
|
|
// If a manual override is in place, either through the "DOCKER_API_VERSION"
|
2022-03-07 10:50:22 +00:00
|
|
|
// (EnvOverrideAPIVersion) environment variable, or if the client is initialized
|
2022-03-07 10:29:38 +00:00
|
|
|
// with a fixed version (WithVersion(xx)), no negotiation is performed.
|
|
|
|
//
|
|
|
|
// If the API server's ping response does not contain an API version, or if the
|
|
|
|
// client did not get a successful ping response, it assumes it is connected with
|
|
|
|
// an old daemon that does not support API version negotiation, in which case it
|
|
|
|
// downgrades to the latest version of the API before version negotiation was
|
|
|
|
// added (1.24).
|
2017-06-21 05:58:16 +00:00
|
|
|
func (cli *Client) NegotiateAPIVersion(ctx context.Context) {
|
2019-04-08 12:14:07 +00:00
|
|
|
if !cli.manualOverride {
|
|
|
|
ping, _ := cli.Ping(ctx)
|
|
|
|
cli.negotiateAPIVersionPing(ping)
|
|
|
|
}
|
2017-06-21 05:58:16 +00:00
|
|
|
}
|
|
|
|
|
2022-03-07 10:29:38 +00:00
|
|
|
// NegotiateAPIVersionPing downgrades the client's API version to match the
|
|
|
|
// APIVersion in the ping response. If the API version in pingResponse is higher
|
|
|
|
// than the maximum version supported by the client, it uses the client's maximum
|
|
|
|
// version.
|
|
|
|
//
|
|
|
|
// If a manual override is in place, either through the "DOCKER_API_VERSION"
|
2022-03-07 10:50:22 +00:00
|
|
|
// (EnvOverrideAPIVersion) environment variable, or if the client is initialized
|
2022-03-07 10:29:38 +00:00
|
|
|
// with a fixed version (WithVersion(xx)), no negotiation is performed.
|
|
|
|
//
|
|
|
|
// If the API server's ping response does not contain an API version, we assume
|
|
|
|
// we are connected with an old daemon without API version negotiation support,
|
|
|
|
// and downgrade to the latest version of the API before version negotiation was
|
|
|
|
// added (1.24).
|
|
|
|
func (cli *Client) NegotiateAPIVersionPing(pingResponse types.Ping) {
|
2019-04-08 12:14:07 +00:00
|
|
|
if !cli.manualOverride {
|
2022-03-07 10:29:38 +00:00
|
|
|
cli.negotiateAPIVersionPing(pingResponse)
|
2016-11-03 00:43:32 +00:00
|
|
|
}
|
2019-04-08 12:14:07 +00:00
|
|
|
}
|
2016-11-03 00:43:32 +00:00
|
|
|
|
2019-04-08 12:14:07 +00:00
|
|
|
// negotiateAPIVersionPing queries the API and updates the version to match the
|
2022-03-07 10:29:38 +00:00
|
|
|
// API version from the ping response.
|
|
|
|
func (cli *Client) negotiateAPIVersionPing(pingResponse types.Ping) {
|
|
|
|
// default to the latest version before versioning headers existed
|
|
|
|
if pingResponse.APIVersion == "" {
|
|
|
|
pingResponse.APIVersion = "1.24"
|
2017-06-21 05:58:16 +00:00
|
|
|
}
|
|
|
|
|
2017-06-30 05:24:49 +00:00
|
|
|
// if the client is not initialized with a version, start with the latest supported version
|
|
|
|
if cli.version == "" {
|
|
|
|
cli.version = api.DefaultVersion
|
|
|
|
}
|
|
|
|
|
2017-09-28 01:12:13 +00:00
|
|
|
// if server version is lower than the client version, downgrade
|
2022-03-07 10:29:38 +00:00
|
|
|
if versions.LessThan(pingResponse.APIVersion, cli.version) {
|
|
|
|
cli.version = pingResponse.APIVersion
|
2017-06-21 05:58:16 +00:00
|
|
|
}
|
2019-04-08 12:14:07 +00:00
|
|
|
|
|
|
|
// Store the results, so that automatic API version negotiation (if enabled)
|
|
|
|
// won't be performed on the next request.
|
|
|
|
if cli.negotiateVersion {
|
|
|
|
cli.negotiated = true
|
|
|
|
}
|
2016-09-06 18:46:37 +00:00
|
|
|
}
|
|
|
|
|
2017-09-07 16:22:11 +00:00
|
|
|
// DaemonHost returns the host address used by the client
|
2017-05-17 14:46:15 +00:00
|
|
|
func (cli *Client) DaemonHost() string {
|
|
|
|
return cli.host
|
|
|
|
}
|
|
|
|
|
2018-05-15 13:12:30 +00:00
|
|
|
// HTTPClient returns a copy of the HTTP client bound to the server
|
|
|
|
func (cli *Client) HTTPClient() *http.Client {
|
2019-08-05 14:48:52 +00:00
|
|
|
c := *cli.client
|
|
|
|
return &c
|
2018-05-15 13:12:30 +00:00
|
|
|
}
|
|
|
|
|
2017-09-07 16:22:11 +00:00
|
|
|
// ParseHostURL parses a url string, validates the string is a host url, and
|
|
|
|
// returns the parsed URL
|
|
|
|
func ParseHostURL(host string) (*url.URL, error) {
|
2016-09-06 18:46:37 +00:00
|
|
|
protoAddrParts := strings.SplitN(host, "://", 2)
|
|
|
|
if len(protoAddrParts) == 1 {
|
2022-03-07 10:29:38 +00:00
|
|
|
return nil, errors.Errorf("unable to parse docker host `%s`", host)
|
2016-09-06 18:46:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
var basePath string
|
|
|
|
proto, addr := protoAddrParts[0], protoAddrParts[1]
|
|
|
|
if proto == "tcp" {
|
|
|
|
parsed, err := url.Parse("tcp://" + addr)
|
|
|
|
if err != nil {
|
2017-09-07 16:22:11 +00:00
|
|
|
return nil, err
|
2016-09-06 18:46:37 +00:00
|
|
|
}
|
|
|
|
addr = parsed.Host
|
|
|
|
basePath = parsed.Path
|
|
|
|
}
|
2017-09-07 16:22:11 +00:00
|
|
|
return &url.URL{
|
|
|
|
Scheme: proto,
|
|
|
|
Host: addr,
|
|
|
|
Path: basePath,
|
|
|
|
}, nil
|
2016-09-06 18:46:37 +00:00
|
|
|
}
|
2016-10-21 05:41:54 +00:00
|
|
|
|
2022-03-07 10:29:38 +00:00
|
|
|
// Dialer returns a dialer for a raw stream connection, with an HTTP/1.1 header,
|
|
|
|
// that can be used for proxying the daemon connection.
|
|
|
|
//
|
2018-03-19 08:33:06 +00:00
|
|
|
// Used by `docker dial-stdio` (docker/cli#889).
|
|
|
|
func (cli *Client) Dialer() func(context.Context) (net.Conn, error) {
|
|
|
|
return func(ctx context.Context) (net.Conn, error) {
|
|
|
|
if transport, ok := cli.client.Transport.(*http.Transport); ok {
|
2018-09-04 22:09:44 +00:00
|
|
|
if transport.DialContext != nil && transport.TLSClientConfig == nil {
|
2018-03-19 08:33:06 +00:00
|
|
|
return transport.DialContext(ctx, cli.proto, cli.addr)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return fallbackDial(cli.proto, cli.addr, resolveTLSConfig(cli.client.Transport))
|
|
|
|
}
|
|
|
|
}
|