github.com/thajeztah/cli@v0.0.0-20240223162942-dc6bfac81a8b/cli/connhelper/connhelper.go (about)

     1  // Package connhelper provides helpers for connecting to a remote daemon host with custom logic.
     2  package connhelper
     3  
     4  import (
     5  	"context"
     6  	"net"
     7  	"net/url"
     8  	"strings"
     9  
    10  	"github.com/docker/cli/cli/connhelper/commandconn"
    11  	"github.com/docker/cli/cli/connhelper/ssh"
    12  	"github.com/pkg/errors"
    13  )
    14  
    15  // ConnectionHelper allows to connect to a remote host with custom stream provider binary.
    16  type ConnectionHelper struct {
    17  	Dialer func(ctx context.Context, network, addr string) (net.Conn, error)
    18  	Host   string // dummy URL used for HTTP requests. e.g. "http://docker"
    19  }
    20  
    21  // GetConnectionHelper returns Docker-specific connection helper for the given URL.
    22  // GetConnectionHelper returns nil without error when no helper is registered for the scheme.
    23  //
    24  // ssh://<user>@<host> URL requires Docker 18.09 or later on the remote host.
    25  func GetConnectionHelper(daemonURL string) (*ConnectionHelper, error) {
    26  	return getConnectionHelper(daemonURL, nil)
    27  }
    28  
    29  // GetConnectionHelperWithSSHOpts returns Docker-specific connection helper for
    30  // the given URL, and accepts additional options for ssh connections. It returns
    31  // nil without error when no helper is registered for the scheme.
    32  //
    33  // Requires Docker 18.09 or later on the remote host.
    34  func GetConnectionHelperWithSSHOpts(daemonURL string, sshFlags []string) (*ConnectionHelper, error) {
    35  	return getConnectionHelper(daemonURL, sshFlags)
    36  }
    37  
    38  func getConnectionHelper(daemonURL string, sshFlags []string) (*ConnectionHelper, error) {
    39  	u, err := url.Parse(daemonURL)
    40  	if err != nil {
    41  		return nil, err
    42  	}
    43  	if u.Scheme == "ssh" {
    44  		sp, err := ssh.ParseURL(daemonURL)
    45  		if err != nil {
    46  			return nil, errors.Wrap(err, "ssh host connection is not valid")
    47  		}
    48  		return &ConnectionHelper{
    49  			Dialer: func(ctx context.Context, network, addr string) (net.Conn, error) {
    50  				args := []string{"docker"}
    51  				if sp.Path != "" {
    52  					args = append(args, "--host", "unix://"+sp.Path)
    53  				}
    54  				sshFlags = addSSHTimeout(sshFlags)
    55  				args = append(args, "system", "dial-stdio")
    56  				return commandconn.New(ctx, "ssh", append(sshFlags, sp.Args(args...)...)...)
    57  			},
    58  			Host: "http://docker.example.com",
    59  		}, nil
    60  	}
    61  	// Future version may support plugins via ~/.docker/config.json. e.g. "dind"
    62  	// See docker/cli#889 for the previous discussion.
    63  	return nil, err
    64  }
    65  
    66  // GetCommandConnectionHelper returns Docker-specific connection helper constructed from an arbitrary command.
    67  func GetCommandConnectionHelper(cmd string, flags ...string) (*ConnectionHelper, error) {
    68  	return &ConnectionHelper{
    69  		Dialer: func(ctx context.Context, network, addr string) (net.Conn, error) {
    70  			return commandconn.New(ctx, cmd, flags...)
    71  		},
    72  		Host: "http://docker.example.com",
    73  	}, nil
    74  }
    75  
    76  func addSSHTimeout(sshFlags []string) []string {
    77  	if !strings.Contains(strings.Join(sshFlags, ""), "ConnectTimeout") {
    78  		sshFlags = append(sshFlags, "-o ConnectTimeout=30")
    79  	}
    80  	return sshFlags
    81  }