github.com/stchris/docker@v1.4.2-0.20150106053530-1510a324dbd5/api/client/cli.go (about) 1 package client 2 3 import ( 4 "crypto/tls" 5 "encoding/json" 6 "errors" 7 "fmt" 8 "io" 9 "net" 10 "net/http" 11 "os" 12 "reflect" 13 "strings" 14 "text/template" 15 "time" 16 17 flag "github.com/docker/docker/pkg/mflag" 18 "github.com/docker/docker/pkg/term" 19 "github.com/docker/docker/registry" 20 "github.com/docker/libtrust" 21 ) 22 23 type DockerCli struct { 24 proto string 25 addr string 26 configFile *registry.ConfigFile 27 in io.ReadCloser 28 out io.Writer 29 err io.Writer 30 key libtrust.PrivateKey 31 tlsConfig *tls.Config 32 scheme string 33 // inFd holds file descriptor of the client's STDIN, if it's a valid file 34 inFd uintptr 35 // outFd holds file descriptor of the client's STDOUT, if it's a valid file 36 outFd uintptr 37 // isTerminalIn describes if client's STDIN is a TTY 38 isTerminalIn bool 39 // isTerminalOut describes if client's STDOUT is a TTY 40 isTerminalOut bool 41 transport *http.Transport 42 } 43 44 var funcMap = template.FuncMap{ 45 "json": func(v interface{}) string { 46 a, _ := json.Marshal(v) 47 return string(a) 48 }, 49 } 50 51 func (cli *DockerCli) getMethod(args ...string) (func(...string) error, bool) { 52 camelArgs := make([]string, len(args)) 53 for i, s := range args { 54 if len(s) == 0 { 55 return nil, false 56 } 57 camelArgs[i] = strings.ToUpper(s[:1]) + strings.ToLower(s[1:]) 58 } 59 methodName := "Cmd" + strings.Join(camelArgs, "") 60 method := reflect.ValueOf(cli).MethodByName(methodName) 61 if !method.IsValid() { 62 return nil, false 63 } 64 return method.Interface().(func(...string) error), true 65 } 66 67 // Cmd executes the specified command 68 func (cli *DockerCli) Cmd(args ...string) error { 69 if len(args) > 1 { 70 method, exists := cli.getMethod(args[:2]...) 71 if exists { 72 return method(args[2:]...) 73 } 74 } 75 if len(args) > 0 { 76 method, exists := cli.getMethod(args[0]) 77 if !exists { 78 fmt.Println("Error: Command not found:", args[0]) 79 return cli.CmdHelp() 80 } 81 return method(args[1:]...) 82 } 83 return cli.CmdHelp() 84 } 85 86 func (cli *DockerCli) Subcmd(name, signature, description string) *flag.FlagSet { 87 flags := flag.NewFlagSet(name, flag.ContinueOnError) 88 flags.Usage = func() { 89 options := "" 90 if flags.FlagCountUndeprecated() > 0 { 91 options = "[OPTIONS] " 92 } 93 fmt.Fprintf(cli.err, "\nUsage: docker %s %s%s\n\n%s\n\n", name, options, signature, description) 94 flags.PrintDefaults() 95 os.Exit(2) 96 } 97 return flags 98 } 99 100 func (cli *DockerCli) LoadConfigFile() (err error) { 101 cli.configFile, err = registry.LoadConfig(os.Getenv("HOME")) 102 if err != nil { 103 fmt.Fprintf(cli.err, "WARNING: %s\n", err) 104 } 105 return err 106 } 107 108 func (cli *DockerCli) CheckTtyInput(attachStdin, ttyMode bool) error { 109 // In order to attach to a container tty, input stream for the client must 110 // be a tty itself: redirecting or piping the client standard input is 111 // incompatible with `docker run -t`, `docker exec -t` or `docker attach`. 112 if ttyMode && attachStdin && !cli.isTerminalIn { 113 return errors.New("cannot enable tty mode on non tty input") 114 } 115 return nil 116 } 117 118 func NewDockerCli(in io.ReadCloser, out, err io.Writer, key libtrust.PrivateKey, proto, addr string, tlsConfig *tls.Config) *DockerCli { 119 var ( 120 inFd uintptr 121 outFd uintptr 122 isTerminalIn = false 123 isTerminalOut = false 124 scheme = "http" 125 ) 126 127 if tlsConfig != nil { 128 scheme = "https" 129 } 130 131 if in != nil { 132 if file, ok := in.(*os.File); ok { 133 inFd = file.Fd() 134 isTerminalIn = term.IsTerminal(inFd) 135 } 136 } 137 138 if out != nil { 139 if file, ok := out.(*os.File); ok { 140 outFd = file.Fd() 141 isTerminalOut = term.IsTerminal(outFd) 142 } 143 } 144 145 if err == nil { 146 err = out 147 } 148 149 // The transport is created here for reuse during the client session 150 tr := &http.Transport{ 151 TLSClientConfig: tlsConfig, 152 } 153 154 // Why 32? See issue 8035 155 timeout := 32 * time.Second 156 if proto == "unix" { 157 // no need in compressing for local communications 158 tr.DisableCompression = true 159 tr.Dial = func(_, _ string) (net.Conn, error) { 160 return net.DialTimeout(proto, addr, timeout) 161 } 162 } else { 163 tr.Dial = (&net.Dialer{Timeout: timeout}).Dial 164 } 165 166 return &DockerCli{ 167 proto: proto, 168 addr: addr, 169 in: in, 170 out: out, 171 err: err, 172 key: key, 173 inFd: inFd, 174 outFd: outFd, 175 isTerminalIn: isTerminalIn, 176 isTerminalOut: isTerminalOut, 177 tlsConfig: tlsConfig, 178 scheme: scheme, 179 transport: tr, 180 } 181 }