github.com/uriddle/docker@v0.0.0-20210926094723-4072e6aeb013/api/client/cli.go (about) 1 package client 2 3 import ( 4 "errors" 5 "fmt" 6 "io" 7 "net/http" 8 "os" 9 "runtime" 10 11 "github.com/docker/docker/api" 12 "github.com/docker/docker/cli" 13 "github.com/docker/docker/cliconfig" 14 "github.com/docker/docker/dockerversion" 15 "github.com/docker/docker/opts" 16 "github.com/docker/docker/pkg/term" 17 "github.com/docker/engine-api/client" 18 "github.com/docker/go-connections/tlsconfig" 19 ) 20 21 // DockerCli represents the docker command line client. 22 // Instances of the client can be returned from NewDockerCli. 23 type DockerCli struct { 24 // initializing closure 25 init func() error 26 27 // configFile has the client configuration file 28 configFile *cliconfig.ConfigFile 29 // in holds the input stream and closer (io.ReadCloser) for the client. 30 in io.ReadCloser 31 // out holds the output stream (io.Writer) for the client. 32 out io.Writer 33 // err holds the error stream (io.Writer) for the client. 34 err io.Writer 35 // keyFile holds the key file as a string. 36 keyFile string 37 // inFd holds the file descriptor of the client's STDIN (if valid). 38 inFd uintptr 39 // outFd holds file descriptor of the client's STDOUT (if valid). 40 outFd uintptr 41 // isTerminalIn indicates whether the client's STDIN is a TTY 42 isTerminalIn bool 43 // isTerminalOut indicates whether the client's STDOUT is a TTY 44 isTerminalOut bool 45 // client is the http client that performs all API operations 46 client client.APIClient 47 // state holds the terminal state 48 state *term.State 49 } 50 51 // Initialize calls the init function that will setup the configuration for the client 52 // such as the TLS, tcp and other parameters used to run the client. 53 func (cli *DockerCli) Initialize() error { 54 if cli.init == nil { 55 return nil 56 } 57 return cli.init() 58 } 59 60 // CheckTtyInput checks if we are trying to attach to a container tty 61 // from a non-tty client input stream, and if so, returns an error. 62 func (cli *DockerCli) CheckTtyInput(attachStdin, ttyMode bool) error { 63 // In order to attach to a container tty, input stream for the client must 64 // be a tty itself: redirecting or piping the client standard input is 65 // incompatible with `docker run -t`, `docker exec -t` or `docker attach`. 66 if ttyMode && attachStdin && !cli.isTerminalIn { 67 return errors.New("cannot enable tty mode on non tty input") 68 } 69 return nil 70 } 71 72 // PsFormat returns the format string specified in the configuration. 73 // String contains columns and format specification, for example {{ID}}\t{{Name}}. 74 func (cli *DockerCli) PsFormat() string { 75 return cli.configFile.PsFormat 76 } 77 78 // ImagesFormat returns the format string specified in the configuration. 79 // String contains columns and format specification, for example {{ID}}\t{{Name}}. 80 func (cli *DockerCli) ImagesFormat() string { 81 return cli.configFile.ImagesFormat 82 } 83 84 func (cli *DockerCli) setRawTerminal() error { 85 if cli.isTerminalIn && os.Getenv("NORAW") == "" { 86 state, err := term.SetRawTerminal(cli.inFd) 87 if err != nil { 88 return err 89 } 90 cli.state = state 91 } 92 return nil 93 } 94 95 func (cli *DockerCli) restoreTerminal(in io.Closer) error { 96 if cli.state != nil { 97 term.RestoreTerminal(cli.inFd, cli.state) 98 } 99 // WARNING: DO NOT REMOVE THE OS CHECK !!! 100 // For some reason this Close call blocks on darwin.. 101 // As the client exists right after, simply discard the close 102 // until we find a better solution. 103 if in != nil && runtime.GOOS != "darwin" { 104 return in.Close() 105 } 106 return nil 107 } 108 109 // NewDockerCli returns a DockerCli instance with IO output and error streams set by in, out and err. 110 // The key file, protocol (i.e. unix) and address are passed in as strings, along with the tls.Config. If the tls.Config 111 // is set the client scheme will be set to https. 112 // The client will be given a 32-second timeout (see https://github.com/docker/docker/pull/8035). 113 func NewDockerCli(in io.ReadCloser, out, err io.Writer, clientFlags *cli.ClientFlags) *DockerCli { 114 cli := &DockerCli{ 115 in: in, 116 out: out, 117 err: err, 118 keyFile: clientFlags.Common.TrustKey, 119 } 120 121 cli.init = func() error { 122 clientFlags.PostParse() 123 configFile, e := cliconfig.Load(cliconfig.ConfigDir()) 124 if e != nil { 125 fmt.Fprintf(cli.err, "WARNING: Error loading config file:%v\n", e) 126 } 127 cli.configFile = configFile 128 129 host, err := getServerHost(clientFlags.Common.Hosts, clientFlags.Common.TLSOptions) 130 if err != nil { 131 return err 132 } 133 134 customHeaders := cli.configFile.HTTPHeaders 135 if customHeaders == nil { 136 customHeaders = map[string]string{} 137 } 138 customHeaders["User-Agent"] = "Docker-Client/" + dockerversion.Version + " (" + runtime.GOOS + ")" 139 140 verStr := api.DefaultVersion.String() 141 if tmpStr := os.Getenv("DOCKER_API_VERSION"); tmpStr != "" { 142 verStr = tmpStr 143 } 144 145 clientTransport, err := newClientTransport(clientFlags.Common.TLSOptions) 146 if err != nil { 147 return err 148 } 149 150 client, err := client.NewClient(host, verStr, clientTransport, customHeaders) 151 if err != nil { 152 return err 153 } 154 cli.client = client 155 156 if cli.in != nil { 157 cli.inFd, cli.isTerminalIn = term.GetFdInfo(cli.in) 158 } 159 if cli.out != nil { 160 cli.outFd, cli.isTerminalOut = term.GetFdInfo(cli.out) 161 } 162 163 return nil 164 } 165 166 return cli 167 } 168 169 func getServerHost(hosts []string, tlsOptions *tlsconfig.Options) (host string, err error) { 170 switch len(hosts) { 171 case 0: 172 host = os.Getenv("DOCKER_HOST") 173 case 1: 174 host = hosts[0] 175 default: 176 return "", errors.New("Please specify only one -H") 177 } 178 179 defaultHost := opts.DefaultTCPHost 180 if tlsOptions != nil { 181 defaultHost = opts.DefaultTLSHost 182 } 183 184 host, err = opts.ParseHost(defaultHost, host) 185 return 186 } 187 188 func newClientTransport(tlsOptions *tlsconfig.Options) (*http.Transport, error) { 189 if tlsOptions == nil { 190 return &http.Transport{}, nil 191 } 192 193 config, err := tlsconfig.Client(*tlsOptions) 194 if err != nil { 195 return nil, err 196 } 197 return &http.Transport{ 198 TLSClientConfig: config, 199 }, nil 200 }