github.com/fabiokung/docker@v0.11.2-0.20170222101415-4534dcd49497/cli/command/cli.go (about)

     1  package command
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"io"
     7  	"net/http"
     8  	"os"
     9  	"path/filepath"
    10  	"runtime"
    11  
    12  	"github.com/docker/docker/api"
    13  	"github.com/docker/docker/api/types"
    14  	"github.com/docker/docker/api/types/versions"
    15  	cliconfig "github.com/docker/docker/cli/config"
    16  	"github.com/docker/docker/cli/config/configfile"
    17  	"github.com/docker/docker/cli/config/credentials"
    18  	cliflags "github.com/docker/docker/cli/flags"
    19  	"github.com/docker/docker/client"
    20  	"github.com/docker/docker/dockerversion"
    21  	dopts "github.com/docker/docker/opts"
    22  	"github.com/docker/go-connections/sockets"
    23  	"github.com/docker/go-connections/tlsconfig"
    24  	"github.com/spf13/cobra"
    25  	"golang.org/x/net/context"
    26  )
    27  
    28  // Streams is an interface which exposes the standard input and output streams
    29  type Streams interface {
    30  	In() *InStream
    31  	Out() *OutStream
    32  	Err() io.Writer
    33  }
    34  
    35  // Cli represents the docker command line client.
    36  type Cli interface {
    37  	Client() client.APIClient
    38  	Out() *OutStream
    39  	Err() io.Writer
    40  	In() *InStream
    41  	ConfigFile() *configfile.ConfigFile
    42  }
    43  
    44  // DockerCli is an instance the docker command line client.
    45  // Instances of the client can be returned from NewDockerCli.
    46  type DockerCli struct {
    47  	configFile      *configfile.ConfigFile
    48  	in              *InStream
    49  	out             *OutStream
    50  	err             io.Writer
    51  	keyFile         string
    52  	client          client.APIClient
    53  	hasExperimental bool
    54  	defaultVersion  string
    55  }
    56  
    57  // HasExperimental returns true if experimental features are accessible.
    58  func (cli *DockerCli) HasExperimental() bool {
    59  	return cli.hasExperimental
    60  }
    61  
    62  // DefaultVersion returns api.defaultVersion of DOCKER_API_VERSION if specified.
    63  func (cli *DockerCli) DefaultVersion() string {
    64  	return cli.defaultVersion
    65  }
    66  
    67  // Client returns the APIClient
    68  func (cli *DockerCli) Client() client.APIClient {
    69  	return cli.client
    70  }
    71  
    72  // Out returns the writer used for stdout
    73  func (cli *DockerCli) Out() *OutStream {
    74  	return cli.out
    75  }
    76  
    77  // Err returns the writer used for stderr
    78  func (cli *DockerCli) Err() io.Writer {
    79  	return cli.err
    80  }
    81  
    82  // In returns the reader used for stdin
    83  func (cli *DockerCli) In() *InStream {
    84  	return cli.in
    85  }
    86  
    87  // ShowHelp shows the command help.
    88  func (cli *DockerCli) ShowHelp(cmd *cobra.Command, args []string) error {
    89  	cmd.SetOutput(cli.err)
    90  	cmd.HelpFunc()(cmd, args)
    91  	return nil
    92  }
    93  
    94  // ConfigFile returns the ConfigFile
    95  func (cli *DockerCli) ConfigFile() *configfile.ConfigFile {
    96  	return cli.configFile
    97  }
    98  
    99  // GetAllCredentials returns all of the credentials stored in all of the
   100  // configured credential stores.
   101  func (cli *DockerCli) GetAllCredentials() (map[string]types.AuthConfig, error) {
   102  	auths := make(map[string]types.AuthConfig)
   103  	for registry := range cli.configFile.CredentialHelpers {
   104  		helper := cli.CredentialsStore(registry)
   105  		newAuths, err := helper.GetAll()
   106  		if err != nil {
   107  			return nil, err
   108  		}
   109  		addAll(auths, newAuths)
   110  	}
   111  	defaultStore := cli.CredentialsStore("")
   112  	newAuths, err := defaultStore.GetAll()
   113  	if err != nil {
   114  		return nil, err
   115  	}
   116  	addAll(auths, newAuths)
   117  	return auths, nil
   118  }
   119  
   120  func addAll(to, from map[string]types.AuthConfig) {
   121  	for reg, ac := range from {
   122  		to[reg] = ac
   123  	}
   124  }
   125  
   126  // CredentialsStore returns a new credentials store based
   127  // on the settings provided in the configuration file. Empty string returns
   128  // the default credential store.
   129  func (cli *DockerCli) CredentialsStore(serverAddress string) credentials.Store {
   130  	if helper := getConfiguredCredentialStore(cli.configFile, serverAddress); helper != "" {
   131  		return credentials.NewNativeStore(cli.configFile, helper)
   132  	}
   133  	return credentials.NewFileStore(cli.configFile)
   134  }
   135  
   136  // getConfiguredCredentialStore returns the credential helper configured for the
   137  // given registry, the default credsStore, or the empty string if neither are
   138  // configured.
   139  func getConfiguredCredentialStore(c *configfile.ConfigFile, serverAddress string) string {
   140  	if c.CredentialHelpers != nil && serverAddress != "" {
   141  		if helper, exists := c.CredentialHelpers[serverAddress]; exists {
   142  			return helper
   143  		}
   144  	}
   145  	return c.CredentialsStore
   146  }
   147  
   148  // Initialize the dockerCli runs initialization that must happen after command
   149  // line flags are parsed.
   150  func (cli *DockerCli) Initialize(opts *cliflags.ClientOptions) error {
   151  	cli.configFile = LoadDefaultConfigFile(cli.err)
   152  
   153  	var err error
   154  	cli.client, err = NewAPIClientFromFlags(opts.Common, cli.configFile)
   155  	if err != nil {
   156  		return err
   157  	}
   158  
   159  	cli.defaultVersion = cli.client.ClientVersion()
   160  
   161  	if opts.Common.TrustKey == "" {
   162  		cli.keyFile = filepath.Join(cliconfig.Dir(), cliflags.DefaultTrustKeyFile)
   163  	} else {
   164  		cli.keyFile = opts.Common.TrustKey
   165  	}
   166  
   167  	if ping, err := cli.client.Ping(context.Background()); err == nil {
   168  		cli.hasExperimental = ping.Experimental
   169  
   170  		// since the new header was added in 1.25, assume server is 1.24 if header is not present.
   171  		if ping.APIVersion == "" {
   172  			ping.APIVersion = "1.24"
   173  		}
   174  
   175  		// if server version is lower than the current cli, downgrade
   176  		if versions.LessThan(ping.APIVersion, cli.client.ClientVersion()) {
   177  			cli.client.UpdateClientVersion(ping.APIVersion)
   178  		}
   179  	}
   180  	return nil
   181  }
   182  
   183  // NewDockerCli returns a DockerCli instance with IO output and error streams set by in, out and err.
   184  func NewDockerCli(in io.ReadCloser, out, err io.Writer) *DockerCli {
   185  	return &DockerCli{in: NewInStream(in), out: NewOutStream(out), err: err}
   186  }
   187  
   188  // LoadDefaultConfigFile attempts to load the default config file and returns
   189  // an initialized ConfigFile struct if none is found.
   190  func LoadDefaultConfigFile(err io.Writer) *configfile.ConfigFile {
   191  	configFile, e := cliconfig.Load(cliconfig.Dir())
   192  	if e != nil {
   193  		fmt.Fprintf(err, "WARNING: Error loading config file:%v\n", e)
   194  	}
   195  	if !configFile.ContainsAuth() {
   196  		credentials.DetectDefaultStore(configFile)
   197  	}
   198  	return configFile
   199  }
   200  
   201  // NewAPIClientFromFlags creates a new APIClient from command line flags
   202  func NewAPIClientFromFlags(opts *cliflags.CommonOptions, configFile *configfile.ConfigFile) (client.APIClient, error) {
   203  	host, err := getServerHost(opts.Hosts, opts.TLSOptions)
   204  	if err != nil {
   205  		return &client.Client{}, err
   206  	}
   207  
   208  	customHeaders := configFile.HTTPHeaders
   209  	if customHeaders == nil {
   210  		customHeaders = map[string]string{}
   211  	}
   212  	customHeaders["User-Agent"] = UserAgent()
   213  
   214  	verStr := api.DefaultVersion
   215  	if tmpStr := os.Getenv("DOCKER_API_VERSION"); tmpStr != "" {
   216  		verStr = tmpStr
   217  	}
   218  
   219  	httpClient, err := newHTTPClient(host, opts.TLSOptions)
   220  	if err != nil {
   221  		return &client.Client{}, err
   222  	}
   223  
   224  	return client.NewClient(host, verStr, httpClient, customHeaders)
   225  }
   226  
   227  func getServerHost(hosts []string, tlsOptions *tlsconfig.Options) (host string, err error) {
   228  	switch len(hosts) {
   229  	case 0:
   230  		host = os.Getenv("DOCKER_HOST")
   231  	case 1:
   232  		host = hosts[0]
   233  	default:
   234  		return "", errors.New("Please specify only one -H")
   235  	}
   236  
   237  	host, err = dopts.ParseHost(tlsOptions != nil, host)
   238  	return
   239  }
   240  
   241  func newHTTPClient(host string, tlsOptions *tlsconfig.Options) (*http.Client, error) {
   242  	if tlsOptions == nil {
   243  		// let the api client configure the default transport.
   244  		return nil, nil
   245  	}
   246  
   247  	config, err := tlsconfig.Client(*tlsOptions)
   248  	if err != nil {
   249  		return nil, err
   250  	}
   251  	tr := &http.Transport{
   252  		TLSClientConfig: config,
   253  	}
   254  	proto, addr, _, err := client.ParseHost(host)
   255  	if err != nil {
   256  		return nil, err
   257  	}
   258  
   259  	sockets.ConfigureTransport(tr, proto, addr)
   260  
   261  	return &http.Client{
   262  		Transport: tr,
   263  	}, nil
   264  }
   265  
   266  // UserAgent returns the user agent string used for making API requests
   267  func UserAgent() string {
   268  	return "Docker-Client/" + dockerversion.Version + " (" + runtime.GOOS + ")"
   269  }