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:
@ -14,6 +14,7 @@ import (
|
||||
"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/connhelper"
|
||||
cliflags "github.com/docker/cli/cli/flags"
|
||||
manifeststore "github.com/docker/cli/cli/manifest/store"
|
||||
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
|
||||
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 {
|
||||
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
|
||||
if customHeaders == nil {
|
||||
customHeaders = map[string]string{}
|
||||
}
|
||||
customHeaders["User-Agent"] = UserAgent()
|
||||
clientOpts = append(clientOpts, client.WithHTTPHeaders(customHeaders))
|
||||
|
||||
verStr := api.DefaultVersion
|
||||
if tmpStr := os.Getenv("DOCKER_API_VERSION"); tmpStr != "" {
|
||||
verStr = tmpStr
|
||||
}
|
||||
clientOpts = append(clientOpts, client.WithVersion(verStr))
|
||||
|
||||
return client.NewClientWithOpts(
|
||||
withHTTPClient(opts.TLSOptions),
|
||||
client.WithHTTPHeaders(customHeaders),
|
||||
client.WithVersion(verStr),
|
||||
client.WithHost(host),
|
||||
)
|
||||
return client.NewClientWithOpts(clientOpts...)
|
||||
}
|
||||
|
||||
func getServerHost(hosts []string, tlsOptions *tlsconfig.Options) (string, error) {
|
||||
func getUnparsedServerHost(hosts []string) (string, error) {
|
||||
var host string
|
||||
switch len(hosts) {
|
||||
case 0:
|
||||
@ -282,8 +306,7 @@ func getServerHost(hosts []string, tlsOptions *tlsconfig.Options) (string, error
|
||||
default:
|
||||
return "", errors.New("Please specify only one -H")
|
||||
}
|
||||
|
||||
return dopts.ParseHost(tlsOptions != nil, host)
|
||||
return host, nil
|
||||
}
|
||||
|
||||
func withHTTPClient(tlsOpts *tlsconfig.Options) func(*client.Client) error {
|
||||
|
||||
@ -19,6 +19,7 @@ func NewSystemCommand(dockerCli command.Cli) *cobra.Command {
|
||||
NewInfoCommand(dockerCli),
|
||||
newDiskUsageCommand(dockerCli),
|
||||
newPruneCommand(dockerCli),
|
||||
newDialStdioCommand(dockerCli),
|
||||
)
|
||||
|
||||
return cmd
|
||||
|
||||
107
cli/command/system/dial_stdio.go
Normal file
107
cli/command/system/dial_stdio.go
Normal file
@ -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()
|
||||
}
|
||||
Reference in New Issue
Block a user