github.com/kaisenlinux/docker.io@v0.0.0-20230510090727-ea55db55fac7/engine/client/client.go (about)

     1  /*
     2  Package client is a Go client for the Docker Engine API.
     3  
     4  For more information about the Engine API, see the documentation:
     5  https://docs.docker.com/engine/api/
     6  
     7  # Usage
     8  
     9  You use the library by creating a client object and calling methods on it. The
    10  client can be created either from environment variables with NewClientWithOpts(client.FromEnv),
    11  or configured manually with NewClient().
    12  
    13  For example, to list running containers (the equivalent of "docker ps"):
    14  
    15  	package main
    16  
    17  	import (
    18  		"context"
    19  		"fmt"
    20  
    21  		"github.com/docker/docker/api/types"
    22  		"github.com/docker/docker/client"
    23  	)
    24  
    25  	func main() {
    26  		cli, err := client.NewClientWithOpts(client.FromEnv)
    27  		if err != nil {
    28  			panic(err)
    29  		}
    30  
    31  		containers, err := cli.ContainerList(context.Background(), types.ContainerListOptions{})
    32  		if err != nil {
    33  			panic(err)
    34  		}
    35  
    36  		for _, container := range containers {
    37  			fmt.Printf("%s %s\n", container.ID[:10], container.Image)
    38  		}
    39  	}
    40  */
    41  package client // import "github.com/docker/docker/client"
    42  
    43  import (
    44  	"context"
    45  	"fmt"
    46  	"net"
    47  	"net/http"
    48  	"net/url"
    49  	"path"
    50  	"strings"
    51  
    52  	"github.com/docker/docker/api"
    53  	"github.com/docker/docker/api/types"
    54  	"github.com/docker/docker/api/types/versions"
    55  	"github.com/docker/go-connections/sockets"
    56  	"github.com/pkg/errors"
    57  )
    58  
    59  // ErrRedirect is the error returned by checkRedirect when the request is non-GET.
    60  var ErrRedirect = errors.New("unexpected redirect in response")
    61  
    62  // Client is the API client that performs all operations
    63  // against a docker server.
    64  type Client struct {
    65  	// scheme sets the scheme for the client
    66  	scheme string
    67  	// host holds the server address to connect to
    68  	host string
    69  	// proto holds the client protocol i.e. unix.
    70  	proto string
    71  	// addr holds the client address.
    72  	addr string
    73  	// basePath holds the path to prepend to the requests.
    74  	basePath string
    75  	// client used to send and receive http requests.
    76  	client *http.Client
    77  	// version of the server to talk to.
    78  	version string
    79  	// custom http headers configured by users.
    80  	customHTTPHeaders map[string]string
    81  	// manualOverride is set to true when the version was set by users.
    82  	manualOverride bool
    83  
    84  	// negotiateVersion indicates if the client should automatically negotiate
    85  	// the API version to use when making requests. API version negotiation is
    86  	// performed on the first request, after which negotiated is set to "true"
    87  	// so that subsequent requests do not re-negotiate.
    88  	negotiateVersion bool
    89  
    90  	// negotiated indicates that API version negotiation took place
    91  	negotiated bool
    92  }
    93  
    94  // CheckRedirect specifies the policy for dealing with redirect responses:
    95  // If the request is non-GET return `ErrRedirect`. Otherwise use the last response.
    96  //
    97  // Go 1.8 changes behavior for HTTP redirects (specifically 301, 307, and 308) in the client .
    98  // The Docker client (and by extension docker API client) can be made to send a request
    99  // like POST /containers//start where what would normally be in the name section of the URL is empty.
   100  // This triggers an HTTP 301 from the daemon.
   101  // In go 1.8 this 301 will be converted to a GET request, and ends up getting a 404 from the daemon.
   102  // This behavior change manifests in the client in that before the 301 was not followed and
   103  // the client did not generate an error, but now results in a message like Error response from daemon: page not found.
   104  func CheckRedirect(req *http.Request, via []*http.Request) error {
   105  	if via[0].Method == http.MethodGet {
   106  		return http.ErrUseLastResponse
   107  	}
   108  	return ErrRedirect
   109  }
   110  
   111  // NewClientWithOpts initializes a new API client with default values. It takes functors
   112  // to modify values when creating it, like `NewClientWithOpts(WithVersion(…))`
   113  // It also initializes the custom http headers to add to each request.
   114  //
   115  // It won't send any version information if the version number is empty. It is
   116  // highly recommended that you set a version or your client may break if the
   117  // server is upgraded.
   118  func NewClientWithOpts(ops ...Opt) (*Client, error) {
   119  	client, err := defaultHTTPClient(DefaultDockerHost)
   120  	if err != nil {
   121  		return nil, err
   122  	}
   123  	c := &Client{
   124  		host:    DefaultDockerHost,
   125  		version: api.DefaultVersion,
   126  		client:  client,
   127  		proto:   defaultProto,
   128  		addr:    defaultAddr,
   129  	}
   130  
   131  	for _, op := range ops {
   132  		if err := op(c); err != nil {
   133  			return nil, err
   134  		}
   135  	}
   136  
   137  	if c.scheme == "" {
   138  		c.scheme = "http"
   139  
   140  		tlsConfig := resolveTLSConfig(c.client.Transport)
   141  		if tlsConfig != nil {
   142  			// TODO(stevvooe): This isn't really the right way to write clients in Go.
   143  			// `NewClient` should probably only take an `*http.Client` and work from there.
   144  			// Unfortunately, the model of having a host-ish/url-thingy as the connection
   145  			// string has us confusing protocol and transport layers. We continue doing
   146  			// this to avoid breaking existing clients but this should be addressed.
   147  			c.scheme = "https"
   148  		}
   149  	}
   150  
   151  	return c, nil
   152  }
   153  
   154  func defaultHTTPClient(host string) (*http.Client, error) {
   155  	url, err := ParseHostURL(host)
   156  	if err != nil {
   157  		return nil, err
   158  	}
   159  	transport := new(http.Transport)
   160  	sockets.ConfigureTransport(transport, url.Scheme, url.Host)
   161  	return &http.Client{
   162  		Transport:     transport,
   163  		CheckRedirect: CheckRedirect,
   164  	}, nil
   165  }
   166  
   167  // Close the transport used by the client
   168  func (cli *Client) Close() error {
   169  	if t, ok := cli.client.Transport.(*http.Transport); ok {
   170  		t.CloseIdleConnections()
   171  	}
   172  	return nil
   173  }
   174  
   175  // getAPIPath returns the versioned request path to call the api.
   176  // It appends the query parameters to the path if they are not empty.
   177  func (cli *Client) getAPIPath(ctx context.Context, p string, query url.Values) string {
   178  	var apiPath string
   179  	if cli.negotiateVersion && !cli.negotiated {
   180  		cli.NegotiateAPIVersion(ctx)
   181  	}
   182  	if cli.version != "" {
   183  		v := strings.TrimPrefix(cli.version, "v")
   184  		apiPath = path.Join(cli.basePath, "/v"+v, p)
   185  	} else {
   186  		apiPath = path.Join(cli.basePath, p)
   187  	}
   188  	return (&url.URL{Path: apiPath, RawQuery: query.Encode()}).String()
   189  }
   190  
   191  // ClientVersion returns the API version used by this client.
   192  func (cli *Client) ClientVersion() string {
   193  	return cli.version
   194  }
   195  
   196  // NegotiateAPIVersion queries the API and updates the version to match the
   197  // API version. Any errors are silently ignored. If a manual override is in place,
   198  // either through the `DOCKER_API_VERSION` environment variable, or if the client
   199  // was initialized with a fixed version (`opts.WithVersion(xx)`), no negotiation
   200  // will be performed.
   201  func (cli *Client) NegotiateAPIVersion(ctx context.Context) {
   202  	if !cli.manualOverride {
   203  		ping, _ := cli.Ping(ctx)
   204  		cli.negotiateAPIVersionPing(ping)
   205  	}
   206  }
   207  
   208  // NegotiateAPIVersionPing updates the client version to match the Ping.APIVersion
   209  // if the ping version is less than the default version.  If a manual override is
   210  // in place, either through the `DOCKER_API_VERSION` environment variable, or if
   211  // the client was initialized with a fixed version (`opts.WithVersion(xx)`), no
   212  // negotiation is performed.
   213  func (cli *Client) NegotiateAPIVersionPing(p types.Ping) {
   214  	if !cli.manualOverride {
   215  		cli.negotiateAPIVersionPing(p)
   216  	}
   217  }
   218  
   219  // negotiateAPIVersionPing queries the API and updates the version to match the
   220  // API version. Any errors are silently ignored.
   221  func (cli *Client) negotiateAPIVersionPing(p types.Ping) {
   222  	// try the latest version before versioning headers existed
   223  	if p.APIVersion == "" {
   224  		p.APIVersion = "1.24"
   225  	}
   226  
   227  	// if the client is not initialized with a version, start with the latest supported version
   228  	if cli.version == "" {
   229  		cli.version = api.DefaultVersion
   230  	}
   231  
   232  	// if server version is lower than the client version, downgrade
   233  	if versions.LessThan(p.APIVersion, cli.version) {
   234  		cli.version = p.APIVersion
   235  	}
   236  
   237  	// Store the results, so that automatic API version negotiation (if enabled)
   238  	// won't be performed on the next request.
   239  	if cli.negotiateVersion {
   240  		cli.negotiated = true
   241  	}
   242  }
   243  
   244  // DaemonHost returns the host address used by the client
   245  func (cli *Client) DaemonHost() string {
   246  	return cli.host
   247  }
   248  
   249  // HTTPClient returns a copy of the HTTP client bound to the server
   250  func (cli *Client) HTTPClient() *http.Client {
   251  	c := *cli.client
   252  	return &c
   253  }
   254  
   255  // ParseHostURL parses a url string, validates the string is a host url, and
   256  // returns the parsed URL
   257  func ParseHostURL(host string) (*url.URL, error) {
   258  	protoAddrParts := strings.SplitN(host, "://", 2)
   259  	if len(protoAddrParts) == 1 {
   260  		return nil, fmt.Errorf("unable to parse docker host `%s`", host)
   261  	}
   262  
   263  	var basePath string
   264  	proto, addr := protoAddrParts[0], protoAddrParts[1]
   265  	if proto == "tcp" {
   266  		parsed, err := url.Parse("tcp://" + addr)
   267  		if err != nil {
   268  			return nil, err
   269  		}
   270  		addr = parsed.Host
   271  		basePath = parsed.Path
   272  	}
   273  	return &url.URL{
   274  		Scheme: proto,
   275  		Host:   addr,
   276  		Path:   basePath,
   277  	}, nil
   278  }
   279  
   280  // CustomHTTPHeaders returns the custom http headers stored by the client.
   281  func (cli *Client) CustomHTTPHeaders() map[string]string {
   282  	m := make(map[string]string)
   283  	for k, v := range cli.customHTTPHeaders {
   284  		m[k] = v
   285  	}
   286  	return m
   287  }
   288  
   289  // SetCustomHTTPHeaders that will be set on every HTTP request made by the client.
   290  // Deprecated: use WithHTTPHeaders when creating the client.
   291  func (cli *Client) SetCustomHTTPHeaders(headers map[string]string) {
   292  	cli.customHTTPHeaders = headers
   293  }
   294  
   295  // Dialer returns a dialer for a raw stream connection, with HTTP/1.1 header, that can be used for proxying the daemon connection.
   296  // Used by `docker dial-stdio` (docker/cli#889).
   297  func (cli *Client) Dialer() func(context.Context) (net.Conn, error) {
   298  	return func(ctx context.Context) (net.Conn, error) {
   299  		if transport, ok := cli.client.Transport.(*http.Transport); ok {
   300  			if transport.DialContext != nil && transport.TLSClientConfig == nil {
   301  				return transport.DialContext(ctx, cli.proto, cli.addr)
   302  			}
   303  		}
   304  		return fallbackDial(cli.proto, cli.addr, resolveTLSConfig(cli.client.Transport))
   305  	}
   306  }