package client import ( "crypto/tls" "encoding/json" "fmt" "io" "net" "net/http" "os" "reflect" "strings" "text/template" "time" flag "github.com/docker/docker/pkg/mflag" "github.com/docker/docker/pkg/term" "github.com/docker/docker/registry" "github.com/docker/libtrust" ) type DockerCli struct { proto string addr string configFile *registry.ConfigFile in io.ReadCloser out io.Writer err io.Writer key libtrust.PrivateKey tlsConfig *tls.Config scheme string // inFd holds file descriptor of the client's STDIN, if it's a valid file inFd uintptr // outFd holds file descriptor of the client's STDOUT, if it's a valid file outFd uintptr // isTerminalIn describes if client's STDIN is a TTY isTerminalIn bool // isTerminalOut describes if client's STDOUT is a TTY isTerminalOut bool transport *http.Transport } var funcMap = template.FuncMap{ "json": func(v interface{}) string { a, _ := json.Marshal(v) return string(a) }, } func (cli *DockerCli) getMethod(args ...string) (func(...string) error, bool) { camelArgs := make([]string, len(args)) for i, s := range args { if len(s) == 0 { return nil, false } camelArgs[i] = strings.ToUpper(s[:1]) + strings.ToLower(s[1:]) } methodName := "Cmd" + strings.Join(camelArgs, "") method := reflect.ValueOf(cli).MethodByName(methodName) if !method.IsValid() { return nil, false } return method.Interface().(func(...string) error), true } // Cmd executes the specified command func (cli *DockerCli) Cmd(args ...string) error { if len(args) > 1 { method, exists := cli.getMethod(args[:2]...) if exists { return method(args[2:]...) } } if len(args) > 0 { method, exists := cli.getMethod(args[0]) if !exists { fmt.Println("Error: Command not found:", args[0]) return cli.CmdHelp() } return method(args[1:]...) } return cli.CmdHelp() } func (cli *DockerCli) Subcmd(name, signature, description string) *flag.FlagSet { flags := flag.NewFlagSet(name, flag.ContinueOnError) flags.Usage = func() { options := "" if flags.FlagCountUndeprecated() > 0 { options = "[OPTIONS] " } fmt.Fprintf(cli.err, "\nUsage: docker %s %s%s\n\n%s\n\n", name, options, signature, description) flags.PrintDefaults() os.Exit(2) } return flags } func (cli *DockerCli) LoadConfigFile() (err error) { cli.configFile, err = registry.LoadConfig(os.Getenv("HOME")) if err != nil { fmt.Fprintf(cli.err, "WARNING: %s\n", err) } return err } func NewDockerCli(in io.ReadCloser, out, err io.Writer, key libtrust.PrivateKey, proto, addr string, tlsConfig *tls.Config) *DockerCli { var ( inFd uintptr outFd uintptr isTerminalIn = false isTerminalOut = false scheme = "http" ) if tlsConfig != nil { scheme = "https" } if in != nil { if file, ok := in.(*os.File); ok { inFd = file.Fd() isTerminalIn = term.IsTerminal(inFd) } } if out != nil { if file, ok := out.(*os.File); ok { outFd = file.Fd() isTerminalOut = term.IsTerminal(outFd) } } if err == nil { err = out } // The transport is created here for reuse during the client session tr := &http.Transport{ TLSClientConfig: tlsConfig, } // Why 32? See issue 8035 timeout := 32 * time.Second if proto == "unix" { // no need in compressing for local communications tr.DisableCompression = true tr.Dial = func(_, _ string) (net.Conn, error) { return net.DialTimeout(proto, addr, timeout) } } else { tr.Dial = (&net.Dialer{Timeout: timeout}).Dial } return &DockerCli{ proto: proto, addr: addr, in: in, out: out, err: err, key: key, inFd: inFd, outFd: outFd, isTerminalIn: isTerminalIn, isTerminalOut: isTerminalOut, tlsConfig: tlsConfig, scheme: scheme, transport: tr, } }