package command import ( "errors" "fmt" "io" "net/http" "os" "path/filepath" "runtime" "github.com/docker/docker/api" cliflags "github.com/docker/docker/cli/flags" "github.com/docker/docker/cliconfig" "github.com/docker/docker/cliconfig/configfile" "github.com/docker/docker/cliconfig/credentials" "github.com/docker/docker/client" "github.com/docker/docker/dockerversion" dopts "github.com/docker/docker/opts" "github.com/docker/go-connections/sockets" "github.com/docker/go-connections/tlsconfig" "golang.org/x/net/context" ) // Streams is an interface which exposes the standard input and output streams type Streams interface { In() *InStream Out() *OutStream Err() io.Writer } // DockerCli represents the docker command line client. // Instances of the client can be returned from NewDockerCli. type DockerCli struct { configFile *configfile.ConfigFile in *InStream out *OutStream err io.Writer keyFile string client client.APIClient } // HasExperimental returns true if experimental features are accessible func (cli *DockerCli) HasExperimental() bool { if cli.client == nil { return false } enabled, _ := cli.client.Ping(context.Background()) return enabled } // Client returns the APIClient func (cli *DockerCli) Client() client.APIClient { return cli.client } // Out returns the writer used for stdout func (cli *DockerCli) Out() *OutStream { return cli.out } // Err returns the writer used for stderr func (cli *DockerCli) Err() io.Writer { return cli.err } // In returns the reader used for stdin func (cli *DockerCli) In() *InStream { return cli.in } // ConfigFile returns the ConfigFile func (cli *DockerCli) ConfigFile() *configfile.ConfigFile { return cli.configFile } // CredentialsStore returns a new credentials store based // on the settings provided in the configuration file. func (cli *DockerCli) CredentialsStore() credentials.Store { if cli.configFile.CredentialsStore != "" { return credentials.NewNativeStore(cli.configFile) } return credentials.NewFileStore(cli.configFile) } // Initialize the dockerCli runs initialization that must happen after command // line flags are parsed. func (cli *DockerCli) Initialize(opts *cliflags.ClientOptions) error { cli.configFile = LoadDefaultConfigFile(cli.err) var err error cli.client, err = NewAPIClientFromFlags(opts.Common, cli.configFile) if err != nil { return err } if opts.Common.TrustKey == "" { cli.keyFile = filepath.Join(cliconfig.ConfigDir(), cliflags.DefaultTrustKeyFile) } else { cli.keyFile = opts.Common.TrustKey } return nil } // NewDockerCli returns a DockerCli instance with IO output and error streams set by in, out and err. func NewDockerCli(in io.ReadCloser, out, err io.Writer) *DockerCli { return &DockerCli{in: NewInStream(in), out: NewOutStream(out), err: err} } // LoadDefaultConfigFile attempts to load the default config file and returns // an initialized ConfigFile struct if none is found. func LoadDefaultConfigFile(err io.Writer) *configfile.ConfigFile { configFile, e := cliconfig.Load(cliconfig.ConfigDir()) if e != nil { fmt.Fprintf(err, "WARNING: Error loading config file:%v\n", e) } if !configFile.ContainsAuth() { credentials.DetectDefaultStore(configFile) } return configFile } // NewAPIClientFromFlags creates a new APIClient from command line flags func NewAPIClientFromFlags(opts *cliflags.CommonOptions, configFile *configfile.ConfigFile) (client.APIClient, error) { host, err := getServerHost(opts.Hosts, opts.TLSOptions) if err != nil { return &client.Client{}, err } customHeaders := configFile.HTTPHeaders if customHeaders == nil { customHeaders = map[string]string{} } customHeaders["User-Agent"] = UserAgent() verStr := api.DefaultVersion if tmpStr := os.Getenv("DOCKER_API_VERSION"); tmpStr != "" { verStr = tmpStr } httpClient, err := newHTTPClient(host, opts.TLSOptions) if err != nil { return &client.Client{}, err } return client.NewClient(host, verStr, httpClient, customHeaders) } func getServerHost(hosts []string, tlsOptions *tlsconfig.Options) (host string, err error) { switch len(hosts) { case 0: host = os.Getenv("DOCKER_HOST") case 1: host = hosts[0] default: return "", errors.New("Please specify only one -H") } host, err = dopts.ParseHost(tlsOptions != nil, host) return } func newHTTPClient(host string, tlsOptions *tlsconfig.Options) (*http.Client, error) { if tlsOptions == nil { // let the api client configure the default transport. return nil, nil } config, err := tlsconfig.Client(*tlsOptions) if err != nil { return nil, err } tr := &http.Transport{ TLSClientConfig: config, } proto, addr, _, err := client.ParseHost(host) if err != nil { return nil, err } sockets.ConfigureTransport(tr, proto, addr) return &http.Client{ Transport: tr, }, nil } // UserAgent returns the user agent string used for making API requests func UserAgent() string { return "Docker-Client/" + dockerversion.Version + " (" + runtime.GOOS + ")" }