mirror of
https://gitea.com/Lydanne/buildx.git
synced 2025-07-10 13:37:08 +08:00
vendor: github.com/moby/buildkit v0.21.0-rc1
Signed-off-by: Jonathan A. Sternberg <jonathan.sternberg@docker.com>
This commit is contained in:
248
vendor/github.com/moby/buildkit/util/testutil/dockerd/client/client.go
generated
vendored
Normal file
248
vendor/github.com/moby/buildkit/util/testutil/dockerd/client/client.go
generated
vendored
Normal file
@ -0,0 +1,248 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// DummyHost is a hostname used for local communication.
|
||||
//
|
||||
// It acts as a valid formatted hostname for local connections (such as "unix://"
|
||||
// or "npipe://") which do not require a hostname. It should never be resolved,
|
||||
// but uses the special-purpose ".localhost" TLD (as defined in [RFC 2606, Section 2]
|
||||
// and [RFC 6761, Section 6.3]).
|
||||
//
|
||||
// [RFC 7230, Section 5.4] defines that an empty header must be used for such
|
||||
// cases:
|
||||
//
|
||||
// If the authority component is missing or undefined for the target URI,
|
||||
// then a client MUST send a Host header field with an empty field-value.
|
||||
//
|
||||
// However, [Go stdlib] enforces the semantics of HTTP(S) over TCP, does not
|
||||
// allow an empty header to be used, and requires req.URL.Scheme to be either
|
||||
// "http" or "https".
|
||||
//
|
||||
// For further details, refer to:
|
||||
//
|
||||
// - https://github.com/docker/engine-api/issues/189
|
||||
// - https://github.com/golang/go/issues/13624
|
||||
// - https://github.com/golang/go/issues/61076
|
||||
// - https://github.com/moby/moby/issues/45935
|
||||
//
|
||||
// [RFC 2606, Section 2]: https://www.rfc-editor.org/rfc/rfc2606.html#section-2
|
||||
// [RFC 6761, Section 6.3]: https://www.rfc-editor.org/rfc/rfc6761#section-6.3
|
||||
// [RFC 7230, Section 5.4]: https://datatracker.ietf.org/doc/html/rfc7230#section-5.4
|
||||
// [Go stdlib]: https://github.com/golang/go/blob/6244b1946bc2101b01955468f1be502dbadd6807/src/net/http/transport.go#L558-L569
|
||||
const DummyHost = "api.moby.localhost"
|
||||
|
||||
// DefaultVersion is the pinned version of the docker API we utilize.
|
||||
const DefaultVersion = "1.47"
|
||||
|
||||
// Client is the API client that performs all operations
|
||||
// against a docker server.
|
||||
type Client struct {
|
||||
// scheme sets the scheme for the client
|
||||
scheme string
|
||||
// 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
|
||||
// client used to send and receive http requests.
|
||||
client *http.Client
|
||||
// version of the server to talk to.
|
||||
version string
|
||||
|
||||
// When the client transport is an *http.Transport (default) we need to do some extra things (like closing idle connections).
|
||||
// Store the original transport as the http.Client transport will be wrapped with tracing libs.
|
||||
baseTransport *http.Transport
|
||||
}
|
||||
|
||||
// ErrRedirect is the error returned by checkRedirect when the request is non-GET.
|
||||
var ErrRedirect = errors.New("unexpected redirect in response")
|
||||
|
||||
// CheckRedirect specifies the policy for dealing with redirect responses. It
|
||||
// can be set on [http.Client.CheckRedirect] to prevent HTTP redirects for
|
||||
// non-GET requests. It returns an [ErrRedirect] for non-GET request, otherwise
|
||||
// returns a [http.ErrUseLastResponse], which is special-cased by http.Client
|
||||
// to use the last response.
|
||||
//
|
||||
// Go 1.8 changed behavior for HTTP redirects (specifically 301, 307, and 308)
|
||||
// in the client. The client (and by extension 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 is 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".
|
||||
func CheckRedirect(_ *http.Request, via []*http.Request) error {
|
||||
if via[0].Method == http.MethodGet {
|
||||
return http.ErrUseLastResponse
|
||||
}
|
||||
return ErrRedirect
|
||||
}
|
||||
|
||||
// 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 ([FromEnv]), and has automatic
|
||||
// API version negotiation enabled ([WithAPIVersionNegotiation]).
|
||||
//
|
||||
// cli, err := client.NewClientWithOpts(
|
||||
// client.FromEnv,
|
||||
// client.WithAPIVersionNegotiation(),
|
||||
// )
|
||||
func NewClientWithOpts(ops ...Opt) (*Client, error) {
|
||||
hostURL, err := ParseHostURL(DefaultDockerHost)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
client, err := defaultHTTPClient(hostURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c := &Client{
|
||||
host: DefaultDockerHost,
|
||||
version: DefaultVersion,
|
||||
client: client,
|
||||
proto: hostURL.Scheme,
|
||||
addr: hostURL.Host,
|
||||
}
|
||||
|
||||
for _, op := range ops {
|
||||
if err := op(c); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if tr, ok := c.client.Transport.(*http.Transport); ok {
|
||||
// Store the base transport before we wrap it in tracing libs below
|
||||
// This is used, as an example, to close idle connections when the client is closed
|
||||
c.baseTransport = tr
|
||||
}
|
||||
|
||||
if c.scheme == "" {
|
||||
// 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.
|
||||
if c.tlsConfig() != nil {
|
||||
c.scheme = "https"
|
||||
} else {
|
||||
c.scheme = "http"
|
||||
}
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func (cli *Client) tlsConfig() *tls.Config {
|
||||
if cli.baseTransport == nil {
|
||||
return nil
|
||||
}
|
||||
return cli.baseTransport.TLSClientConfig
|
||||
}
|
||||
|
||||
func defaultHTTPClient(hostURL *url.URL) (*http.Client, error) {
|
||||
// Necessary to prevent long-lived processes using the
|
||||
// client from leaking connections due to idle connections
|
||||
// not being released.
|
||||
transport := &http.Transport{
|
||||
MaxIdleConns: 6,
|
||||
IdleConnTimeout: 30 * time.Second,
|
||||
}
|
||||
if err := configureTransport(transport, hostURL.Scheme, hostURL.Host); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &http.Client{
|
||||
Transport: transport,
|
||||
CheckRedirect: CheckRedirect,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Close the transport used by the client
|
||||
func (cli *Client) Close() error {
|
||||
if cli.baseTransport != nil {
|
||||
cli.baseTransport.CloseIdleConnections()
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ParseHostURL parses a url string, validates the string is a host url, and
|
||||
// returns the parsed URL
|
||||
func ParseHostURL(host string) (*url.URL, error) {
|
||||
proto, addr, ok := strings.Cut(host, "://")
|
||||
if !ok || addr == "" {
|
||||
return nil, errors.Errorf("unable to parse docker host `%s`", host)
|
||||
}
|
||||
|
||||
var basePath string
|
||||
if proto == "tcp" {
|
||||
parsed, err := url.Parse("tcp://" + addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
addr = parsed.Host
|
||||
basePath = parsed.Path
|
||||
}
|
||||
return &url.URL{
|
||||
Scheme: proto,
|
||||
Host: addr,
|
||||
Path: basePath,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (cli *Client) dialerFromTransport() func(context.Context, string, string) (net.Conn, error) {
|
||||
if cli.baseTransport == nil || cli.baseTransport.DialContext == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if cli.baseTransport.TLSClientConfig != nil {
|
||||
// When using a tls config we don't use the configured dialer but instead a fallback dialer...
|
||||
// Note: It seems like this should use the normal dialer and wrap the returned net.Conn in a tls.Conn
|
||||
// I honestly don't know why it doesn't do that, but it doesn't and such a change is entirely unrelated to the change in this commit.
|
||||
return nil
|
||||
}
|
||||
return cli.baseTransport.DialContext
|
||||
}
|
||||
|
||||
// Dialer returns a dialer for a raw stream connection, with an HTTP/1.1 header,
|
||||
// that can be used for proxying the daemon connection. It is used by
|
||||
// ["docker dial-stdio"].
|
||||
//
|
||||
// ["docker dial-stdio"]: https://github.com/docker/cli/pull/1014
|
||||
func (cli *Client) Dialer() func(context.Context) (net.Conn, error) {
|
||||
return func(ctx context.Context) (net.Conn, error) {
|
||||
if dialFn := cli.dialerFromTransport(); dialFn != nil {
|
||||
return dialFn(ctx, cli.proto, cli.addr)
|
||||
}
|
||||
switch cli.proto {
|
||||
case "unix":
|
||||
return net.Dial(cli.proto, cli.addr)
|
||||
case "npipe":
|
||||
return DialPipe(cli.addr, 32*time.Second)
|
||||
default:
|
||||
if tlsConfig := cli.tlsConfig(); tlsConfig != nil {
|
||||
return tls.Dial(cli.proto, cli.addr, tlsConfig)
|
||||
}
|
||||
return net.Dial(cli.proto, cli.addr)
|
||||
}
|
||||
}
|
||||
}
|
7
vendor/github.com/moby/buildkit/util/testutil/dockerd/client/client_unix.go
generated
vendored
Normal file
7
vendor/github.com/moby/buildkit/util/testutil/dockerd/client/client_unix.go
generated
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
//go:build !windows
|
||||
|
||||
package client
|
||||
|
||||
// DefaultDockerHost defines OS-specific default host if the DOCKER_HOST
|
||||
// (EnvOverrideHost) environment variable is unset or empty.
|
||||
const DefaultDockerHost = "unix:///var/run/docker.sock"
|
5
vendor/github.com/moby/buildkit/util/testutil/dockerd/client/client_windows.go
generated
vendored
Normal file
5
vendor/github.com/moby/buildkit/util/testutil/dockerd/client/client_windows.go
generated
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
package client
|
||||
|
||||
// DefaultDockerHost defines OS-specific default host if the DOCKER_HOST
|
||||
// (EnvOverrideHost) environment variable is unset or empty.
|
||||
const DefaultDockerHost = "npipe:////./pipe/docker_engine"
|
13
vendor/github.com/moby/buildkit/util/testutil/dockerd/client/errors.go
generated
vendored
Normal file
13
vendor/github.com/moby/buildkit/util/testutil/dockerd/client/errors.go
generated
vendored
Normal file
@ -0,0 +1,13 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// ErrorConnectionFailed returns an error with host in the error message when connection to docker daemon failed.
|
||||
func ErrorConnectionFailed(host string) error {
|
||||
if host == "" {
|
||||
return errors.New("Cannot connect to the Docker daemon. Is the docker daemon running on this host?")
|
||||
}
|
||||
return errors.Errorf("Cannot connect to the Docker daemon at %s. Is the docker daemon running?", host)
|
||||
}
|
118
vendor/github.com/moby/buildkit/util/testutil/dockerd/client/hijack.go
generated
vendored
Normal file
118
vendor/github.com/moby/buildkit/util/testutil/dockerd/client/hijack.go
generated
vendored
Normal file
@ -0,0 +1,118 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"net"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// DialHijack returns a hijacked connection with negotiated protocol proto.
|
||||
func (cli *Client) DialHijack(ctx context.Context, url, proto string, meta map[string][]string) (net.Conn, error) {
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
conn, _, err := cli.setupHijackConn(req, proto)
|
||||
return conn, err
|
||||
}
|
||||
|
||||
func (cli *Client) setupHijackConn(req *http.Request, proto string) (_ net.Conn, _ string, retErr error) {
|
||||
ctx := req.Context()
|
||||
req.Header.Set("Connection", "Upgrade")
|
||||
req.Header.Set("Upgrade", proto)
|
||||
|
||||
dialer := cli.Dialer()
|
||||
conn, err := dialer(ctx)
|
||||
if err != nil {
|
||||
return nil, "", errors.Wrap(err, "cannot connect to the Docker daemon. Is 'docker daemon' running on this host?")
|
||||
}
|
||||
defer func() {
|
||||
if retErr != nil {
|
||||
conn.Close()
|
||||
}
|
||||
}()
|
||||
|
||||
// When we set up a TCP connection for hijack, there could be long periods
|
||||
// of inactivity (a long running command with no output) that in certain
|
||||
// network setups may cause ECONNTIMEOUT, leaving the client in an unknown
|
||||
// state. Setting TCP KeepAlive on the socket connection will prohibit
|
||||
// ECONNTIMEOUT unless the socket connection truly is broken
|
||||
if tcpConn, ok := conn.(*net.TCPConn); ok {
|
||||
_ = tcpConn.SetKeepAlive(true)
|
||||
_ = tcpConn.SetKeepAlivePeriod(30 * time.Second)
|
||||
}
|
||||
|
||||
hc := &hijackedConn{conn, bufio.NewReader(conn)}
|
||||
|
||||
// Server hijacks the connection, error 'connection closed' expected
|
||||
resp, err := hc.RoundTrip(req)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
if resp.StatusCode != http.StatusSwitchingProtocols {
|
||||
_ = resp.Body.Close()
|
||||
return nil, "", errors.Errorf("unable to upgrade to %s, received %d", proto, resp.StatusCode)
|
||||
}
|
||||
|
||||
if hc.r.Buffered() > 0 {
|
||||
// If there is buffered content, wrap the connection. We return an
|
||||
// object that implements CloseWrite if the underlying connection
|
||||
// implements it.
|
||||
if _, ok := hc.Conn.(CloseWriter); ok {
|
||||
conn = &hijackedConnCloseWriter{hc}
|
||||
} else {
|
||||
conn = hc
|
||||
}
|
||||
} else {
|
||||
hc.r.Reset(nil)
|
||||
}
|
||||
|
||||
mediaType := resp.Header.Get("Content-Type")
|
||||
return conn, mediaType, nil
|
||||
}
|
||||
|
||||
// CloseWriter is an interface that implements structs
|
||||
// that close input streams to prevent from writing.
|
||||
type CloseWriter interface {
|
||||
CloseWrite() error
|
||||
}
|
||||
|
||||
// hijackedConn wraps a net.Conn and is returned by setupHijackConn in the case
|
||||
// that a) there was already buffered data in the http layer when Hijack() was
|
||||
// called, and b) the underlying net.Conn does *not* implement CloseWrite().
|
||||
// hijackedConn does not implement CloseWrite() either.
|
||||
type hijackedConn struct {
|
||||
net.Conn
|
||||
r *bufio.Reader
|
||||
}
|
||||
|
||||
func (c *hijackedConn) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
if err := req.Write(c.Conn); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return http.ReadResponse(c.r, req)
|
||||
}
|
||||
|
||||
func (c *hijackedConn) Read(b []byte) (int, error) {
|
||||
return c.r.Read(b)
|
||||
}
|
||||
|
||||
// hijackedConnCloseWriter is a hijackedConn which additionally implements
|
||||
// CloseWrite(). It is returned by setupHijackConn in the case that a) there
|
||||
// was already buffered data in the http layer when Hijack() was called, and b)
|
||||
// the underlying net.Conn *does* implement CloseWrite().
|
||||
type hijackedConnCloseWriter struct {
|
||||
*hijackedConn
|
||||
}
|
||||
|
||||
var _ CloseWriter = &hijackedConnCloseWriter{}
|
||||
|
||||
func (c *hijackedConnCloseWriter) CloseWrite() error {
|
||||
conn := c.Conn.(CloseWriter)
|
||||
return conn.CloseWrite()
|
||||
}
|
28
vendor/github.com/moby/buildkit/util/testutil/dockerd/client/options.go
generated
vendored
Normal file
28
vendor/github.com/moby/buildkit/util/testutil/dockerd/client/options.go
generated
vendored
Normal file
@ -0,0 +1,28 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// Opt is a configuration option to initialize a [Client].
|
||||
type Opt func(*Client) error
|
||||
|
||||
// WithHost overrides the client host with the specified one.
|
||||
func WithHost(host string) Opt {
|
||||
return func(c *Client) error {
|
||||
hostURL, err := ParseHostURL(host)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.host = host
|
||||
c.proto = hostURL.Scheme
|
||||
c.addr = hostURL.Host
|
||||
c.basePath = hostURL.Path
|
||||
if transport, ok := c.client.Transport.(*http.Transport); ok {
|
||||
return configureTransport(transport, c.proto, c.addr)
|
||||
}
|
||||
return errors.Errorf("cannot apply host to transport: %T", c.client.Transport)
|
||||
}
|
||||
}
|
25
vendor/github.com/moby/buildkit/util/testutil/dockerd/client/ping.go
generated
vendored
Normal file
25
vendor/github.com/moby/buildkit/util/testutil/dockerd/client/ping.go
generated
vendored
Normal file
@ -0,0 +1,25 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"path"
|
||||
)
|
||||
|
||||
type PingResponse struct{}
|
||||
|
||||
func (cli *Client) Ping(ctx context.Context) error {
|
||||
// Using cli.buildRequest() + cli.doRequest() instead of cli.sendRequest()
|
||||
// because ping requests are used during API version negotiation, so we want
|
||||
// to hit the non-versioned /_ping endpoint, not /v1.xx/_ping
|
||||
req, err := cli.buildRequest(ctx, http.MethodHead, path.Join(cli.basePath, "/_ping"), nil, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
serverResp, err := cli.doRequest(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer ensureReaderClosed(serverResp)
|
||||
return cli.checkResponseErr(serverResp)
|
||||
}
|
162
vendor/github.com/moby/buildkit/util/testutil/dockerd/client/request.go
generated
vendored
Normal file
162
vendor/github.com/moby/buildkit/util/testutil/dockerd/client/request.go
generated
vendored
Normal file
@ -0,0 +1,162 @@
|
||||
//nolint:forbidigo
|
||||
package client
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func (cli *Client) buildRequest(ctx context.Context, method, path string, body io.Reader, headers http.Header) (*http.Request, error) {
|
||||
req, err := http.NewRequestWithContext(ctx, method, path, body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req = cli.addHeaders(req, headers)
|
||||
req.URL.Scheme = cli.scheme
|
||||
req.URL.Host = cli.addr
|
||||
|
||||
if cli.proto == "unix" || cli.proto == "npipe" {
|
||||
// Override host header for non-tcp connections.
|
||||
req.Host = DummyHost
|
||||
}
|
||||
|
||||
if body != nil && req.Header.Get("Content-Type") == "" {
|
||||
req.Header.Set("Content-Type", "text/plain")
|
||||
}
|
||||
return req, nil
|
||||
}
|
||||
|
||||
func (cli *Client) doRequest(req *http.Request) (*http.Response, error) {
|
||||
resp, err := cli.client.Do(req)
|
||||
if err != nil {
|
||||
if cli.scheme != "https" && strings.Contains(err.Error(), "malformed HTTP response") {
|
||||
return nil, errors.Errorf("%v.\n* Are you trying to connect to a TLS-enabled daemon without TLS?", err)
|
||||
}
|
||||
|
||||
if cli.scheme == "https" && strings.Contains(err.Error(), "bad certificate") {
|
||||
return nil, errors.Wrap(err, "the server probably has client authentication (--tlsverify) enabled; check your TLS client certification settings")
|
||||
}
|
||||
|
||||
// Don't decorate context sentinel errors; users may be comparing to
|
||||
// them directly.
|
||||
if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if uErr, ok := err.(*url.Error); ok {
|
||||
if nErr, ok := uErr.Err.(*net.OpError); ok {
|
||||
if os.IsPermission(nErr.Err) {
|
||||
return nil, errors.Wrapf(err, "permission denied while trying to connect to the Docker daemon socket at %v", cli.host)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if nErr, ok := err.(net.Error); ok {
|
||||
// FIXME(thaJeztah): any net.Error should be considered a connection error (but we should include the original error)?
|
||||
if nErr.Timeout() {
|
||||
return nil, ErrorConnectionFailed(cli.host)
|
||||
}
|
||||
if strings.Contains(nErr.Error(), "connection refused") || strings.Contains(nErr.Error(), "dial unix") {
|
||||
return nil, ErrorConnectionFailed(cli.host)
|
||||
}
|
||||
}
|
||||
|
||||
// Although there's not a strongly typed error for this in go-winio,
|
||||
// lots of people are using the default configuration for the docker
|
||||
// daemon on Windows where the daemon is listening on a named pipe
|
||||
// `//./pipe/docker_engine, and the client must be running elevated.
|
||||
// Give users a clue rather than the not-overly useful message
|
||||
// such as `error during connect: Get http://%2F%2F.%2Fpipe%2Fdocker_engine/v1.26/info:
|
||||
// open //./pipe/docker_engine: The system cannot find the file specified.`.
|
||||
// Note we can't string compare "The system cannot find the file specified" as
|
||||
// this is localised - for example in French the error would be
|
||||
// `open //./pipe/docker_engine: Le fichier spécifié est introuvable.`
|
||||
if strings.Contains(err.Error(), `open //./pipe/docker_engine`) {
|
||||
// Checks if client is running with elevated privileges
|
||||
if f, elevatedErr := os.Open(`\\.\PHYSICALDRIVE0`); elevatedErr != nil {
|
||||
err = errors.Wrap(err, "in the default daemon configuration on Windows, the docker client must be run with elevated privileges to connect")
|
||||
} else {
|
||||
_ = f.Close()
|
||||
err = errors.Wrap(err, "this error may indicate that the docker daemon is not running")
|
||||
}
|
||||
}
|
||||
|
||||
return nil, errors.Wrap(err, "error during connect")
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (cli *Client) checkResponseErr(serverResp *http.Response) error {
|
||||
if serverResp == nil {
|
||||
return nil
|
||||
}
|
||||
if serverResp.StatusCode >= 200 && serverResp.StatusCode < 400 {
|
||||
return nil
|
||||
}
|
||||
|
||||
var body []byte
|
||||
var err error
|
||||
var reqURL string
|
||||
if serverResp.Request != nil {
|
||||
reqURL = serverResp.Request.URL.String()
|
||||
}
|
||||
statusMsg := serverResp.Status
|
||||
if statusMsg == "" {
|
||||
statusMsg = http.StatusText(serverResp.StatusCode)
|
||||
}
|
||||
if serverResp.Body != nil {
|
||||
bodyMax := 1 * 1024 * 1024 // 1 MiB
|
||||
bodyR := &io.LimitedReader{
|
||||
R: serverResp.Body,
|
||||
N: int64(bodyMax),
|
||||
}
|
||||
body, err = io.ReadAll(bodyR)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if bodyR.N == 0 {
|
||||
return fmt.Errorf("request returned %s with a message (> %d bytes) for API route and version %s, check if the server supports the requested API version", statusMsg, bodyMax, reqURL)
|
||||
}
|
||||
}
|
||||
if len(body) == 0 {
|
||||
return fmt.Errorf("request returned %s for API route and version %s, check if the server supports the requested API version", statusMsg, reqURL)
|
||||
}
|
||||
|
||||
var daemonErr error
|
||||
if serverResp.Header.Get("Content-Type") == "application/json" {
|
||||
var errorResponse struct {
|
||||
Message string `json:"message"`
|
||||
}
|
||||
if err := json.Unmarshal(body, &errorResponse); err != nil {
|
||||
return errors.Wrap(err, "Error reading JSON")
|
||||
}
|
||||
daemonErr = errors.New(strings.TrimSpace(errorResponse.Message))
|
||||
} else {
|
||||
daemonErr = errors.New(strings.TrimSpace(string(body)))
|
||||
}
|
||||
return errors.Wrap(daemonErr, "Error response from daemon")
|
||||
}
|
||||
|
||||
func (cli *Client) addHeaders(req *http.Request, headers http.Header) *http.Request {
|
||||
for k, v := range headers {
|
||||
req.Header[http.CanonicalHeaderKey(k)] = v
|
||||
}
|
||||
return req
|
||||
}
|
||||
|
||||
func ensureReaderClosed(response *http.Response) {
|
||||
if response.Body != nil {
|
||||
// Drain up to 512 bytes and close the body to let the Transport reuse the connection
|
||||
_, _ = io.CopyN(io.Discard, response.Body, 512)
|
||||
_ = response.Body.Close()
|
||||
}
|
||||
}
|
32
vendor/github.com/moby/buildkit/util/testutil/dockerd/client/sockets.go
generated
vendored
Normal file
32
vendor/github.com/moby/buildkit/util/testutil/dockerd/client/sockets.go
generated
vendored
Normal file
@ -0,0 +1,32 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"net"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
const defaultTimeout = 10 * time.Second
|
||||
|
||||
// configureTransport configures the specified [http.Transport] according to the specified proto
|
||||
// and addr.
|
||||
//
|
||||
// If the proto is unix (using a unix socket to communicate) or npipe the compression is disabled.
|
||||
// For other protos, compression is enabled. If you want to manually enable/disable compression,
|
||||
// make sure you do it _after_ any subsequent calls to ConfigureTransport is made against the same
|
||||
// [http.Transport].
|
||||
func configureTransport(tr *http.Transport, proto, addr string) error {
|
||||
switch proto {
|
||||
case "unix":
|
||||
return configureUnixTransport(tr, proto, addr)
|
||||
case "npipe":
|
||||
return configureNpipeTransport(tr, proto, addr)
|
||||
default:
|
||||
tr.Proxy = http.ProxyFromEnvironment
|
||||
tr.DisableCompression = false
|
||||
tr.DialContext = (&net.Dialer{
|
||||
Timeout: defaultTimeout,
|
||||
}).DialContext
|
||||
}
|
||||
return nil
|
||||
}
|
40
vendor/github.com/moby/buildkit/util/testutil/dockerd/client/sockets_unix.go
generated
vendored
Normal file
40
vendor/github.com/moby/buildkit/util/testutil/dockerd/client/sockets_unix.go
generated
vendored
Normal file
@ -0,0 +1,40 @@
|
||||
//go:build !windows
|
||||
|
||||
package client
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"net/http"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
const maxUnixSocketPathSize = len(syscall.RawSockaddrUnix{}.Path)
|
||||
|
||||
func configureUnixTransport(tr *http.Transport, proto, addr string) error {
|
||||
if len(addr) > maxUnixSocketPathSize {
|
||||
return errors.Errorf("unix socket path %q is too long", addr)
|
||||
}
|
||||
// No need for compression in local communications.
|
||||
tr.DisableCompression = true
|
||||
dialer := &net.Dialer{
|
||||
Timeout: defaultTimeout,
|
||||
}
|
||||
tr.DialContext = func(ctx context.Context, _, _ string) (net.Conn, error) {
|
||||
return dialer.DialContext(ctx, proto, addr)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func configureNpipeTransport(_ *http.Transport, _, _ string) error {
|
||||
return errors.New("protocol not available")
|
||||
}
|
||||
|
||||
// DialPipe connects to a Windows named pipe.
|
||||
// This is not supported on other OSes.
|
||||
func DialPipe(_ string, _ time.Duration) (net.Conn, error) {
|
||||
return nil, syscall.EAFNOSUPPORT
|
||||
}
|
29
vendor/github.com/moby/buildkit/util/testutil/dockerd/client/sockets_windows.go
generated
vendored
Normal file
29
vendor/github.com/moby/buildkit/util/testutil/dockerd/client/sockets_windows.go
generated
vendored
Normal file
@ -0,0 +1,29 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/Microsoft/go-winio"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func configureUnixTransport(_ *http.Transport, _, _ string) error {
|
||||
return errors.New("protocol not available")
|
||||
}
|
||||
|
||||
func configureNpipeTransport(tr *http.Transport, _, addr string) error {
|
||||
// No need for compression in local communications.
|
||||
tr.DisableCompression = true
|
||||
tr.DialContext = func(ctx context.Context, _, _ string) (net.Conn, error) {
|
||||
return winio.DialPipeContext(ctx, addr)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DialPipe connects to a Windows named pipe.
|
||||
func DialPipe(addr string, timeout time.Duration) (net.Conn, error) {
|
||||
return winio.DialPipe(addr, &timeout)
|
||||
}
|
6
vendor/github.com/moby/buildkit/util/testutil/dockerd/daemon.go
generated
vendored
6
vendor/github.com/moby/buildkit/util/testutil/dockerd/daemon.go
generated
vendored
@ -16,12 +16,12 @@ import (
|
||||
)
|
||||
|
||||
type LogT interface {
|
||||
Logf(string, ...interface{})
|
||||
Logf(string, ...any)
|
||||
}
|
||||
|
||||
type nopLog struct{}
|
||||
|
||||
func (nopLog) Logf(string, ...interface{}) {}
|
||||
func (nopLog) Logf(string, ...any) {}
|
||||
|
||||
const (
|
||||
shortLen = 12
|
||||
@ -73,7 +73,7 @@ func NewDaemon(workingDir string, ops ...Option) (*Daemon, error) {
|
||||
dockerdBinary: DefaultDockerdBinary,
|
||||
Log: nopLog{},
|
||||
sockPath: filepath.Join(sockRoot, id+".sock"),
|
||||
envs: append([]string{}, os.Environ()...),
|
||||
envs: os.Environ(),
|
||||
}
|
||||
|
||||
for _, op := range ops {
|
||||
|
Reference in New Issue
Block a user