This code was introduced in 15aa2a663b,
but from those changes, it appears that overwriting the config value was
merely out of convenience, and that struct being used as an intermediate.
While changing the config here should be mostly ephemeral, and not written
back to the config-file, let's be clear on intent, and not mutatte the config
as part of this code.
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
183 lines
5.2 KiB
Go
183 lines
5.2 KiB
Go
package container
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"io"
|
|
|
|
"github.com/docker/cli/cli"
|
|
"github.com/docker/cli/cli/command"
|
|
"github.com/docker/cli/cli/command/completion"
|
|
"github.com/docker/docker/api/types"
|
|
"github.com/docker/docker/api/types/container"
|
|
"github.com/docker/docker/client"
|
|
"github.com/moby/sys/signal"
|
|
"github.com/pkg/errors"
|
|
"github.com/sirupsen/logrus"
|
|
"github.com/spf13/cobra"
|
|
)
|
|
|
|
type attachOptions struct {
|
|
noStdin bool
|
|
proxy bool
|
|
detachKeys string
|
|
|
|
container string
|
|
}
|
|
|
|
func inspectContainerAndCheckState(ctx context.Context, cli client.APIClient, args string) (*types.ContainerJSON, error) {
|
|
c, err := cli.ContainerInspect(ctx, args)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if !c.State.Running {
|
|
return nil, errors.New("You cannot attach to a stopped container, start it first")
|
|
}
|
|
if c.State.Paused {
|
|
return nil, errors.New("You cannot attach to a paused container, unpause it first")
|
|
}
|
|
if c.State.Restarting {
|
|
return nil, errors.New("You cannot attach to a restarting container, wait until it is running")
|
|
}
|
|
|
|
return &c, nil
|
|
}
|
|
|
|
// NewAttachCommand creates a new cobra.Command for `docker attach`
|
|
func NewAttachCommand(dockerCli command.Cli) *cobra.Command {
|
|
var opts attachOptions
|
|
|
|
cmd := &cobra.Command{
|
|
Use: "attach [OPTIONS] CONTAINER",
|
|
Short: "Attach local standard input, output, and error streams to a running container",
|
|
Args: cli.ExactArgs(1),
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
opts.container = args[0]
|
|
return runAttach(dockerCli, &opts)
|
|
},
|
|
Annotations: map[string]string{
|
|
"aliases": "docker container attach, docker attach",
|
|
},
|
|
ValidArgsFunction: completion.ContainerNames(dockerCli, false, func(container types.Container) bool {
|
|
return container.State != "paused"
|
|
}),
|
|
}
|
|
|
|
flags := cmd.Flags()
|
|
flags.BoolVar(&opts.noStdin, "no-stdin", false, "Do not attach STDIN")
|
|
flags.BoolVar(&opts.proxy, "sig-proxy", true, "Proxy all received signals to the process")
|
|
flags.StringVar(&opts.detachKeys, "detach-keys", "", "Override the key sequence for detaching a container")
|
|
return cmd
|
|
}
|
|
|
|
func runAttach(dockerCli command.Cli, opts *attachOptions) error {
|
|
ctx := context.Background()
|
|
apiClient := dockerCli.Client()
|
|
|
|
// request channel to wait for client
|
|
resultC, errC := apiClient.ContainerWait(ctx, opts.container, "")
|
|
|
|
c, err := inspectContainerAndCheckState(ctx, apiClient, opts.container)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := dockerCli.In().CheckTty(!opts.noStdin, c.Config.Tty); err != nil {
|
|
return err
|
|
}
|
|
|
|
detachKeys := dockerCli.ConfigFile().DetachKeys
|
|
if opts.detachKeys != "" {
|
|
detachKeys = opts.detachKeys
|
|
}
|
|
|
|
options := types.ContainerAttachOptions{
|
|
Stream: true,
|
|
Stdin: !opts.noStdin && c.Config.OpenStdin,
|
|
Stdout: true,
|
|
Stderr: true,
|
|
DetachKeys: detachKeys,
|
|
}
|
|
|
|
var in io.ReadCloser
|
|
if options.Stdin {
|
|
in = dockerCli.In()
|
|
}
|
|
|
|
if opts.proxy && !c.Config.Tty {
|
|
sigc := notifyAllSignals()
|
|
go ForwardAllSignals(ctx, dockerCli, opts.container, sigc)
|
|
defer signal.StopCatch(sigc)
|
|
}
|
|
|
|
resp, errAttach := apiClient.ContainerAttach(ctx, opts.container, options)
|
|
if errAttach != nil {
|
|
return errAttach
|
|
}
|
|
defer resp.Close()
|
|
|
|
// If use docker attach command to attach to a stop container, it will return
|
|
// "You cannot attach to a stopped container" error, it's ok, but when
|
|
// attach to a running container, it(docker attach) use inspect to check
|
|
// the container's state, if it pass the state check on the client side,
|
|
// and then the container is stopped, docker attach command still attach to
|
|
// the container and not exit.
|
|
//
|
|
// Recheck the container's state to avoid attach block.
|
|
_, err = inspectContainerAndCheckState(ctx, apiClient, opts.container)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if c.Config.Tty && dockerCli.Out().IsTerminal() {
|
|
resizeTTY(ctx, dockerCli, opts.container)
|
|
}
|
|
|
|
streamer := hijackedIOStreamer{
|
|
streams: dockerCli,
|
|
inputStream: in,
|
|
outputStream: dockerCli.Out(),
|
|
errorStream: dockerCli.Err(),
|
|
resp: resp,
|
|
tty: c.Config.Tty,
|
|
detachKeys: options.DetachKeys,
|
|
}
|
|
|
|
if err := streamer.stream(ctx); err != nil {
|
|
return err
|
|
}
|
|
|
|
return getExitStatus(errC, resultC)
|
|
}
|
|
|
|
func getExitStatus(errC <-chan error, resultC <-chan container.WaitResponse) error {
|
|
select {
|
|
case result := <-resultC:
|
|
if result.Error != nil {
|
|
return fmt.Errorf(result.Error.Message)
|
|
}
|
|
if result.StatusCode != 0 {
|
|
return cli.StatusError{StatusCode: int(result.StatusCode)}
|
|
}
|
|
case err := <-errC:
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func resizeTTY(ctx context.Context, dockerCli command.Cli, containerID string) {
|
|
height, width := dockerCli.Out().GetTtySize()
|
|
// 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
|
|
// resize it, then go back to normal. Without this, every attach after the first will
|
|
// require the user to manually resize or hit enter.
|
|
resizeTtyTo(ctx, dockerCli.Client(), containerID, height+1, width+1, false)
|
|
|
|
// After the above resizing occurs, the call to MonitorTtySize below will handle resetting back
|
|
// to the actual size.
|
|
if err := MonitorTtySize(ctx, dockerCli, containerID, false); err != nil {
|
|
logrus.Debugf("Error monitoring TTY size: %s", err)
|
|
}
|
|
}
|