Merge pull request #26107 from dnephin/client_cleanup
Shrink the DockerCLI type
This commit is contained in:
commit
d959e7fac4
|
@ -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,33 +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
|
||||||
in io.ReadCloser
|
out *OutStream
|
||||||
// out holds the output stream (io.Writer) for the client.
|
err io.Writer
|
||||||
out io.Writer
|
keyFile string
|
||||||
// err holds the error stream (io.Writer) for the client.
|
client client.APIClient
|
||||||
err io.Writer
|
|
||||||
// keyFile holds the key file as a string.
|
|
||||||
keyFile string
|
|
||||||
// inFd holds the file descriptor of the client's STDIN (if valid).
|
|
||||||
inFd uintptr
|
|
||||||
// outFd holds file descriptor of the client's STDOUT (if valid).
|
|
||||||
outFd uintptr
|
|
||||||
// isTerminalIn indicates whether the client's STDIN is a TTY
|
|
||||||
isTerminalIn bool
|
|
||||||
// isTerminalOut indicates whether the client's STDOUT is a TTY
|
|
||||||
isTerminalOut bool
|
|
||||||
// client is the http client that performs all API operations
|
|
||||||
client client.APIClient
|
|
||||||
// inState holds the terminal input state
|
|
||||||
inState *term.State
|
|
||||||
// outState holds the terminal output state
|
|
||||||
outState *term.State
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Client returns the APIClient
|
// Client returns the APIClient
|
||||||
|
@ -60,7 +38,7 @@ func (cli *DockerCli) Client() client.APIClient {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Out returns the writer used for stdout
|
// Out returns the writer used for stdout
|
||||||
func (cli *DockerCli) Out() io.Writer {
|
func (cli *DockerCli) Out() *OutStream {
|
||||||
return cli.out
|
return cli.out
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -70,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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -79,93 +57,16 @@ func (cli *DockerCli) ConfigFile() *configfile.ConfigFile {
|
||||||
return cli.configFile
|
return cli.configFile
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsTerminalIn returns true if the clients stdin is a TTY
|
|
||||||
func (cli *DockerCli) IsTerminalIn() bool {
|
|
||||||
return cli.isTerminalIn
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsTerminalOut returns true if the clients stdout is a TTY
|
|
||||||
func (cli *DockerCli) IsTerminalOut() bool {
|
|
||||||
return cli.isTerminalOut
|
|
||||||
}
|
|
||||||
|
|
||||||
// OutFd returns the fd for the stdout stream
|
|
||||||
func (cli *DockerCli) OutFd() uintptr {
|
|
||||||
return cli.outFd
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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 {
|
|
||||||
if os.Getenv("NORAW") == "" {
|
|
||||||
if cli.isTerminalIn {
|
|
||||||
state, err := term.SetRawTerminal(cli.inFd)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
cli.inState = state
|
|
||||||
}
|
|
||||||
if cli.isTerminalOut {
|
|
||||||
state, err := term.SetRawTerminalOutput(cli.outFd)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
cli.outState = state
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cli *DockerCli) restoreTerminal(in io.Closer) error {
|
|
||||||
if cli.inState != nil {
|
|
||||||
term.RestoreTerminal(cli.inFd, cli.inState)
|
|
||||||
}
|
|
||||||
if cli.outState != nil {
|
|
||||||
term.RestoreTerminal(cli.outFd, cli.outState)
|
|
||||||
}
|
|
||||||
// WARNING: DO NOT REMOVE THE OS CHECK !!!
|
|
||||||
// For some reason this Close call blocks on darwin..
|
|
||||||
// As the client exists right after, simply discard the close
|
|
||||||
// until we find a better solution.
|
|
||||||
if in != nil && runtime.GOOS != "darwin" {
|
|
||||||
return in.Close()
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize the dockerCli runs initialization that must happen after command
|
// Initialize the dockerCli runs initialization that must happen after command
|
||||||
// line flags are parsed.
|
// line flags are parsed.
|
||||||
func (cli *DockerCli) Initialize(opts *cliflags.ClientOptions) error {
|
func (cli *DockerCli) Initialize(opts *cliflags.ClientOptions) error {
|
||||||
cli.configFile = LoadDefaultConfigFile(cli.err)
|
cli.configFile = LoadDefaultConfigFile(cli.err)
|
||||||
|
|
||||||
client, err := NewAPIClientFromFlags(opts.Common, cli.configFile)
|
var err error
|
||||||
|
cli.client, err = NewAPIClientFromFlags(opts.Common, cli.configFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
cli.client = client
|
|
||||||
|
|
||||||
if cli.in != nil {
|
|
||||||
cli.inFd, cli.isTerminalIn = term.GetFdInfo(cli.in)
|
|
||||||
}
|
|
||||||
if cli.out != nil {
|
|
||||||
cli.outFd, cli.isTerminalOut = term.GetFdInfo(cli.out)
|
|
||||||
}
|
|
||||||
|
|
||||||
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 {
|
||||||
|
@ -177,11 +78,7 @@ func (cli *DockerCli) Initialize(opts *cliflags.ClientOptions) 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{
|
return &DockerCli{in: NewInStream(in), out: NewOutStream(out), err: err}
|
||||||
in: in,
|
|
||||||
out: out,
|
|
||||||
err: err,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoadDefaultConfigFile attempts to load the default config file and returns
|
// LoadDefaultConfigFile attempts to load the default config file and returns
|
||||||
|
|
|
@ -46,8 +46,9 @@ func NewAttachCommand(dockerCli *client.DockerCli) *cobra.Command {
|
||||||
|
|
||||||
func runAttach(dockerCli *client.DockerCli, opts *attachOptions) error {
|
func runAttach(dockerCli *client.DockerCli, opts *attachOptions) error {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
client := dockerCli.Client()
|
||||||
|
|
||||||
c, err := dockerCli.Client().ContainerInspect(ctx, opts.container)
|
c, err := client.ContainerInspect(ctx, opts.container)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -60,7 +61,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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -82,11 +83,11 @@ func runAttach(dockerCli *client.DockerCli, opts *attachOptions) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
if opts.proxy && !c.Config.Tty {
|
if opts.proxy && !c.Config.Tty {
|
||||||
sigc := dockerCli.ForwardAllSignals(ctx, opts.container)
|
sigc := ForwardAllSignals(ctx, dockerCli, opts.container)
|
||||||
defer signal.StopCatch(sigc)
|
defer signal.StopCatch(sigc)
|
||||||
}
|
}
|
||||||
|
|
||||||
resp, errAttach := dockerCli.Client().ContainerAttach(ctx, opts.container, options)
|
resp, errAttach := client.ContainerAttach(ctx, opts.container, options)
|
||||||
if errAttach != nil && errAttach != httputil.ErrPersistEOF {
|
if errAttach != nil && errAttach != httputil.ErrPersistEOF {
|
||||||
// ContainerAttach returns an ErrPersistEOF (connection closed)
|
// ContainerAttach returns an ErrPersistEOF (connection closed)
|
||||||
// means server met an error and put it in Hijacked connection
|
// means server met an error and put it in Hijacked connection
|
||||||
|
@ -95,21 +96,21 @@ func runAttach(dockerCli *client.DockerCli, opts *attachOptions) error {
|
||||||
}
|
}
|
||||||
defer resp.Close()
|
defer resp.Close()
|
||||||
|
|
||||||
if c.Config.Tty && dockerCli.IsTerminalOut() {
|
if c.Config.Tty && dockerCli.Out().IsTerminal() {
|
||||||
height, width := dockerCli.GetTtySize()
|
height, width := dockerCli.Out().GetTtySize()
|
||||||
// To handle the case where a user repeatedly attaches/detaches without resizing their
|
// To handle the case where a user repeatedly attaches/detaches without resizing their
|
||||||
// terminal, the only way to get the shell prompt to display for attaches 2+ is to artificially
|
// terminal, the only way to get the shell prompt to display for attaches 2+ is to artificially
|
||||||
// resize it, then go back to normal. Without this, every attach after the first will
|
// resize it, then go back to normal. Without this, every attach after the first will
|
||||||
// require the user to manually resize or hit enter.
|
// require the user to manually resize or hit enter.
|
||||||
dockerCli.ResizeTtyTo(ctx, opts.container, height+1, width+1, false)
|
resizeTtyTo(ctx, client, opts.container, height+1, width+1, false)
|
||||||
|
|
||||||
// After the above resizing occurs, the call to MonitorTtySize below will handle resetting back
|
// After the above resizing occurs, the call to MonitorTtySize below will handle resetting back
|
||||||
// to the actual size.
|
// to the actual size.
|
||||||
if err := dockerCli.MonitorTtySize(ctx, opts.container, false); err != nil {
|
if err := MonitorTtySize(ctx, dockerCli, opts.container, false); err != nil {
|
||||||
logrus.Debugf("Error monitoring TTY size: %s", err)
|
logrus.Debugf("Error monitoring TTY size: %s", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err := dockerCli.HoldHijackedConnection(ctx, c.Config.Tty, in, dockerCli.Out(), dockerCli.Err(), resp); err != nil {
|
if err := holdHijackedConnection(ctx, dockerCli, c.Config.Tty, in, dockerCli.Out(), dockerCli.Err(), resp); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -103,8 +103,8 @@ func pullImage(ctx context.Context, dockerCli *client.DockerCli, image string, o
|
||||||
return jsonmessage.DisplayJSONMessagesStream(
|
return jsonmessage.DisplayJSONMessagesStream(
|
||||||
responseBody,
|
responseBody,
|
||||||
out,
|
out,
|
||||||
dockerCli.OutFd(),
|
dockerCli.Out().FD(),
|
||||||
dockerCli.IsTerminalOut(),
|
dockerCli.Out().IsTerminal(),
|
||||||
nil)
|
nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,7 @@ import (
|
||||||
"github.com/docker/docker/api/client"
|
"github.com/docker/docker/api/client"
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
"github.com/docker/docker/cli"
|
"github.com/docker/docker/cli"
|
||||||
|
apiclient "github.com/docker/docker/client"
|
||||||
"github.com/docker/docker/pkg/promise"
|
"github.com/docker/docker/pkg/promise"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
@ -66,8 +67,9 @@ func runExec(dockerCli *client.DockerCli, opts *execOptions, container string, e
|
||||||
execConfig.DetachKeys = dockerCli.ConfigFile().DetachKeys
|
execConfig.DetachKeys = dockerCli.ConfigFile().DetachKeys
|
||||||
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
client := dockerCli.Client()
|
||||||
|
|
||||||
response, err := dockerCli.Client().ContainerExecCreate(ctx, container, *execConfig)
|
response, err := client.ContainerExecCreate(ctx, container, *execConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -80,7 +82,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 {
|
||||||
|
@ -89,7 +91,7 @@ func runExec(dockerCli *client.DockerCli, opts *execOptions, container string, e
|
||||||
Tty: execConfig.Tty,
|
Tty: execConfig.Tty,
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := dockerCli.Client().ContainerExecStart(ctx, execID, execStartCheck); err != nil {
|
if err := client.ContainerExecStart(ctx, execID, execStartCheck); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
// For now don't print this - wait for when we support exec wait()
|
// For now don't print this - wait for when we support exec wait()
|
||||||
|
@ -118,17 +120,17 @@ func runExec(dockerCli *client.DockerCli, opts *execOptions, container string, e
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
resp, err := dockerCli.Client().ContainerExecAttach(ctx, execID, *execConfig)
|
resp, err := client.ContainerExecAttach(ctx, execID, *execConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer resp.Close()
|
defer resp.Close()
|
||||||
errCh = promise.Go(func() error {
|
errCh = promise.Go(func() error {
|
||||||
return dockerCli.HoldHijackedConnection(ctx, execConfig.Tty, in, out, stderr, resp)
|
return holdHijackedConnection(ctx, dockerCli, 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 := MonitorTtySize(ctx, dockerCli, 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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -139,7 +141,7 @@ func runExec(dockerCli *client.DockerCli, opts *execOptions, container string, e
|
||||||
}
|
}
|
||||||
|
|
||||||
var status int
|
var status int
|
||||||
if _, status, err = dockerCli.GetExecExitCode(ctx, execID); err != nil {
|
if _, status, err = getExecExitCode(ctx, client, execID); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -150,6 +152,21 @@ func runExec(dockerCli *client.DockerCli, opts *execOptions, container string, e
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// getExecExitCode perform an inspect on the exec command. It returns
|
||||||
|
// the running state and the exit code.
|
||||||
|
func getExecExitCode(ctx context.Context, client apiclient.ContainerAPIClient, execID string) (bool, int, error) {
|
||||||
|
resp, err := client.ContainerExecInspect(ctx, execID)
|
||||||
|
if err != nil {
|
||||||
|
// If we can't connect, then the daemon probably died.
|
||||||
|
if err != apiclient.ErrConnectionFailed {
|
||||||
|
return false, -1, err
|
||||||
|
}
|
||||||
|
return false, -1, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp.Running, resp.ExitCode, nil
|
||||||
|
}
|
||||||
|
|
||||||
// parseExec parses the specified args for the specified command and generates
|
// parseExec parses the specified args for the specified command and generates
|
||||||
// an ExecConfig from it.
|
// an ExecConfig from it.
|
||||||
func parseExec(opts *execOptions, container string, execCmd []string) (*types.ExecConfig, error) {
|
func parseExec(opts *execOptions, container string, execCmd []string) (*types.ExecConfig, error) {
|
||||||
|
|
|
@ -38,7 +38,7 @@ func NewExportCommand(dockerCli *client.DockerCli) *cobra.Command {
|
||||||
}
|
}
|
||||||
|
|
||||||
func runExport(dockerCli *client.DockerCli, opts exportOptions) error {
|
func runExport(dockerCli *client.DockerCli, opts exportOptions) error {
|
||||||
if opts.output == "" && dockerCli.IsTerminalOut() {
|
if opts.output == "" && dockerCli.Out().IsTerminal() {
|
||||||
return errors.New("Cowardly refusing to save to a terminal. Use the -o flag or redirect.")
|
return errors.New("Cowardly refusing to save to a terminal. Use the -o flag or redirect.")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,30 +1,36 @@
|
||||||
package client
|
package container
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io"
|
"io"
|
||||||
|
"runtime"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"golang.org/x/net/context"
|
|
||||||
|
|
||||||
"github.com/Sirupsen/logrus"
|
"github.com/Sirupsen/logrus"
|
||||||
|
"github.com/docker/docker/api/client"
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
"github.com/docker/docker/pkg/stdcopy"
|
"github.com/docker/docker/pkg/stdcopy"
|
||||||
|
"golang.org/x/net/context"
|
||||||
)
|
)
|
||||||
|
|
||||||
// HoldHijackedConnection handles copying input to and output from streams to the
|
type streams interface {
|
||||||
|
In() *client.InStream
|
||||||
|
Out() *client.OutStream
|
||||||
|
}
|
||||||
|
|
||||||
|
// holdHijackedConnection handles copying input to and output from streams to the
|
||||||
// connection
|
// connection
|
||||||
func (cli *DockerCli) HoldHijackedConnection(ctx context.Context, tty bool, inputStream io.ReadCloser, outputStream, errorStream io.Writer, resp types.HijackedResponse) error {
|
func holdHijackedConnection(ctx context.Context, streams streams, tty bool, inputStream io.ReadCloser, outputStream, errorStream io.Writer, resp types.HijackedResponse) error {
|
||||||
var (
|
var (
|
||||||
err error
|
err error
|
||||||
restoreOnce sync.Once
|
restoreOnce sync.Once
|
||||||
)
|
)
|
||||||
if inputStream != nil && tty {
|
if inputStream != nil && tty {
|
||||||
if err := cli.setRawTerminal(); err != nil {
|
if err := setRawTerminal(streams); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer func() {
|
defer func() {
|
||||||
restoreOnce.Do(func() {
|
restoreOnce.Do(func() {
|
||||||
cli.restoreTerminal(inputStream)
|
restoreTerminal(streams, inputStream)
|
||||||
})
|
})
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
@ -39,7 +45,7 @@ func (cli *DockerCli) HoldHijackedConnection(ctx context.Context, tty bool, inpu
|
||||||
// so any following print messages will be in normal type.
|
// so any following print messages will be in normal type.
|
||||||
if inputStream != nil {
|
if inputStream != nil {
|
||||||
restoreOnce.Do(func() {
|
restoreOnce.Do(func() {
|
||||||
cli.restoreTerminal(inputStream)
|
restoreTerminal(streams, inputStream)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -59,7 +65,7 @@ func (cli *DockerCli) HoldHijackedConnection(ctx context.Context, tty bool, inpu
|
||||||
// so any following print messages will be in normal type.
|
// so any following print messages will be in normal type.
|
||||||
if tty {
|
if tty {
|
||||||
restoreOnce.Do(func() {
|
restoreOnce.Do(func() {
|
||||||
cli.restoreTerminal(inputStream)
|
restoreTerminal(streams, inputStream)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
logrus.Debug("[hijack] End of stdin")
|
logrus.Debug("[hijack] End of stdin")
|
||||||
|
@ -93,3 +99,23 @@ func (cli *DockerCli) HoldHijackedConnection(ctx context.Context, tty bool, inpu
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func setRawTerminal(streams streams) error {
|
||||||
|
if err := streams.In().SetRawTerminal(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return streams.Out().SetRawTerminal()
|
||||||
|
}
|
||||||
|
|
||||||
|
func restoreTerminal(streams streams, in io.Closer) error {
|
||||||
|
streams.In().RestoreTerminal()
|
||||||
|
streams.Out().RestoreTerminal()
|
||||||
|
// WARNING: DO NOT REMOVE THE OS CHECK !!!
|
||||||
|
// For some reason this Close call blocks on darwin..
|
||||||
|
// As the client exists right after, simply discard the close
|
||||||
|
// until we find a better solution.
|
||||||
|
if in != nil && runtime.GOOS != "darwin" {
|
||||||
|
return in.Close()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -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 {
|
||||||
|
@ -135,7 +135,7 @@ func runRun(dockerCli *client.DockerCli, flags *pflag.FlagSet, opts *runOptions,
|
||||||
// a far better user experience rather than relying on subsequent resizes
|
// a far better user experience rather than relying on subsequent resizes
|
||||||
// to cause things to catch up.
|
// to cause things to catch up.
|
||||||
if runtime.GOOS == "windows" {
|
if runtime.GOOS == "windows" {
|
||||||
hostConfig.ConsoleSize[0], hostConfig.ConsoleSize[1] = dockerCli.GetTtySize()
|
hostConfig.ConsoleSize[0], hostConfig.ConsoleSize[1] = dockerCli.Out().GetTtySize()
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx, cancelFun := context.WithCancel(context.Background())
|
ctx, cancelFun := context.WithCancel(context.Background())
|
||||||
|
@ -146,7 +146,7 @@ func runRun(dockerCli *client.DockerCli, flags *pflag.FlagSet, opts *runOptions,
|
||||||
return runStartContainerErr(err)
|
return runStartContainerErr(err)
|
||||||
}
|
}
|
||||||
if opts.sigProxy {
|
if opts.sigProxy {
|
||||||
sigc := dockerCli.ForwardAllSignals(ctx, createResponse.ID)
|
sigc := ForwardAllSignals(ctx, dockerCli, createResponse.ID)
|
||||||
defer signal.StopCatch(sigc)
|
defer signal.StopCatch(sigc)
|
||||||
}
|
}
|
||||||
var (
|
var (
|
||||||
|
@ -203,7 +203,7 @@ func runRun(dockerCli *client.DockerCli, flags *pflag.FlagSet, opts *runOptions,
|
||||||
defer resp.Close()
|
defer resp.Close()
|
||||||
|
|
||||||
errCh = promise.Go(func() error {
|
errCh = promise.Go(func() error {
|
||||||
errHijack := dockerCli.HoldHijackedConnection(ctx, config.Tty, in, out, cerr, resp)
|
errHijack := holdHijackedConnection(ctx, dockerCli, config.Tty, in, out, cerr, resp)
|
||||||
if errHijack == nil {
|
if errHijack == nil {
|
||||||
return errAttach
|
return errAttach
|
||||||
}
|
}
|
||||||
|
@ -234,8 +234,8 @@ func runRun(dockerCli *client.DockerCli, flags *pflag.FlagSet, opts *runOptions,
|
||||||
return runStartContainerErr(err)
|
return runStartContainerErr(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config.AttachStdin || config.AttachStdout || config.AttachStderr) && config.Tty && dockerCli.IsTerminalOut() {
|
if (config.AttachStdin || config.AttachStdout || config.AttachStderr) && config.Tty && dockerCli.Out().IsTerminal() {
|
||||||
if err := dockerCli.MonitorTtySize(ctx, createResponse.ID, false); err != nil {
|
if err := MonitorTtySize(ctx, dockerCli, createResponse.ID, false); err != nil {
|
||||||
fmt.Fprintf(stderr, "Error monitoring TTY size: %s\n", err)
|
fmt.Fprintf(stderr, "Error monitoring TTY size: %s\n", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -64,7 +64,7 @@ func runStart(dockerCli *client.DockerCli, opts *startOptions) error {
|
||||||
|
|
||||||
// We always use c.ID instead of container to maintain consistency during `docker start`
|
// We always use c.ID instead of container to maintain consistency during `docker start`
|
||||||
if !c.Config.Tty {
|
if !c.Config.Tty {
|
||||||
sigc := dockerCli.ForwardAllSignals(ctx, c.ID)
|
sigc := ForwardAllSignals(ctx, dockerCli, c.ID)
|
||||||
defer signal.StopCatch(sigc)
|
defer signal.StopCatch(sigc)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -95,7 +95,7 @@ func runStart(dockerCli *client.DockerCli, opts *startOptions) error {
|
||||||
}
|
}
|
||||||
defer resp.Close()
|
defer resp.Close()
|
||||||
cErr := promise.Go(func() error {
|
cErr := promise.Go(func() error {
|
||||||
errHijack := dockerCli.HoldHijackedConnection(ctx, c.Config.Tty, in, dockerCli.Out(), dockerCli.Err(), resp)
|
errHijack := holdHijackedConnection(ctx, dockerCli, c.Config.Tty, in, dockerCli.Out(), dockerCli.Err(), resp)
|
||||||
if errHijack == nil {
|
if errHijack == nil {
|
||||||
return errAttach
|
return errAttach
|
||||||
}
|
}
|
||||||
|
@ -118,8 +118,8 @@ func runStart(dockerCli *client.DockerCli, opts *startOptions) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 5. Wait for attachment to break.
|
// 5. Wait for attachment to break.
|
||||||
if c.Config.Tty && dockerCli.IsTerminalOut() {
|
if c.Config.Tty && dockerCli.Out().IsTerminal() {
|
||||||
if err := dockerCli.MonitorTtySize(ctx, c.ID, false); err != nil {
|
if err := MonitorTtySize(ctx, dockerCli, c.ID, false); err != nil {
|
||||||
fmt.Fprintf(dockerCli.Err(), "Error monitoring TTY size: %s\n", err)
|
fmt.Fprintf(dockerCli.Err(), "Error monitoring TTY size: %s\n", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,103 @@
|
||||||
|
package container
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
gosignal "os/signal"
|
||||||
|
"runtime"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/Sirupsen/logrus"
|
||||||
|
"github.com/docker/docker/api/client"
|
||||||
|
"github.com/docker/docker/api/types"
|
||||||
|
apiclient "github.com/docker/docker/client"
|
||||||
|
"github.com/docker/docker/pkg/signal"
|
||||||
|
"golang.org/x/net/context"
|
||||||
|
)
|
||||||
|
|
||||||
|
// resizeTtyTo resizes tty to specific height and width
|
||||||
|
func resizeTtyTo(ctx context.Context, client apiclient.ContainerAPIClient, id string, height, width int, isExec bool) {
|
||||||
|
if height == 0 && width == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
options := types.ResizeOptions{
|
||||||
|
Height: height,
|
||||||
|
Width: width,
|
||||||
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
if isExec {
|
||||||
|
err = client.ContainerExecResize(ctx, id, options)
|
||||||
|
} else {
|
||||||
|
err = client.ContainerResize(ctx, id, options)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
logrus.Debugf("Error resize: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MonitorTtySize updates the container tty size when the terminal tty changes size
|
||||||
|
func MonitorTtySize(ctx context.Context, cli *client.DockerCli, id string, isExec bool) error {
|
||||||
|
resizeTty := func() {
|
||||||
|
height, width := cli.Out().GetTtySize()
|
||||||
|
resizeTtyTo(ctx, cli.Client(), id, height, width, isExec)
|
||||||
|
}
|
||||||
|
|
||||||
|
resizeTty()
|
||||||
|
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
go func() {
|
||||||
|
prevH, prevW := cli.Out().GetTtySize()
|
||||||
|
for {
|
||||||
|
time.Sleep(time.Millisecond * 250)
|
||||||
|
h, w := cli.Out().GetTtySize()
|
||||||
|
|
||||||
|
if prevW != w || prevH != h {
|
||||||
|
resizeTty()
|
||||||
|
}
|
||||||
|
prevH = h
|
||||||
|
prevW = w
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
} else {
|
||||||
|
sigchan := make(chan os.Signal, 1)
|
||||||
|
gosignal.Notify(sigchan, signal.SIGWINCH)
|
||||||
|
go func() {
|
||||||
|
for range sigchan {
|
||||||
|
resizeTty()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ForwardAllSignals forwards signals to the container
|
||||||
|
func ForwardAllSignals(ctx context.Context, cli *client.DockerCli, cid string) chan os.Signal {
|
||||||
|
sigc := make(chan os.Signal, 128)
|
||||||
|
signal.CatchAll(sigc)
|
||||||
|
go func() {
|
||||||
|
for s := range sigc {
|
||||||
|
if s == signal.SIGCHLD || s == signal.SIGPIPE {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
var sig string
|
||||||
|
for sigStr, sigN := range signal.SignalMap {
|
||||||
|
if sigN == s {
|
||||||
|
sig = sigStr
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if sig == "" {
|
||||||
|
fmt.Fprintf(cli.Err(), "Unsupported signal: %v. Discarding.\n", s)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := cli.Client().ContainerKill(ctx, cid, sig); err != nil {
|
||||||
|
logrus.Debugf("Error sending signal: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
return sigc
|
||||||
|
}
|
|
@ -227,7 +227,7 @@ func runBuild(dockerCli *client.DockerCli, options buildOptions) error {
|
||||||
|
|
||||||
// Setup an upload progress bar
|
// Setup an upload progress bar
|
||||||
progressOutput := streamformatter.NewStreamFormatter().NewProgressOutput(progBuff, true)
|
progressOutput := streamformatter.NewStreamFormatter().NewProgressOutput(progBuff, true)
|
||||||
if !dockerCli.IsTerminalOut() {
|
if !dockerCli.Out().IsTerminal() {
|
||||||
progressOutput = &lastProgressOutput{output: progressOutput}
|
progressOutput = &lastProgressOutput{output: progressOutput}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -293,7 +293,7 @@ func runBuild(dockerCli *client.DockerCli, options buildOptions) error {
|
||||||
}
|
}
|
||||||
defer response.Body.Close()
|
defer response.Body.Close()
|
||||||
|
|
||||||
err = jsonmessage.DisplayJSONMessagesStream(response.Body, buildBuff, dockerCli.OutFd(), dockerCli.IsTerminalOut(), nil)
|
err = jsonmessage.DisplayJSONMessagesStream(response.Body, buildBuff, dockerCli.Out().FD(), dockerCli.Out().IsTerminal(), nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if jerr, ok := err.(*jsonmessage.JSONError); ok {
|
if jerr, ok := err.(*jsonmessage.JSONError); ok {
|
||||||
// If no error code is set, default to 1
|
// If no error code is set, default to 1
|
||||||
|
|
|
@ -84,5 +84,5 @@ func runImport(dockerCli *client.DockerCli, opts importOptions) error {
|
||||||
}
|
}
|
||||||
defer responseBody.Close()
|
defer responseBody.Close()
|
||||||
|
|
||||||
return jsonmessage.DisplayJSONMessagesStream(responseBody, dockerCli.Out(), dockerCli.OutFd(), dockerCli.IsTerminalOut(), nil)
|
return jsonmessage.DisplayJSONMessagesToStream(responseBody, dockerCli.Out(), nil)
|
||||||
}
|
}
|
||||||
|
|
|
@ -49,7 +49,7 @@ func runLoad(dockerCli *client.DockerCli, opts loadOptions) error {
|
||||||
defer file.Close()
|
defer file.Close()
|
||||||
input = file
|
input = file
|
||||||
}
|
}
|
||||||
if !dockerCli.IsTerminalOut() {
|
if !dockerCli.Out().IsTerminal() {
|
||||||
opts.quiet = true
|
opts.quiet = true
|
||||||
}
|
}
|
||||||
response, err := dockerCli.Client().ImageLoad(context.Background(), input, opts.quiet)
|
response, err := dockerCli.Client().ImageLoad(context.Background(), input, opts.quiet)
|
||||||
|
@ -59,7 +59,7 @@ func runLoad(dockerCli *client.DockerCli, opts loadOptions) error {
|
||||||
defer response.Body.Close()
|
defer response.Body.Close()
|
||||||
|
|
||||||
if response.Body != nil && response.JSON {
|
if response.Body != nil && response.JSON {
|
||||||
return jsonmessage.DisplayJSONMessagesStream(response.Body, dockerCli.Out(), dockerCli.OutFd(), dockerCli.IsTerminalOut(), nil)
|
return jsonmessage.DisplayJSONMessagesToStream(response.Body, dockerCli.Out(), nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = io.Copy(dockerCli.Out(), response.Body)
|
_, err = io.Copy(dockerCli.Out(), response.Body)
|
||||||
|
|
|
@ -57,6 +57,5 @@ func runPush(dockerCli *client.DockerCli, remote string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
defer responseBody.Close()
|
defer responseBody.Close()
|
||||||
|
return jsonmessage.DisplayJSONMessagesToStream(responseBody, dockerCli.Out(), nil)
|
||||||
return jsonmessage.DisplayJSONMessagesStream(responseBody, dockerCli.Out(), dockerCli.OutFd(), dockerCli.IsTerminalOut(), nil)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,7 +38,7 @@ func NewSaveCommand(dockerCli *client.DockerCli) *cobra.Command {
|
||||||
}
|
}
|
||||||
|
|
||||||
func runSave(dockerCli *client.DockerCli, opts saveOptions) error {
|
func runSave(dockerCli *client.DockerCli, opts saveOptions) error {
|
||||||
if opts.output == "" && dockerCli.IsTerminalOut() {
|
if opts.output == "" && dockerCli.Out().IsTerminal() {
|
||||||
return errors.New("Cowardly refusing to save to a terminal. Use the -o flag or redirect.")
|
return errors.New("Cowardly refusing to save to a terminal. Use the -o flag or redirect.")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,75 @@
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetRawTerminal sets raw mode on the input terminal
|
||||||
|
func (i *InStream) SetRawTerminal() (err error) {
|
||||||
|
if os.Getenv("NORAW") != "" || !i.isTerminal {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
i.state, err = term.SetRawTerminal(i.fd)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// RestoreTerminal restores normal mode to the terminal
|
||||||
|
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}
|
||||||
|
}
|
|
@ -0,0 +1,69 @@
|
||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/Sirupsen/logrus"
|
||||||
|
"github.com/docker/docker/pkg/term"
|
||||||
|
)
|
||||||
|
|
||||||
|
// OutStream is an output stream used by the DockerCli to write normal program
|
||||||
|
// output.
|
||||||
|
type OutStream struct {
|
||||||
|
out io.Writer
|
||||||
|
fd uintptr
|
||||||
|
isTerminal bool
|
||||||
|
state *term.State
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *OutStream) Write(p []byte) (int, error) {
|
||||||
|
return o.out.Write(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FD returns the file descriptor number for this stream
|
||||||
|
func (o *OutStream) FD() uintptr {
|
||||||
|
return o.fd
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsTerminal returns true if this stream is connected to a terminal
|
||||||
|
func (o *OutStream) IsTerminal() bool {
|
||||||
|
return o.isTerminal
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetRawTerminal sets raw mode on the output terminal
|
||||||
|
func (o *OutStream) SetRawTerminal() (err error) {
|
||||||
|
if os.Getenv("NORAW") != "" || !o.isTerminal {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
o.state, err = term.SetRawTerminalOutput(o.fd)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// RestoreTerminal restores normal mode to the terminal
|
||||||
|
func (o *OutStream) RestoreTerminal() {
|
||||||
|
if o.state != nil {
|
||||||
|
term.RestoreTerminal(o.fd, o.state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTtySize returns the height and width in characters of the tty
|
||||||
|
func (o *OutStream) GetTtySize() (int, int) {
|
||||||
|
if !o.isTerminal {
|
||||||
|
return 0, 0
|
||||||
|
}
|
||||||
|
ws, err := term.GetWinsize(o.fd)
|
||||||
|
if err != nil {
|
||||||
|
logrus.Debugf("Error getting size: %s", err)
|
||||||
|
if ws == nil {
|
||||||
|
return 0, 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return int(ws.Height), int(ws.Width)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewOutStream returns a new OutStream object from a Writer
|
||||||
|
func NewOutStream(out io.Writer) *OutStream {
|
||||||
|
fd, isTerminal := term.GetFdInfo(out)
|
||||||
|
return &OutStream{out: out, 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")
|
||||||
}
|
}
|
||||||
|
|
|
@ -440,14 +440,14 @@ func (cli *DockerCli) TrustedPush(ctx context.Context, repoInfo *registry.Reposi
|
||||||
// We want trust signatures to always take an explicit tag,
|
// We want trust signatures to always take an explicit tag,
|
||||||
// otherwise it will act as an untrusted push.
|
// otherwise it will act as an untrusted push.
|
||||||
if tag == "" {
|
if tag == "" {
|
||||||
if err = jsonmessage.DisplayJSONMessagesStream(responseBody, cli.out, cli.outFd, cli.isTerminalOut, nil); err != nil {
|
if err = jsonmessage.DisplayJSONMessagesToStream(responseBody, cli.Out(), nil); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
fmt.Fprintln(cli.out, "No tag specified, skipping trust metadata push")
|
fmt.Fprintln(cli.out, "No tag specified, skipping trust metadata push")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = jsonmessage.DisplayJSONMessagesStream(responseBody, cli.out, cli.outFd, cli.isTerminalOut, handleTarget); err != nil {
|
if err = jsonmessage.DisplayJSONMessagesToStream(responseBody, cli.Out(), handleTarget); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -580,7 +580,7 @@ func (cli *DockerCli) ImagePullPrivileged(ctx context.Context, authConfig types.
|
||||||
}
|
}
|
||||||
defer responseBody.Close()
|
defer responseBody.Close()
|
||||||
|
|
||||||
return jsonmessage.DisplayJSONMessagesStream(responseBody, cli.out, cli.outFd, cli.isTerminalOut, nil)
|
return jsonmessage.DisplayJSONMessagesToStream(responseBody, cli.Out(), nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ImagePushPrivileged push the image
|
// ImagePushPrivileged push the image
|
||||||
|
|
|
@ -5,110 +5,10 @@ import (
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
gosignal "os/signal"
|
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
|
||||||
|
|
||||||
"golang.org/x/net/context"
|
|
||||||
|
|
||||||
"github.com/Sirupsen/logrus"
|
|
||||||
"github.com/docker/docker/api/types"
|
|
||||||
"github.com/docker/docker/client"
|
|
||||||
"github.com/docker/docker/pkg/signal"
|
|
||||||
"github.com/docker/docker/pkg/term"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func (cli *DockerCli) resizeTty(ctx context.Context, id string, isExec bool) {
|
|
||||||
height, width := cli.GetTtySize()
|
|
||||||
cli.ResizeTtyTo(ctx, id, height, width, isExec)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ResizeTtyTo resizes tty to specific height and width
|
|
||||||
// TODO: this can be unexported again once all container related commands move to package container
|
|
||||||
func (cli *DockerCli) ResizeTtyTo(ctx context.Context, id string, height, width int, isExec bool) {
|
|
||||||
if height == 0 && width == 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
options := types.ResizeOptions{
|
|
||||||
Height: height,
|
|
||||||
Width: width,
|
|
||||||
}
|
|
||||||
|
|
||||||
var err error
|
|
||||||
if isExec {
|
|
||||||
err = cli.client.ContainerExecResize(ctx, id, options)
|
|
||||||
} else {
|
|
||||||
err = cli.client.ContainerResize(ctx, id, options)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
logrus.Debugf("Error resize: %s", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetExecExitCode perform an inspect on the exec command. It returns
|
|
||||||
// the running state and the exit code.
|
|
||||||
func (cli *DockerCli) GetExecExitCode(ctx context.Context, execID string) (bool, int, error) {
|
|
||||||
resp, err := cli.client.ContainerExecInspect(ctx, execID)
|
|
||||||
if err != nil {
|
|
||||||
// If we can't connect, then the daemon probably died.
|
|
||||||
if err != client.ErrConnectionFailed {
|
|
||||||
return false, -1, err
|
|
||||||
}
|
|
||||||
return false, -1, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return resp.Running, resp.ExitCode, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// MonitorTtySize updates the container tty size when the terminal tty changes size
|
|
||||||
func (cli *DockerCli) MonitorTtySize(ctx context.Context, id string, isExec bool) error {
|
|
||||||
cli.resizeTty(ctx, id, isExec)
|
|
||||||
|
|
||||||
if runtime.GOOS == "windows" {
|
|
||||||
go func() {
|
|
||||||
prevH, prevW := cli.GetTtySize()
|
|
||||||
for {
|
|
||||||
time.Sleep(time.Millisecond * 250)
|
|
||||||
h, w := cli.GetTtySize()
|
|
||||||
|
|
||||||
if prevW != w || prevH != h {
|
|
||||||
cli.resizeTty(ctx, id, isExec)
|
|
||||||
}
|
|
||||||
prevH = h
|
|
||||||
prevW = w
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
} else {
|
|
||||||
sigchan := make(chan os.Signal, 1)
|
|
||||||
gosignal.Notify(sigchan, signal.SIGWINCH)
|
|
||||||
go func() {
|
|
||||||
for range sigchan {
|
|
||||||
cli.resizeTty(ctx, id, isExec)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetTtySize returns the height and width in characters of the tty
|
|
||||||
func (cli *DockerCli) GetTtySize() (int, int) {
|
|
||||||
if !cli.isTerminalOut {
|
|
||||||
return 0, 0
|
|
||||||
}
|
|
||||||
ws, err := term.GetWinsize(cli.outFd)
|
|
||||||
if err != nil {
|
|
||||||
logrus.Debugf("Error getting size: %s", err)
|
|
||||||
if ws == nil {
|
|
||||||
return 0, 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return int(ws.Height), int(ws.Width)
|
|
||||||
}
|
|
||||||
|
|
||||||
// CopyToFile writes the content of the reader to the specified file
|
// CopyToFile writes the content of the reader to the specified file
|
||||||
func CopyToFile(outfile string, r io.Reader) error {
|
func CopyToFile(outfile string, r io.Reader) error {
|
||||||
tmpFile, err := ioutil.TempFile(filepath.Dir(outfile), ".docker_temp_")
|
tmpFile, err := ioutil.TempFile(filepath.Dir(outfile), ".docker_temp_")
|
||||||
|
@ -134,37 +34,6 @@ func CopyToFile(outfile string, r io.Reader) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ForwardAllSignals forwards signals to the container
|
|
||||||
// TODO: this can be unexported again once all container commands are under
|
|
||||||
// api/client/container
|
|
||||||
func (cli *DockerCli) ForwardAllSignals(ctx context.Context, cid string) chan os.Signal {
|
|
||||||
sigc := make(chan os.Signal, 128)
|
|
||||||
signal.CatchAll(sigc)
|
|
||||||
go func() {
|
|
||||||
for s := range sigc {
|
|
||||||
if s == signal.SIGCHLD || s == signal.SIGPIPE {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
var sig string
|
|
||||||
for sigStr, sigN := range signal.SignalMap {
|
|
||||||
if sigN == s {
|
|
||||||
sig = sigStr
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if sig == "" {
|
|
||||||
fmt.Fprintf(cli.err, "Unsupported signal: %v. Discarding.\n", s)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := cli.client.ContainerKill(ctx, cid, sig); err != nil {
|
|
||||||
logrus.Debugf("Error sending signal: %s", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
return sigc
|
|
||||||
}
|
|
||||||
|
|
||||||
// capitalizeFirst capitalizes the first character of string
|
// capitalizeFirst capitalizes the first character of string
|
||||||
func capitalizeFirst(s string) string {
|
func capitalizeFirst(s string) string {
|
||||||
switch l := len(s); l {
|
switch l := len(s); l {
|
||||||
|
|
|
@ -219,3 +219,14 @@ func DisplayJSONMessagesStream(in io.Reader, out io.Writer, terminalFd uintptr,
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type stream interface {
|
||||||
|
io.Writer
|
||||||
|
FD() uintptr
|
||||||
|
IsTerminal() bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// DisplayJSONMessagesToStream prints json messages to the output stream
|
||||||
|
func DisplayJSONMessagesToStream(in io.Reader, stream stream, auxCallback func(*json.RawMessage)) error {
|
||||||
|
return DisplayJSONMessagesStream(in, stream, stream.FD(), stream.IsTerminal(), auxCallback)
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue