github.com/guilhermebr/docker@v1.4.2-0.20150428121140-67da055cebca/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) getMethod(args ...string) (func(...string) error, bool) { 67 camelArgs := make([]string, len(args)) 68 for i, s := range args { 69 if len(s) == 0 { 70 return nil, false 71 } 72 camelArgs[i] = strings.ToUpper(s[:1]) + strings.ToLower(s[1:]) 73 } 74 methodName := "Cmd" + strings.Join(camelArgs, "") 75 method := reflect.ValueOf(cli).MethodByName(methodName) 76 if !method.IsValid() { 77 return nil, false 78 } 79 return method.Interface().(func(...string) error), true 80 } 81 82 // Cmd executes the specified command. 83 func (cli *DockerCli) Cmd(args ...string) error { 84 if len(args) > 1 { 85 method, exists := cli.getMethod(args[:2]...) 86 if exists { 87 return method(args[2:]...) 88 } 89 } 90 if len(args) > 0 { 91 method, exists := cli.getMethod(args[0]) 92 if !exists { 93 fmt.Fprintf(cli.err, "docker: '%s' is not a docker command. See 'docker --help'.\n", args[0]) 94 os.Exit(1) 95 } 96 return method(args[1:]...) 97 } 98 return cli.CmdHelp() 99 } 100 101 // Subcmd is a subcommand of the main "docker" command. 102 // A subcommand represents an action that can be performed 103 // from the Docker command line client. 104 // 105 // To see all available subcommands, run "docker --help". 106 func (cli *DockerCli) Subcmd(name, signature, description string, exitOnError bool) *flag.FlagSet { 107 var errorHandling flag.ErrorHandling 108 if exitOnError { 109 errorHandling = flag.ExitOnError 110 } else { 111 errorHandling = flag.ContinueOnError 112 } 113 flags := flag.NewFlagSet(name, errorHandling) 114 flags.Usage = func() { 115 options := "" 116 if signature != "" { 117 signature = " " + signature 118 } 119 if flags.FlagCountUndeprecated() > 0 { 120 options = " [OPTIONS]" 121 } 122 fmt.Fprintf(cli.out, "\nUsage: docker %s%s%s\n\n%s\n\n", name, options, signature, description) 123 flags.SetOutput(cli.out) 124 flags.PrintDefaults() 125 os.Exit(0) 126 } 127 return flags 128 } 129 130 // CheckTtyInput checks if we are trying to attach to a container tty 131 // from a non-tty client input stream, and if so, returns an error. 132 func (cli *DockerCli) CheckTtyInput(attachStdin, ttyMode bool) error { 133 // In order to attach to a container tty, input stream for the client must 134 // be a tty itself: redirecting or piping the client standard input is 135 // incompatible with `docker run -t`, `docker exec -t` or `docker attach`. 136 if ttyMode && attachStdin && !cli.isTerminalIn { 137 return errors.New("cannot enable tty mode on non tty input") 138 } 139 return nil 140 } 141 142 // NewDockerCli returns a DockerCli instance with IO output and error streams set by in, out and err. 143 // The key file, protocol (i.e. unix) and address are passed in as strings, along with the tls.Config. If the tls.Config 144 // is set the client scheme will be set to https. 145 // The client will be given a 32-second timeout (see https://github.com/docker/docker/pull/8035). 146 func NewDockerCli(in io.ReadCloser, out, err io.Writer, keyFile string, proto, addr string, tlsConfig *tls.Config) *DockerCli { 147 var ( 148 inFd uintptr 149 outFd uintptr 150 isTerminalIn = false 151 isTerminalOut = false 152 scheme = "http" 153 ) 154 155 if tlsConfig != nil { 156 scheme = "https" 157 } 158 if in != nil { 159 inFd, isTerminalIn = term.GetFdInfo(in) 160 } 161 162 if out != nil { 163 outFd, isTerminalOut = term.GetFdInfo(out) 164 } 165 166 if err == nil { 167 err = out 168 } 169 170 // The transport is created here for reuse during the client session. 171 tr := &http.Transport{ 172 TLSClientConfig: tlsConfig, 173 } 174 175 // Why 32? See https://github.com/docker/docker/pull/8035. 176 timeout := 32 * time.Second 177 if proto == "unix" { 178 // No need for compression in local communications. 179 tr.DisableCompression = true 180 tr.Dial = func(_, _ string) (net.Conn, error) { 181 return net.DialTimeout(proto, addr, timeout) 182 } 183 } else { 184 tr.Proxy = http.ProxyFromEnvironment 185 tr.Dial = (&net.Dialer{Timeout: timeout}).Dial 186 } 187 188 configFile, e := cliconfig.Load(filepath.Join(homedir.Get(), ".docker")) 189 if e != nil { 190 fmt.Fprintf(err, "WARNING: Error loading config file:%v\n", e) 191 } 192 193 return &DockerCli{ 194 proto: proto, 195 addr: addr, 196 configFile: configFile, 197 in: in, 198 out: out, 199 err: err, 200 keyFile: keyFile, 201 inFd: inFd, 202 outFd: outFd, 203 isTerminalIn: isTerminalIn, 204 isTerminalOut: isTerminalOut, 205 tlsConfig: tlsConfig, 206 scheme: scheme, 207 transport: tr, 208 } 209 }