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