diff --git a/src/cmd/linuxkit/docker/cmd.go b/src/cmd/linuxkit/docker/cmd.go index 6e38c23f3..00fdeb374 100644 --- a/src/cmd/linuxkit/docker/cmd.go +++ b/src/cmd/linuxkit/docker/cmd.go @@ -5,23 +5,49 @@ import ( "errors" "io" "os" + "sync" "github.com/containerd/containerd/reference" + "github.com/docker/cli/cli/connhelper" dockertypes "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/container" "github.com/docker/docker/client" log "github.com/sirupsen/logrus" ) +var ( + clientOnce sync.Once + memoizedClient *client.Client + errClient error +) + // Client get a docker client. func Client() (*client.Client, error) { - // for maximum compatibility as we use nothing new - // 1.30 corresponds to Docker 17.06, supported until 2020. - err := os.Setenv("DOCKER_API_VERSION", "1.30") - if err != nil { - return nil, err + clientOnce.Do(func() { + memoizedClient, errClient = createClient() + }) + return memoizedClient, errClient +} + +func createClient() (*client.Client, error) { + options := []client.Opt{ + client.WithAPIVersionNegotiation(), + client.WithTLSClientConfigFromEnv(), + client.WithHostFromEnv(), } - return client.NewEnvClient() + + // Support connection over ssh. + if host := os.Getenv(client.EnvOverrideHost); host != "" { + helper, err := connhelper.GetConnectionHelper(host) + if err != nil { + return nil, err + } + if helper != nil { + options = append(options, client.WithDialContext(helper.Dialer)) + } + } + + return client.NewClientWithOpts(options...) } // HasImage check if the provided ref is available in the docker cache. diff --git a/src/cmd/linuxkit/vendor/github.com/docker/cli/cli/connhelper/connhelper.go b/src/cmd/linuxkit/vendor/github.com/docker/cli/cli/connhelper/connhelper.go new file mode 100644 index 000000000..9ac9d6744 --- /dev/null +++ b/src/cmd/linuxkit/vendor/github.com/docker/cli/cli/connhelper/connhelper.go @@ -0,0 +1,68 @@ +// Package connhelper provides helpers for connecting to a remote daemon host with custom logic. +package connhelper + +import ( + "context" + "net" + "net/url" + + "github.com/docker/cli/cli/connhelper/commandconn" + "github.com/docker/cli/cli/connhelper/ssh" + "github.com/pkg/errors" +) + +// ConnectionHelper allows to connect to a remote host with custom stream provider binary. +type ConnectionHelper struct { + Dialer func(ctx context.Context, network, addr string) (net.Conn, error) + Host string // dummy URL used for HTTP requests. e.g. "http://docker" +} + +// GetConnectionHelper returns Docker-specific connection helper for the given URL. +// GetConnectionHelper returns nil without error when no helper is registered for the scheme. +// +// ssh://@ URL requires Docker 18.09 or later on the remote host. +func GetConnectionHelper(daemonURL string) (*ConnectionHelper, error) { + return getConnectionHelper(daemonURL, nil) +} + +// GetConnectionHelperWithSSHOpts returns Docker-specific connection helper for +// the given URL, and accepts additional options for ssh connections. It returns +// nil without error when no helper is registered for the scheme. +// +// Requires Docker 18.09 or later on the remote host. +func GetConnectionHelperWithSSHOpts(daemonURL string, sshFlags []string) (*ConnectionHelper, error) { + return getConnectionHelper(daemonURL, sshFlags) +} + +func getConnectionHelper(daemonURL string, sshFlags []string) (*ConnectionHelper, error) { + u, err := url.Parse(daemonURL) + if err != nil { + return nil, err + } + switch scheme := u.Scheme; scheme { + case "ssh": + sp, err := ssh.ParseURL(daemonURL) + if err != nil { + return nil, errors.Wrap(err, "ssh host connection is not valid") + } + return &ConnectionHelper{ + Dialer: func(ctx context.Context, network, addr string) (net.Conn, error) { + return commandconn.New(ctx, "ssh", append(sshFlags, sp.Args("docker", "system", "dial-stdio")...)...) + }, + Host: "http://docker.example.com", + }, nil + } + // Future version may support plugins via ~/.docker/config.json. e.g. "dind" + // See docker/cli#889 for the previous discussion. + return nil, err +} + +// GetCommandConnectionHelper returns Docker-specific connection helper constructed from an arbitrary command. +func GetCommandConnectionHelper(cmd string, flags ...string) (*ConnectionHelper, error) { + return &ConnectionHelper{ + Dialer: func(ctx context.Context, network, addr string) (net.Conn, error) { + return commandconn.New(ctx, cmd, flags...) + }, + Host: "http://docker.example.com", + }, nil +} diff --git a/src/cmd/linuxkit/vendor/github.com/docker/cli/cli/connhelper/ssh/ssh.go b/src/cmd/linuxkit/vendor/github.com/docker/cli/cli/connhelper/ssh/ssh.go new file mode 100644 index 000000000..bde01ae7f --- /dev/null +++ b/src/cmd/linuxkit/vendor/github.com/docker/cli/cli/connhelper/ssh/ssh.go @@ -0,0 +1,64 @@ +// Package ssh provides the connection helper for ssh:// URL. +package ssh + +import ( + "net/url" + + "github.com/pkg/errors" +) + +// ParseURL parses URL +func ParseURL(daemonURL string) (*Spec, error) { + u, err := url.Parse(daemonURL) + if err != nil { + return nil, err + } + if u.Scheme != "ssh" { + return nil, errors.Errorf("expected scheme ssh, got %q", u.Scheme) + } + + var sp Spec + + if u.User != nil { + sp.User = u.User.Username() + if _, ok := u.User.Password(); ok { + return nil, errors.New("plain-text password is not supported") + } + } + sp.Host = u.Hostname() + if sp.Host == "" { + return nil, errors.Errorf("no host specified") + } + sp.Port = u.Port() + if u.Path != "" { + return nil, errors.Errorf("extra path after the host: %q", u.Path) + } + if u.RawQuery != "" { + return nil, errors.Errorf("extra query after the host: %q", u.RawQuery) + } + if u.Fragment != "" { + return nil, errors.Errorf("extra fragment after the host: %q", u.Fragment) + } + return &sp, err +} + +// Spec of SSH URL +type Spec struct { + User string + Host string + Port string +} + +// Args returns args except "ssh" itself combined with optional additional command args +func (sp *Spec) Args(add ...string) []string { + var args []string + if sp.User != "" { + args = append(args, "-l", sp.User) + } + if sp.Port != "" { + args = append(args, "-p", sp.Port) + } + args = append(args, "--", sp.Host) + args = append(args, add...) + return args +} diff --git a/src/cmd/linuxkit/vendor/modules.txt b/src/cmd/linuxkit/vendor/modules.txt index ffc4e1c35..4d8a54474 100644 --- a/src/cmd/linuxkit/vendor/modules.txt +++ b/src/cmd/linuxkit/vendor/modules.txt @@ -160,7 +160,9 @@ github.com/docker/cli/cli/config github.com/docker/cli/cli/config/configfile github.com/docker/cli/cli/config/credentials github.com/docker/cli/cli/config/types +github.com/docker/cli/cli/connhelper github.com/docker/cli/cli/connhelper/commandconn +github.com/docker/cli/cli/connhelper/ssh # github.com/docker/distribution v2.8.1+incompatible github.com/docker/distribution github.com/docker/distribution/digestset