2014-03-28 23:21:55 +00:00
|
|
|
package client
|
|
|
|
|
|
|
|
import (
|
2014-12-06 00:50:56 +00:00
|
|
|
"errors"
|
2014-03-28 23:21:55 +00:00
|
|
|
"fmt"
|
|
|
|
"io"
|
2015-12-17 01:32:17 +00:00
|
|
|
"net/http"
|
2015-05-05 04:18:28 +00:00
|
|
|
"os"
|
2016-06-22 22:36:51 +00:00
|
|
|
"path/filepath"
|
2015-12-03 04:53:06 +00:00
|
|
|
"runtime"
|
2014-03-28 23:21:55 +00:00
|
|
|
|
2015-12-14 17:06:42 +00:00
|
|
|
"github.com/docker/docker/api"
|
2016-04-21 21:51:28 +00:00
|
|
|
cliflags "github.com/docker/docker/cli/flags"
|
2015-04-22 12:06:58 +00:00
|
|
|
"github.com/docker/docker/cliconfig"
|
2016-04-21 22:37:08 +00:00
|
|
|
"github.com/docker/docker/cliconfig/configfile"
|
2016-02-08 00:55:17 +00:00
|
|
|
"github.com/docker/docker/cliconfig/credentials"
|
2016-09-06 18:46:37 +00:00
|
|
|
"github.com/docker/docker/client"
|
2015-12-03 04:53:06 +00:00
|
|
|
"github.com/docker/docker/dockerversion"
|
2016-06-22 17:08:04 +00:00
|
|
|
dopts "github.com/docker/docker/opts"
|
2014-07-24 22:19:50 +00:00
|
|
|
"github.com/docker/docker/pkg/term"
|
2016-02-03 23:41:26 +00:00
|
|
|
"github.com/docker/go-connections/sockets"
|
2015-12-30 00:27:12 +00:00
|
|
|
"github.com/docker/go-connections/tlsconfig"
|
2014-03-28 23:21:55 +00:00
|
|
|
)
|
|
|
|
|
2015-04-14 13:14:33 +00:00
|
|
|
// DockerCli represents the docker command line client.
|
|
|
|
// Instances of the client can be returned from NewDockerCli.
|
2014-08-10 04:31:59 +00:00
|
|
|
type DockerCli struct {
|
2015-05-05 04:18:28 +00:00
|
|
|
// initializing closure
|
|
|
|
init func() error
|
|
|
|
|
2015-04-22 12:06:58 +00:00
|
|
|
// configFile has the client configuration file
|
2016-04-21 22:37:08 +00:00
|
|
|
configFile *configfile.ConfigFile
|
2015-04-14 13:14:33 +00:00
|
|
|
// in holds the input stream and closer (io.ReadCloser) for the client.
|
2016-08-29 15:11:29 +00:00
|
|
|
// TODO: remove
|
2015-04-14 13:14:33 +00:00
|
|
|
in io.ReadCloser
|
|
|
|
// err holds the error stream (io.Writer) for the client.
|
|
|
|
err io.Writer
|
|
|
|
// keyFile holds the key file as a string.
|
|
|
|
keyFile string
|
|
|
|
// inFd holds the file descriptor of the client's STDIN (if valid).
|
2016-08-29 15:11:29 +00:00
|
|
|
// TODO: remove
|
2014-09-10 13:35:48 +00:00
|
|
|
inFd uintptr
|
2015-04-14 13:14:33 +00:00
|
|
|
// isTerminalIn indicates whether the client's STDIN is a TTY
|
2016-08-29 15:11:29 +00:00
|
|
|
// TODO: remove
|
2014-09-10 13:35:48 +00:00
|
|
|
isTerminalIn bool
|
2015-12-03 04:53:06 +00:00
|
|
|
// client is the http client that performs all API operations
|
2016-01-08 00:47:12 +00:00
|
|
|
client client.APIClient
|
2016-08-28 09:01:22 +00:00
|
|
|
// inState holds the terminal input state
|
2016-08-29 15:11:29 +00:00
|
|
|
// TODO: remove
|
2016-06-22 23:34:01 +00:00
|
|
|
inState *term.State
|
2016-08-29 15:11:29 +00:00
|
|
|
out *OutStream
|
2014-08-10 04:31:59 +00:00
|
|
|
}
|
|
|
|
|
2016-04-19 16:59:48 +00:00
|
|
|
// Client returns the APIClient
|
|
|
|
func (cli *DockerCli) Client() client.APIClient {
|
|
|
|
return cli.client
|
|
|
|
}
|
|
|
|
|
|
|
|
// Out returns the writer used for stdout
|
2016-08-29 15:11:29 +00:00
|
|
|
func (cli *DockerCli) Out() *OutStream {
|
2016-04-19 16:59:48 +00:00
|
|
|
return cli.out
|
|
|
|
}
|
|
|
|
|
|
|
|
// Err returns the writer used for stderr
|
|
|
|
func (cli *DockerCli) Err() io.Writer {
|
|
|
|
return cli.err
|
|
|
|
}
|
|
|
|
|
2016-05-31 23:49:32 +00:00
|
|
|
// In returns the reader used for stdin
|
|
|
|
func (cli *DockerCli) In() io.ReadCloser {
|
|
|
|
return cli.in
|
|
|
|
}
|
|
|
|
|
|
|
|
// ConfigFile returns the ConfigFile
|
|
|
|
func (cli *DockerCli) ConfigFile() *configfile.ConfigFile {
|
|
|
|
return cli.configFile
|
|
|
|
}
|
|
|
|
|
2016-06-20 13:27:56 +00:00
|
|
|
// IsTerminalIn returns true if the clients stdin is a TTY
|
2016-08-29 15:11:29 +00:00
|
|
|
// TODO: remove
|
2016-06-20 13:27:56 +00:00
|
|
|
func (cli *DockerCli) IsTerminalIn() bool {
|
|
|
|
return cli.isTerminalIn
|
|
|
|
}
|
|
|
|
|
2015-04-22 06:45:18 +00:00
|
|
|
// CheckTtyInput checks if we are trying to attach to a container tty
|
|
|
|
// from a non-tty client input stream, and if so, returns an error.
|
2014-12-06 00:50:56 +00:00
|
|
|
func (cli *DockerCli) CheckTtyInput(attachStdin, ttyMode bool) error {
|
|
|
|
// In order to attach to a container tty, input stream for the client must
|
|
|
|
// be a tty itself: redirecting or piping the client standard input is
|
|
|
|
// incompatible with `docker run -t`, `docker exec -t` or `docker attach`.
|
|
|
|
if ttyMode && attachStdin && !cli.isTerminalIn {
|
2016-05-25 17:19:17 +00:00
|
|
|
eText := "the input device is not a TTY"
|
|
|
|
if runtime.GOOS == "windows" {
|
|
|
|
return errors.New(eText + ". If you are using mintty, try prefixing the command with 'winpty'")
|
|
|
|
}
|
|
|
|
return errors.New(eText)
|
2014-12-06 00:50:56 +00:00
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2016-01-20 21:18:36 +00:00
|
|
|
func (cli *DockerCli) setRawTerminal() error {
|
2016-06-22 23:34:01 +00:00
|
|
|
if os.Getenv("NORAW") == "" {
|
|
|
|
if cli.isTerminalIn {
|
|
|
|
state, err := term.SetRawTerminal(cli.inFd)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
cli.inState = state
|
|
|
|
}
|
2016-08-29 15:11:29 +00:00
|
|
|
if err := cli.out.setRawTerminal(); err != nil {
|
|
|
|
return err
|
2016-01-20 21:18:36 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (cli *DockerCli) restoreTerminal(in io.Closer) error {
|
2016-06-22 23:34:01 +00:00
|
|
|
if cli.inState != nil {
|
|
|
|
term.RestoreTerminal(cli.inFd, cli.inState)
|
|
|
|
}
|
2016-08-29 15:11:29 +00:00
|
|
|
cli.out.restoreTerminal()
|
2016-02-02 01:28:40 +00:00
|
|
|
// WARNING: DO NOT REMOVE THE OS CHECK !!!
|
|
|
|
// For some reason this Close call blocks on darwin..
|
|
|
|
// As the client exists right after, simply discard the close
|
|
|
|
// until we find a better solution.
|
|
|
|
if in != nil && runtime.GOOS != "darwin" {
|
2016-01-20 21:18:36 +00:00
|
|
|
return in.Close()
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2016-06-22 17:08:04 +00:00
|
|
|
// Initialize the dockerCli runs initialization that must happen after command
|
|
|
|
// line flags are parsed.
|
2016-08-29 15:11:29 +00:00
|
|
|
func (cli *DockerCli) Initialize(opts *cliflags.ClientOptions) (err error) {
|
2016-06-22 17:08:04 +00:00
|
|
|
cli.configFile = LoadDefaultConfigFile(cli.err)
|
|
|
|
|
2016-08-29 15:11:29 +00:00
|
|
|
cli.client, err = NewAPIClientFromFlags(opts.Common, cli.configFile)
|
2016-06-22 17:08:04 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if cli.in != nil {
|
|
|
|
cli.inFd, cli.isTerminalIn = term.GetFdInfo(cli.in)
|
|
|
|
}
|
2016-06-22 22:36:51 +00:00
|
|
|
|
|
|
|
if opts.Common.TrustKey == "" {
|
|
|
|
cli.keyFile = filepath.Join(cliconfig.ConfigDir(), cliflags.DefaultTrustKeyFile)
|
|
|
|
} else {
|
|
|
|
cli.keyFile = opts.Common.TrustKey
|
|
|
|
}
|
|
|
|
|
2016-06-22 17:08:04 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2015-04-14 13:14:33 +00:00
|
|
|
// NewDockerCli returns a DockerCli instance with IO output and error streams set by in, out and err.
|
2016-06-22 22:36:51 +00:00
|
|
|
func NewDockerCli(in io.ReadCloser, out, err io.Writer) *DockerCli {
|
2016-08-29 15:11:29 +00:00
|
|
|
return &DockerCli{in: in, out: NewOutStream(out), err: err}
|
2014-03-28 23:21:55 +00:00
|
|
|
}
|
2015-12-03 04:53:06 +00:00
|
|
|
|
2016-04-19 16:59:48 +00:00
|
|
|
// 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
|
2016-06-22 17:08:04 +00:00
|
|
|
func NewAPIClientFromFlags(opts *cliflags.CommonOptions, configFile *configfile.ConfigFile) (client.APIClient, error) {
|
|
|
|
host, err := getServerHost(opts.Hosts, opts.TLSOptions)
|
2016-04-19 16:59:48 +00:00
|
|
|
if err != nil {
|
|
|
|
return &client.Client{}, err
|
|
|
|
}
|
|
|
|
|
|
|
|
customHeaders := configFile.HTTPHeaders
|
|
|
|
if customHeaders == nil {
|
|
|
|
customHeaders = map[string]string{}
|
|
|
|
}
|
|
|
|
customHeaders["User-Agent"] = clientUserAgent()
|
|
|
|
|
|
|
|
verStr := api.DefaultVersion
|
|
|
|
if tmpStr := os.Getenv("DOCKER_API_VERSION"); tmpStr != "" {
|
|
|
|
verStr = tmpStr
|
|
|
|
}
|
|
|
|
|
2016-06-22 17:08:04 +00:00
|
|
|
httpClient, err := newHTTPClient(host, opts.TLSOptions)
|
2016-04-19 16:59:48 +00:00
|
|
|
if err != nil {
|
|
|
|
return &client.Client{}, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return client.NewClient(host, verStr, httpClient, customHeaders)
|
|
|
|
}
|
|
|
|
|
2015-12-03 04:53:06 +00:00
|
|
|
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")
|
|
|
|
}
|
|
|
|
|
2016-06-22 17:08:04 +00:00
|
|
|
host, err = dopts.ParseHost(tlsOptions != nil, host)
|
2015-12-03 04:53:06 +00:00
|
|
|
return
|
|
|
|
}
|
2015-12-17 01:32:17 +00:00
|
|
|
|
2016-02-03 23:41:26 +00:00
|
|
|
func newHTTPClient(host string, tlsOptions *tlsconfig.Options) (*http.Client, error) {
|
2015-12-17 01:32:17 +00:00
|
|
|
if tlsOptions == nil {
|
2016-02-03 23:41:26 +00:00
|
|
|
// let the api client configure the default transport.
|
|
|
|
return nil, nil
|
2015-12-17 01:32:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
config, err := tlsconfig.Client(*tlsOptions)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2016-02-03 23:41:26 +00:00
|
|
|
tr := &http.Transport{
|
2015-12-17 01:32:17 +00:00
|
|
|
TLSClientConfig: config,
|
2016-02-03 23:41:26 +00:00
|
|
|
}
|
|
|
|
proto, addr, _, err := client.ParseHost(host)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
sockets.ConfigureTransport(tr, proto, addr)
|
|
|
|
|
|
|
|
return &http.Client{
|
|
|
|
Transport: tr,
|
2015-12-17 01:32:17 +00:00
|
|
|
}, nil
|
|
|
|
}
|
2016-03-18 21:42:40 +00:00
|
|
|
|
|
|
|
func clientUserAgent() string {
|
|
|
|
return "Docker-Client/" + dockerversion.Version + " (" + runtime.GOOS + ")"
|
|
|
|
}
|