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