mirror of https://github.com/docker/cli.git
support SSH connection
e.g. docker -H ssh://me@server The `docker` CLI also needs to be installed on the remote host to provide `docker system dial-stdio`, which proxies the daemon socket to stdio. Please refer to docs/reference/commandline/dockerd.md . Signed-off-by: Akihiro Suda <suda.akihiro@lab.ntt.co.jp>
This commit is contained in:
parent
261ff66d61
commit
6f61cf053a
|
@ -14,6 +14,7 @@ import (
|
||||||
"github.com/docker/cli/cli/config"
|
"github.com/docker/cli/cli/config"
|
||||||
cliconfig "github.com/docker/cli/cli/config"
|
cliconfig "github.com/docker/cli/cli/config"
|
||||||
"github.com/docker/cli/cli/config/configfile"
|
"github.com/docker/cli/cli/config/configfile"
|
||||||
|
"github.com/docker/cli/cli/connhelper"
|
||||||
cliflags "github.com/docker/cli/cli/flags"
|
cliflags "github.com/docker/cli/cli/flags"
|
||||||
manifeststore "github.com/docker/cli/cli/manifest/store"
|
manifeststore "github.com/docker/cli/cli/manifest/store"
|
||||||
registryclient "github.com/docker/cli/cli/registry/client"
|
registryclient "github.com/docker/cli/cli/registry/client"
|
||||||
|
@ -248,31 +249,54 @@ func NewDockerCli(in io.ReadCloser, out, err io.Writer, isTrusted bool) *DockerC
|
||||||
|
|
||||||
// NewAPIClientFromFlags creates a new APIClient from command line flags
|
// NewAPIClientFromFlags creates a new APIClient from command line flags
|
||||||
func NewAPIClientFromFlags(opts *cliflags.CommonOptions, configFile *configfile.ConfigFile) (client.APIClient, error) {
|
func NewAPIClientFromFlags(opts *cliflags.CommonOptions, configFile *configfile.ConfigFile) (client.APIClient, error) {
|
||||||
host, err := getServerHost(opts.Hosts, opts.TLSOptions)
|
unparsedHost, err := getUnparsedServerHost(opts.Hosts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &client.Client{}, err
|
return &client.Client{}, err
|
||||||
}
|
}
|
||||||
|
var clientOpts []func(*client.Client) error
|
||||||
|
helper, err := connhelper.GetConnectionHelper(unparsedHost)
|
||||||
|
if err != nil {
|
||||||
|
return &client.Client{}, err
|
||||||
|
}
|
||||||
|
if helper == nil {
|
||||||
|
clientOpts = append(clientOpts, withHTTPClient(opts.TLSOptions))
|
||||||
|
host, err := dopts.ParseHost(opts.TLSOptions != nil, unparsedHost)
|
||||||
|
if err != nil {
|
||||||
|
return &client.Client{}, err
|
||||||
|
}
|
||||||
|
clientOpts = append(clientOpts, client.WithHost(host))
|
||||||
|
} else {
|
||||||
|
clientOpts = append(clientOpts, func(c *client.Client) error {
|
||||||
|
httpClient := &http.Client{
|
||||||
|
// No tls
|
||||||
|
// No proxy
|
||||||
|
Transport: &http.Transport{
|
||||||
|
DialContext: helper.Dialer,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return client.WithHTTPClient(httpClient)(c)
|
||||||
|
})
|
||||||
|
clientOpts = append(clientOpts, client.WithHost(helper.Host))
|
||||||
|
clientOpts = append(clientOpts, client.WithDialContext(helper.Dialer))
|
||||||
|
}
|
||||||
|
|
||||||
customHeaders := configFile.HTTPHeaders
|
customHeaders := configFile.HTTPHeaders
|
||||||
if customHeaders == nil {
|
if customHeaders == nil {
|
||||||
customHeaders = map[string]string{}
|
customHeaders = map[string]string{}
|
||||||
}
|
}
|
||||||
customHeaders["User-Agent"] = UserAgent()
|
customHeaders["User-Agent"] = UserAgent()
|
||||||
|
clientOpts = append(clientOpts, client.WithHTTPHeaders(customHeaders))
|
||||||
|
|
||||||
verStr := api.DefaultVersion
|
verStr := api.DefaultVersion
|
||||||
if tmpStr := os.Getenv("DOCKER_API_VERSION"); tmpStr != "" {
|
if tmpStr := os.Getenv("DOCKER_API_VERSION"); tmpStr != "" {
|
||||||
verStr = tmpStr
|
verStr = tmpStr
|
||||||
}
|
}
|
||||||
|
clientOpts = append(clientOpts, client.WithVersion(verStr))
|
||||||
|
|
||||||
return client.NewClientWithOpts(
|
return client.NewClientWithOpts(clientOpts...)
|
||||||
withHTTPClient(opts.TLSOptions),
|
|
||||||
client.WithHTTPHeaders(customHeaders),
|
|
||||||
client.WithVersion(verStr),
|
|
||||||
client.WithHost(host),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func getServerHost(hosts []string, tlsOptions *tlsconfig.Options) (string, error) {
|
func getUnparsedServerHost(hosts []string) (string, error) {
|
||||||
var host string
|
var host string
|
||||||
switch len(hosts) {
|
switch len(hosts) {
|
||||||
case 0:
|
case 0:
|
||||||
|
@ -282,8 +306,7 @@ func getServerHost(hosts []string, tlsOptions *tlsconfig.Options) (string, error
|
||||||
default:
|
default:
|
||||||
return "", errors.New("Please specify only one -H")
|
return "", errors.New("Please specify only one -H")
|
||||||
}
|
}
|
||||||
|
return host, nil
|
||||||
return dopts.ParseHost(tlsOptions != nil, host)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func withHTTPClient(tlsOpts *tlsconfig.Options) func(*client.Client) error {
|
func withHTTPClient(tlsOpts *tlsconfig.Options) func(*client.Client) error {
|
||||||
|
|
|
@ -19,6 +19,7 @@ func NewSystemCommand(dockerCli command.Cli) *cobra.Command {
|
||||||
NewInfoCommand(dockerCli),
|
NewInfoCommand(dockerCli),
|
||||||
newDiskUsageCommand(dockerCli),
|
newDiskUsageCommand(dockerCli),
|
||||||
newPruneCommand(dockerCli),
|
newPruneCommand(dockerCli),
|
||||||
|
newDialStdioCommand(dockerCli),
|
||||||
)
|
)
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
|
|
|
@ -0,0 +1,107 @@
|
||||||
|
package system
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/docker/cli/cli"
|
||||||
|
"github.com/docker/cli/cli/command"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
// newDialStdioCommand creates a new cobra.Command for `docker system dial-stdio`
|
||||||
|
func newDialStdioCommand(dockerCli command.Cli) *cobra.Command {
|
||||||
|
cmd := &cobra.Command{
|
||||||
|
Use: "dial-stdio",
|
||||||
|
Short: "Proxy the stdio stream to the daemon connection. Should not be invoked manually.",
|
||||||
|
Args: cli.NoArgs,
|
||||||
|
Hidden: true,
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
return runDialStdio(dockerCli)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
func runDialStdio(dockerCli command.Cli) error {
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
defer cancel()
|
||||||
|
dialer := dockerCli.Client().Dialer()
|
||||||
|
conn, err := dialer(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "failed to open the raw stream connection")
|
||||||
|
}
|
||||||
|
connHalfCloser, ok := conn.(halfCloser)
|
||||||
|
if !ok {
|
||||||
|
return errors.New("the raw stream connection does not implement halfCloser")
|
||||||
|
}
|
||||||
|
stdin2conn := make(chan error)
|
||||||
|
conn2stdout := make(chan error)
|
||||||
|
go func() {
|
||||||
|
stdin2conn <- copier(connHalfCloser, &halfReadCloserWrapper{os.Stdin}, "stdin to stream")
|
||||||
|
}()
|
||||||
|
go func() {
|
||||||
|
conn2stdout <- copier(&halfWriteCloserWrapper{os.Stdout}, connHalfCloser, "stream to stdout")
|
||||||
|
}()
|
||||||
|
select {
|
||||||
|
case err = <-stdin2conn:
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// wait for stdout
|
||||||
|
err = <-conn2stdout
|
||||||
|
case err = <-conn2stdout:
|
||||||
|
// return immediately without waiting for stdin to be closed.
|
||||||
|
// (stdin is never closed when tty)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func copier(to halfWriteCloser, from halfReadCloser, debugDescription string) error {
|
||||||
|
defer func() {
|
||||||
|
if err := from.CloseRead(); err != nil {
|
||||||
|
logrus.Errorf("error while CloseRead (%s): %v", debugDescription, err)
|
||||||
|
}
|
||||||
|
if err := to.CloseWrite(); err != nil {
|
||||||
|
logrus.Errorf("error while CloseWrite (%s): %v", debugDescription, err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
if _, err := io.Copy(to, from); err != nil {
|
||||||
|
return errors.Wrapf(err, "error while Copy (%s)", debugDescription)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type halfReadCloser interface {
|
||||||
|
io.Reader
|
||||||
|
CloseRead() error
|
||||||
|
}
|
||||||
|
|
||||||
|
type halfWriteCloser interface {
|
||||||
|
io.Writer
|
||||||
|
CloseWrite() error
|
||||||
|
}
|
||||||
|
|
||||||
|
type halfCloser interface {
|
||||||
|
halfReadCloser
|
||||||
|
halfWriteCloser
|
||||||
|
}
|
||||||
|
|
||||||
|
type halfReadCloserWrapper struct {
|
||||||
|
io.ReadCloser
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *halfReadCloserWrapper) CloseRead() error {
|
||||||
|
return x.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
type halfWriteCloserWrapper struct {
|
||||||
|
io.WriteCloser
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *halfWriteCloserWrapper) CloseWrite() error {
|
||||||
|
return x.Close()
|
||||||
|
}
|
|
@ -0,0 +1,239 @@
|
||||||
|
// Package connhelper provides helpers for connecting to a remote daemon host with custom logic.
|
||||||
|
package connhelper
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/docker/cli/cli/connhelper/ssh"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
// URL is like "ssh://me@server01".
|
||||||
|
func GetConnectionHelper(daemonURL string) (*ConnectionHelper, error) {
|
||||||
|
u, err := url.Parse(daemonURL)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
switch scheme := u.Scheme; scheme {
|
||||||
|
case "ssh":
|
||||||
|
sshCmd, sshArgs, err := ssh.New(daemonURL)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &ConnectionHelper{
|
||||||
|
Dialer: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||||
|
return newCommandConn(ctx, sshCmd, sshArgs...)
|
||||||
|
},
|
||||||
|
Host: "http://docker",
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
// Future version may support plugins via ~/.docker/config.json. e.g. "dind"
|
||||||
|
// See docker/cli#889 for the previous discussion.
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func newCommandConn(ctx context.Context, cmd string, args ...string) (net.Conn, error) {
|
||||||
|
var (
|
||||||
|
c commandConn
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
c.cmd = exec.CommandContext(ctx, cmd, args...)
|
||||||
|
// we assume that args never contains sensitive information
|
||||||
|
logrus.Debugf("connhelper: starting %s with %v", cmd, args)
|
||||||
|
c.cmd.Env = os.Environ()
|
||||||
|
setPdeathsig(c.cmd)
|
||||||
|
c.stdin, err = c.cmd.StdinPipe()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
c.stdout, err = c.cmd.StdoutPipe()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
c.cmd.Stderr = &stderrWriter{
|
||||||
|
stderrMu: &c.stderrMu,
|
||||||
|
stderr: &c.stderr,
|
||||||
|
debugPrefix: fmt.Sprintf("connhelper (%s):", cmd),
|
||||||
|
}
|
||||||
|
c.localAddr = dummyAddr{network: "dummy", s: "dummy-0"}
|
||||||
|
c.remoteAddr = dummyAddr{network: "dummy", s: "dummy-1"}
|
||||||
|
return &c, c.cmd.Start()
|
||||||
|
}
|
||||||
|
|
||||||
|
// commandConn implements net.Conn
|
||||||
|
type commandConn struct {
|
||||||
|
cmd *exec.Cmd
|
||||||
|
stdin io.WriteCloser
|
||||||
|
stdout io.ReadCloser
|
||||||
|
stderrMu sync.Mutex
|
||||||
|
stderr bytes.Buffer
|
||||||
|
stdioClosedMu sync.Mutex // for stdinClosed and stdoutClosed
|
||||||
|
stdinClosed bool
|
||||||
|
stdoutClosed bool
|
||||||
|
localAddr net.Addr
|
||||||
|
remoteAddr net.Addr
|
||||||
|
}
|
||||||
|
|
||||||
|
// killIfStdioClosed kills the cmd if both stdin and stdout are closed.
|
||||||
|
func (c *commandConn) killIfStdioClosed() error {
|
||||||
|
c.stdioClosedMu.Lock()
|
||||||
|
stdioClosed := c.stdoutClosed && c.stdinClosed
|
||||||
|
c.stdioClosedMu.Unlock()
|
||||||
|
if !stdioClosed {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
var err error
|
||||||
|
// NOTE: maybe already killed here
|
||||||
|
if err = c.cmd.Process.Kill(); err == nil {
|
||||||
|
err = c.cmd.Wait()
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
// err is typically "os: process already finished".
|
||||||
|
// we check ProcessState here instead of `strings.Contains(err, "os: process already finished")`
|
||||||
|
if c.cmd.ProcessState.Exited() {
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *commandConn) onEOF(eof error) error {
|
||||||
|
werr := c.cmd.Wait()
|
||||||
|
if werr == nil {
|
||||||
|
return eof
|
||||||
|
}
|
||||||
|
c.stderrMu.Lock()
|
||||||
|
stderr := c.stderr.String()
|
||||||
|
c.stderrMu.Unlock()
|
||||||
|
return errors.Errorf("command %v has exited with %v, please make sure the URL is valid, and Docker 18.09 or later is installed on the remote host: stderr=%q", c.cmd.Args, werr, stderr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ignorableCloseError(err error) bool {
|
||||||
|
errS := err.Error()
|
||||||
|
ss := []string{
|
||||||
|
os.ErrClosed.Error(),
|
||||||
|
}
|
||||||
|
for _, s := range ss {
|
||||||
|
if strings.Contains(errS, s) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *commandConn) CloseRead() error {
|
||||||
|
// NOTE: maybe already closed here
|
||||||
|
if err := c.stdout.Close(); err != nil && !ignorableCloseError(err) {
|
||||||
|
logrus.Warnf("commandConn.CloseRead: %v", err)
|
||||||
|
}
|
||||||
|
c.stdioClosedMu.Lock()
|
||||||
|
c.stdoutClosed = true
|
||||||
|
c.stdioClosedMu.Unlock()
|
||||||
|
return c.killIfStdioClosed()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *commandConn) Read(p []byte) (int, error) {
|
||||||
|
n, err := c.stdout.Read(p)
|
||||||
|
if err == io.EOF {
|
||||||
|
err = c.onEOF(err)
|
||||||
|
}
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *commandConn) CloseWrite() error {
|
||||||
|
// NOTE: maybe already closed here
|
||||||
|
if err := c.stdin.Close(); err != nil && !ignorableCloseError(err) {
|
||||||
|
logrus.Warnf("commandConn.CloseWrite: %v", err)
|
||||||
|
}
|
||||||
|
c.stdioClosedMu.Lock()
|
||||||
|
c.stdinClosed = true
|
||||||
|
c.stdioClosedMu.Unlock()
|
||||||
|
return c.killIfStdioClosed()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *commandConn) Write(p []byte) (int, error) {
|
||||||
|
n, err := c.stdin.Write(p)
|
||||||
|
if err == io.EOF {
|
||||||
|
err = c.onEOF(err)
|
||||||
|
}
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *commandConn) Close() error {
|
||||||
|
var err error
|
||||||
|
if err = c.CloseRead(); err != nil {
|
||||||
|
logrus.Warnf("commandConn.Close: CloseRead: %v", err)
|
||||||
|
}
|
||||||
|
if err = c.CloseWrite(); err != nil {
|
||||||
|
logrus.Warnf("commandConn.Close: CloseWrite: %v", err)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *commandConn) LocalAddr() net.Addr {
|
||||||
|
return c.localAddr
|
||||||
|
}
|
||||||
|
func (c *commandConn) RemoteAddr() net.Addr {
|
||||||
|
return c.remoteAddr
|
||||||
|
}
|
||||||
|
func (c *commandConn) SetDeadline(t time.Time) error {
|
||||||
|
logrus.Debugf("unimplemented call: SetDeadline(%v)", t)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
func (c *commandConn) SetReadDeadline(t time.Time) error {
|
||||||
|
logrus.Debugf("unimplemented call: SetReadDeadline(%v)", t)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
func (c *commandConn) SetWriteDeadline(t time.Time) error {
|
||||||
|
logrus.Debugf("unimplemented call: SetWriteDeadline(%v)", t)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type dummyAddr struct {
|
||||||
|
network string
|
||||||
|
s string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d dummyAddr) Network() string {
|
||||||
|
return d.network
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d dummyAddr) String() string {
|
||||||
|
return d.s
|
||||||
|
}
|
||||||
|
|
||||||
|
type stderrWriter struct {
|
||||||
|
stderrMu *sync.Mutex
|
||||||
|
stderr *bytes.Buffer
|
||||||
|
debugPrefix string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *stderrWriter) Write(p []byte) (int, error) {
|
||||||
|
logrus.Debugf("%s%s", w.debugPrefix, string(p))
|
||||||
|
w.stderrMu.Lock()
|
||||||
|
if w.stderr.Len() > 4096 {
|
||||||
|
w.stderr.Reset()
|
||||||
|
}
|
||||||
|
n, err := w.stderr.Write(p)
|
||||||
|
w.stderrMu.Unlock()
|
||||||
|
return n, err
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
package connhelper
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os/exec"
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
func setPdeathsig(cmd *exec.Cmd) {
|
||||||
|
cmd.SysProcAttr = &syscall.SysProcAttr{
|
||||||
|
Pdeathsig: syscall.SIGKILL,
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
// +build !linux
|
||||||
|
|
||||||
|
package connhelper
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os/exec"
|
||||||
|
)
|
||||||
|
|
||||||
|
func setPdeathsig(cmd *exec.Cmd) {
|
||||||
|
}
|
|
@ -0,0 +1,44 @@
|
||||||
|
// +build !windows
|
||||||
|
|
||||||
|
package connhelper
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"io"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"gotest.tools/assert"
|
||||||
|
is "gotest.tools/assert/cmp"
|
||||||
|
)
|
||||||
|
|
||||||
|
// For https://github.com/docker/cli/pull/1014#issuecomment-409308139
|
||||||
|
func TestCommandConnEOFWithError(t *testing.T) {
|
||||||
|
ctx := context.TODO()
|
||||||
|
cmd := "sh"
|
||||||
|
args := []string{"-c", "echo hello; echo some error >&2; exit 42"}
|
||||||
|
c, err := newCommandConn(ctx, cmd, args...)
|
||||||
|
assert.NilError(t, err)
|
||||||
|
b := make([]byte, 32)
|
||||||
|
n, err := c.Read(b)
|
||||||
|
assert.Check(t, is.Equal(len("hello\n"), n))
|
||||||
|
assert.NilError(t, err)
|
||||||
|
n, err = c.Read(b)
|
||||||
|
assert.Check(t, is.Equal(0, n))
|
||||||
|
assert.ErrorContains(t, err, "some error")
|
||||||
|
assert.ErrorContains(t, err, "42")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCommandConnEOFWithoutError(t *testing.T) {
|
||||||
|
ctx := context.TODO()
|
||||||
|
cmd := "sh"
|
||||||
|
args := []string{"-c", "echo hello; echo some debug log >&2; exit 0"}
|
||||||
|
c, err := newCommandConn(ctx, cmd, args...)
|
||||||
|
assert.NilError(t, err)
|
||||||
|
b := make([]byte, 32)
|
||||||
|
n, err := c.Read(b)
|
||||||
|
assert.Check(t, is.Equal(len("hello\n"), n))
|
||||||
|
assert.NilError(t, err)
|
||||||
|
n, err = c.Read(b)
|
||||||
|
assert.Check(t, is.Equal(0, n))
|
||||||
|
assert.Check(t, is.Equal(io.EOF, err))
|
||||||
|
}
|
|
@ -0,0 +1,70 @@
|
||||||
|
// Package ssh provides the connection helper for ssh:// URL.
|
||||||
|
// Requires Docker 18.09 or later on the remote host.
|
||||||
|
package ssh
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/url"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// New returns cmd and its args
|
||||||
|
func New(daemonURL string) (string, []string, error) {
|
||||||
|
sp, err := parseSSHURL(daemonURL)
|
||||||
|
if err != nil {
|
||||||
|
return "", nil, err
|
||||||
|
}
|
||||||
|
return "ssh", append(sp.Args(), []string{"--", "docker", "system", "dial-stdio"}...), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseSSHURL(daemonURL string) (*sshSpec, error) {
|
||||||
|
u, err := url.Parse(daemonURL)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if u.Scheme != "ssh" {
|
||||||
|
return nil, errors.Errorf("expected scheme ssh, got %s", u.Scheme)
|
||||||
|
}
|
||||||
|
|
||||||
|
var sp sshSpec
|
||||||
|
|
||||||
|
if u.User != nil {
|
||||||
|
sp.user = u.User.Username()
|
||||||
|
if _, ok := u.User.Password(); ok {
|
||||||
|
return nil, errors.New("ssh helper does not accept plain-text password")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sp.host = u.Hostname()
|
||||||
|
if sp.host == "" {
|
||||||
|
return nil, errors.Errorf("host is not specified")
|
||||||
|
}
|
||||||
|
sp.port = u.Port()
|
||||||
|
if u.Path != "" {
|
||||||
|
return nil, errors.Errorf("extra path: %s", u.Path)
|
||||||
|
}
|
||||||
|
if u.RawQuery != "" {
|
||||||
|
return nil, errors.Errorf("extra query: %s", u.RawQuery)
|
||||||
|
}
|
||||||
|
if u.Fragment != "" {
|
||||||
|
return nil, errors.Errorf("extra fragment: %s", u.Fragment)
|
||||||
|
}
|
||||||
|
return &sp, err
|
||||||
|
}
|
||||||
|
|
||||||
|
type sshSpec struct {
|
||||||
|
user string
|
||||||
|
host string
|
||||||
|
port string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sp *sshSpec) Args() []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)
|
||||||
|
return args
|
||||||
|
}
|
|
@ -0,0 +1,64 @@
|
||||||
|
package ssh
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"gotest.tools/assert"
|
||||||
|
is "gotest.tools/assert/cmp"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestParseSSHURL(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
url string
|
||||||
|
expectedArgs []string
|
||||||
|
expectedError string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
url: "ssh://foo",
|
||||||
|
expectedArgs: []string{
|
||||||
|
"foo",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
url: "ssh://me@foo:10022",
|
||||||
|
expectedArgs: []string{
|
||||||
|
"-l", "me",
|
||||||
|
"-p", "10022",
|
||||||
|
"foo",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
url: "ssh://me:passw0rd@foo",
|
||||||
|
expectedError: "ssh helper does not accept plain-text password",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
url: "ssh://foo/bar",
|
||||||
|
expectedError: "extra path",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
url: "ssh://foo?bar",
|
||||||
|
expectedError: "extra query",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
url: "ssh://foo#bar",
|
||||||
|
expectedError: "extra fragment",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
url: "ssh://",
|
||||||
|
expectedError: "host is not specified",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
url: "foo://bar",
|
||||||
|
expectedError: "expected scheme ssh",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tc := range testCases {
|
||||||
|
sp, err := parseSSHURL(tc.url)
|
||||||
|
if tc.expectedError == "" {
|
||||||
|
assert.NilError(t, err)
|
||||||
|
assert.Check(t, is.DeepEqual(tc.expectedArgs, sp.Args()))
|
||||||
|
} else {
|
||||||
|
assert.ErrorContains(t, err, tc.expectedError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -67,7 +67,8 @@ func (commonOpts *CommonOptions) InstallFlags(flags *pflag.FlagSet) {
|
||||||
flags.Var(opts.NewQuotedString(&tlsOptions.CertFile), "tlscert", "Path to TLS certificate file")
|
flags.Var(opts.NewQuotedString(&tlsOptions.CertFile), "tlscert", "Path to TLS certificate file")
|
||||||
flags.Var(opts.NewQuotedString(&tlsOptions.KeyFile), "tlskey", "Path to TLS key file")
|
flags.Var(opts.NewQuotedString(&tlsOptions.KeyFile), "tlskey", "Path to TLS key file")
|
||||||
|
|
||||||
hostOpt := opts.NewNamedListOptsRef("hosts", &commonOpts.Hosts, opts.ValidateHost)
|
// opts.ValidateHost is not used here, so as to allow connection helpers
|
||||||
|
hostOpt := opts.NewNamedListOptsRef("hosts", &commonOpts.Hosts, nil)
|
||||||
flags.VarP(hostOpt, "host", "H", "Daemon socket(s) to connect to")
|
flags.VarP(hostOpt, "host", "H", "Daemon socket(s) to connect to")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -181,6 +181,19 @@ The Docker client will honor the `HTTP_PROXY`, `HTTPS_PROXY`, and `NO_PROXY`
|
||||||
environment variables (or the lowercase versions thereof). `HTTPS_PROXY` takes
|
environment variables (or the lowercase versions thereof). `HTTPS_PROXY` takes
|
||||||
precedence over `HTTP_PROXY`.
|
precedence over `HTTP_PROXY`.
|
||||||
|
|
||||||
|
Starting with Docker 18.09, the Docker client supports connecting to a remote
|
||||||
|
daemon via SSH:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ docker -H ssh://me@example.com:22 ps
|
||||||
|
$ docker -H ssh://me@example.com ps
|
||||||
|
$ docker -H ssh://example.com ps
|
||||||
|
```
|
||||||
|
|
||||||
|
To use SSH connection, you need to set up `ssh` so that it can reach the
|
||||||
|
remote host with public key authentication.
|
||||||
|
Also, you need to have `docker` binary 18.09 or later on the daemon host.
|
||||||
|
|
||||||
#### Bind Docker to another host/port or a Unix socket
|
#### Bind Docker to another host/port or a Unix socket
|
||||||
|
|
||||||
> **Warning**:
|
> **Warning**:
|
||||||
|
|
|
@ -8,7 +8,7 @@ github.com/coreos/etcd v3.3.9
|
||||||
github.com/cpuguy83/go-md2man v1.0.8
|
github.com/cpuguy83/go-md2man v1.0.8
|
||||||
github.com/davecgh/go-spew 346938d642f2ec3594ed81d874461961cd0faa76 # v1.1.0
|
github.com/davecgh/go-spew 346938d642f2ec3594ed81d874461961cd0faa76 # v1.1.0
|
||||||
github.com/docker/distribution 83389a148052d74ac602f5f1d62f86ff2f3c4aa5
|
github.com/docker/distribution 83389a148052d74ac602f5f1d62f86ff2f3c4aa5
|
||||||
github.com/docker/docker b711437bbd8596312c962d4189e9ad4d2108c2dc
|
github.com/docker/docker 562df8c2d6f48601c8d1df7256389569d25c0bf1
|
||||||
github.com/docker/docker-credential-helpers 5241b46610f2491efdf9d1c85f1ddf5b02f6d962
|
github.com/docker/docker-credential-helpers 5241b46610f2491efdf9d1c85f1ddf5b02f6d962
|
||||||
# the docker/go package contains a customized version of canonical/json
|
# the docker/go package contains a customized version of canonical/json
|
||||||
# and is used by Notary. The package is periodically rebased on current Go versions.
|
# and is used by Notary. The package is periodically rebased on current Go versions.
|
||||||
|
|
|
@ -7,7 +7,7 @@ package volume
|
||||||
// See hack/generate-swagger-api.sh
|
// See hack/generate-swagger-api.sh
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
// VolumeCreateBody
|
// VolumeCreateBody Volume configuration
|
||||||
// swagger:model VolumeCreateBody
|
// swagger:model VolumeCreateBody
|
||||||
type VolumeCreateBody struct {
|
type VolumeCreateBody struct {
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,7 @@ package volume
|
||||||
|
|
||||||
import "github.com/docker/docker/api/types"
|
import "github.com/docker/docker/api/types"
|
||||||
|
|
||||||
// VolumeListOKBody
|
// VolumeListOKBody Volume list response
|
||||||
// swagger:model VolumeListOKBody
|
// swagger:model VolumeListOKBody
|
||||||
type VolumeListOKBody struct {
|
type VolumeListOKBody struct {
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
package client // import "github.com/docker/docker/client"
|
package client // import "github.com/docker/docker/client"
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
|
||||||
"golang.org/x/net/context"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// BuildCancel requests the daemon to cancel ongoing build request
|
// BuildCancel requests the daemon to cancel ongoing build request
|
||||||
|
|
|
@ -173,10 +173,17 @@ func WithTLSClientConfig(cacertPath, certPath, keyPath string) func(*Client) err
|
||||||
|
|
||||||
// WithDialer applies the dialer.DialContext to the client transport. This can be
|
// WithDialer applies the dialer.DialContext to the client transport. This can be
|
||||||
// used to set the Timeout and KeepAlive settings of the client.
|
// used to set the Timeout and KeepAlive settings of the client.
|
||||||
|
// Deprecated: use WithDialContext
|
||||||
func WithDialer(dialer *net.Dialer) func(*Client) error {
|
func WithDialer(dialer *net.Dialer) func(*Client) error {
|
||||||
|
return WithDialContext(dialer.DialContext)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithDialContext applies the dialer to the client transport. This can be
|
||||||
|
// used to set the Timeout and KeepAlive settings of the client.
|
||||||
|
func WithDialContext(dialContext func(ctx context.Context, network, addr string) (net.Conn, error)) func(*Client) error {
|
||||||
return func(c *Client) error {
|
return func(c *Client) error {
|
||||||
if transport, ok := c.client.Transport.(*http.Transport); ok {
|
if transport, ok := c.client.Transport.(*http.Transport); ok {
|
||||||
transport.DialContext = dialer.DialContext
|
transport.DialContext = dialContext
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return errors.Errorf("cannot apply dialer to transport: %T", c.client.Transport)
|
return errors.Errorf("cannot apply dialer to transport: %T", c.client.Transport)
|
||||||
|
@ -400,3 +407,16 @@ func (cli *Client) CustomHTTPHeaders() map[string]string {
|
||||||
func (cli *Client) SetCustomHTTPHeaders(headers map[string]string) {
|
func (cli *Client) SetCustomHTTPHeaders(headers map[string]string) {
|
||||||
cli.customHTTPHeaders = headers
|
cli.customHTTPHeaders = headers
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Dialer returns a dialer for a raw stream connection, with HTTP/1.1 header, that can be used for proxying the daemon connection.
|
||||||
|
// 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 {
|
||||||
|
if transport.DialContext != nil {
|
||||||
|
return transport.DialContext(ctx, cli.proto, cli.addr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fallbackDial(cli.proto, cli.addr, resolveTLSConfig(cli.client.Transport))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -30,7 +30,7 @@ func (cli *Client) postHijacked(ctx context.Context, path string, query url.Valu
|
||||||
}
|
}
|
||||||
req = cli.addHeaders(req, headers)
|
req = cli.addHeaders(req, headers)
|
||||||
|
|
||||||
conn, err := cli.setupHijackConn(req, "tcp")
|
conn, err := cli.setupHijackConn(ctx, req, "tcp")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return types.HijackedResponse{}, err
|
return types.HijackedResponse{}, err
|
||||||
}
|
}
|
||||||
|
@ -38,7 +38,9 @@ func (cli *Client) postHijacked(ctx context.Context, path string, query url.Valu
|
||||||
return types.HijackedResponse{Conn: conn, Reader: bufio.NewReader(conn)}, err
|
return types.HijackedResponse{Conn: conn, Reader: bufio.NewReader(conn)}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func dial(proto, addr string, tlsConfig *tls.Config) (net.Conn, error) {
|
// fallbackDial is used when WithDialer() was not called.
|
||||||
|
// See cli.Dialer().
|
||||||
|
func fallbackDial(proto, addr string, tlsConfig *tls.Config) (net.Conn, error) {
|
||||||
if tlsConfig != nil && proto != "unix" && proto != "npipe" {
|
if tlsConfig != nil && proto != "unix" && proto != "npipe" {
|
||||||
return tls.Dial(proto, addr, tlsConfig)
|
return tls.Dial(proto, addr, tlsConfig)
|
||||||
}
|
}
|
||||||
|
@ -48,12 +50,13 @@ func dial(proto, addr string, tlsConfig *tls.Config) (net.Conn, error) {
|
||||||
return net.Dial(proto, addr)
|
return net.Dial(proto, addr)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cli *Client) setupHijackConn(req *http.Request, proto string) (net.Conn, error) {
|
func (cli *Client) setupHijackConn(ctx context.Context, req *http.Request, proto string) (net.Conn, error) {
|
||||||
req.Host = cli.addr
|
req.Host = cli.addr
|
||||||
req.Header.Set("Connection", "Upgrade")
|
req.Header.Set("Connection", "Upgrade")
|
||||||
req.Header.Set("Upgrade", proto)
|
req.Header.Set("Upgrade", proto)
|
||||||
|
|
||||||
conn, err := dial(cli.proto, cli.addr, resolveTLSConfig(cli.client.Transport))
|
dialer := cli.Dialer()
|
||||||
|
conn, err := dialer(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "cannot connect to the Docker daemon. Is 'docker daemon' running on this host?")
|
return nil, errors.Wrap(err, "cannot connect to the Docker daemon. Is 'docker daemon' running on this host?")
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,6 +39,7 @@ type CommonAPIClient interface {
|
||||||
NegotiateAPIVersion(ctx context.Context)
|
NegotiateAPIVersion(ctx context.Context)
|
||||||
NegotiateAPIVersionPing(types.Ping)
|
NegotiateAPIVersionPing(types.Ping)
|
||||||
DialSession(ctx context.Context, proto string, meta map[string][]string) (net.Conn, error)
|
DialSession(ctx context.Context, proto string, meta map[string][]string) (net.Conn, error)
|
||||||
|
Dialer() func(context.Context) (net.Conn, error)
|
||||||
Close() error
|
Close() error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -14,5 +14,5 @@ func (cli *Client) DialSession(ctx context.Context, proto string, meta map[strin
|
||||||
}
|
}
|
||||||
req = cli.addHeaders(req, meta)
|
req = cli.addHeaders(req, meta)
|
||||||
|
|
||||||
return cli.setupHijackConn(req, proto)
|
return cli.setupHijackConn(ctx, req, proto)
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,7 +34,7 @@ func EnsureRemoveAll(dir string) error {
|
||||||
for {
|
for {
|
||||||
err := os.RemoveAll(dir)
|
err := os.RemoveAll(dir)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return err
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
pe, ok := err.(*os.PathError)
|
pe, ok := err.(*os.PathError)
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
# the following lines are in sorted order, FYI
|
# the following lines are in sorted order, FYI
|
||||||
github.com/Azure/go-ansiterm d6e3b3328b783f23731bc4d058875b0371ff8109
|
github.com/Azure/go-ansiterm d6e3b3328b783f23731bc4d058875b0371ff8109
|
||||||
github.com/Microsoft/hcsshim v0.6.11
|
github.com/Microsoft/hcsshim v0.6.12
|
||||||
github.com/Microsoft/go-winio v0.4.8
|
github.com/Microsoft/go-winio v0.4.8
|
||||||
github.com/docker/libtrust 9cbd2a1374f46905c68a4eb3694a130610adc62a
|
github.com/docker/libtrust 9cbd2a1374f46905c68a4eb3694a130610adc62a
|
||||||
github.com/go-check/check 4ed411733c5785b40214c70bce814c3a3a689609 https://github.com/cpuguy83/check.git
|
github.com/go-check/check 4ed411733c5785b40214c70bce814c3a3a689609 https://github.com/cpuguy83/check.git
|
||||||
github.com/golang/gddo 9b12a26f3fbd7397dee4e20939ddca719d840d2a
|
github.com/golang/gddo 9b12a26f3fbd7397dee4e20939ddca719d840d2a
|
||||||
github.com/gorilla/context v1.1
|
github.com/gorilla/context v1.1
|
||||||
github.com/gorilla/mux v1.1
|
github.com/gorilla/mux v1.1
|
||||||
github.com/Microsoft/opengcs v0.3.6
|
github.com/Microsoft/opengcs v0.3.8
|
||||||
github.com/kr/pty 5cf931ef8f
|
github.com/kr/pty 5cf931ef8f
|
||||||
github.com/mattn/go-shellwords v1.0.3
|
github.com/mattn/go-shellwords v1.0.3
|
||||||
github.com/sirupsen/logrus v1.0.3
|
github.com/sirupsen/logrus v1.0.3
|
||||||
|
@ -22,11 +22,11 @@ gotest.tools v2.1.0
|
||||||
github.com/google/go-cmp v0.2.0
|
github.com/google/go-cmp v0.2.0
|
||||||
|
|
||||||
github.com/RackSec/srslog 456df3a81436d29ba874f3590eeeee25d666f8a5
|
github.com/RackSec/srslog 456df3a81436d29ba874f3590eeeee25d666f8a5
|
||||||
github.com/imdario/mergo v0.3.5
|
github.com/imdario/mergo v0.3.6
|
||||||
golang.org/x/sync fd80eb99c8f653c847d294a001bdf2a3a6f768f5
|
golang.org/x/sync fd80eb99c8f653c847d294a001bdf2a3a6f768f5
|
||||||
|
|
||||||
# buildkit
|
# buildkit
|
||||||
github.com/moby/buildkit 9acf51e49185b348608e0096b2903dd72907adcb
|
github.com/moby/buildkit 98f1604134f945d48538ffca0e18662337b4a850
|
||||||
github.com/tonistiigi/fsutil 8abad97ee3969cdf5e9c367f46adba2c212b3ddb
|
github.com/tonistiigi/fsutil 8abad97ee3969cdf5e9c367f46adba2c212b3ddb
|
||||||
github.com/grpc-ecosystem/grpc-opentracing 8e809c8a86450a29b90dcc9efbf062d0fe6d9746
|
github.com/grpc-ecosystem/grpc-opentracing 8e809c8a86450a29b90dcc9efbf062d0fe6d9746
|
||||||
github.com/opentracing/opentracing-go 1361b9cd60be79c4c3a7fa9841b3c132e40066a7
|
github.com/opentracing/opentracing-go 1361b9cd60be79c4c3a7fa9841b3c132e40066a7
|
||||||
|
@ -37,7 +37,7 @@ github.com/mitchellh/hashstructure 2bca23e0e452137f789efbc8610126fd8b94f73b
|
||||||
#get libnetwork packages
|
#get libnetwork packages
|
||||||
|
|
||||||
# When updating, also update LIBNETWORK_COMMIT in hack/dockerfile/install/proxy accordingly
|
# When updating, also update LIBNETWORK_COMMIT in hack/dockerfile/install/proxy accordingly
|
||||||
github.com/docker/libnetwork 430c00a6a6b3dfdd774f21e1abd4ad6b0216c629
|
github.com/docker/libnetwork d00ceed44cc447c77f25cdf5d59e83163bdcb4c9
|
||||||
github.com/docker/go-events 9461782956ad83b30282bf90e31fa6a70c255ba9
|
github.com/docker/go-events 9461782956ad83b30282bf90e31fa6a70c255ba9
|
||||||
github.com/armon/go-radix e39d623f12e8e41c7b5529e9a9dd67a1e2261f80
|
github.com/armon/go-radix e39d623f12e8e41c7b5529e9a9dd67a1e2261f80
|
||||||
github.com/armon/go-metrics eb0af217e5e9747e41dd5303755356b62d28e3ec
|
github.com/armon/go-metrics eb0af217e5e9747e41dd5303755356b62d28e3ec
|
||||||
|
@ -95,7 +95,7 @@ github.com/philhofer/fwd 98c11a7a6ec829d672b03833c3d69a7fae1ca972
|
||||||
github.com/tinylib/msgp 3b556c64540842d4f82967be066a7f7fffc3adad
|
github.com/tinylib/msgp 3b556c64540842d4f82967be066a7f7fffc3adad
|
||||||
|
|
||||||
# fsnotify
|
# fsnotify
|
||||||
github.com/fsnotify/fsnotify 4da3e2cfbabc9f751898f250b49f2439785783a1
|
github.com/fsnotify/fsnotify v1.4.7
|
||||||
|
|
||||||
# awslogs deps
|
# awslogs deps
|
||||||
github.com/aws/aws-sdk-go v1.12.66
|
github.com/aws/aws-sdk-go v1.12.66
|
||||||
|
@ -114,22 +114,22 @@ github.com/googleapis/gax-go v2.0.0
|
||||||
google.golang.org/genproto 694d95ba50e67b2e363f3483057db5d4910c18f9
|
google.golang.org/genproto 694d95ba50e67b2e363f3483057db5d4910c18f9
|
||||||
|
|
||||||
# containerd
|
# containerd
|
||||||
github.com/containerd/containerd 08f7ee9828af1783dc98cc5cc1739e915697c667
|
github.com/containerd/containerd b41633746ed4833f52c3c071e8edcfa2713e5677
|
||||||
github.com/containerd/fifo 3d5202aec260678c48179c56f40e6f38a095738c
|
github.com/containerd/fifo 3d5202aec260678c48179c56f40e6f38a095738c
|
||||||
github.com/containerd/continuity d3c23511c1bf5851696cba83143d9cbcd666869b
|
github.com/containerd/continuity 0377f7d767206f3a9e8881d0f02267b0d89c7a62
|
||||||
github.com/containerd/cgroups fe281dd265766145e943a034aa41086474ea6130
|
github.com/containerd/cgroups fe281dd265766145e943a034aa41086474ea6130
|
||||||
github.com/containerd/console 9290d21dc56074581f619579c43d970b4514bc08
|
github.com/containerd/console 5d1b48d6114b8c9666f0c8b916f871af97b0a761
|
||||||
github.com/containerd/go-runc f271fa2021de855d4d918dbef83c5fe19db1bdd
|
github.com/containerd/go-runc f271fa2021de855d4d918dbef83c5fe19db1bdd
|
||||||
github.com/containerd/typeurl f6943554a7e7e88b3c14aad190bf05932da84788
|
github.com/containerd/typeurl a93fcdb778cd272c6e9b3028b2f42d813e785d40
|
||||||
github.com/stevvooe/ttrpc d4528379866b0ce7e9d71f3eb96f0582fc374577
|
github.com/containerd/ttrpc 94dde388801693c54f88a6596f713b51a8b30b2d
|
||||||
github.com/gogo/googleapis 08a7655d27152912db7aaf4f983275eaf8d128ef
|
github.com/gogo/googleapis 08a7655d27152912db7aaf4f983275eaf8d128ef
|
||||||
|
|
||||||
# cluster
|
# cluster
|
||||||
github.com/docker/swarmkit edd5641391926a50bc5f7040e20b7efc05003c26
|
github.com/docker/swarmkit 68266392a176434d282760d2d6d0ab4c68edcae6
|
||||||
github.com/gogo/protobuf v1.0.0
|
github.com/gogo/protobuf v1.0.0
|
||||||
github.com/cloudflare/cfssl 7fb22c8cba7ecaf98e4082d22d65800cf45e042a
|
github.com/cloudflare/cfssl 1.3.2
|
||||||
github.com/fernet/fernet-go 1b2437bc582b3cfbb341ee5a29f8ef5b42912ff2
|
github.com/fernet/fernet-go 1b2437bc582b3cfbb341ee5a29f8ef5b42912ff2
|
||||||
github.com/google/certificate-transparency d90e65c3a07988180c5b1ece71791c0b6506826e
|
github.com/google/certificate-transparency-go v1.0.20
|
||||||
golang.org/x/crypto 1a580b3eff7814fc9b40602fd35256c63b50f491
|
golang.org/x/crypto 1a580b3eff7814fc9b40602fd35256c63b50f491
|
||||||
golang.org/x/time a4bde12657593d5e90d0533a3e4fd95e635124cb
|
golang.org/x/time a4bde12657593d5e90d0533a3e4fd95e635124cb
|
||||||
github.com/hashicorp/go-memdb cb9a474f84cc5e41b273b20c6927680b2a8776ad
|
github.com/hashicorp/go-memdb cb9a474f84cc5e41b273b20c6927680b2a8776ad
|
||||||
|
|
Loading…
Reference in New Issue