|
@@ -0,0 +1,130 @@
|
|
|
+package container
|
|
|
+
|
|
|
+import (
|
|
|
+ "fmt"
|
|
|
+ "io"
|
|
|
+ "net/http/httputil"
|
|
|
+
|
|
|
+ "golang.org/x/net/context"
|
|
|
+
|
|
|
+ "github.com/Sirupsen/logrus"
|
|
|
+ "github.com/docker/docker/api/client"
|
|
|
+ "github.com/docker/docker/cli"
|
|
|
+ "github.com/docker/docker/pkg/signal"
|
|
|
+ "github.com/docker/engine-api/types"
|
|
|
+ "github.com/spf13/cobra"
|
|
|
+)
|
|
|
+
|
|
|
+type attachOptions struct {
|
|
|
+ noStdin bool
|
|
|
+ proxy bool
|
|
|
+ detachKeys string
|
|
|
+
|
|
|
+ container string
|
|
|
+}
|
|
|
+
|
|
|
+// NewAttachCommand creats a new cobra.Command for `docker attach`
|
|
|
+func NewAttachCommand(dockerCli *client.DockerCli) *cobra.Command {
|
|
|
+ var opts attachOptions
|
|
|
+
|
|
|
+ cmd := &cobra.Command{
|
|
|
+ Use: "attach [OPTIONS] CONTAINER",
|
|
|
+ Short: "Attach to a running container",
|
|
|
+ Args: cli.ExactArgs(1),
|
|
|
+ RunE: func(cmd *cobra.Command, args []string) error {
|
|
|
+ opts.container = args[0]
|
|
|
+ return runAttach(dockerCli, &opts)
|
|
|
+ },
|
|
|
+ }
|
|
|
+ cmd.SetFlagErrorFunc(flagErrorFunc)
|
|
|
+
|
|
|
+ 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 *client.DockerCli, opts *attachOptions) error {
|
|
|
+ ctx := context.Background()
|
|
|
+
|
|
|
+ c, err := dockerCli.Client().ContainerInspect(ctx, opts.container)
|
|
|
+ if err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+
|
|
|
+ if !c.State.Running {
|
|
|
+ return fmt.Errorf("You cannot attach to a stopped container, start it first")
|
|
|
+ }
|
|
|
+
|
|
|
+ if c.State.Paused {
|
|
|
+ return fmt.Errorf("You cannot attach to a paused container, unpause it first")
|
|
|
+ }
|
|
|
+
|
|
|
+ if err := dockerCli.CheckTtyInput(!opts.noStdin, c.Config.Tty); err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+
|
|
|
+ if opts.detachKeys != "" {
|
|
|
+ dockerCli.ConfigFile().DetachKeys = opts.detachKeys
|
|
|
+ }
|
|
|
+
|
|
|
+ options := types.ContainerAttachOptions{
|
|
|
+ Stream: true,
|
|
|
+ Stdin: !opts.noStdin && c.Config.OpenStdin,
|
|
|
+ Stdout: true,
|
|
|
+ Stderr: true,
|
|
|
+ DetachKeys: dockerCli.ConfigFile().DetachKeys,
|
|
|
+ }
|
|
|
+
|
|
|
+ var in io.ReadCloser
|
|
|
+ if options.Stdin {
|
|
|
+ in = dockerCli.In()
|
|
|
+ }
|
|
|
+
|
|
|
+ if opts.proxy && !c.Config.Tty {
|
|
|
+ sigc := dockerCli.ForwardAllSignals(ctx, opts.container)
|
|
|
+ defer signal.StopCatch(sigc)
|
|
|
+ }
|
|
|
+
|
|
|
+ resp, errAttach := dockerCli.Client().ContainerAttach(ctx, opts.container, options)
|
|
|
+ if errAttach != nil && errAttach != httputil.ErrPersistEOF {
|
|
|
+ // ContainerAttach returns an ErrPersistEOF (connection closed)
|
|
|
+ // means server met an error and put it in Hijacked connection
|
|
|
+ // keep the error and read detailed error message from hijacked connection later
|
|
|
+ return errAttach
|
|
|
+ }
|
|
|
+ defer resp.Close()
|
|
|
+
|
|
|
+ if c.Config.Tty && dockerCli.IsTerminalOut() {
|
|
|
+ height, width := dockerCli.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.
|
|
|
+ dockerCli.ResizeTtyTo(ctx, opts.container, 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 := dockerCli.MonitorTtySize(ctx, opts.container, false); err != nil {
|
|
|
+ logrus.Debugf("Error monitoring TTY size: %s", err)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if err := dockerCli.HoldHijackedConnection(ctx, c.Config.Tty, in, dockerCli.Out(), dockerCli.Err(), resp); err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+
|
|
|
+ if errAttach != nil {
|
|
|
+ return errAttach
|
|
|
+ }
|
|
|
+
|
|
|
+ _, status, err := dockerCli.GetExitCode(ctx, opts.container)
|
|
|
+ if err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+ if status != 0 {
|
|
|
+ return cli.StatusError{StatusCode: status}
|
|
|
+ }
|
|
|
+
|
|
|
+ return nil
|
|
|
+}
|