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 <vincent@sbr.pm>
This commit is contained in:
Vincent Demeester 2016-01-03 23:03:39 +01:00
parent eb551baf6f
commit 15aa2a663b
27 changed files with 583 additions and 61 deletions

View File

@ -18,6 +18,7 @@ func (cli *DockerCli) CmdAttach(args ...string) error {
cmd := Cli.Subcmd("attach", []string{"CONTAINER"}, Cli.DockerCommands["attach"].Description, true) cmd := Cli.Subcmd("attach", []string{"CONTAINER"}, Cli.DockerCommands["attach"].Description, true)
noStdin := cmd.Bool([]string{"-no-stdin"}, false, "Do not attach STDIN") 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") 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.Require(flag.Exact, 1)
@ -46,12 +47,17 @@ func (cli *DockerCli) CmdAttach(args ...string) error {
} }
} }
if *detachKeys != "" {
cli.configFile.DetachKeys = *detachKeys
}
options := types.ContainerAttachOptions{ options := types.ContainerAttachOptions{
ContainerID: cmd.Arg(0), ContainerID: cmd.Arg(0),
Stream: true, Stream: true,
Stdin: !*noStdin && c.Config.OpenStdin, Stdin: !*noStdin && c.Config.OpenStdin,
Stdout: true, Stdout: true,
Stderr: true, Stderr: true,
DetachKeys: cli.configFile.DetachKeys,
} }
var in io.ReadCloser var in io.ReadCloser

View File

@ -16,6 +16,7 @@ import (
// Usage: docker exec [OPTIONS] CONTAINER COMMAND [ARG...] // Usage: docker exec [OPTIONS] CONTAINER COMMAND [ARG...]
func (cli *DockerCli) CmdExec(args ...string) error { func (cli *DockerCli) CmdExec(args ...string) error {
cmd := Cli.Subcmd("exec", []string{"CONTAINER COMMAND [ARG...]"}, Cli.DockerCommands["exec"].Description, true) 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) execConfig, err := runconfig.ParseExec(cmd, args)
// just in case the ParseExec does not exit // just in case the ParseExec does not exit
@ -23,6 +24,13 @@ func (cli *DockerCli) CmdExec(args ...string) error {
return Cli.StatusError{StatusCode: 1} 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) response, err := cli.client.ContainerExecCreate(*execConfig)
if err != nil { if err != nil {
return err return err

View File

@ -24,6 +24,9 @@ func (cli *Client) ContainerAttach(options types.ContainerAttachOptions) (types.
if options.Stderr { if options.Stderr {
query.Set("stderr", "1") query.Set("stderr", "1")
} }
if options.DetachKeys != "" {
query.Set("detachKeys", options.DetachKeys)
}
headers := map[string][]string{"Content-Type": {"text/plain"}} headers := map[string][]string{"Content-Type": {"text/plain"}}
return cli.postHijacked("/containers/"+options.ContainerID+"/attach", query, nil, headers) return cli.postHijacked("/containers/"+options.ContainerID+"/attach", query, nil, headers)

View File

@ -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") 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") flSigProxy = cmd.Bool([]string{"-sig-proxy"}, true, "Proxy received signals to the process")
flName = cmd.String([]string{"-name"}, "", "Assign a name to the container") 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 flAttach *opts.ListOpts
ErrConflictAttachDetach = fmt.Errorf("Conflicting options: -a and -d") 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{ options := types.ContainerAttachOptions{
ContainerID: createResponse.ID, ContainerID: createResponse.ID,
Stream: true, Stream: true,
Stdin: config.AttachStdin, Stdin: config.AttachStdin,
Stdout: config.AttachStdout, Stdout: config.AttachStdout,
Stderr: config.AttachStderr, Stderr: config.AttachStderr,
DetachKeys: cli.configFile.DetachKeys,
} }
resp, err := cli.client.ContainerAttach(options) resp, err := cli.client.ContainerAttach(options)

View File

@ -49,6 +49,7 @@ func (cli *DockerCli) CmdStart(args ...string) error {
cmd := Cli.Subcmd("start", []string{"CONTAINER [CONTAINER...]"}, Cli.DockerCommands["start"].Description, true) 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") attach := cmd.Bool([]string{"a", "-attach"}, false, "Attach STDOUT/STDERR and forward signals")
openStdin := cmd.Bool([]string{"i", "-interactive"}, false, "Attach container's STDIN") 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.Require(flag.Min, 1)
cmd.ParseFlags(args, true) cmd.ParseFlags(args, true)
@ -72,12 +73,17 @@ func (cli *DockerCli) CmdStart(args ...string) error {
defer signal.StopCatch(sigc) defer signal.StopCatch(sigc)
} }
if *detachKeys != "" {
cli.configFile.DetachKeys = *detachKeys
}
options := types.ContainerAttachOptions{ options := types.ContainerAttachOptions{
ContainerID: containerID, ContainerID: containerID,
Stream: true, Stream: true,
Stdin: *openStdin && c.Config.OpenStdin, Stdin: *openStdin && c.Config.OpenStdin,
Stdout: true, Stdout: true,
Stderr: true, Stderr: true,
DetachKeys: cli.configFile.DetachKeys,
} }
var in io.ReadCloser var in io.ReadCloser

View File

@ -19,6 +19,7 @@ import (
derr "github.com/docker/docker/errors" derr "github.com/docker/docker/errors"
"github.com/docker/docker/pkg/ioutils" "github.com/docker/docker/pkg/ioutils"
"github.com/docker/docker/pkg/signal" "github.com/docker/docker/pkg/signal"
"github.com/docker/docker/pkg/term"
"github.com/docker/docker/runconfig" "github.com/docker/docker/runconfig"
"github.com/docker/docker/utils" "github.com/docker/docker/utils"
"golang.org/x/net/context" "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 { 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 return err
} }
containerName := vars["name"] containerName := vars["name"]
_, upgrade := r.Header["Upgrade"] _, 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{ attachWithLogsConfig := &daemon.ContainerAttachWithLogsConfig{
Hijacker: w.(http.Hijacker), Hijacker: w.(http.Hijacker),
Upgrade: upgrade, Upgrade: upgrade,
UseStdin: httputils.BoolValue(r, "stdin"), UseStdin: httputils.BoolValue(r, "stdin"),
UseStdout: httputils.BoolValue(r, "stdout"), UseStdout: httputils.BoolValue(r, "stdout"),
UseStderr: httputils.BoolValue(r, "stderr"), UseStderr: httputils.BoolValue(r, "stderr"),
Logs: httputils.BoolValue(r, "logs"), Logs: httputils.BoolValue(r, "logs"),
Stream: httputils.BoolValue(r, "stream"), Stream: httputils.BoolValue(r, "stream"),
DetachKeys: keys,
} }
return s.backend.ContainerAttachWithLogs(containerName, attachWithLogsConfig) 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) 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) { h := websocket.Handler(func(ws *websocket.Conn) {
defer ws.Close() defer ws.Close()
wsAttachWithLogsConfig := &daemon.ContainerWsAttachWithLogsConfig{ wsAttachWithLogsConfig := &daemon.ContainerWsAttachWithLogsConfig{
InStream: ws, InStream: ws,
OutStream: ws, OutStream: ws,
ErrStream: ws, ErrStream: ws,
Logs: httputils.BoolValue(r, "logs"), Logs: httputils.BoolValue(r, "logs"),
Stream: httputils.BoolValue(r, "stream"), Stream: httputils.BoolValue(r, "stream"),
DetachKeys: keys,
} }
if err := s.backend.ContainerWsAttachWithLogs(containerName, wsAttachWithLogsConfig); err != nil { if err := s.backend.ContainerWsAttachWithLogs(containerName, wsAttachWithLogsConfig); err != nil {

View File

@ -17,6 +17,7 @@ type ContainerAttachOptions struct {
Stdin bool Stdin bool
Stdout bool Stdout bool
Stderr bool Stderr bool
DetachKeys string
} }
// ContainerCommitOptions holds parameters to commit changes into a container. // ContainerCommitOptions holds parameters to commit changes into a container.

View File

@ -45,5 +45,6 @@ type ExecConfig struct {
AttachStderr bool // Attach the standard output AttachStderr bool // Attach the standard output
AttachStdout bool // Attach the standard error AttachStdout bool // Attach the standard error
Detach bool // Execute in detach mode Detach bool // Execute in detach mode
DetachKeys string // Escape keys for detach
Cmd []string // Execution commands and args Cmd []string // Execution commands and args
} }

View File

@ -51,6 +51,7 @@ type ConfigFile struct {
HTTPHeaders map[string]string `json:"HttpHeaders,omitempty"` HTTPHeaders map[string]string `json:"HttpHeaders,omitempty"`
PsFormat string `json:"psFormat,omitempty"` PsFormat string `json:"psFormat,omitempty"`
ImagesFormat string `json:"imagesFormat,omitempty"` ImagesFormat string `json:"imagesFormat,omitempty"`
DetachKeys string `json:"detachKeys,omitempty"`
filename string // Note: not serialized - for internal use only filename string // Note: not serialized - for internal use only
} }

View File

@ -329,13 +329,13 @@ func (container *Container) GetExecIDs() []string {
// Attach connects to the container's TTY, delegating to standard // Attach connects to the container's TTY, delegating to standard
// streams or websockets depending on the configuration. // streams or websockets depending on the configuration.
func (container *Container) Attach(stdin io.ReadCloser, stdout io.Writer, stderr io.Writer) chan error { 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) return AttachStreams(container.StreamConfig, container.Config.OpenStdin, container.Config.StdinOnce, container.Config.Tty, stdin, stdout, stderr, keys)
} }
// AttachStreams connects streams to a TTY. // AttachStreams connects streams to a TTY.
// Used by exec too. Should this move somewhere else? // 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 ( var (
cStdout, cStderr io.ReadCloser cStdout, cStderr io.ReadCloser
cStdin io.WriteCloser cStdin io.WriteCloser
@ -382,7 +382,7 @@ func AttachStreams(streamConfig *runconfig.StreamConfig, openStdin, stdinOnce, t
var err error var err error
if tty { if tty {
_, err = copyEscapable(cStdin, stdin) _, err = copyEscapable(cStdin, stdin, keys)
} else { } else {
_, err = io.Copy(cStdin, stdin) _, 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 // 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) buf := make([]byte, 32*1024)
for { for {
nr, er := src.Read(buf) nr, er := src.Read(buf)
if nr > 0 { if nr > 0 {
// ---- Docker addition // ---- Docker addition
// char 16 is C-p for i, key := range keys {
if nr == 1 && buf[0] == 16 { if nr != 1 || buf[0] != key {
nr, er = src.Read(buf) break
// char 17 is C-q }
if nr == 1 && buf[0] == 17 { if i == len(keys)-1 {
if err := src.Close(); err != nil { if err := src.Close(); err != nil {
return 0, err return 0, err
} }
return 0, nil return 0, nil
} }
nr, er = src.Read(buf)
} }
// ---- End of docker // ---- End of docker
nw, ew := dst.Write(buf[0:nr]) nw, ew := dst.Write(buf[0:nr])

View File

@ -15,13 +15,14 @@ import (
// ContainerAttachWithLogsConfig holds the streams to use when connecting to a container to view logs. // ContainerAttachWithLogsConfig holds the streams to use when connecting to a container to view logs.
type ContainerAttachWithLogsConfig struct { type ContainerAttachWithLogsConfig struct {
Hijacker http.Hijacker Hijacker http.Hijacker
Upgrade bool Upgrade bool
UseStdin bool UseStdin bool
UseStdout bool UseStdout bool
UseStderr bool UseStderr bool
Logs bool Logs bool
Stream bool Stream bool
DetachKeys []byte
} }
// ContainerAttachWithLogs attaches to logs according to the config passed in. See ContainerAttachWithLogsConfig. // 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 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) fmt.Fprintf(outStream, "Error attaching: %s\n", err)
} }
return nil return nil
@ -87,6 +88,7 @@ type ContainerWsAttachWithLogsConfig struct {
InStream io.ReadCloser InStream io.ReadCloser
OutStream, ErrStream io.Writer OutStream, ErrStream io.Writer
Logs, Stream bool Logs, Stream bool
DetachKeys []byte
} }
// ContainerWsAttachWithLogs websocket connection // ContainerWsAttachWithLogs websocket connection
@ -95,10 +97,10 @@ func (daemon *Daemon) ContainerWsAttachWithLogs(prefixOrName string, c *Containe
if err != nil { if err != nil {
return err 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 { if logs {
logDriver, err := daemon.getLogger(container) logDriver, err := daemon.getLogger(container)
if err != nil { if err != nil {
@ -144,7 +146,7 @@ func (daemon *Daemon) attachWithLogs(container *container.Container, stdin io.Re
}() }()
stdinPipe = r 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 // If we are in stdinonce mode, wait for the process to end
// otherwise, simply return // otherwise, simply return
if container.Config.StdinOnce && !container.Config.Tty { if container.Config.StdinOnce && !container.Config.Tty {

View File

@ -14,6 +14,7 @@ import (
derr "github.com/docker/docker/errors" derr "github.com/docker/docker/errors"
"github.com/docker/docker/pkg/pools" "github.com/docker/docker/pkg/pools"
"github.com/docker/docker/pkg/promise" "github.com/docker/docker/pkg/promise"
"github.com/docker/docker/pkg/term"
) )
func (d *Daemon) registerExecCommand(container *container.Container, config *exec.Config) { 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...) cmd := strslice.New(config.Cmd...)
entrypoint, args := d.getEntrypointAndArgs(strslice.New(), 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{ processConfig := &execdriver.ProcessConfig{
CommonProcessConfig: execdriver.CommonProcessConfig{ CommonProcessConfig: execdriver.CommonProcessConfig{
Tty: config.Tty, Tty: config.Tty,
@ -103,6 +112,7 @@ func (d *Daemon) ContainerExecCreate(config *types.ExecConfig) (string, error) {
execConfig.OpenStderr = config.AttachStderr execConfig.OpenStderr = config.AttachStderr
execConfig.ProcessConfig = processConfig execConfig.ProcessConfig = processConfig
execConfig.ContainerID = container.ID execConfig.ContainerID = container.ID
execConfig.DetachKeys = keys
d.registerExecCommand(container, execConfig) d.registerExecCommand(container, execConfig)
@ -158,7 +168,8 @@ func (d *Daemon) ContainerExecStart(name string, stdin io.ReadCloser, stdout io.
ec.NewNopInputPipe() 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) execErr := make(chan error)
// Note, the ExecConfig data will be removed when the container // Note, the ExecConfig data will be removed when the container

View File

@ -25,6 +25,7 @@ type Config struct {
OpenStdout bool OpenStdout bool
CanRemove bool CanRemove bool
ContainerID string ContainerID string
DetachKeys []byte
// waitStart will be closed immediately after the exec is really started. // waitStart will be closed immediately after the exec is really started.
waitStart chan struct{} waitStart chan struct{}

View File

@ -862,10 +862,9 @@ This endpoint returns a live stream of a container's resource usage statistics.
"total_usage" : 36488948, "total_usage" : 36488948,
"usage_in_kernelmode" : 20000000 "usage_in_kernelmode" : 20000000
}, },
"system_cpu_usage" : 20091722000000000,  "system_cpu_usage" : 20091722000000000,
"throttling_data" : {} "throttling_data" : {}
} } }
}
Query Parameters: Query Parameters:
@ -922,6 +921,12 @@ Start the container `id`
HTTP/1.1 204 No Content 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-<value>`
where `<value>` is one of: `a-z`, `@`, `^`, `[`, `,` or `_`.
Status Codes: Status Codes:
- **204** no error - **204** no error
@ -1133,6 +1138,9 @@ Attach to the container `id`
Query Parameters: Query Parameters:
- **detacheys** Override the key sequence for detaching a
container. Format is a single character `[a-Z]` or `ctrl-<value>`
where `<value>` is one of: `a-z`, `@`, `^`, `[`, `,` or `_`.
- **logs** 1/True/true or 0/False/false, return logs. Default `false`. - **logs** 1/True/true or 0/False/false, return logs. Default `false`.
- **stream** 1/True/true or 0/False/false, return stream. - **stream** 1/True/true or 0/False/false, return stream.
Default `false`. Default `false`.
@ -1213,6 +1221,9 @@ Implements websocket protocol handshake according to [RFC 6455](http://tools.iet
Query Parameters: Query Parameters:
- **detacheys** Override the key sequence for detaching a
container. Format is a single character `[a-Z]` or `ctrl-<value>`
where `<value>` is one of: `a-z`, `@`, `^`, `[`, `,` or `_`.
- **logs** 1/True/true or 0/False/false, return logs. Default `false`. - **logs** 1/True/true or 0/False/false, return logs. Default `false`.
- **stream** 1/True/true or 0/False/false, return stream. - **stream** 1/True/true or 0/False/false, return stream.
Default `false`. Default `false`.
@ -2420,6 +2431,7 @@ Sets up an exec instance in a running container `id`
"AttachStdin": false, "AttachStdin": false,
"AttachStdout": true, "AttachStdout": true,
"AttachStderr": true, "AttachStderr": true,
"DetachKeys": "ctrl-p,ctrl-q",
"Tty": false, "Tty": false,
"Cmd": [ "Cmd": [
"date" "date"
@ -2441,6 +2453,9 @@ Json Parameters:
- **AttachStdin** - Boolean value, attaches to `stdin` of the `exec` command. - **AttachStdin** - Boolean value, attaches to `stdin` of the `exec` command.
- **AttachStdout** - Boolean value, attaches to `stdout` of the `exec` command. - **AttachStdout** - Boolean value, attaches to `stdout` of the `exec` command.
- **AttachStderr** - Boolean value, attaches to `stderr` 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-<value>`
where `<value>` is one of: `a-z`, `@`, `^`, `[`, `,` or `_`.
- **Tty** - Boolean value to allocate a pseudo-TTY. - **Tty** - Boolean value to allocate a pseudo-TTY.
- **Cmd** - Command to run specified as a string or an array of strings. - **Cmd** - Command to run specified as a string or an array of strings.

View File

@ -14,9 +14,10 @@ parent = "smn_cli"
Attach to a running container Attach to a running container
--help Print usage --detach-keys="<sequence>" Set up escape key sequence
--no-stdin Do not attach STDIN --help Print usage
--sig-proxy=true Proxy all received signals to the process --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 `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 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 simultaneously, screen sharing style, or quickly view the progress of your
detached process. detached process.
You can detach from the container and leave it running with `CTRL-p CTRL-q` To stop a container, use `CTRL-c`. This key sequence sends `SIGKILL` to the
(for a quiet exit) or with `CTRL-c` if `--sig-proxy` is false. 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
If `--sig-proxy` is true (the default),`CTRL-c` sends a `SIGINT` to the using `CTRL-p CTRL-q` key sequence.
container.
> **Note:** > **Note:**
> A process running as PID 1 inside a container is treated specially by > 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 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`). 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="<sequence>"` flag with the `docker attach` command. The format of
the `<sequence>` 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 #### Examples
$ docker run -d --name topdemo ubuntu /usr/bin/top -b $ docker run -d --name topdemo ubuntu /usr/bin/top -b

View File

@ -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, 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 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 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 `<sequence>` value for the
property. The format of the `<sequence>` 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. The property `imagesFormat` specifies the default format for `docker images` output.
When the `--format` flag is not provided with the `docker images` command, When the `--format` flag is not provided with the `docker images` command,
@ -115,8 +134,9 @@ Following is a sample `config.json` file:
"HttpHeaders": { "HttpHeaders": {
"MyHeader": "MyValue" "MyHeader": "MyValue"
}, },
"psFormat": "table {{.ID}}\\t{{.Image}}\\t{{.Command}}\\t{{.Labels}}" "psFormat": "table {{.ID}}\\t{{.Image}}\\t{{.Command}}\\t{{.Labels}}",
"imagesFormat": "table {{.ID}}\\t{{.Repository}}\\t{{.Tag}}\\t{{.CreatedAt}}" "imagesFormat": "table {{.ID}}\\t{{.Repository}}\\t{{.Tag}}\\t{{.CreatedAt}}",
"detachKeys": "ctrl-e,e"
} }
### Notary ### Notary

View File

@ -15,6 +15,7 @@ parent = "smn_cli"
Run a command in a running container Run a command in a running container
-d, --detach Detached mode: run command in the background -d, --detach Detached mode: run command in the background
--detach-keys Specify the escape key sequence used to detach a container
--help Print usage --help Print usage
-i, --interactive Keep STDIN open even if not attached -i, --interactive Keep STDIN open even if not attached
--privileged Give extended Linux capabilities to the command --privileged Give extended Linux capabilities to the command

View File

@ -28,6 +28,7 @@ parent = "smn_cli"
--cpuset-cpus="" CPUs in which to allow execution (0-3, 0,1) --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) --cpuset-mems="" Memory nodes (MEMs) in which to allow execution (0-3, 0,1)
-d, --detach Run container in background and print container ID -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=[] 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-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) --device-read-iops=[] Limit read rate (IO per second) from a device (e.g., --device-read-iops=/dev/sda:1000)

View File

@ -15,5 +15,6 @@ parent = "smn_cli"
Start one or more containers Start one or more containers
-a, --attach Attach STDOUT/STDERR and forward signals -a, --attach Attach STDOUT/STDERR and forward signals
--detach-keys Specify the escape key sequence used to detach a container
--help Print usage --help Print usage
-i, --interactive Attach container's STDIN -i, --interactive Attach container's STDIN

View File

@ -14,6 +14,7 @@ import (
"strings" "strings"
"time" "time"
"github.com/docker/docker/pkg/homedir"
"github.com/docker/docker/pkg/integration/checker" "github.com/docker/docker/pkg/integration/checker"
"github.com/docker/docker/pkg/mount" "github.com/docker/docker/pkg/mount"
"github.com/docker/docker/pkg/parsers" "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")) 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) { func (s *DockerSuite) TestRunAttachDetach(c *check.C) {
name := "attach-detach" 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() stdout, err := cmd.StdoutPipe()
c.Assert(err, checker.IsNil) c.Assert(err, checker.IsNil)
cpty, tty, err := pty.Open() cpty, tty, err := pty.Open()
@ -120,21 +124,248 @@ func (s *DockerSuite) TestRunAttachDetach(c *check.C) {
ch <- struct{}{} 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") running, err := inspectField(name, "State.Running")
c.Assert(err, checker.IsNil) c.Assert(err, checker.IsNil)
c.Assert(running, checker.Equals, "true", check.Commentf("expected container to still be running")) 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() { go func() {
exec.Command(dockerBinary, "kill", name).Run() cmd.Wait()
ch <- struct{}{}
}() }()
select { select {
case <-ch: case <-ch:
case <-time.After(10 * time.Millisecond): case <-time.After(10 * time.Second):
c.Fatal("timed out waiting for container to exit") 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) { func (s *DockerSuite) TestRunWithCPUQuota(c *check.C) {
testRequires(c, cpuCfsQuota) testRequires(c, cpuCfsQuota)

View File

@ -6,6 +6,7 @@ docker-attach - Attach to a running container
# SYNOPSIS # SYNOPSIS
**docker attach** **docker attach**
[**--detach-keys**[=*[]*]]
[**--help**] [**--help**]
[**--no-stdin**] [**--no-stdin**]
[**--sig-proxy**[=*true*]] [**--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 simultaneously, screen sharing style, or quickly view the progress of your
detached process. detached process.
You can detach from the container (and leave it running) with `CTRL-p CTRL-q` To stop a container, use `CTRL-c`. This key sequence sends `SIGKILL` to the
(for a quiet exit) or `CTRL-c` which will send a `SIGKILL` to the container. container. You can detach from the container (and leave it running) using a
When you are attached to a container, and exit its main process, the process's configurable key sequence. The default sequence is `CTRL-p CTRL-q`. You
exit code will be returned to the client. 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 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`). attaching to a tty-enabled container (i.e.: launched with `-t`).
# OPTIONS # OPTIONS
**--detach-keys**=""
Override the key sequence for detaching a container. Format is a single character `[a-Z]` or `ctrl-<value>` where `<value>` is one of: `a-z`, `@`, `^`, `[`, `,` or `_`.
**--help** **--help**
Print usage statement Print usage statement
@ -36,6 +41,30 @@ attaching to a tty-enabled container (i.e.: launched with `-t`).
**--sig-proxy**=*true*|*false* **--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*. 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="<sequence>"` flag with the `docker attach` command. The format of
the `<sequence>` 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 # EXAMPLES
## Attaching to a container ## Attaching to a container

View File

@ -7,6 +7,7 @@ docker-exec - Run a command in a running container
# SYNOPSIS # SYNOPSIS
**docker exec** **docker exec**
[**-d**|**--detach**] [**-d**|**--detach**]
[**--detach-keys**[=*[]*]]
[**--help**] [**--help**]
[**-i**|**--interactive**] [**-i**|**--interactive**]
[**--privileged**] [**--privileged**]
@ -26,7 +27,10 @@ container is unpaused, and then run
# OPTIONS # OPTIONS
**-d**, **--detach**=*true*|*false* **-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-<value>` where `<value>` is one of: `a-z`, `@`, `^`, `[`, `,` or `_`.
**--detach-keys**=""
Define the key sequence which detaches the container.
**--help** **--help**
Print usage statement Print usage statement

View File

@ -20,6 +20,7 @@ docker-run - Run a command in a new container
[**--cpuset-cpus**[=*CPUSET-CPUS*]] [**--cpuset-cpus**[=*CPUSET-CPUS*]]
[**--cpuset-mems**[=*CPUSET-MEMS*]] [**--cpuset-mems**[=*CPUSET-MEMS*]]
[**-d**|**--detach**] [**-d**|**--detach**]
[**--detach-keys**[=*[]*]]
[**--device**[=*[]*]] [**--device**[=*[]*]]
[**--device-read-bps**[=*[]*]] [**--device-read-bps**[=*[]*]]
[**--device-read-iops**[=*[]*]] [**--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 detached container with **docker attach**. If you choose to run a container in
the detached mode, then you cannot use the **-rm** option. the detached mode, then you cannot use the **-rm** option.
When attached in the tty mode, you can detach from a running container without When attached in the tty mode, you can detach from the container (and leave it
stopping the process by pressing the keys CTRL-P CTRL-Q. 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-<value>` where `<value>` is one of: `a-z`, `@`, `^`, `[`, `,` or `_`.
**--device**=[] **--device**=[]
Add a host device to the container (e.g. --device=/dev/sdc:/dev/xvdc:rwm) Add a host device to the container (e.g. --device=/dev/sdc:/dev/xvdc:rwm)

View File

@ -7,6 +7,7 @@ docker-start - Start one or more containers
# SYNOPSIS # SYNOPSIS
**docker start** **docker start**
[**-a**|**--attach**] [**-a**|**--attach**]
[**--detach-keys**[=*[]*]]
[**--help**] [**--help**]
[**-i**|**--interactive**] [**-i**|**--interactive**]
CONTAINER [CONTAINER...] CONTAINER [CONTAINER...]
@ -17,7 +18,11 @@ Start one or more containers.
# OPTIONS # OPTIONS
**-a**, **--attach**=*true*|*false* **-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-<value>` where `<value>` is one of: `a-z`, `@`, `^`, `[`, `,` or `_`.
**--help** **--help**
Print usage statement Print usage statement

View File

@ -223,6 +223,7 @@ inside it)
Block until a container stops, then print its exit code Block until a container stops, then print its exit code
See **docker-wait(1)** for full documentation on the **wait** command. See **docker-wait(1)** for full documentation on the **wait** command.
# EXEC DRIVER OPTIONS # EXEC DRIVER OPTIONS
Use the **--exec-opt** flags to specify options to the execution driver. The only Use the **--exec-opt** flags to specify options to the execution driver. The only

66
pkg/term/ascii.go Normal file
View File

@ -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
}

43
pkg/term/ascii_test.go Normal file
View File

@ -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])
}
}