From 15aa2a663b47b6126a66efefcadb64edfbffb9f5 Mon Sep 17 00:00:00 2001 From: Vincent Demeester Date: Sun, 3 Jan 2016 23:03:39 +0100 Subject: [PATCH] Implement configurable detach key Implement configurable detach keys (for `attach`, exec`, `run` and `start`) using the client-side configuration - Adds a `--detach-keys` flag to `attach`, `exec`, `run` and `start` commands. - Adds a new configuration field (in `~/.docker/config.json`) to configure the default escape keys for docker client. Signed-off-by: Vincent Demeester --- api/client/attach.go | 6 + api/client/exec.go | 8 + api/client/lib/container_attach.go | 3 + api/client/run.go | 6 + api/client/start.go | 6 + .../router/container/container_routes.go | 49 +++- api/types/client.go | 1 + api/types/configs.go | 1 + cliconfig/config.go | 1 + container/container.go | 25 +- daemon/attach.go | 24 +- daemon/exec.go | 13 +- daemon/exec/exec.go | 1 + docs/reference/api/docker_remote_api_v1.22.md | 21 +- docs/reference/commandline/attach.md | 41 ++- docs/reference/commandline/cli.md | 26 +- docs/reference/commandline/exec.md | 1 + docs/reference/commandline/run.md | 1 + docs/reference/commandline/start.md | 1 + integration-cli/docker_cli_run_unix_test.go | 239 +++++++++++++++++- man/docker-attach.1.md | 37 ++- man/docker-exec.1.md | 6 +- man/docker-run.1.md | 10 +- man/docker-start.1.md | 7 +- man/docker.1.md | 1 + pkg/term/ascii.go | 66 +++++ pkg/term/ascii_test.go | 43 ++++ 27 files changed, 583 insertions(+), 61 deletions(-) create mode 100644 pkg/term/ascii.go create mode 100644 pkg/term/ascii_test.go diff --git a/api/client/attach.go b/api/client/attach.go index f0979e2bf8..1cb097dfb5 100644 --- a/api/client/attach.go +++ b/api/client/attach.go @@ -18,6 +18,7 @@ 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) @@ -46,12 +47,17 @@ func (cli *DockerCli) CmdAttach(args ...string) error { } } + if *detachKeys != "" { + cli.configFile.DetachKeys = *detachKeys + } + options := types.ContainerAttachOptions{ ContainerID: cmd.Arg(0), Stream: true, Stdin: !*noStdin && c.Config.OpenStdin, Stdout: true, Stderr: true, + DetachKeys: cli.configFile.DetachKeys, } var in io.ReadCloser diff --git a/api/client/exec.go b/api/client/exec.go index 06cdd2e4c8..ca30ee5a11 100644 --- a/api/client/exec.go +++ b/api/client/exec.go @@ -16,6 +16,7 @@ import ( // Usage: docker exec [OPTIONS] CONTAINER COMMAND [ARG...] func (cli *DockerCli) CmdExec(args ...string) error { cmd := Cli.Subcmd("exec", []string{"CONTAINER COMMAND [ARG...]"}, Cli.DockerCommands["exec"].Description, true) + detachKeys := cmd.String([]string{"-detach-keys"}, "", "Override the key sequence for detaching a container") execConfig, err := runconfig.ParseExec(cmd, args) // just in case the ParseExec does not exit @@ -23,6 +24,13 @@ func (cli *DockerCli) CmdExec(args ...string) error { return Cli.StatusError{StatusCode: 1} } + if *detachKeys != "" { + cli.configFile.DetachKeys = *detachKeys + } + + // Send client escape keys + execConfig.DetachKeys = cli.configFile.DetachKeys + response, err := cli.client.ContainerExecCreate(*execConfig) if err != nil { return err diff --git a/api/client/lib/container_attach.go b/api/client/lib/container_attach.go index 439ec2d0d7..aac4d65304 100644 --- a/api/client/lib/container_attach.go +++ b/api/client/lib/container_attach.go @@ -24,6 +24,9 @@ func (cli *Client) ContainerAttach(options types.ContainerAttachOptions) (types. if options.Stderr { query.Set("stderr", "1") } + if options.DetachKeys != "" { + query.Set("detachKeys", options.DetachKeys) + } headers := map[string][]string{"Content-Type": {"text/plain"}} return cli.postHijacked("/containers/"+options.ContainerID+"/attach", query, nil, headers) diff --git a/api/client/run.go b/api/client/run.go index d93574b29b..ec35530a47 100644 --- a/api/client/run.go +++ b/api/client/run.go @@ -74,6 +74,7 @@ func (cli *DockerCli) CmdRun(args ...string) error { flDetach = cmd.Bool([]string{"d", "-detach"}, false, "Run container in background and print container ID") flSigProxy = cmd.Bool([]string{"-sig-proxy"}, true, "Proxy received signals to the process") flName = cmd.String([]string{"-name"}, "", "Assign a name to the container") + flDetachKeys = cmd.String([]string{"-detach-keys"}, "", "Override the key sequence for detaching a container") flAttach *opts.ListOpts ErrConflictAttachDetach = fmt.Errorf("Conflicting options: -a and -d") @@ -188,12 +189,17 @@ func (cli *DockerCli) CmdRun(args ...string) error { } } + if *flDetachKeys != "" { + cli.configFile.DetachKeys = *flDetachKeys + } + options := types.ContainerAttachOptions{ ContainerID: createResponse.ID, Stream: true, Stdin: config.AttachStdin, Stdout: config.AttachStdout, Stderr: config.AttachStderr, + DetachKeys: cli.configFile.DetachKeys, } resp, err := cli.client.ContainerAttach(options) diff --git a/api/client/start.go b/api/client/start.go index cdd364a11a..fcfa3f366d 100644 --- a/api/client/start.go +++ b/api/client/start.go @@ -49,6 +49,7 @@ func (cli *DockerCli) CmdStart(args ...string) error { cmd := Cli.Subcmd("start", []string{"CONTAINER [CONTAINER...]"}, Cli.DockerCommands["start"].Description, true) attach := cmd.Bool([]string{"a", "-attach"}, false, "Attach STDOUT/STDERR and forward signals") openStdin := cmd.Bool([]string{"i", "-interactive"}, false, "Attach container's STDIN") + detachKeys := cmd.String([]string{"-detach-keys"}, "", "Override the key sequence for detaching a container") cmd.Require(flag.Min, 1) cmd.ParseFlags(args, true) @@ -72,12 +73,17 @@ func (cli *DockerCli) CmdStart(args ...string) error { defer signal.StopCatch(sigc) } + if *detachKeys != "" { + cli.configFile.DetachKeys = *detachKeys + } + options := types.ContainerAttachOptions{ ContainerID: containerID, Stream: true, Stdin: *openStdin && c.Config.OpenStdin, Stdout: true, Stderr: true, + DetachKeys: cli.configFile.DetachKeys, } var in io.ReadCloser diff --git a/api/server/router/container/container_routes.go b/api/server/router/container/container_routes.go index d200f56aeb..f0661d2ac4 100644 --- a/api/server/router/container/container_routes.go +++ b/api/server/router/container/container_routes.go @@ -19,6 +19,7 @@ import ( derr "github.com/docker/docker/errors" "github.com/docker/docker/pkg/ioutils" "github.com/docker/docker/pkg/signal" + "github.com/docker/docker/pkg/term" "github.com/docker/docker/runconfig" "github.com/docker/docker/utils" "golang.org/x/net/context" @@ -420,21 +421,32 @@ func (s *containerRouter) postContainersResize(ctx context.Context, w http.Respo } func (s *containerRouter) postContainersAttach(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { - if err := httputils.ParseForm(r); err != nil { + err := httputils.ParseForm(r) + if err != nil { return err } containerName := vars["name"] _, upgrade := r.Header["Upgrade"] + keys := []byte{} + detachKeys := r.FormValue("detachKeys") + if detachKeys != "" { + keys, err = term.ToBytes(detachKeys) + if err != nil { + logrus.Warnf("Invalid escape keys provided (%s) using default : ctrl-p ctrl-q", detachKeys) + } + } + attachWithLogsConfig := &daemon.ContainerAttachWithLogsConfig{ - Hijacker: w.(http.Hijacker), - Upgrade: upgrade, - UseStdin: httputils.BoolValue(r, "stdin"), - UseStdout: httputils.BoolValue(r, "stdout"), - UseStderr: httputils.BoolValue(r, "stderr"), - Logs: httputils.BoolValue(r, "logs"), - Stream: httputils.BoolValue(r, "stream"), + Hijacker: w.(http.Hijacker), + Upgrade: upgrade, + UseStdin: httputils.BoolValue(r, "stdin"), + UseStdout: httputils.BoolValue(r, "stdout"), + UseStderr: httputils.BoolValue(r, "stderr"), + Logs: httputils.BoolValue(r, "logs"), + Stream: httputils.BoolValue(r, "stream"), + DetachKeys: keys, } return s.backend.ContainerAttachWithLogs(containerName, attachWithLogsConfig) @@ -450,15 +462,26 @@ func (s *containerRouter) wsContainersAttach(ctx context.Context, w http.Respons return derr.ErrorCodeNoSuchContainer.WithArgs(containerName) } + var keys []byte + var err error + detachKeys := r.FormValue("detachKeys") + if detachKeys != "" { + keys, err = term.ToBytes(detachKeys) + if err != nil { + logrus.Warnf("Invalid escape keys provided (%s) using default : ctrl-p ctrl-q", detachKeys) + } + } + h := websocket.Handler(func(ws *websocket.Conn) { defer ws.Close() wsAttachWithLogsConfig := &daemon.ContainerWsAttachWithLogsConfig{ - InStream: ws, - OutStream: ws, - ErrStream: ws, - Logs: httputils.BoolValue(r, "logs"), - Stream: httputils.BoolValue(r, "stream"), + InStream: ws, + OutStream: ws, + ErrStream: ws, + Logs: httputils.BoolValue(r, "logs"), + Stream: httputils.BoolValue(r, "stream"), + DetachKeys: keys, } if err := s.backend.ContainerWsAttachWithLogs(containerName, wsAttachWithLogsConfig); err != nil { diff --git a/api/types/client.go b/api/types/client.go index 9a5f25cb70..30f3b6fb8d 100644 --- a/api/types/client.go +++ b/api/types/client.go @@ -17,6 +17,7 @@ type ContainerAttachOptions struct { Stdin bool Stdout bool Stderr bool + DetachKeys string } // ContainerCommitOptions holds parameters to commit changes into a container. diff --git a/api/types/configs.go b/api/types/configs.go index ba8f0e006f..43ea4c10a5 100644 --- a/api/types/configs.go +++ b/api/types/configs.go @@ -45,5 +45,6 @@ type ExecConfig struct { AttachStderr bool // Attach the standard output AttachStdout bool // Attach the standard error Detach bool // Execute in detach mode + DetachKeys string // Escape keys for detach Cmd []string // Execution commands and args } diff --git a/cliconfig/config.go b/cliconfig/config.go index 16e875cc7e..7e657ecbd2 100644 --- a/cliconfig/config.go +++ b/cliconfig/config.go @@ -51,6 +51,7 @@ type ConfigFile struct { HTTPHeaders map[string]string `json:"HttpHeaders,omitempty"` PsFormat string `json:"psFormat,omitempty"` ImagesFormat string `json:"imagesFormat,omitempty"` + DetachKeys string `json:"detachKeys,omitempty"` filename string // Note: not serialized - for internal use only } diff --git a/container/container.go b/container/container.go index ebe70b44ea..a4ae82cf20 100644 --- a/container/container.go +++ b/container/container.go @@ -329,13 +329,13 @@ func (container *Container) GetExecIDs() []string { // Attach connects to the container's TTY, delegating to standard // streams or websockets depending on the configuration. -func (container *Container) Attach(stdin io.ReadCloser, stdout io.Writer, stderr io.Writer) chan error { - return AttachStreams(container.StreamConfig, container.Config.OpenStdin, container.Config.StdinOnce, container.Config.Tty, stdin, stdout, stderr) +func (container *Container) Attach(stdin io.ReadCloser, stdout io.Writer, stderr io.Writer, keys []byte) chan error { + return AttachStreams(container.StreamConfig, container.Config.OpenStdin, container.Config.StdinOnce, container.Config.Tty, stdin, stdout, stderr, keys) } // AttachStreams connects streams to a TTY. // Used by exec too. Should this move somewhere else? -func AttachStreams(streamConfig *runconfig.StreamConfig, openStdin, stdinOnce, tty bool, stdin io.ReadCloser, stdout io.Writer, stderr io.Writer) chan error { +func AttachStreams(streamConfig *runconfig.StreamConfig, openStdin, stdinOnce, tty bool, stdin io.ReadCloser, stdout io.Writer, stderr io.Writer, keys []byte) chan error { var ( cStdout, cStderr io.ReadCloser cStdin io.WriteCloser @@ -382,7 +382,7 @@ func AttachStreams(streamConfig *runconfig.StreamConfig, openStdin, stdinOnce, t var err error if tty { - _, err = copyEscapable(cStdin, stdin) + _, err = copyEscapable(cStdin, stdin, keys) } else { _, err = io.Copy(cStdin, stdin) @@ -438,22 +438,27 @@ func AttachStreams(streamConfig *runconfig.StreamConfig, openStdin, stdinOnce, t } // Code c/c from io.Copy() modified to handle escape sequence -func copyEscapable(dst io.Writer, src io.ReadCloser) (written int64, err error) { +func copyEscapable(dst io.Writer, src io.ReadCloser, keys []byte) (written int64, err error) { + if len(keys) == 0 { + // Default keys : ctrl-p ctrl-q + keys = []byte{16, 17} + } buf := make([]byte, 32*1024) for { nr, er := src.Read(buf) if nr > 0 { // ---- Docker addition - // char 16 is C-p - if nr == 1 && buf[0] == 16 { - nr, er = src.Read(buf) - // char 17 is C-q - if nr == 1 && buf[0] == 17 { + for i, key := range keys { + if nr != 1 || buf[0] != key { + break + } + if i == len(keys)-1 { if err := src.Close(); err != nil { return 0, err } return 0, nil } + nr, er = src.Read(buf) } // ---- End of docker nw, ew := dst.Write(buf[0:nr]) diff --git a/daemon/attach.go b/daemon/attach.go index 451d41c01c..a0374097ec 100644 --- a/daemon/attach.go +++ b/daemon/attach.go @@ -15,13 +15,14 @@ import ( // ContainerAttachWithLogsConfig holds the streams to use when connecting to a container to view logs. type ContainerAttachWithLogsConfig struct { - Hijacker http.Hijacker - Upgrade bool - UseStdin bool - UseStdout bool - UseStderr bool - Logs bool - Stream bool + Hijacker http.Hijacker + Upgrade bool + UseStdin bool + UseStdout bool + UseStderr bool + Logs bool + Stream bool + DetachKeys []byte } // ContainerAttachWithLogs attaches to logs according to the config passed in. See ContainerAttachWithLogsConfig. @@ -75,7 +76,7 @@ func (daemon *Daemon) ContainerAttachWithLogs(prefixOrName string, c *ContainerA stderr = errStream } - if err := daemon.attachWithLogs(container, stdin, stdout, stderr, c.Logs, c.Stream); err != nil { + if err := daemon.attachWithLogs(container, stdin, stdout, stderr, c.Logs, c.Stream, c.DetachKeys); err != nil { fmt.Fprintf(outStream, "Error attaching: %s\n", err) } return nil @@ -87,6 +88,7 @@ type ContainerWsAttachWithLogsConfig struct { InStream io.ReadCloser OutStream, ErrStream io.Writer Logs, Stream bool + DetachKeys []byte } // ContainerWsAttachWithLogs websocket connection @@ -95,10 +97,10 @@ func (daemon *Daemon) ContainerWsAttachWithLogs(prefixOrName string, c *Containe if err != nil { return err } - return daemon.attachWithLogs(container, c.InStream, c.OutStream, c.ErrStream, c.Logs, c.Stream) + return daemon.attachWithLogs(container, c.InStream, c.OutStream, c.ErrStream, c.Logs, c.Stream, c.DetachKeys) } -func (daemon *Daemon) attachWithLogs(container *container.Container, stdin io.ReadCloser, stdout, stderr io.Writer, logs, stream bool) error { +func (daemon *Daemon) attachWithLogs(container *container.Container, stdin io.ReadCloser, stdout, stderr io.Writer, logs, stream bool, keys []byte) error { if logs { logDriver, err := daemon.getLogger(container) if err != nil { @@ -144,7 +146,7 @@ func (daemon *Daemon) attachWithLogs(container *container.Container, stdin io.Re }() stdinPipe = r } - <-container.Attach(stdinPipe, stdout, stderr) + <-container.Attach(stdinPipe, stdout, stderr, keys) // If we are in stdinonce mode, wait for the process to end // otherwise, simply return if container.Config.StdinOnce && !container.Config.Tty { diff --git a/daemon/exec.go b/daemon/exec.go index f400bc72f5..fb40941821 100644 --- a/daemon/exec.go +++ b/daemon/exec.go @@ -14,6 +14,7 @@ import ( derr "github.com/docker/docker/errors" "github.com/docker/docker/pkg/pools" "github.com/docker/docker/pkg/promise" + "github.com/docker/docker/pkg/term" ) func (d *Daemon) registerExecCommand(container *container.Container, config *exec.Config) { @@ -88,6 +89,14 @@ func (d *Daemon) ContainerExecCreate(config *types.ExecConfig) (string, error) { cmd := strslice.New(config.Cmd...) entrypoint, args := d.getEntrypointAndArgs(strslice.New(), cmd) + keys := []byte{} + if config.DetachKeys != "" { + keys, err = term.ToBytes(config.DetachKeys) + if err != nil { + logrus.Warnf("Wrong escape keys provided (%s, error: %s) using default : ctrl-p ctrl-q", config.DetachKeys, err.Error()) + } + } + processConfig := &execdriver.ProcessConfig{ CommonProcessConfig: execdriver.CommonProcessConfig{ Tty: config.Tty, @@ -103,6 +112,7 @@ func (d *Daemon) ContainerExecCreate(config *types.ExecConfig) (string, error) { execConfig.OpenStderr = config.AttachStderr execConfig.ProcessConfig = processConfig execConfig.ContainerID = container.ID + execConfig.DetachKeys = keys d.registerExecCommand(container, execConfig) @@ -158,7 +168,8 @@ func (d *Daemon) ContainerExecStart(name string, stdin io.ReadCloser, stdout io. ec.NewNopInputPipe() } - attachErr := container.AttachStreams(ec.StreamConfig, ec.OpenStdin, true, ec.ProcessConfig.Tty, cStdin, cStdout, cStderr) + attachErr := container.AttachStreams(ec.StreamConfig, ec.OpenStdin, true, ec.ProcessConfig.Tty, cStdin, cStdout, cStderr, ec.DetachKeys) + execErr := make(chan error) // Note, the ExecConfig data will be removed when the container diff --git a/daemon/exec/exec.go b/daemon/exec/exec.go index ed06f95483..5504ed3575 100644 --- a/daemon/exec/exec.go +++ b/daemon/exec/exec.go @@ -25,6 +25,7 @@ type Config struct { OpenStdout bool CanRemove bool ContainerID string + DetachKeys []byte // waitStart will be closed immediately after the exec is really started. waitStart chan struct{} diff --git a/docs/reference/api/docker_remote_api_v1.22.md b/docs/reference/api/docker_remote_api_v1.22.md index c829d5f255..f519e20982 100644 --- a/docs/reference/api/docker_remote_api_v1.22.md +++ b/docs/reference/api/docker_remote_api_v1.22.md @@ -862,10 +862,9 @@ This endpoint returns a live stream of a container's resource usage statistics. "total_usage" : 36488948, "usage_in_kernelmode" : 20000000 }, - "system_cpu_usage" : 20091722000000000, +  "system_cpu_usage" : 20091722000000000, "throttling_data" : {} - } - } + } } Query Parameters: @@ -922,6 +921,12 @@ Start the container `id` HTTP/1.1 204 No Content +Query Parameters: + +- **detacheys** – Override the key sequence for detaching a + container. Format is a single character `[a-Z]` or `ctrl-` + where `` is one of: `a-z`, `@`, `^`, `[`, `,` or `_`. + Status Codes: - **204** – no error @@ -1133,6 +1138,9 @@ Attach to the container `id` Query Parameters: +- **detacheys** – Override the key sequence for detaching a + container. Format is a single character `[a-Z]` or `ctrl-` + where `` is one of: `a-z`, `@`, `^`, `[`, `,` or `_`. - **logs** – 1/True/true or 0/False/false, return logs. Default `false`. - **stream** – 1/True/true or 0/False/false, return stream. Default `false`. @@ -1213,6 +1221,9 @@ Implements websocket protocol handshake according to [RFC 6455](http://tools.iet Query Parameters: +- **detacheys** – Override the key sequence for detaching a + container. Format is a single character `[a-Z]` or `ctrl-` + where `` is one of: `a-z`, `@`, `^`, `[`, `,` or `_`. - **logs** – 1/True/true or 0/False/false, return logs. Default `false`. - **stream** – 1/True/true or 0/False/false, return stream. Default `false`. @@ -2420,6 +2431,7 @@ Sets up an exec instance in a running container `id` "AttachStdin": false, "AttachStdout": true, "AttachStderr": true, + "DetachKeys": "ctrl-p,ctrl-q", "Tty": false, "Cmd": [ "date" @@ -2441,6 +2453,9 @@ Json Parameters: - **AttachStdin** - Boolean value, attaches to `stdin` of the `exec` command. - **AttachStdout** - Boolean value, attaches to `stdout` of the `exec` command. - **AttachStderr** - Boolean value, attaches to `stderr` of the `exec` command. +- **Detacheys** – Override the key sequence for detaching a + container. Format is a single character `[a-Z]` or `ctrl-` + where `` is one of: `a-z`, `@`, `^`, `[`, `,` or `_`. - **Tty** - Boolean value to allocate a pseudo-TTY. - **Cmd** - Command to run specified as a string or an array of strings. diff --git a/docs/reference/commandline/attach.md b/docs/reference/commandline/attach.md index 5712c92f51..124474bef2 100644 --- a/docs/reference/commandline/attach.md +++ b/docs/reference/commandline/attach.md @@ -14,9 +14,10 @@ parent = "smn_cli" Attach to a running container - --help Print usage - --no-stdin Do not attach STDIN - --sig-proxy=true Proxy all received signals to the process + --detach-keys="" Set up escape key sequence + --help Print usage + --no-stdin Do not attach STDIN + --sig-proxy=true Proxy all received signals to the process The `docker attach` command allows you to attach to a running container using the container's ID or name, either to view its ongoing output or to control it @@ -24,11 +25,10 @@ interactively. You can attach to the same contained process multiple times simultaneously, screen sharing style, or quickly view the progress of your detached process. -You can detach from the container and leave it running with `CTRL-p CTRL-q` -(for a quiet exit) or with `CTRL-c` if `--sig-proxy` is false. - -If `--sig-proxy` is true (the default),`CTRL-c` sends a `SIGINT` to the -container. +To stop a container, use `CTRL-c`. This key sequence sends `SIGKILL` to the +container. If `--sig-proxy` is true (the default),`CTRL-c` sends a `SIGINT` to +the container. You can detach from a container and leave it running using the +using `CTRL-p CTRL-q` key sequence. > **Note:** > A process running as PID 1 inside a container is treated specially by @@ -39,6 +39,31 @@ container. It is forbidden to redirect the standard input of a `docker attach` command while attaching to a tty-enabled container (i.e.: launched with `-t`). + +## Override the detach sequence + +If you want, you can configure a override the Docker key sequence for detach. +This is is useful if the Docker default sequence conflicts with key squence you +use for other applications. There are two ways to defines a your own detach key +sequence, as a per-container override or as a configuration property on your +entire configuration. + +To override the sequence for an individual container, use the +`--detach-keys=""` flag with the `docker attach` command. The format of +the `` is either a letter [a-Z], or the `ctrl-` combined with any of +the following: + +* `a-z` (a single lowercase alpha character ) +* `@` (ampersand) +* `[` (left bracket) +* `\\` (two backward slashes) +* `_` (underscore) +* `^` (caret) + +These `a`, `ctrl-a`, `X`, or `ctrl-\\` values are all examples of valid key +sequences. To configure a different configuration default key sequence for all +containers, see [**Configuration file** section](cli.md#configuration-files). + #### Examples $ docker run -d --name topdemo ubuntu /usr/bin/top -b diff --git a/docs/reference/commandline/cli.md b/docs/reference/commandline/cli.md index 608cb1275a..e3773f7d63 100644 --- a/docs/reference/commandline/cli.md +++ b/docs/reference/commandline/cli.md @@ -101,7 +101,26 @@ The property `psFormat` specifies the default format for `docker ps` output. When the `--format` flag is not provided with the `docker ps` command, Docker's client uses this property. If this property is not set, the client falls back to the default table format. For a list of supported formatting -directives, see the [**Formatting** section in the `docker ps` documentation](ps.md) +directives, see the +[**Formatting** section in the `docker ps` documentation](ps.md) + +Once attached to a container, users detach from it and leave it running using +the using `CTRL-p CTRL-q` key sequence. This detach key sequence is customizable +using the `detachKeys` property. Specify a `` value for the +property. The format of the `` is either a letter [a-Z], or the `ctrl-` +combined with any of the following: + +* `a-z` (a single lowercase alpha character ) +* `@` (ampersand) +* `[` (left bracket) +* `\\` (two backward slashes) +* `_` (underscore) +* `^` (caret) + +Your customization applies to all containers started in with your Docker client. +Users can override your custom or the default key sequence on a per-container +basis. To do this, the user specifies the `--detach-keys` flag with the `docker +attach`, `docker exec`, `docker run` or `docker start` command. The property `imagesFormat` specifies the default format for `docker images` output. When the `--format` flag is not provided with the `docker images` command, @@ -115,8 +134,9 @@ Following is a sample `config.json` file: "HttpHeaders": { "MyHeader": "MyValue" }, - "psFormat": "table {{.ID}}\\t{{.Image}}\\t{{.Command}}\\t{{.Labels}}" - "imagesFormat": "table {{.ID}}\\t{{.Repository}}\\t{{.Tag}}\\t{{.CreatedAt}}" + "psFormat": "table {{.ID}}\\t{{.Image}}\\t{{.Command}}\\t{{.Labels}}", + "imagesFormat": "table {{.ID}}\\t{{.Repository}}\\t{{.Tag}}\\t{{.CreatedAt}}", + "detachKeys": "ctrl-e,e" } ### Notary diff --git a/docs/reference/commandline/exec.md b/docs/reference/commandline/exec.md index 971a2f2d67..80796a59c9 100644 --- a/docs/reference/commandline/exec.md +++ b/docs/reference/commandline/exec.md @@ -15,6 +15,7 @@ parent = "smn_cli" Run a command in a running container -d, --detach Detached mode: run command in the background + --detach-keys Specify the escape key sequence used to detach a container --help Print usage -i, --interactive Keep STDIN open even if not attached --privileged Give extended Linux capabilities to the command diff --git a/docs/reference/commandline/run.md b/docs/reference/commandline/run.md index 89243c6bf3..4d4e8e30ad 100644 --- a/docs/reference/commandline/run.md +++ b/docs/reference/commandline/run.md @@ -28,6 +28,7 @@ parent = "smn_cli" --cpuset-cpus="" CPUs in which to allow execution (0-3, 0,1) --cpuset-mems="" Memory nodes (MEMs) in which to allow execution (0-3, 0,1) -d, --detach Run container in background and print container ID + --detach-keys Specify the escape key sequence used to detach a container --device=[] Add a host device to the container --device-read-bps=[] Limit read rate (bytes per second) from a device (e.g., --device-read-bps=/dev/sda:1mb) --device-read-iops=[] Limit read rate (IO per second) from a device (e.g., --device-read-iops=/dev/sda:1000) diff --git a/docs/reference/commandline/start.md b/docs/reference/commandline/start.md index 6e1b1d67ba..156a2aae08 100644 --- a/docs/reference/commandline/start.md +++ b/docs/reference/commandline/start.md @@ -15,5 +15,6 @@ parent = "smn_cli" Start one or more containers -a, --attach Attach STDOUT/STDERR and forward signals + --detach-keys Specify the escape key sequence used to detach a container --help Print usage -i, --interactive Attach container's STDIN diff --git a/integration-cli/docker_cli_run_unix_test.go b/integration-cli/docker_cli_run_unix_test.go index 053da76f80..0075e89b0c 100644 --- a/integration-cli/docker_cli_run_unix_test.go +++ b/integration-cli/docker_cli_run_unix_test.go @@ -14,6 +14,7 @@ import ( "strings" "time" + "github.com/docker/docker/pkg/homedir" "github.com/docker/docker/pkg/integration/checker" "github.com/docker/docker/pkg/mount" "github.com/docker/docker/pkg/parsers" @@ -87,10 +88,13 @@ func (s *DockerSuite) TestRunDeviceDirectory(c *check.C) { c.Assert(strings.Trim(out, "\r\n"), checker.Contains, "seq", check.Commentf("expected output /dev/othersnd/seq")) } -// TestRunDetach checks attaching and detaching with the escape sequence. +// TestRunDetach checks attaching and detaching with the default escape sequence. func (s *DockerSuite) TestRunAttachDetach(c *check.C) { name := "attach-detach" - cmd := exec.Command(dockerBinary, "run", "--name", name, "-it", "busybox", "cat") + + dockerCmd(c, "run", "--name", name, "-itd", "busybox", "cat") + + cmd := exec.Command(dockerBinary, "attach", name) stdout, err := cmd.StdoutPipe() c.Assert(err, checker.IsNil) cpty, tty, err := pty.Open() @@ -120,21 +124,248 @@ func (s *DockerSuite) TestRunAttachDetach(c *check.C) { ch <- struct{}{} }() + select { + case <-ch: + case <-time.After(10 * time.Second): + c.Fatal("timed out waiting for container to exit") + } + running, err := inspectField(name, "State.Running") c.Assert(err, checker.IsNil) c.Assert(running, checker.Equals, "true", check.Commentf("expected container to still be running")) +} +// TestRunDetach checks attaching and detaching with the escape sequence specified via flags. +func (s *DockerSuite) TestRunAttachDetachFromFlag(c *check.C) { + name := "attach-detach" + keyCtrlA := []byte{1} + keyA := []byte{97} + + dockerCmd(c, "run", "--name", name, "-itd", "busybox", "cat") + + cmd := exec.Command(dockerBinary, "attach", "--detach-keys='ctrl-a,a'", name) + stdout, err := cmd.StdoutPipe() + if err != nil { + c.Fatal(err) + } + cpty, tty, err := pty.Open() + if err != nil { + c.Fatal(err) + } + defer cpty.Close() + cmd.Stdin = tty + if err := cmd.Start(); err != nil { + c.Fatal(err) + } + c.Assert(waitRun(name), check.IsNil) + + if _, err := cpty.Write([]byte("hello\n")); err != nil { + c.Fatal(err) + } + + out, err := bufio.NewReader(stdout).ReadString('\n') + if err != nil { + c.Fatal(err) + } + if strings.TrimSpace(out) != "hello" { + c.Fatalf("expected 'hello', got %q", out) + } + + // escape sequence + if _, err := cpty.Write(keyCtrlA); err != nil { + c.Fatal(err) + } + time.Sleep(100 * time.Millisecond) + if _, err := cpty.Write(keyA); err != nil { + c.Fatal(err) + } + + ch := make(chan struct{}) go func() { - exec.Command(dockerBinary, "kill", name).Run() + cmd.Wait() + ch <- struct{}{} }() select { case <-ch: - case <-time.After(10 * time.Millisecond): + case <-time.After(10 * time.Second): c.Fatal("timed out waiting for container to exit") } + + running, err := inspectField(name, "State.Running") + c.Assert(err, checker.IsNil) + c.Assert(running, checker.Equals, "true", check.Commentf("expected container to still be running")) } +// TestRunDetach checks attaching and detaching with the escape sequence specified via config file. +func (s *DockerSuite) TestRunAttachDetachFromConfig(c *check.C) { + keyCtrlA := []byte{1} + keyA := []byte{97} + + // Setup config + homeKey := homedir.Key() + homeVal := homedir.Get() + tmpDir, err := ioutil.TempDir("", "fake-home") + c.Assert(err, checker.IsNil) + defer os.RemoveAll(tmpDir) + + dotDocker := filepath.Join(tmpDir, ".docker") + os.Mkdir(dotDocker, 0600) + tmpCfg := filepath.Join(dotDocker, "config.json") + + defer func() { os.Setenv(homeKey, homeVal) }() + os.Setenv(homeKey, tmpDir) + + data := `{ + "detachKeys": "ctrl-a,a" + }` + + err = ioutil.WriteFile(tmpCfg, []byte(data), 0600) + c.Assert(err, checker.IsNil) + + // Then do the work + name := "attach-detach" + dockerCmd(c, "run", "--name", name, "-itd", "busybox", "cat") + + cmd := exec.Command(dockerBinary, "attach", name) + stdout, err := cmd.StdoutPipe() + if err != nil { + c.Fatal(err) + } + cpty, tty, err := pty.Open() + if err != nil { + c.Fatal(err) + } + defer cpty.Close() + cmd.Stdin = tty + if err := cmd.Start(); err != nil { + c.Fatal(err) + } + c.Assert(waitRun(name), check.IsNil) + + if _, err := cpty.Write([]byte("hello\n")); err != nil { + c.Fatal(err) + } + + out, err := bufio.NewReader(stdout).ReadString('\n') + if err != nil { + c.Fatal(err) + } + if strings.TrimSpace(out) != "hello" { + c.Fatalf("expected 'hello', got %q", out) + } + + // escape sequence + if _, err := cpty.Write(keyCtrlA); err != nil { + c.Fatal(err) + } + time.Sleep(100 * time.Millisecond) + if _, err := cpty.Write(keyA); err != nil { + c.Fatal(err) + } + + ch := make(chan struct{}) + go func() { + cmd.Wait() + ch <- struct{}{} + }() + + select { + case <-ch: + case <-time.After(10 * time.Second): + c.Fatal("timed out waiting for container to exit") + } + + running, err := inspectField(name, "State.Running") + c.Assert(err, checker.IsNil) + c.Assert(running, checker.Equals, "true", check.Commentf("expected container to still be running")) +} + +// TestRunDetach checks attaching and detaching with the detach flags, making sure it overrides config file +func (s *DockerSuite) TestRunAttachDetachKeysOverrideConfig(c *check.C) { + keyCtrlA := []byte{1} + keyA := []byte{97} + + // Setup config + homeKey := homedir.Key() + homeVal := homedir.Get() + tmpDir, err := ioutil.TempDir("", "fake-home") + c.Assert(err, checker.IsNil) + defer os.RemoveAll(tmpDir) + + dotDocker := filepath.Join(tmpDir, ".docker") + os.Mkdir(dotDocker, 0600) + tmpCfg := filepath.Join(dotDocker, "config.json") + + defer func() { os.Setenv(homeKey, homeVal) }() + os.Setenv(homeKey, tmpDir) + + data := `{ + "detachKeys": "ctrl-e,e" + }` + + err = ioutil.WriteFile(tmpCfg, []byte(data), 0600) + c.Assert(err, checker.IsNil) + + // Then do the work + name := "attach-detach" + dockerCmd(c, "run", "--name", name, "-itd", "busybox", "cat") + + cmd := exec.Command(dockerBinary, "attach", "--detach-keys='ctrl-a,a'", name) + stdout, err := cmd.StdoutPipe() + if err != nil { + c.Fatal(err) + } + cpty, tty, err := pty.Open() + if err != nil { + c.Fatal(err) + } + defer cpty.Close() + cmd.Stdin = tty + if err := cmd.Start(); err != nil { + c.Fatal(err) + } + c.Assert(waitRun(name), check.IsNil) + + if _, err := cpty.Write([]byte("hello\n")); err != nil { + c.Fatal(err) + } + + out, err := bufio.NewReader(stdout).ReadString('\n') + if err != nil { + c.Fatal(err) + } + if strings.TrimSpace(out) != "hello" { + c.Fatalf("expected 'hello', got %q", out) + } + + // escape sequence + if _, err := cpty.Write(keyCtrlA); err != nil { + c.Fatal(err) + } + time.Sleep(100 * time.Millisecond) + if _, err := cpty.Write(keyA); err != nil { + c.Fatal(err) + } + + ch := make(chan struct{}) + go func() { + cmd.Wait() + ch <- struct{}{} + }() + + select { + case <-ch: + case <-time.After(10 * time.Second): + c.Fatal("timed out waiting for container to exit") + } + + running, err := inspectField(name, "State.Running") + c.Assert(err, checker.IsNil) + c.Assert(running, checker.Equals, "true", check.Commentf("expected container to still be running")) +} + +// "test" should be printed func (s *DockerSuite) TestRunWithCPUQuota(c *check.C) { testRequires(c, cpuCfsQuota) diff --git a/man/docker-attach.1.md b/man/docker-attach.1.md index 260593201f..adecceb37f 100644 --- a/man/docker-attach.1.md +++ b/man/docker-attach.1.md @@ -6,6 +6,7 @@ docker-attach - Attach to a running container # SYNOPSIS **docker attach** +[**--detach-keys**[=*[]*]] [**--help**] [**--no-stdin**] [**--sig-proxy**[=*true*]] @@ -18,15 +19,19 @@ interactively. You can attach to the same contained process multiple times simultaneously, screen sharing style, or quickly view the progress of your detached process. -You can detach from the container (and leave it running) with `CTRL-p CTRL-q` -(for a quiet exit) or `CTRL-c` which will send a `SIGKILL` to the container. -When you are attached to a container, and exit its main process, the process's -exit code will be returned to the client. +To stop a container, use `CTRL-c`. This key sequence sends `SIGKILL` to the +container. You can detach from the container (and leave it running) using a +configurable key sequence. The default sequence is `CTRL-p CTRL-q`. You +configure the key sequence using the **--detach-keys** option or a configuration +file. See **config-json(5)** for documentation on using a configuration file. It is forbidden to redirect the standard input of a `docker attach` command while attaching to a tty-enabled container (i.e.: launched with `-t`). # OPTIONS +**--detach-keys**="" + Override the key sequence for detaching a container. Format is a single character `[a-Z]` or `ctrl-` where `` is one of: `a-z`, `@`, `^`, `[`, `,` or `_`. + **--help** Print usage statement @@ -36,6 +41,30 @@ attaching to a tty-enabled container (i.e.: launched with `-t`). **--sig-proxy**=*true*|*false* Proxy all received signals to the process (non-TTY mode only). SIGCHLD, SIGKILL, and SIGSTOP are not proxied. The default is *true*. +# Override the detach sequence + +If you want, you can configure a override the Docker key sequence for detach. +This is is useful if the Docker default sequence conflicts with key squence you +use for other applications. There are two ways to defines a your own detach key +sequence, as a per-container override or as a configuration property on your +entire configuration. + +To override the sequence for an individual container, use the +`--detach-keys=""` flag with the `docker attach` command. The format of +the `` is either a letter [a-Z], or the `ctrl-` combined with any of +the following: + +* `a-z` (a single lowercase alpha character ) +* `@` (ampersand) +* `[` (left bracket) +* `\\` (two backward slashes) +* `_` (underscore) +* `^` (caret) + +These `a`, `ctrl-a`, `X`, or `ctrl-\\` values are all examples of valid key +sequences. To configure a different configuration default key sequence for all +containers, see **docker(1)**. + # EXAMPLES ## Attaching to a container diff --git a/man/docker-exec.1.md b/man/docker-exec.1.md index 388fe81b2c..49f6dbc286 100644 --- a/man/docker-exec.1.md +++ b/man/docker-exec.1.md @@ -7,6 +7,7 @@ docker-exec - Run a command in a running container # SYNOPSIS **docker exec** [**-d**|**--detach**] +[**--detach-keys**[=*[]*]] [**--help**] [**-i**|**--interactive**] [**--privileged**] @@ -26,7 +27,10 @@ container is unpaused, and then run # OPTIONS **-d**, **--detach**=*true*|*false* - Detached mode: run command in the background. The default is *false*. + Override the key sequence for detaching a container. Format is a single character `[a-Z]` or `ctrl-` where `` is one of: `a-z`, `@`, `^`, `[`, `,` or `_`. + +**--detach-keys**="" + Define the key sequence which detaches the container. **--help** Print usage statement diff --git a/man/docker-run.1.md b/man/docker-run.1.md index e963d5277e..be8d427c8f 100644 --- a/man/docker-run.1.md +++ b/man/docker-run.1.md @@ -20,6 +20,7 @@ docker-run - Run a command in a new container [**--cpuset-cpus**[=*CPUSET-CPUS*]] [**--cpuset-mems**[=*CPUSET-MEMS*]] [**-d**|**--detach**] +[**--detach-keys**[=*[]*]] [**--device**[=*[]*]] [**--device-read-bps**[=*[]*]] [**--device-read-iops**[=*[]*]] @@ -190,8 +191,13 @@ the other shell to view a list of the running containers. You can reattach to a detached container with **docker attach**. If you choose to run a container in the detached mode, then you cannot use the **-rm** option. - When attached in the tty mode, you can detach from a running container without -stopping the process by pressing the keys CTRL-P CTRL-Q. + When attached in the tty mode, you can detach from the container (and leave it +running) using a configurable key sequence. The default sequence is `CTRL-p CTRL-q`. +You configure the key sequence using the **--detach-keys** option or a configuration file. +See **config-json(5)** for documentation on using a configuration file. + +**--detach-keys**="" + Override the key sequence for detaching a container. Format is a single character `[a-Z]` or `ctrl-` where `` is one of: `a-z`, `@`, `^`, `[`, `,` or `_`. **--device**=[] Add a host device to the container (e.g. --device=/dev/sdc:/dev/xvdc:rwm) diff --git a/man/docker-start.1.md b/man/docker-start.1.md index 678f687f86..c00b0a1668 100644 --- a/man/docker-start.1.md +++ b/man/docker-start.1.md @@ -7,6 +7,7 @@ docker-start - Start one or more containers # SYNOPSIS **docker start** [**-a**|**--attach**] +[**--detach-keys**[=*[]*]] [**--help**] [**-i**|**--interactive**] CONTAINER [CONTAINER...] @@ -17,7 +18,11 @@ Start one or more containers. # OPTIONS **-a**, **--attach**=*true*|*false* - Attach container's STDOUT and STDERR and forward all signals to the process. The default is *false*. + Attach container's STDOUT and STDERR and forward all signals to the + process. The default is *false*. + +**--detach-keys**="" + Override the key sequence for detaching a container. Format is a single character `[a-Z]` or `ctrl-` where `` is one of: `a-z`, `@`, `^`, `[`, `,` or `_`. **--help** Print usage statement diff --git a/man/docker.1.md b/man/docker.1.md index fc7cd58a65..83e57affa2 100644 --- a/man/docker.1.md +++ b/man/docker.1.md @@ -223,6 +223,7 @@ inside it) Block until a container stops, then print its exit code See **docker-wait(1)** for full documentation on the **wait** command. + # EXEC DRIVER OPTIONS Use the **--exec-opt** flags to specify options to the execution driver. The only diff --git a/pkg/term/ascii.go b/pkg/term/ascii.go new file mode 100644 index 0000000000..f5262bccf5 --- /dev/null +++ b/pkg/term/ascii.go @@ -0,0 +1,66 @@ +package term + +import ( + "fmt" + "strings" +) + +// ASCII list the possible supported ASCII key sequence +var ASCII = []string{ + "ctrl-@", + "ctrl-a", + "ctrl-b", + "ctrl-c", + "ctrl-d", + "ctrl-e", + "ctrl-f", + "ctrl-g", + "ctrl-h", + "ctrl-i", + "ctrl-j", + "ctrl-k", + "ctrl-l", + "ctrl-m", + "ctrl-n", + "ctrl-o", + "ctrl-p", + "ctrl-q", + "ctrl-r", + "ctrl-s", + "ctrl-t", + "ctrl-u", + "ctrl-v", + "ctrl-w", + "ctrl-x", + "ctrl-y", + "ctrl-z", + "ctrl-[", + "ctrl-\\", + "ctrl-]", + "ctrl-^", + "ctrl-_", +} + +// ToBytes converts a string representing a suite of key-sequence to the corresponding ASCII code. +func ToBytes(keys string) ([]byte, error) { + codes := []byte{} +next: + for _, key := range strings.Split(keys, ",") { + if len(key) != 1 { + for code, ctrl := range ASCII { + if ctrl == key { + codes = append(codes, byte(code)) + continue next + } + } + if key == "DEL" { + codes = append(codes, 127) + } else { + return nil, fmt.Errorf("Unknown character: '%s'", key) + } + } else { + codes = append(codes, byte(key[0])) + } + } + return codes, nil +} diff --git a/pkg/term/ascii_test.go b/pkg/term/ascii_test.go new file mode 100644 index 0000000000..4a1e7f302c --- /dev/null +++ b/pkg/term/ascii_test.go @@ -0,0 +1,43 @@ +package term + +import "testing" + +func TestToBytes(t *testing.T) { + codes, err := ToBytes("ctrl-a,a") + if err != nil { + t.Fatal(err) + } + if len(codes) != 2 { + t.Fatalf("Expected 2 codes, got %d", len(codes)) + } + if codes[0] != 1 || codes[1] != 97 { + t.Fatalf("Expected '1' '97', got '%d' '%d'", codes[0], codes[1]) + } + + codes, err = ToBytes("shift-z") + if err == nil { + t.Fatalf("Expected error, got none") + } + + codes, err = ToBytes("ctrl-@,ctrl-[,~,ctrl-o") + if err != nil { + t.Fatal(err) + } + if len(codes) != 4 { + t.Fatalf("Expected 4 codes, got %d", len(codes)) + } + if codes[0] != 0 || codes[1] != 27 || codes[2] != 126 || codes[3] != 15 { + t.Fatalf("Expected '0' '27' '126', '15', got '%d' '%d' '%d' '%d'", codes[0], codes[1], codes[2], codes[3]) + } + + codes, err = ToBytes("DEL,+") + if err != nil { + t.Fatal(err) + } + if len(codes) != 2 { + t.Fatalf("Expected 2 codes, got %d", len(codes)) + } + if codes[0] != 127 || codes[1] != 43 { + t.Fatalf("Expected '127 '43'', got '%d' '%d'", codes[0], codes[1]) + } +}