mirror of
https://github.com/moby/moby.git
synced 2022-11-09 12:21:53 -05:00
![Vincent Batts](/assets/img/avatar_default.png)
First off, sorry for the noise. This is a cleaner step of #8508 Found more of a root cause of the open file handles. After more testing I found that the open file descriptors will still occur for TCP:// connections to the daemon, causing client and/or daemon to fail. The issue was instantiating a new http.Transport on _ever_ client request. So each instance held the prior connection alive, but was only ever used once. By moving it out to the initilization of DockerCli, we can now have reuse of idled connections. Simplifies the garbage overhead of the client too, though that's not usually a deal. Signed-off-by: Vincent Batts <vbatts@redhat.com>
166 lines
3.8 KiB
Go
166 lines
3.8 KiB
Go
package client
|
|
|
|
import (
|
|
"crypto/tls"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"net"
|
|
"net/http"
|
|
"os"
|
|
"reflect"
|
|
"strings"
|
|
"text/template"
|
|
"time"
|
|
|
|
flag "github.com/docker/docker/pkg/mflag"
|
|
"github.com/docker/docker/pkg/term"
|
|
"github.com/docker/docker/registry"
|
|
"github.com/docker/libtrust"
|
|
)
|
|
|
|
type DockerCli struct {
|
|
proto string
|
|
addr string
|
|
configFile *registry.ConfigFile
|
|
in io.ReadCloser
|
|
out io.Writer
|
|
err io.Writer
|
|
key libtrust.PrivateKey
|
|
tlsConfig *tls.Config
|
|
scheme string
|
|
// inFd holds file descriptor of the client's STDIN, if it's a valid file
|
|
inFd uintptr
|
|
// outFd holds file descriptor of the client's STDOUT, if it's a valid file
|
|
outFd uintptr
|
|
// isTerminalIn describes if client's STDIN is a TTY
|
|
isTerminalIn bool
|
|
// isTerminalOut describes if client's STDOUT is a TTY
|
|
isTerminalOut bool
|
|
transport *http.Transport
|
|
}
|
|
|
|
var funcMap = template.FuncMap{
|
|
"json": func(v interface{}) string {
|
|
a, _ := json.Marshal(v)
|
|
return string(a)
|
|
},
|
|
}
|
|
|
|
func (cli *DockerCli) getMethod(args ...string) (func(...string) error, bool) {
|
|
camelArgs := make([]string, len(args))
|
|
for i, s := range args {
|
|
if len(s) == 0 {
|
|
return nil, false
|
|
}
|
|
camelArgs[i] = strings.ToUpper(s[:1]) + strings.ToLower(s[1:])
|
|
}
|
|
methodName := "Cmd" + strings.Join(camelArgs, "")
|
|
method := reflect.ValueOf(cli).MethodByName(methodName)
|
|
if !method.IsValid() {
|
|
return nil, false
|
|
}
|
|
return method.Interface().(func(...string) error), true
|
|
}
|
|
|
|
// Cmd executes the specified command
|
|
func (cli *DockerCli) Cmd(args ...string) error {
|
|
if len(args) > 1 {
|
|
method, exists := cli.getMethod(args[:2]...)
|
|
if exists {
|
|
return method(args[2:]...)
|
|
}
|
|
}
|
|
if len(args) > 0 {
|
|
method, exists := cli.getMethod(args[0])
|
|
if !exists {
|
|
fmt.Println("Error: Command not found:", args[0])
|
|
return cli.CmdHelp(args[1:]...)
|
|
}
|
|
return method(args[1:]...)
|
|
}
|
|
return cli.CmdHelp(args...)
|
|
}
|
|
|
|
func (cli *DockerCli) Subcmd(name, signature, description string) *flag.FlagSet {
|
|
flags := flag.NewFlagSet(name, flag.ContinueOnError)
|
|
flags.Usage = func() {
|
|
options := ""
|
|
if flags.FlagCountUndeprecated() > 0 {
|
|
options = "[OPTIONS] "
|
|
}
|
|
fmt.Fprintf(cli.err, "\nUsage: docker %s %s%s\n\n%s\n\n", name, options, signature, description)
|
|
flags.PrintDefaults()
|
|
os.Exit(2)
|
|
}
|
|
return flags
|
|
}
|
|
|
|
func (cli *DockerCli) LoadConfigFile() (err error) {
|
|
cli.configFile, err = registry.LoadConfig(os.Getenv("HOME"))
|
|
if err != nil {
|
|
fmt.Fprintf(cli.err, "WARNING: %s\n", err)
|
|
}
|
|
return err
|
|
}
|
|
|
|
func NewDockerCli(in io.ReadCloser, out, err io.Writer, key libtrust.PrivateKey, proto, addr string, tlsConfig *tls.Config) *DockerCli {
|
|
var (
|
|
inFd uintptr
|
|
outFd uintptr
|
|
isTerminalIn = false
|
|
isTerminalOut = false
|
|
scheme = "http"
|
|
)
|
|
|
|
if tlsConfig != nil {
|
|
scheme = "https"
|
|
}
|
|
|
|
if in != nil {
|
|
if file, ok := in.(*os.File); ok {
|
|
inFd = file.Fd()
|
|
isTerminalIn = term.IsTerminal(inFd)
|
|
}
|
|
}
|
|
|
|
if out != nil {
|
|
if file, ok := out.(*os.File); ok {
|
|
outFd = file.Fd()
|
|
isTerminalOut = term.IsTerminal(outFd)
|
|
}
|
|
}
|
|
|
|
if err == nil {
|
|
err = out
|
|
}
|
|
|
|
// The transport is created here for reuse during the client session
|
|
tr := &http.Transport{
|
|
TLSClientConfig: tlsConfig,
|
|
Dial: func(dial_network, dial_addr string) (net.Conn, error) {
|
|
// Why 32? See issue 8035
|
|
return net.DialTimeout(proto, addr, 32*time.Second)
|
|
},
|
|
}
|
|
if proto == "unix" {
|
|
// no need in compressing for local communications
|
|
tr.DisableCompression = true
|
|
}
|
|
|
|
return &DockerCli{
|
|
proto: proto,
|
|
addr: addr,
|
|
in: in,
|
|
out: out,
|
|
err: err,
|
|
key: key,
|
|
inFd: inFd,
|
|
outFd: outFd,
|
|
isTerminalIn: isTerminalIn,
|
|
isTerminalOut: isTerminalOut,
|
|
tlsConfig: tlsConfig,
|
|
scheme: scheme,
|
|
transport: tr,
|
|
}
|
|
}
|