Extract input stream into a new type.
Signed-off-by: Daniel Nephin <dnephin@docker.com>
This commit is contained in:
parent
a0ab33124a
commit
bec81075bf
6 changed files with 93 additions and 77 deletions
|
@ -17,7 +17,6 @@ import (
|
||||||
"github.com/docker/docker/client"
|
"github.com/docker/docker/client"
|
||||||
"github.com/docker/docker/dockerversion"
|
"github.com/docker/docker/dockerversion"
|
||||||
dopts "github.com/docker/docker/opts"
|
dopts "github.com/docker/docker/opts"
|
||||||
"github.com/docker/docker/pkg/term"
|
|
||||||
"github.com/docker/go-connections/sockets"
|
"github.com/docker/go-connections/sockets"
|
||||||
"github.com/docker/go-connections/tlsconfig"
|
"github.com/docker/go-connections/tlsconfig"
|
||||||
)
|
)
|
||||||
|
@ -25,30 +24,12 @@ import (
|
||||||
// DockerCli represents the docker command line client.
|
// DockerCli represents the docker command line client.
|
||||||
// Instances of the client can be returned from NewDockerCli.
|
// Instances of the client can be returned from NewDockerCli.
|
||||||
type DockerCli struct {
|
type DockerCli struct {
|
||||||
// initializing closure
|
|
||||||
init func() error
|
|
||||||
|
|
||||||
// configFile has the client configuration file
|
|
||||||
configFile *configfile.ConfigFile
|
configFile *configfile.ConfigFile
|
||||||
// in holds the input stream and closer (io.ReadCloser) for the client.
|
in *InStream
|
||||||
// TODO: remove
|
out *OutStream
|
||||||
in io.ReadCloser
|
err io.Writer
|
||||||
// err holds the error stream (io.Writer) for the client.
|
keyFile string
|
||||||
err io.Writer
|
client client.APIClient
|
||||||
// keyFile holds the key file as a string.
|
|
||||||
keyFile string
|
|
||||||
// inFd holds the file descriptor of the client's STDIN (if valid).
|
|
||||||
// TODO: remove
|
|
||||||
inFd uintptr
|
|
||||||
// isTerminalIn indicates whether the client's STDIN is a TTY
|
|
||||||
// TODO: remove
|
|
||||||
isTerminalIn bool
|
|
||||||
// client is the http client that performs all API operations
|
|
||||||
client client.APIClient
|
|
||||||
// inState holds the terminal input state
|
|
||||||
// TODO: remove
|
|
||||||
inState *term.State
|
|
||||||
out *OutStream
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Client returns the APIClient
|
// Client returns the APIClient
|
||||||
|
@ -67,7 +48,7 @@ func (cli *DockerCli) Err() io.Writer {
|
||||||
}
|
}
|
||||||
|
|
||||||
// In returns the reader used for stdin
|
// In returns the reader used for stdin
|
||||||
func (cli *DockerCli) In() io.ReadCloser {
|
func (cli *DockerCli) In() *InStream {
|
||||||
return cli.in
|
return cli.in
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -76,48 +57,15 @@ func (cli *DockerCli) ConfigFile() *configfile.ConfigFile {
|
||||||
return cli.configFile
|
return cli.configFile
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsTerminalIn returns true if the clients stdin is a TTY
|
|
||||||
// TODO: remove
|
|
||||||
func (cli *DockerCli) IsTerminalIn() bool {
|
|
||||||
return cli.isTerminalIn
|
|
||||||
}
|
|
||||||
|
|
||||||
// CheckTtyInput checks if we are trying to attach to a container tty
|
|
||||||
// from a non-tty client input stream, and if so, returns an error.
|
|
||||||
func (cli *DockerCli) CheckTtyInput(attachStdin, ttyMode bool) error {
|
|
||||||
// In order to attach to a container tty, input stream for the client must
|
|
||||||
// be a tty itself: redirecting or piping the client standard input is
|
|
||||||
// incompatible with `docker run -t`, `docker exec -t` or `docker attach`.
|
|
||||||
if ttyMode && attachStdin && !cli.isTerminalIn {
|
|
||||||
eText := "the input device is not a TTY"
|
|
||||||
if runtime.GOOS == "windows" {
|
|
||||||
return errors.New(eText + ". If you are using mintty, try prefixing the command with 'winpty'")
|
|
||||||
}
|
|
||||||
return errors.New(eText)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cli *DockerCli) setRawTerminal() error {
|
func (cli *DockerCli) setRawTerminal() error {
|
||||||
if os.Getenv("NORAW") == "" {
|
if err := cli.in.setRawTerminal(); err != nil {
|
||||||
if cli.isTerminalIn {
|
return err
|
||||||
state, err := term.SetRawTerminal(cli.inFd)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
cli.inState = state
|
|
||||||
}
|
|
||||||
if err := cli.out.setRawTerminal(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return nil
|
return cli.out.setRawTerminal()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cli *DockerCli) restoreTerminal(in io.Closer) error {
|
func (cli *DockerCli) restoreTerminal(in io.Closer) error {
|
||||||
if cli.inState != nil {
|
cli.in.restoreTerminal()
|
||||||
term.RestoreTerminal(cli.inFd, cli.inState)
|
|
||||||
}
|
|
||||||
cli.out.restoreTerminal()
|
cli.out.restoreTerminal()
|
||||||
// WARNING: DO NOT REMOVE THE OS CHECK !!!
|
// WARNING: DO NOT REMOVE THE OS CHECK !!!
|
||||||
// For some reason this Close call blocks on darwin..
|
// For some reason this Close call blocks on darwin..
|
||||||
|
@ -138,11 +86,6 @@ func (cli *DockerCli) Initialize(opts *cliflags.ClientOptions) (err error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if cli.in != nil {
|
|
||||||
cli.inFd, cli.isTerminalIn = term.GetFdInfo(cli.in)
|
|
||||||
}
|
|
||||||
|
|
||||||
if opts.Common.TrustKey == "" {
|
if opts.Common.TrustKey == "" {
|
||||||
cli.keyFile = filepath.Join(cliconfig.ConfigDir(), cliflags.DefaultTrustKeyFile)
|
cli.keyFile = filepath.Join(cliconfig.ConfigDir(), cliflags.DefaultTrustKeyFile)
|
||||||
} else {
|
} else {
|
||||||
|
@ -154,7 +97,7 @@ func (cli *DockerCli) Initialize(opts *cliflags.ClientOptions) (err error) {
|
||||||
|
|
||||||
// NewDockerCli returns a DockerCli instance with IO output and error streams set by in, out and err.
|
// NewDockerCli returns a DockerCli instance with IO output and error streams set by in, out and err.
|
||||||
func NewDockerCli(in io.ReadCloser, out, err io.Writer) *DockerCli {
|
func NewDockerCli(in io.ReadCloser, out, err io.Writer) *DockerCli {
|
||||||
return &DockerCli{in: in, out: NewOutStream(out), err: err}
|
return &DockerCli{in: NewInStream(in), out: NewOutStream(out), err: err}
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoadDefaultConfigFile attempts to load the default config file and returns
|
// LoadDefaultConfigFile attempts to load the default config file and returns
|
||||||
|
|
|
@ -60,7 +60,7 @@ func runAttach(dockerCli *client.DockerCli, opts *attachOptions) error {
|
||||||
return fmt.Errorf("You cannot attach to a paused container, unpause it first")
|
return fmt.Errorf("You cannot attach to a paused container, unpause it first")
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := dockerCli.CheckTtyInput(!opts.noStdin, c.Config.Tty); err != nil {
|
if err := dockerCli.In().CheckTty(!opts.noStdin, c.Config.Tty); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -80,7 +80,7 @@ func runExec(dockerCli *client.DockerCli, opts *execOptions, container string, e
|
||||||
|
|
||||||
//Temp struct for execStart so that we don't need to transfer all the execConfig
|
//Temp struct for execStart so that we don't need to transfer all the execConfig
|
||||||
if !execConfig.Detach {
|
if !execConfig.Detach {
|
||||||
if err := dockerCli.CheckTtyInput(execConfig.AttachStdin, execConfig.Tty); err != nil {
|
if err := dockerCli.In().CheckTty(execConfig.AttachStdin, execConfig.Tty); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -127,7 +127,7 @@ func runExec(dockerCli *client.DockerCli, opts *execOptions, container string, e
|
||||||
return dockerCli.HoldHijackedConnection(ctx, execConfig.Tty, in, out, stderr, resp)
|
return dockerCli.HoldHijackedConnection(ctx, execConfig.Tty, in, out, stderr, resp)
|
||||||
})
|
})
|
||||||
|
|
||||||
if execConfig.Tty && dockerCli.IsTerminalIn() {
|
if execConfig.Tty && dockerCli.In().IsTerminal() {
|
||||||
if err := dockerCli.MonitorTtySize(ctx, execID, true); err != nil {
|
if err := dockerCli.MonitorTtySize(ctx, execID, true); err != nil {
|
||||||
fmt.Fprintf(dockerCli.Err(), "Error monitoring TTY size: %s\n", err)
|
fmt.Fprintf(dockerCli.Err(), "Error monitoring TTY size: %s\n", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -109,7 +109,7 @@ func runRun(dockerCli *client.DockerCli, flags *pflag.FlagSet, opts *runOptions,
|
||||||
config.ArgsEscaped = false
|
config.ArgsEscaped = false
|
||||||
|
|
||||||
if !opts.detach {
|
if !opts.detach {
|
||||||
if err := dockerCli.CheckTtyInput(config.AttachStdin, config.Tty); err != nil {
|
if err := dockerCli.In().CheckTty(config.AttachStdin, config.Tty); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
73
api/client/in.go
Normal file
73
api/client/in.go
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"runtime"
|
||||||
|
|
||||||
|
"github.com/docker/docker/pkg/term"
|
||||||
|
)
|
||||||
|
|
||||||
|
// InStream is an input stream used by the DockerCli to read user input
|
||||||
|
type InStream struct {
|
||||||
|
in io.ReadCloser
|
||||||
|
fd uintptr
|
||||||
|
isTerminal bool
|
||||||
|
state *term.State
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *InStream) Read(p []byte) (int, error) {
|
||||||
|
return i.in.Read(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close implements the Closer interface
|
||||||
|
func (i *InStream) Close() error {
|
||||||
|
return i.in.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
// FD returns the file descriptor number for this stream
|
||||||
|
func (i *InStream) FD() uintptr {
|
||||||
|
return i.fd
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsTerminal returns true if this stream is connected to a terminal
|
||||||
|
func (i *InStream) IsTerminal() bool {
|
||||||
|
return i.isTerminal
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *InStream) setRawTerminal() (err error) {
|
||||||
|
if os.Getenv("NORAW") != "" || !i.isTerminal {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
i.state, err = term.SetRawTerminal(i.fd)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *InStream) restoreTerminal() {
|
||||||
|
if i.state != nil {
|
||||||
|
term.RestoreTerminal(i.fd, i.state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CheckTty checks if we are trying to attach to a container tty
|
||||||
|
// from a non-tty client input stream, and if so, returns an error.
|
||||||
|
func (i *InStream) CheckTty(attachStdin, ttyMode bool) error {
|
||||||
|
// In order to attach to a container tty, input stream for the client must
|
||||||
|
// be a tty itself: redirecting or piping the client standard input is
|
||||||
|
// incompatible with `docker run -t`, `docker exec -t` or `docker attach`.
|
||||||
|
if ttyMode && attachStdin && !i.isTerminal {
|
||||||
|
eText := "the input device is not a TTY"
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
return errors.New(eText + ". If you are using mintty, try prefixing the command with 'winpty'")
|
||||||
|
}
|
||||||
|
return errors.New(eText)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewInStream returns a new OutStream object from a Writer
|
||||||
|
func NewInStream(in io.ReadCloser) *InStream {
|
||||||
|
fd, isTerminal := term.GetFdInfo(in)
|
||||||
|
return &InStream{in: in, fd: fd, isTerminal: isTerminal}
|
||||||
|
}
|
|
@ -89,7 +89,7 @@ func (cli *DockerCli) RetrieveAuthConfigs() map[string]types.AuthConfig {
|
||||||
func (cli *DockerCli) ConfigureAuth(flUser, flPassword, serverAddress string, isDefaultRegistry bool) (types.AuthConfig, error) {
|
func (cli *DockerCli) ConfigureAuth(flUser, flPassword, serverAddress string, isDefaultRegistry bool) (types.AuthConfig, error) {
|
||||||
// On Windows, force the use of the regular OS stdin stream. Fixes #14336/#14210
|
// On Windows, force the use of the regular OS stdin stream. Fixes #14336/#14210
|
||||||
if runtime.GOOS == "windows" {
|
if runtime.GOOS == "windows" {
|
||||||
cli.in = os.Stdin
|
cli.in = NewInStream(os.Stdin)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !isDefaultRegistry {
|
if !isDefaultRegistry {
|
||||||
|
@ -108,7 +108,7 @@ func (cli *DockerCli) ConfigureAuth(flUser, flPassword, serverAddress string, is
|
||||||
// Linux will hit this if you attempt `cat | docker login`, and Windows
|
// Linux will hit this if you attempt `cat | docker login`, and Windows
|
||||||
// will hit this if you attempt docker login from mintty where stdin
|
// will hit this if you attempt docker login from mintty where stdin
|
||||||
// is a pipe, not a character based console.
|
// is a pipe, not a character based console.
|
||||||
if flPassword == "" && !cli.isTerminalIn {
|
if flPassword == "" && !cli.In().IsTerminal() {
|
||||||
return authconfig, fmt.Errorf("Error: Cannot perform an interactive login from a non TTY device")
|
return authconfig, fmt.Errorf("Error: Cannot perform an interactive login from a non TTY device")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -130,17 +130,17 @@ func (cli *DockerCli) ConfigureAuth(flUser, flPassword, serverAddress string, is
|
||||||
return authconfig, fmt.Errorf("Error: Non-null Username Required")
|
return authconfig, fmt.Errorf("Error: Non-null Username Required")
|
||||||
}
|
}
|
||||||
if flPassword == "" {
|
if flPassword == "" {
|
||||||
oldState, err := term.SaveState(cli.inFd)
|
oldState, err := term.SaveState(cli.In().FD())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return authconfig, err
|
return authconfig, err
|
||||||
}
|
}
|
||||||
fmt.Fprintf(cli.out, "Password: ")
|
fmt.Fprintf(cli.out, "Password: ")
|
||||||
term.DisableEcho(cli.inFd, oldState)
|
term.DisableEcho(cli.In().FD(), oldState)
|
||||||
|
|
||||||
flPassword = readInput(cli.in, cli.out)
|
flPassword = readInput(cli.in, cli.out)
|
||||||
fmt.Fprint(cli.out, "\n")
|
fmt.Fprint(cli.out, "\n")
|
||||||
|
|
||||||
term.RestoreTerminal(cli.inFd, oldState)
|
term.RestoreTerminal(cli.In().FD(), oldState)
|
||||||
if flPassword == "" {
|
if flPassword == "" {
|
||||||
return authconfig, fmt.Errorf("Error: Password Required")
|
return authconfig, fmt.Errorf("Error: Password Required")
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue