github.com/chenchun/docker@v1.3.2-0.20150629222414-20467faf132b/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/http" 10 "path/filepath" 11 "reflect" 12 "strings" 13 "text/template" 14 15 "github.com/docker/docker/cliconfig" 16 "github.com/docker/docker/pkg/homedir" 17 flag "github.com/docker/docker/pkg/mflag" 18 "github.com/docker/docker/pkg/term" 19 "github.com/docker/docker/utils" 20 ) 21 22 // DockerCli represents the docker command line client. 23 // Instances of the client can be returned from NewDockerCli. 24 type DockerCli struct { 25 // proto holds the client protocol i.e. unix. 26 proto string 27 // addr holds the client address. 28 addr string 29 30 // configFile has the client configuration file 31 configFile *cliconfig.ConfigFile 32 // in holds the input stream and closer (io.ReadCloser) for the client. 33 in io.ReadCloser 34 // out holds the output stream (io.Writer) for the client. 35 out io.Writer 36 // err holds the error stream (io.Writer) for the client. 37 err io.Writer 38 // keyFile holds the key file as a string. 39 keyFile string 40 // tlsConfig holds the TLS configuration for the client, and will 41 // set the scheme to https in NewDockerCli if present. 42 tlsConfig *tls.Config 43 // scheme holds the scheme of the client i.e. https. 44 scheme string 45 // inFd holds the file descriptor of the client's STDIN (if valid). 46 inFd uintptr 47 // outFd holds file descriptor of the client's STDOUT (if valid). 48 outFd uintptr 49 // isTerminalIn indicates whether the client's STDIN is a TTY 50 isTerminalIn bool 51 // isTerminalOut dindicates whether the client's STDOUT is a TTY 52 isTerminalOut bool 53 // transport holds the client transport instance. 54 transport *http.Transport 55 } 56 57 var funcMap = template.FuncMap{ 58 "json": func(v interface{}) string { 59 a, _ := json.Marshal(v) 60 return string(a) 61 }, 62 } 63 64 func (cli *DockerCli) Out() io.Writer { 65 return cli.out 66 } 67 68 func (cli *DockerCli) Err() io.Writer { 69 return cli.err 70 } 71 72 func (cli *DockerCli) getMethod(args ...string) (func(...string) error, bool) { 73 camelArgs := make([]string, len(args)) 74 for i, s := range args { 75 if len(s) == 0 { 76 return nil, false 77 } 78 camelArgs[i] = strings.ToUpper(s[:1]) + strings.ToLower(s[1:]) 79 } 80 methodName := "Cmd" + strings.Join(camelArgs, "") 81 method := reflect.ValueOf(cli).MethodByName(methodName) 82 if !method.IsValid() { 83 return nil, false 84 } 85 return method.Interface().(func(...string) error), true 86 } 87 88 // Cmd executes the specified command. 89 func (cli *DockerCli) Cmd(args ...string) error { 90 if len(args) > 1 { 91 method, exists := cli.getMethod(args[:2]...) 92 if exists { 93 return method(args[2:]...) 94 } 95 } 96 if len(args) > 0 { 97 method, exists := cli.getMethod(args[0]) 98 if !exists { 99 return fmt.Errorf("docker: '%s' is not a docker command.\nSee 'docker --help'.", args[0]) 100 } 101 return method(args[1:]...) 102 } 103 return cli.CmdHelp() 104 } 105 106 // Subcmd is a subcommand of the main "docker" command. 107 // A subcommand represents an action that can be performed 108 // from the Docker command line client. 109 // 110 // Multiple subcommand synopses may be provided with one 'Usage' line being 111 // printed for each in the following way: 112 // 113 // Usage: docker <subcmd-name> [OPTIONS] <synopsis 0> 114 // docker <subcmd-name> [OPTIONS] <synopsis 1> 115 // ... 116 // 117 // If no undeprecated flags are added to the returned FlagSet, "[OPTIONS]" will 118 // not be included on the usage synopsis lines. If no synopses are given, only 119 // one usage synopsis line will be printed with nothing following the 120 // "[OPTIONS]" section 121 // 122 // To see all available subcommands, run "docker --help". 123 func (cli *DockerCli) Subcmd(name string, synopses []string, description string, exitOnError bool) *flag.FlagSet { 124 var errorHandling flag.ErrorHandling 125 if exitOnError { 126 errorHandling = flag.ExitOnError 127 } else { 128 errorHandling = flag.ContinueOnError 129 } 130 131 flags := flag.NewFlagSet(name, errorHandling) 132 133 flags.Usage = func() { 134 flags.ShortUsage() 135 flags.PrintDefaults() 136 } 137 138 flags.ShortUsage = func() { 139 options := "" 140 if flags.FlagCountUndeprecated() > 0 { 141 options = " [OPTIONS]" 142 } 143 144 if len(synopses) == 0 { 145 synopses = []string{""} 146 } 147 148 // Allow for multiple command usage synopses. 149 for i, synopsis := range synopses { 150 lead := "\t" 151 if i == 0 { 152 // First line needs the word 'Usage'. 153 lead = "Usage:\t" 154 } 155 156 if synopsis != "" { 157 synopsis = " " + synopsis 158 } 159 160 fmt.Fprintf(flags.Out(), "\n%sdocker %s%s%s", lead, name, options, synopsis) 161 } 162 163 fmt.Fprintf(flags.Out(), "\n\n%s\n", description) 164 } 165 166 return flags 167 } 168 169 // CheckTtyInput checks if we are trying to attach to a container tty 170 // from a non-tty client input stream, and if so, returns an error. 171 func (cli *DockerCli) CheckTtyInput(attachStdin, ttyMode bool) error { 172 // In order to attach to a container tty, input stream for the client must 173 // be a tty itself: redirecting or piping the client standard input is 174 // incompatible with `docker run -t`, `docker exec -t` or `docker attach`. 175 if ttyMode && attachStdin && !cli.isTerminalIn { 176 return errors.New("cannot enable tty mode on non tty input") 177 } 178 return nil 179 } 180 181 // NewDockerCli returns a DockerCli instance with IO output and error streams set by in, out and err. 182 // The key file, protocol (i.e. unix) and address are passed in as strings, along with the tls.Config. If the tls.Config 183 // is set the client scheme will be set to https. 184 // The client will be given a 32-second timeout (see https://github.com/docker/docker/pull/8035). 185 func NewDockerCli(in io.ReadCloser, out, err io.Writer, keyFile string, proto, addr string, tlsConfig *tls.Config) *DockerCli { 186 var ( 187 inFd uintptr 188 outFd uintptr 189 isTerminalIn = false 190 isTerminalOut = false 191 scheme = "http" 192 ) 193 194 if tlsConfig != nil { 195 scheme = "https" 196 } 197 if in != nil { 198 inFd, isTerminalIn = term.GetFdInfo(in) 199 } 200 201 if out != nil { 202 outFd, isTerminalOut = term.GetFdInfo(out) 203 } 204 205 if err == nil { 206 err = out 207 } 208 209 // The transport is created here for reuse during the client session. 210 tr := &http.Transport{ 211 TLSClientConfig: tlsConfig, 212 } 213 utils.ConfigureTCPTransport(tr, proto, addr) 214 215 configFile, e := cliconfig.Load(filepath.Join(homedir.Get(), ".docker")) 216 if e != nil { 217 fmt.Fprintf(err, "WARNING: Error loading config file:%v\n", e) 218 } 219 220 return &DockerCli{ 221 proto: proto, 222 addr: addr, 223 configFile: configFile, 224 in: in, 225 out: out, 226 err: err, 227 keyFile: keyFile, 228 inFd: inFd, 229 outFd: outFd, 230 isTerminalIn: isTerminalIn, 231 isTerminalOut: isTerminalOut, 232 tlsConfig: tlsConfig, 233 scheme: scheme, 234 transport: tr, 235 } 236 }