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