From eceb8625a94e3504fca7b27d8156bcf11a93bf4f Mon Sep 17 00:00:00 2001 From: Zhang Wei Date: Mon, 6 Jun 2016 00:17:39 +0800 Subject: [PATCH] Move attach command to cobra. Signed-off-by: Zhang Wei --- api/client/attach.go | 113 ----------------- api/client/commands.go | 1 - api/client/container/attach.go | 130 ++++++++++++++++++++ api/client/utils.go | 6 +- cli/cobraadaptor/adaptor.go | 1 + cli/usage.go | 1 - integration-cli/docker_cli_run_unix_test.go | 8 +- 7 files changed, 139 insertions(+), 121 deletions(-) delete mode 100644 api/client/attach.go create mode 100644 api/client/container/attach.go diff --git a/api/client/attach.go b/api/client/attach.go deleted file mode 100644 index 3fc3a86c8a..0000000000 --- a/api/client/attach.go +++ /dev/null @@ -1,113 +0,0 @@ -package client - -import ( - "fmt" - "io" - "net/http/httputil" - - "golang.org/x/net/context" - - "github.com/Sirupsen/logrus" - Cli "github.com/docker/docker/cli" - flag "github.com/docker/docker/pkg/mflag" - "github.com/docker/docker/pkg/signal" - "github.com/docker/engine-api/types" -) - -// CmdAttach attaches to a running container. -// -// Usage: docker attach [OPTIONS] CONTAINER -func (cli *DockerCli) CmdAttach(args ...string) error { - cmd := Cli.Subcmd("attach", []string{"CONTAINER"}, Cli.DockerCommands["attach"].Description, true) - noStdin := cmd.Bool([]string{"-no-stdin"}, false, "Do not attach STDIN") - proxy := cmd.Bool([]string{"-sig-proxy"}, true, "Proxy all received signals to the process") - detachKeys := cmd.String([]string{"-detach-keys"}, "", "Override the key sequence for detaching a container") - - cmd.Require(flag.Exact, 1) - - cmd.ParseFlags(args, true) - - ctx := context.Background() - - c, err := cli.client.ContainerInspect(ctx, cmd.Arg(0)) - 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 := cli.CheckTtyInput(!*noStdin, c.Config.Tty); err != nil { - return err - } - - if *detachKeys != "" { - cli.configFile.DetachKeys = *detachKeys - } - - container := cmd.Arg(0) - - options := types.ContainerAttachOptions{ - Stream: true, - Stdin: !*noStdin && c.Config.OpenStdin, - Stdout: true, - Stderr: true, - DetachKeys: cli.configFile.DetachKeys, - } - - var in io.ReadCloser - if options.Stdin { - in = cli.in - } - - if *proxy && !c.Config.Tty { - sigc := cli.ForwardAllSignals(ctx, container) - defer signal.StopCatch(sigc) - } - - resp, errAttach := cli.client.ContainerAttach(ctx, 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 && cli.isTerminalOut { - height, width := cli.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. - cli.resizeTtyTo(ctx, cmd.Arg(0), 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 := cli.MonitorTtySize(ctx, cmd.Arg(0), false); err != nil { - logrus.Debugf("Error monitoring TTY size: %s", err) - } - } - if err := cli.HoldHijackedConnection(ctx, c.Config.Tty, in, cli.out, cli.err, resp); err != nil { - return err - } - - if errAttach != nil { - return errAttach - } - - _, status, err := cli.GetExitCode(ctx, container) - if err != nil { - return err - } - if status != 0 { - return Cli.StatusError{StatusCode: status} - } - - return nil -} diff --git a/api/client/commands.go b/api/client/commands.go index a1ae5cd5a8..a9921f027c 100644 --- a/api/client/commands.go +++ b/api/client/commands.go @@ -3,7 +3,6 @@ package client // Command returns a cli command handler if one exists func (cli *DockerCli) Command(name string) func(...string) error { return map[string]func(...string) error{ - "attach": cli.CmdAttach, "build": cli.CmdBuild, "commit": cli.CmdCommit, "cp": cli.CmdCp, diff --git a/api/client/container/attach.go b/api/client/container/attach.go new file mode 100644 index 0000000000..fc91b3c275 --- /dev/null +++ b/api/client/container/attach.go @@ -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 +} diff --git a/api/client/utils.go b/api/client/utils.go index c090dbeed8..12ad2ff4cd 100644 --- a/api/client/utils.go +++ b/api/client/utils.go @@ -62,10 +62,12 @@ func (cli *DockerCli) RegistryAuthenticationPrivilegedFunc(index *registrytypes. func (cli *DockerCli) resizeTty(ctx context.Context, id string, isExec bool) { height, width := cli.GetTtySize() - cli.resizeTtyTo(ctx, id, height, width, isExec) + cli.ResizeTtyTo(ctx, id, height, width, isExec) } -func (cli *DockerCli) resizeTtyTo(ctx context.Context, id string, height, width int, isExec bool) { +// 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 } diff --git a/cli/cobraadaptor/adaptor.go b/cli/cobraadaptor/adaptor.go index 4ceb57757d..cf3966f141 100644 --- a/cli/cobraadaptor/adaptor.go +++ b/cli/cobraadaptor/adaptor.go @@ -34,6 +34,7 @@ func NewCobraAdaptor(clientFlags *cliflags.ClientFlags) CobraAdaptor { rootCmd.SetFlagErrorFunc(cli.FlagErrorFunc) rootCmd.SetOutput(stdout) rootCmd.AddCommand( + container.NewAttachCommand(dockerCli), container.NewCreateCommand(dockerCli), container.NewDiffCommand(dockerCli), container.NewExportCommand(dockerCli), diff --git a/cli/usage.go b/cli/usage.go index f6152e5c93..6a42f9281f 100644 --- a/cli/usage.go +++ b/cli/usage.go @@ -8,7 +8,6 @@ type Command struct { // DockerCommandUsage lists the top level docker commands and their short usage var DockerCommandUsage = []Command{ - {"attach", "Attach to a running container"}, {"build", "Build an image from a Dockerfile"}, {"commit", "Create a new image from a container's changes"}, {"cp", "Copy files/folders between a container and the local filesystem"}, diff --git a/integration-cli/docker_cli_run_unix_test.go b/integration-cli/docker_cli_run_unix_test.go index 0ede9b9fa5..d427791a0f 100644 --- a/integration-cli/docker_cli_run_unix_test.go +++ b/integration-cli/docker_cli_run_unix_test.go @@ -150,7 +150,7 @@ func (s *DockerSuite) TestRunAttachDetachFromFlag(c *check.C) { dockerCmd(c, "run", "--name", name, "-itd", "busybox", "cat") - cmd := exec.Command(dockerBinary, "attach", "--detach-keys='ctrl-a,a'", name) + cmd := exec.Command(dockerBinary, "attach", "--detach-keys=ctrl-a,a", name) stdout, err := cmd.StdoutPipe() if err != nil { c.Fatal(err) @@ -210,7 +210,7 @@ func (s *DockerSuite) TestRunAttachDetachFromInvalidFlag(c *check.C) { c.Assert(waitRun(name), check.IsNil) // specify an invalid detach key, container will ignore it and use default - cmd := exec.Command(dockerBinary, "attach", "--detach-keys='ctrl-A,a'", name) + cmd := exec.Command(dockerBinary, "attach", "--detach-keys=ctrl-A,a", name) stdout, err := cmd.StdoutPipe() if err != nil { c.Fatal(err) @@ -348,7 +348,7 @@ func (s *DockerSuite) TestRunAttachDetachKeysOverrideConfig(c *check.C) { name := "attach-detach" dockerCmd(c, "run", "--name", name, "-itd", "busybox", "cat") - cmd := exec.Command(dockerBinary, "attach", "--detach-keys='ctrl-a,a'", name) + cmd := exec.Command(dockerBinary, "attach", "--detach-keys=ctrl-a,a", name) stdout, err := cmd.StdoutPipe() if err != nil { c.Fatal(err) @@ -408,7 +408,7 @@ func (s *DockerSuite) TestRunAttachInvalidDetachKeySequencePreserved(c *check.C) dockerCmd(c, "run", "--name", name, "-itd", "busybox", "cat") - cmd := exec.Command(dockerBinary, "attach", "--detach-keys='a,b,c'", name) + cmd := exec.Command(dockerBinary, "attach", "--detach-keys=a,b,c", name) stdout, err := cmd.StdoutPipe() if err != nil { c.Fatal(err)