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