github.com/portworx/docker@v1.12.1/api/client/cli.go (about)

     1  package client
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"io"
     7  	"net/http"
     8  	"os"
     9  	"runtime"
    10  
    11  	"github.com/docker/docker/api"
    12  	cliflags "github.com/docker/docker/cli/flags"
    13  	"github.com/docker/docker/cliconfig"
    14  	"github.com/docker/docker/cliconfig/configfile"
    15  	"github.com/docker/docker/cliconfig/credentials"
    16  	"github.com/docker/docker/dockerversion"
    17  	"github.com/docker/docker/opts"
    18  	"github.com/docker/docker/pkg/term"
    19  	"github.com/docker/engine-api/client"
    20  	"github.com/docker/go-connections/sockets"
    21  	"github.com/docker/go-connections/tlsconfig"
    22  )
    23  
    24  // DockerCli represents the docker command line client.
    25  // Instances of the client can be returned from NewDockerCli.
    26  type DockerCli struct {
    27  	// initializing closure
    28  	init func() error
    29  
    30  	// configFile has the client configuration file
    31  	configFile *configfile.ConfigFile
    32  	// in holds the input stream and closer (io.ReadCloser) for the client.
    33  	in io.ReadCloser
    34  	// out holds the output stream (io.Writer) for the client.
    35  	out io.Writer
    36  	// err holds the error stream (io.Writer) for the client.
    37  	err io.Writer
    38  	// keyFile holds the key file as a string.
    39  	keyFile string
    40  	// inFd holds the file descriptor of the client's STDIN (if valid).
    41  	inFd uintptr
    42  	// outFd holds file descriptor of the client's STDOUT (if valid).
    43  	outFd uintptr
    44  	// isTerminalIn indicates whether the client's STDIN is a TTY
    45  	isTerminalIn bool
    46  	// isTerminalOut indicates whether the client's STDOUT is a TTY
    47  	isTerminalOut bool
    48  	// client is the http client that performs all API operations
    49  	client client.APIClient
    50  	// state holds the terminal input state
    51  	inState *term.State
    52  	// outState holds the terminal output state
    53  	outState *term.State
    54  }
    55  
    56  // Initialize calls the init function that will setup the configuration for the client
    57  // such as the TLS, tcp and other parameters used to run the client.
    58  func (cli *DockerCli) Initialize() error {
    59  	if cli.init == nil {
    60  		return nil
    61  	}
    62  	return cli.init()
    63  }
    64  
    65  // Client returns the APIClient
    66  func (cli *DockerCli) Client() client.APIClient {
    67  	return cli.client
    68  }
    69  
    70  // Out returns the writer used for stdout
    71  func (cli *DockerCli) Out() io.Writer {
    72  	return cli.out
    73  }
    74  
    75  // Err returns the writer used for stderr
    76  func (cli *DockerCli) Err() io.Writer {
    77  	return cli.err
    78  }
    79  
    80  // In returns the reader used for stdin
    81  func (cli *DockerCli) In() io.ReadCloser {
    82  	return cli.in
    83  }
    84  
    85  // ConfigFile returns the ConfigFile
    86  func (cli *DockerCli) ConfigFile() *configfile.ConfigFile {
    87  	return cli.configFile
    88  }
    89  
    90  // IsTerminalOut returns true if the clients stdin is a TTY
    91  func (cli *DockerCli) IsTerminalOut() bool {
    92  	return cli.isTerminalOut
    93  }
    94  
    95  // OutFd returns the fd for the stdout stream
    96  func (cli *DockerCli) OutFd() uintptr {
    97  	return cli.outFd
    98  }
    99  
   100  // CheckTtyInput checks if we are trying to attach to a container tty
   101  // from a non-tty client input stream, and if so, returns an error.
   102  func (cli *DockerCli) CheckTtyInput(attachStdin, ttyMode bool) error {
   103  	// In order to attach to a container tty, input stream for the client must
   104  	// be a tty itself: redirecting or piping the client standard input is
   105  	// incompatible with `docker run -t`, `docker exec -t` or `docker attach`.
   106  	if ttyMode && attachStdin && !cli.isTerminalIn {
   107  		eText := "the input device is not a TTY"
   108  		if runtime.GOOS == "windows" {
   109  			return errors.New(eText + ".  If you are using mintty, try prefixing the command with 'winpty'")
   110  		}
   111  		return errors.New(eText)
   112  	}
   113  	return nil
   114  }
   115  
   116  // PsFormat returns the format string specified in the configuration.
   117  // String contains columns and format specification, for example {{ID}}\t{{Name}}.
   118  func (cli *DockerCli) PsFormat() string {
   119  	return cli.configFile.PsFormat
   120  }
   121  
   122  // ImagesFormat returns the format string specified in the configuration.
   123  // String contains columns and format specification, for example {{ID}}\t{{Name}}.
   124  func (cli *DockerCli) ImagesFormat() string {
   125  	return cli.configFile.ImagesFormat
   126  }
   127  
   128  func (cli *DockerCli) setRawTerminal() error {
   129  	if os.Getenv("NORAW") == "" {
   130  		if cli.isTerminalIn {
   131  			state, err := term.SetRawTerminal(cli.inFd)
   132  			if err != nil {
   133  				return err
   134  			}
   135  			cli.inState = state
   136  		}
   137  		if cli.isTerminalOut {
   138  			state, err := term.SetRawTerminalOutput(cli.outFd)
   139  			if err != nil {
   140  				return err
   141  			}
   142  			cli.outState = state
   143  		}
   144  	}
   145  	return nil
   146  }
   147  
   148  func (cli *DockerCli) restoreTerminal(in io.Closer) error {
   149  	if cli.inState != nil {
   150  		term.RestoreTerminal(cli.inFd, cli.inState)
   151  	}
   152  	if cli.outState != nil {
   153  		term.RestoreTerminal(cli.outFd, cli.outState)
   154  	}
   155  	// WARNING: DO NOT REMOVE THE OS CHECK !!!
   156  	// For some reason this Close call blocks on darwin..
   157  	// As the client exists right after, simply discard the close
   158  	// until we find a better solution.
   159  	if in != nil && runtime.GOOS != "darwin" {
   160  		return in.Close()
   161  	}
   162  	return nil
   163  }
   164  
   165  // NewDockerCli returns a DockerCli instance with IO output and error streams set by in, out and err.
   166  // The key file, protocol (i.e. unix) and address are passed in as strings, along with the tls.Config. If the tls.Config
   167  // is set the client scheme will be set to https.
   168  // The client will be given a 32-second timeout (see https://github.com/docker/docker/pull/8035).
   169  func NewDockerCli(in io.ReadCloser, out, err io.Writer, clientFlags *cliflags.ClientFlags) *DockerCli {
   170  	cli := &DockerCli{
   171  		in:      in,
   172  		out:     out,
   173  		err:     err,
   174  		keyFile: clientFlags.Common.TrustKey,
   175  	}
   176  
   177  	cli.init = func() error {
   178  		clientFlags.PostParse()
   179  		cli.configFile = LoadDefaultConfigFile(err)
   180  
   181  		client, err := NewAPIClientFromFlags(clientFlags, cli.configFile)
   182  		if err != nil {
   183  			return err
   184  		}
   185  
   186  		cli.client = client
   187  
   188  		if cli.in != nil {
   189  			cli.inFd, cli.isTerminalIn = term.GetFdInfo(cli.in)
   190  		}
   191  		if cli.out != nil {
   192  			cli.outFd, cli.isTerminalOut = term.GetFdInfo(cli.out)
   193  		}
   194  
   195  		return nil
   196  	}
   197  
   198  	return cli
   199  }
   200  
   201  // LoadDefaultConfigFile attempts to load the default config file and returns
   202  // an initialized ConfigFile struct if none is found.
   203  func LoadDefaultConfigFile(err io.Writer) *configfile.ConfigFile {
   204  	configFile, e := cliconfig.Load(cliconfig.ConfigDir())
   205  	if e != nil {
   206  		fmt.Fprintf(err, "WARNING: Error loading config file:%v\n", e)
   207  	}
   208  	if !configFile.ContainsAuth() {
   209  		credentials.DetectDefaultStore(configFile)
   210  	}
   211  	return configFile
   212  }
   213  
   214  // NewAPIClientFromFlags creates a new APIClient from command line flags
   215  func NewAPIClientFromFlags(clientFlags *cliflags.ClientFlags, configFile *configfile.ConfigFile) (client.APIClient, error) {
   216  	host, err := getServerHost(clientFlags.Common.Hosts, clientFlags.Common.TLSOptions)
   217  	if err != nil {
   218  		return &client.Client{}, err
   219  	}
   220  
   221  	customHeaders := configFile.HTTPHeaders
   222  	if customHeaders == nil {
   223  		customHeaders = map[string]string{}
   224  	}
   225  	customHeaders["User-Agent"] = clientUserAgent()
   226  
   227  	verStr := api.DefaultVersion
   228  	if tmpStr := os.Getenv("DOCKER_API_VERSION"); tmpStr != "" {
   229  		verStr = tmpStr
   230  	}
   231  
   232  	httpClient, err := newHTTPClient(host, clientFlags.Common.TLSOptions)
   233  	if err != nil {
   234  		return &client.Client{}, err
   235  	}
   236  
   237  	return client.NewClient(host, verStr, httpClient, customHeaders)
   238  }
   239  
   240  func getServerHost(hosts []string, tlsOptions *tlsconfig.Options) (host string, err error) {
   241  	switch len(hosts) {
   242  	case 0:
   243  		host = os.Getenv("DOCKER_HOST")
   244  	case 1:
   245  		host = hosts[0]
   246  	default:
   247  		return "", errors.New("Please specify only one -H")
   248  	}
   249  
   250  	host, err = opts.ParseHost(tlsOptions != nil, host)
   251  	return
   252  }
   253  
   254  func newHTTPClient(host string, tlsOptions *tlsconfig.Options) (*http.Client, error) {
   255  	if tlsOptions == nil {
   256  		// let the api client configure the default transport.
   257  		return nil, nil
   258  	}
   259  
   260  	config, err := tlsconfig.Client(*tlsOptions)
   261  	if err != nil {
   262  		return nil, err
   263  	}
   264  	tr := &http.Transport{
   265  		TLSClientConfig: config,
   266  	}
   267  	proto, addr, _, err := client.ParseHost(host)
   268  	if err != nil {
   269  		return nil, err
   270  	}
   271  
   272  	sockets.ConfigureTransport(tr, proto, addr)
   273  
   274  	return &http.Client{
   275  		Transport: tr,
   276  	}, nil
   277  }
   278  
   279  func clientUserAgent() string {
   280  	return "Docker-Client/" + dockerversion.Version + " (" + runtime.GOOS + ")"
   281  }