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