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>
| ... | ... |
@@ -18,6 +18,7 @@ func (cli *DockerCli) CmdAttach(args ...string) error {
|
| 18 | 18 |
cmd := Cli.Subcmd("attach", []string{"CONTAINER"}, Cli.DockerCommands["attach"].Description, true)
|
| 19 | 19 |
noStdin := cmd.Bool([]string{"-no-stdin"}, false, "Do not attach STDIN")
|
| 20 | 20 |
proxy := cmd.Bool([]string{"-sig-proxy"}, true, "Proxy all received signals to the process")
|
| 21 |
+ detachKeys := cmd.String([]string{"-detach-keys"}, "", "Override the key sequence for detaching a container")
|
|
| 21 | 22 |
|
| 22 | 23 |
cmd.Require(flag.Exact, 1) |
| 23 | 24 |
|
| ... | ... |
@@ -46,12 +47,17 @@ func (cli *DockerCli) CmdAttach(args ...string) error {
|
| 46 | 46 |
} |
| 47 | 47 |
} |
| 48 | 48 |
|
| 49 |
+ if *detachKeys != "" {
|
|
| 50 |
+ cli.configFile.DetachKeys = *detachKeys |
|
| 51 |
+ } |
|
| 52 |
+ |
|
| 49 | 53 |
options := types.ContainerAttachOptions{
|
| 50 | 54 |
ContainerID: cmd.Arg(0), |
| 51 | 55 |
Stream: true, |
| 52 | 56 |
Stdin: !*noStdin && c.Config.OpenStdin, |
| 53 | 57 |
Stdout: true, |
| 54 | 58 |
Stderr: true, |
| 59 |
+ DetachKeys: cli.configFile.DetachKeys, |
|
| 55 | 60 |
} |
| 56 | 61 |
|
| 57 | 62 |
var in io.ReadCloser |
| ... | ... |
@@ -16,6 +16,7 @@ import ( |
| 16 | 16 |
// Usage: docker exec [OPTIONS] CONTAINER COMMAND [ARG...] |
| 17 | 17 |
func (cli *DockerCli) CmdExec(args ...string) error {
|
| 18 | 18 |
cmd := Cli.Subcmd("exec", []string{"CONTAINER COMMAND [ARG...]"}, Cli.DockerCommands["exec"].Description, true)
|
| 19 |
+ detachKeys := cmd.String([]string{"-detach-keys"}, "", "Override the key sequence for detaching a container")
|
|
| 19 | 20 |
|
| 20 | 21 |
execConfig, err := runconfig.ParseExec(cmd, args) |
| 21 | 22 |
// just in case the ParseExec does not exit |
| ... | ... |
@@ -23,6 +24,13 @@ func (cli *DockerCli) CmdExec(args ...string) error {
|
| 23 | 23 |
return Cli.StatusError{StatusCode: 1}
|
| 24 | 24 |
} |
| 25 | 25 |
|
| 26 |
+ if *detachKeys != "" {
|
|
| 27 |
+ cli.configFile.DetachKeys = *detachKeys |
|
| 28 |
+ } |
|
| 29 |
+ |
|
| 30 |
+ // Send client escape keys |
|
| 31 |
+ execConfig.DetachKeys = cli.configFile.DetachKeys |
|
| 32 |
+ |
|
| 26 | 33 |
response, err := cli.client.ContainerExecCreate(*execConfig) |
| 27 | 34 |
if err != nil {
|
| 28 | 35 |
return err |
| ... | ... |
@@ -24,6 +24,9 @@ func (cli *Client) ContainerAttach(options types.ContainerAttachOptions) (types. |
| 24 | 24 |
if options.Stderr {
|
| 25 | 25 |
query.Set("stderr", "1")
|
| 26 | 26 |
} |
| 27 |
+ if options.DetachKeys != "" {
|
|
| 28 |
+ query.Set("detachKeys", options.DetachKeys)
|
|
| 29 |
+ } |
|
| 27 | 30 |
|
| 28 | 31 |
headers := map[string][]string{"Content-Type": {"text/plain"}}
|
| 29 | 32 |
return cli.postHijacked("/containers/"+options.ContainerID+"/attach", query, nil, headers)
|
| ... | ... |
@@ -74,6 +74,7 @@ func (cli *DockerCli) CmdRun(args ...string) error {
|
| 74 | 74 |
flDetach = cmd.Bool([]string{"d", "-detach"}, false, "Run container in background and print container ID")
|
| 75 | 75 |
flSigProxy = cmd.Bool([]string{"-sig-proxy"}, true, "Proxy received signals to the process")
|
| 76 | 76 |
flName = cmd.String([]string{"-name"}, "", "Assign a name to the container")
|
| 77 |
+ flDetachKeys = cmd.String([]string{"-detach-keys"}, "", "Override the key sequence for detaching a container")
|
|
| 77 | 78 |
flAttach *opts.ListOpts |
| 78 | 79 |
|
| 79 | 80 |
ErrConflictAttachDetach = fmt.Errorf("Conflicting options: -a and -d")
|
| ... | ... |
@@ -188,12 +189,17 @@ func (cli *DockerCli) CmdRun(args ...string) error {
|
| 188 | 188 |
} |
| 189 | 189 |
} |
| 190 | 190 |
|
| 191 |
+ if *flDetachKeys != "" {
|
|
| 192 |
+ cli.configFile.DetachKeys = *flDetachKeys |
|
| 193 |
+ } |
|
| 194 |
+ |
|
| 191 | 195 |
options := types.ContainerAttachOptions{
|
| 192 | 196 |
ContainerID: createResponse.ID, |
| 193 | 197 |
Stream: true, |
| 194 | 198 |
Stdin: config.AttachStdin, |
| 195 | 199 |
Stdout: config.AttachStdout, |
| 196 | 200 |
Stderr: config.AttachStderr, |
| 201 |
+ DetachKeys: cli.configFile.DetachKeys, |
|
| 197 | 202 |
} |
| 198 | 203 |
|
| 199 | 204 |
resp, err := cli.client.ContainerAttach(options) |
| ... | ... |
@@ -49,6 +49,7 @@ func (cli *DockerCli) CmdStart(args ...string) error {
|
| 49 | 49 |
cmd := Cli.Subcmd("start", []string{"CONTAINER [CONTAINER...]"}, Cli.DockerCommands["start"].Description, true)
|
| 50 | 50 |
attach := cmd.Bool([]string{"a", "-attach"}, false, "Attach STDOUT/STDERR and forward signals")
|
| 51 | 51 |
openStdin := cmd.Bool([]string{"i", "-interactive"}, false, "Attach container's STDIN")
|
| 52 |
+ detachKeys := cmd.String([]string{"-detach-keys"}, "", "Override the key sequence for detaching a container")
|
|
| 52 | 53 |
cmd.Require(flag.Min, 1) |
| 53 | 54 |
|
| 54 | 55 |
cmd.ParseFlags(args, true) |
| ... | ... |
@@ -72,12 +73,17 @@ func (cli *DockerCli) CmdStart(args ...string) error {
|
| 72 | 72 |
defer signal.StopCatch(sigc) |
| 73 | 73 |
} |
| 74 | 74 |
|
| 75 |
+ if *detachKeys != "" {
|
|
| 76 |
+ cli.configFile.DetachKeys = *detachKeys |
|
| 77 |
+ } |
|
| 78 |
+ |
|
| 75 | 79 |
options := types.ContainerAttachOptions{
|
| 76 | 80 |
ContainerID: containerID, |
| 77 | 81 |
Stream: true, |
| 78 | 82 |
Stdin: *openStdin && c.Config.OpenStdin, |
| 79 | 83 |
Stdout: true, |
| 80 | 84 |
Stderr: true, |
| 85 |
+ DetachKeys: cli.configFile.DetachKeys, |
|
| 81 | 86 |
} |
| 82 | 87 |
|
| 83 | 88 |
var in io.ReadCloser |
| ... | ... |
@@ -19,6 +19,7 @@ import ( |
| 19 | 19 |
derr "github.com/docker/docker/errors" |
| 20 | 20 |
"github.com/docker/docker/pkg/ioutils" |
| 21 | 21 |
"github.com/docker/docker/pkg/signal" |
| 22 |
+ "github.com/docker/docker/pkg/term" |
|
| 22 | 23 |
"github.com/docker/docker/runconfig" |
| 23 | 24 |
"github.com/docker/docker/utils" |
| 24 | 25 |
"golang.org/x/net/context" |
| ... | ... |
@@ -420,21 +421,32 @@ func (s *containerRouter) postContainersResize(ctx context.Context, w http.Respo |
| 420 | 420 |
} |
| 421 | 421 |
|
| 422 | 422 |
func (s *containerRouter) postContainersAttach(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
| 423 |
- if err := httputils.ParseForm(r); err != nil {
|
|
| 423 |
+ err := httputils.ParseForm(r) |
|
| 424 |
+ if err != nil {
|
|
| 424 | 425 |
return err |
| 425 | 426 |
} |
| 426 | 427 |
containerName := vars["name"] |
| 427 | 428 |
|
| 428 | 429 |
_, upgrade := r.Header["Upgrade"] |
| 429 | 430 |
|
| 431 |
+ keys := []byte{}
|
|
| 432 |
+ detachKeys := r.FormValue("detachKeys")
|
|
| 433 |
+ if detachKeys != "" {
|
|
| 434 |
+ keys, err = term.ToBytes(detachKeys) |
|
| 435 |
+ if err != nil {
|
|
| 436 |
+ logrus.Warnf("Invalid escape keys provided (%s) using default : ctrl-p ctrl-q", detachKeys)
|
|
| 437 |
+ } |
|
| 438 |
+ } |
|
| 439 |
+ |
|
| 430 | 440 |
attachWithLogsConfig := &daemon.ContainerAttachWithLogsConfig{
|
| 431 |
- Hijacker: w.(http.Hijacker), |
|
| 432 |
- Upgrade: upgrade, |
|
| 433 |
- UseStdin: httputils.BoolValue(r, "stdin"), |
|
| 434 |
- UseStdout: httputils.BoolValue(r, "stdout"), |
|
| 435 |
- UseStderr: httputils.BoolValue(r, "stderr"), |
|
| 436 |
- Logs: httputils.BoolValue(r, "logs"), |
|
| 437 |
- Stream: httputils.BoolValue(r, "stream"), |
|
| 441 |
+ Hijacker: w.(http.Hijacker), |
|
| 442 |
+ Upgrade: upgrade, |
|
| 443 |
+ UseStdin: httputils.BoolValue(r, "stdin"), |
|
| 444 |
+ UseStdout: httputils.BoolValue(r, "stdout"), |
|
| 445 |
+ UseStderr: httputils.BoolValue(r, "stderr"), |
|
| 446 |
+ Logs: httputils.BoolValue(r, "logs"), |
|
| 447 |
+ Stream: httputils.BoolValue(r, "stream"), |
|
| 448 |
+ DetachKeys: keys, |
|
| 438 | 449 |
} |
| 439 | 450 |
|
| 440 | 451 |
return s.backend.ContainerAttachWithLogs(containerName, attachWithLogsConfig) |
| ... | ... |
@@ -450,15 +462,26 @@ func (s *containerRouter) wsContainersAttach(ctx context.Context, w http.Respons |
| 450 | 450 |
return derr.ErrorCodeNoSuchContainer.WithArgs(containerName) |
| 451 | 451 |
} |
| 452 | 452 |
|
| 453 |
+ var keys []byte |
|
| 454 |
+ var err error |
|
| 455 |
+ detachKeys := r.FormValue("detachKeys")
|
|
| 456 |
+ if detachKeys != "" {
|
|
| 457 |
+ keys, err = term.ToBytes(detachKeys) |
|
| 458 |
+ if err != nil {
|
|
| 459 |
+ logrus.Warnf("Invalid escape keys provided (%s) using default : ctrl-p ctrl-q", detachKeys)
|
|
| 460 |
+ } |
|
| 461 |
+ } |
|
| 462 |
+ |
|
| 453 | 463 |
h := websocket.Handler(func(ws *websocket.Conn) {
|
| 454 | 464 |
defer ws.Close() |
| 455 | 465 |
|
| 456 | 466 |
wsAttachWithLogsConfig := &daemon.ContainerWsAttachWithLogsConfig{
|
| 457 |
- InStream: ws, |
|
| 458 |
- OutStream: ws, |
|
| 459 |
- ErrStream: ws, |
|
| 460 |
- Logs: httputils.BoolValue(r, "logs"), |
|
| 461 |
- Stream: httputils.BoolValue(r, "stream"), |
|
| 467 |
+ InStream: ws, |
|
| 468 |
+ OutStream: ws, |
|
| 469 |
+ ErrStream: ws, |
|
| 470 |
+ Logs: httputils.BoolValue(r, "logs"), |
|
| 471 |
+ Stream: httputils.BoolValue(r, "stream"), |
|
| 472 |
+ DetachKeys: keys, |
|
| 462 | 473 |
} |
| 463 | 474 |
|
| 464 | 475 |
if err := s.backend.ContainerWsAttachWithLogs(containerName, wsAttachWithLogsConfig); err != nil {
|
| ... | ... |
@@ -45,5 +45,6 @@ type ExecConfig struct {
|
| 45 | 45 |
AttachStderr bool // Attach the standard output |
| 46 | 46 |
AttachStdout bool // Attach the standard error |
| 47 | 47 |
Detach bool // Execute in detach mode |
| 48 |
+ DetachKeys string // Escape keys for detach |
|
| 48 | 49 |
Cmd []string // Execution commands and args |
| 49 | 50 |
} |
| ... | ... |
@@ -51,6 +51,7 @@ type ConfigFile struct {
|
| 51 | 51 |
HTTPHeaders map[string]string `json:"HttpHeaders,omitempty"` |
| 52 | 52 |
PsFormat string `json:"psFormat,omitempty"` |
| 53 | 53 |
ImagesFormat string `json:"imagesFormat,omitempty"` |
| 54 |
+ DetachKeys string `json:"detachKeys,omitempty"` |
|
| 54 | 55 |
filename string // Note: not serialized - for internal use only |
| 55 | 56 |
} |
| 56 | 57 |
|
| ... | ... |
@@ -329,13 +329,13 @@ func (container *Container) GetExecIDs() []string {
|
| 329 | 329 |
|
| 330 | 330 |
// Attach connects to the container's TTY, delegating to standard |
| 331 | 331 |
// streams or websockets depending on the configuration. |
| 332 |
-func (container *Container) Attach(stdin io.ReadCloser, stdout io.Writer, stderr io.Writer) chan error {
|
|
| 333 |
- return AttachStreams(container.StreamConfig, container.Config.OpenStdin, container.Config.StdinOnce, container.Config.Tty, stdin, stdout, stderr) |
|
| 332 |
+func (container *Container) Attach(stdin io.ReadCloser, stdout io.Writer, stderr io.Writer, keys []byte) chan error {
|
|
| 333 |
+ return AttachStreams(container.StreamConfig, container.Config.OpenStdin, container.Config.StdinOnce, container.Config.Tty, stdin, stdout, stderr, keys) |
|
| 334 | 334 |
} |
| 335 | 335 |
|
| 336 | 336 |
// AttachStreams connects streams to a TTY. |
| 337 | 337 |
// Used by exec too. Should this move somewhere else? |
| 338 |
-func AttachStreams(streamConfig *runconfig.StreamConfig, openStdin, stdinOnce, tty bool, stdin io.ReadCloser, stdout io.Writer, stderr io.Writer) chan error {
|
|
| 338 |
+func AttachStreams(streamConfig *runconfig.StreamConfig, openStdin, stdinOnce, tty bool, stdin io.ReadCloser, stdout io.Writer, stderr io.Writer, keys []byte) chan error {
|
|
| 339 | 339 |
var ( |
| 340 | 340 |
cStdout, cStderr io.ReadCloser |
| 341 | 341 |
cStdin io.WriteCloser |
| ... | ... |
@@ -382,7 +382,7 @@ func AttachStreams(streamConfig *runconfig.StreamConfig, openStdin, stdinOnce, t |
| 382 | 382 |
|
| 383 | 383 |
var err error |
| 384 | 384 |
if tty {
|
| 385 |
- _, err = copyEscapable(cStdin, stdin) |
|
| 385 |
+ _, err = copyEscapable(cStdin, stdin, keys) |
|
| 386 | 386 |
} else {
|
| 387 | 387 |
_, err = io.Copy(cStdin, stdin) |
| 388 | 388 |
|
| ... | ... |
@@ -438,22 +438,27 @@ func AttachStreams(streamConfig *runconfig.StreamConfig, openStdin, stdinOnce, t |
| 438 | 438 |
} |
| 439 | 439 |
|
| 440 | 440 |
// Code c/c from io.Copy() modified to handle escape sequence |
| 441 |
-func copyEscapable(dst io.Writer, src io.ReadCloser) (written int64, err error) {
|
|
| 441 |
+func copyEscapable(dst io.Writer, src io.ReadCloser, keys []byte) (written int64, err error) {
|
|
| 442 |
+ if len(keys) == 0 {
|
|
| 443 |
+ // Default keys : ctrl-p ctrl-q |
|
| 444 |
+ keys = []byte{16, 17}
|
|
| 445 |
+ } |
|
| 442 | 446 |
buf := make([]byte, 32*1024) |
| 443 | 447 |
for {
|
| 444 | 448 |
nr, er := src.Read(buf) |
| 445 | 449 |
if nr > 0 {
|
| 446 | 450 |
// ---- Docker addition |
| 447 |
- // char 16 is C-p |
|
| 448 |
- if nr == 1 && buf[0] == 16 {
|
|
| 449 |
- nr, er = src.Read(buf) |
|
| 450 |
- // char 17 is C-q |
|
| 451 |
- if nr == 1 && buf[0] == 17 {
|
|
| 451 |
+ for i, key := range keys {
|
|
| 452 |
+ if nr != 1 || buf[0] != key {
|
|
| 453 |
+ break |
|
| 454 |
+ } |
|
| 455 |
+ if i == len(keys)-1 {
|
|
| 452 | 456 |
if err := src.Close(); err != nil {
|
| 453 | 457 |
return 0, err |
| 454 | 458 |
} |
| 455 | 459 |
return 0, nil |
| 456 | 460 |
} |
| 461 |
+ nr, er = src.Read(buf) |
|
| 457 | 462 |
} |
| 458 | 463 |
// ---- End of docker |
| 459 | 464 |
nw, ew := dst.Write(buf[0:nr]) |
| ... | ... |
@@ -15,13 +15,14 @@ import ( |
| 15 | 15 |
|
| 16 | 16 |
// ContainerAttachWithLogsConfig holds the streams to use when connecting to a container to view logs. |
| 17 | 17 |
type ContainerAttachWithLogsConfig struct {
|
| 18 |
- Hijacker http.Hijacker |
|
| 19 |
- Upgrade bool |
|
| 20 |
- UseStdin bool |
|
| 21 |
- UseStdout bool |
|
| 22 |
- UseStderr bool |
|
| 23 |
- Logs bool |
|
| 24 |
- Stream bool |
|
| 18 |
+ Hijacker http.Hijacker |
|
| 19 |
+ Upgrade bool |
|
| 20 |
+ UseStdin bool |
|
| 21 |
+ UseStdout bool |
|
| 22 |
+ UseStderr bool |
|
| 23 |
+ Logs bool |
|
| 24 |
+ Stream bool |
|
| 25 |
+ DetachKeys []byte |
|
| 25 | 26 |
} |
| 26 | 27 |
|
| 27 | 28 |
// ContainerAttachWithLogs attaches to logs according to the config passed in. See ContainerAttachWithLogsConfig. |
| ... | ... |
@@ -75,7 +76,7 @@ func (daemon *Daemon) ContainerAttachWithLogs(prefixOrName string, c *ContainerA |
| 75 | 75 |
stderr = errStream |
| 76 | 76 |
} |
| 77 | 77 |
|
| 78 |
- if err := daemon.attachWithLogs(container, stdin, stdout, stderr, c.Logs, c.Stream); err != nil {
|
|
| 78 |
+ if err := daemon.attachWithLogs(container, stdin, stdout, stderr, c.Logs, c.Stream, c.DetachKeys); err != nil {
|
|
| 79 | 79 |
fmt.Fprintf(outStream, "Error attaching: %s\n", err) |
| 80 | 80 |
} |
| 81 | 81 |
return nil |
| ... | ... |
@@ -87,6 +88,7 @@ type ContainerWsAttachWithLogsConfig struct {
|
| 87 | 87 |
InStream io.ReadCloser |
| 88 | 88 |
OutStream, ErrStream io.Writer |
| 89 | 89 |
Logs, Stream bool |
| 90 |
+ DetachKeys []byte |
|
| 90 | 91 |
} |
| 91 | 92 |
|
| 92 | 93 |
// ContainerWsAttachWithLogs websocket connection |
| ... | ... |
@@ -95,10 +97,10 @@ func (daemon *Daemon) ContainerWsAttachWithLogs(prefixOrName string, c *Containe |
| 95 | 95 |
if err != nil {
|
| 96 | 96 |
return err |
| 97 | 97 |
} |
| 98 |
- return daemon.attachWithLogs(container, c.InStream, c.OutStream, c.ErrStream, c.Logs, c.Stream) |
|
| 98 |
+ return daemon.attachWithLogs(container, c.InStream, c.OutStream, c.ErrStream, c.Logs, c.Stream, c.DetachKeys) |
|
| 99 | 99 |
} |
| 100 | 100 |
|
| 101 |
-func (daemon *Daemon) attachWithLogs(container *container.Container, stdin io.ReadCloser, stdout, stderr io.Writer, logs, stream bool) error {
|
|
| 101 |
+func (daemon *Daemon) attachWithLogs(container *container.Container, stdin io.ReadCloser, stdout, stderr io.Writer, logs, stream bool, keys []byte) error {
|
|
| 102 | 102 |
if logs {
|
| 103 | 103 |
logDriver, err := daemon.getLogger(container) |
| 104 | 104 |
if err != nil {
|
| ... | ... |
@@ -144,7 +146,7 @@ func (daemon *Daemon) attachWithLogs(container *container.Container, stdin io.Re |
| 144 | 144 |
}() |
| 145 | 145 |
stdinPipe = r |
| 146 | 146 |
} |
| 147 |
- <-container.Attach(stdinPipe, stdout, stderr) |
|
| 147 |
+ <-container.Attach(stdinPipe, stdout, stderr, keys) |
|
| 148 | 148 |
// If we are in stdinonce mode, wait for the process to end |
| 149 | 149 |
// otherwise, simply return |
| 150 | 150 |
if container.Config.StdinOnce && !container.Config.Tty {
|
| ... | ... |
@@ -14,6 +14,7 @@ import ( |
| 14 | 14 |
derr "github.com/docker/docker/errors" |
| 15 | 15 |
"github.com/docker/docker/pkg/pools" |
| 16 | 16 |
"github.com/docker/docker/pkg/promise" |
| 17 |
+ "github.com/docker/docker/pkg/term" |
|
| 17 | 18 |
) |
| 18 | 19 |
|
| 19 | 20 |
func (d *Daemon) registerExecCommand(container *container.Container, config *exec.Config) {
|
| ... | ... |
@@ -88,6 +89,14 @@ func (d *Daemon) ContainerExecCreate(config *types.ExecConfig) (string, error) {
|
| 88 | 88 |
cmd := strslice.New(config.Cmd...) |
| 89 | 89 |
entrypoint, args := d.getEntrypointAndArgs(strslice.New(), cmd) |
| 90 | 90 |
|
| 91 |
+ keys := []byte{}
|
|
| 92 |
+ if config.DetachKeys != "" {
|
|
| 93 |
+ keys, err = term.ToBytes(config.DetachKeys) |
|
| 94 |
+ if err != nil {
|
|
| 95 |
+ logrus.Warnf("Wrong escape keys provided (%s, error: %s) using default : ctrl-p ctrl-q", config.DetachKeys, err.Error())
|
|
| 96 |
+ } |
|
| 97 |
+ } |
|
| 98 |
+ |
|
| 91 | 99 |
processConfig := &execdriver.ProcessConfig{
|
| 92 | 100 |
CommonProcessConfig: execdriver.CommonProcessConfig{
|
| 93 | 101 |
Tty: config.Tty, |
| ... | ... |
@@ -103,6 +112,7 @@ func (d *Daemon) ContainerExecCreate(config *types.ExecConfig) (string, error) {
|
| 103 | 103 |
execConfig.OpenStderr = config.AttachStderr |
| 104 | 104 |
execConfig.ProcessConfig = processConfig |
| 105 | 105 |
execConfig.ContainerID = container.ID |
| 106 |
+ execConfig.DetachKeys = keys |
|
| 106 | 107 |
|
| 107 | 108 |
d.registerExecCommand(container, execConfig) |
| 108 | 109 |
|
| ... | ... |
@@ -158,7 +168,8 @@ func (d *Daemon) ContainerExecStart(name string, stdin io.ReadCloser, stdout io. |
| 158 | 158 |
ec.NewNopInputPipe() |
| 159 | 159 |
} |
| 160 | 160 |
|
| 161 |
- attachErr := container.AttachStreams(ec.StreamConfig, ec.OpenStdin, true, ec.ProcessConfig.Tty, cStdin, cStdout, cStderr) |
|
| 161 |
+ attachErr := container.AttachStreams(ec.StreamConfig, ec.OpenStdin, true, ec.ProcessConfig.Tty, cStdin, cStdout, cStderr, ec.DetachKeys) |
|
| 162 |
+ |
|
| 162 | 163 |
execErr := make(chan error) |
| 163 | 164 |
|
| 164 | 165 |
// Note, the ExecConfig data will be removed when the container |
| ... | ... |
@@ -862,10 +862,9 @@ This endpoint returns a live stream of a container's resource usage statistics. |
| 862 | 862 |
"total_usage" : 36488948, |
| 863 | 863 |
"usage_in_kernelmode" : 20000000 |
| 864 | 864 |
}, |
| 865 |
- "system_cpu_usage" : 20091722000000000, |
|
| 865 |
+ "system_cpu_usage" : 20091722000000000, |
|
| 866 | 866 |
"throttling_data" : {}
|
| 867 |
- } |
|
| 868 |
- } |
|
| 867 |
+ } } |
|
| 869 | 868 |
|
| 870 | 869 |
Query Parameters: |
| 871 | 870 |
|
| ... | ... |
@@ -922,6 +921,12 @@ Start the container `id` |
| 922 | 922 |
|
| 923 | 923 |
HTTP/1.1 204 No Content |
| 924 | 924 |
|
| 925 |
+Query Parameters: |
|
| 926 |
+ |
|
| 927 |
+- **detacheys** – Override the key sequence for detaching a |
|
| 928 |
+ container. Format is a single character `[a-Z]` or `ctrl-<value>` |
|
| 929 |
+ where `<value>` is one of: `a-z`, `@`, `^`, `[`, `,` or `_`. |
|
| 930 |
+ |
|
| 925 | 931 |
Status Codes: |
| 926 | 932 |
|
| 927 | 933 |
- **204** – no error |
| ... | ... |
@@ -1133,6 +1138,9 @@ Attach to the container `id` |
| 1133 | 1133 |
|
| 1134 | 1134 |
Query Parameters: |
| 1135 | 1135 |
|
| 1136 |
+- **detacheys** – Override the key sequence for detaching a |
|
| 1137 |
+ container. Format is a single character `[a-Z]` or `ctrl-<value>` |
|
| 1138 |
+ where `<value>` is one of: `a-z`, `@`, `^`, `[`, `,` or `_`. |
|
| 1136 | 1139 |
- **logs** – 1/True/true or 0/False/false, return logs. Default `false`. |
| 1137 | 1140 |
- **stream** – 1/True/true or 0/False/false, return stream. |
| 1138 | 1141 |
Default `false`. |
| ... | ... |
@@ -1213,6 +1221,9 @@ Implements websocket protocol handshake according to [RFC 6455](http://tools.iet |
| 1213 | 1213 |
|
| 1214 | 1214 |
Query Parameters: |
| 1215 | 1215 |
|
| 1216 |
+- **detacheys** – Override the key sequence for detaching a |
|
| 1217 |
+ container. Format is a single character `[a-Z]` or `ctrl-<value>` |
|
| 1218 |
+ where `<value>` is one of: `a-z`, `@`, `^`, `[`, `,` or `_`. |
|
| 1216 | 1219 |
- **logs** – 1/True/true or 0/False/false, return logs. Default `false`. |
| 1217 | 1220 |
- **stream** – 1/True/true or 0/False/false, return stream. |
| 1218 | 1221 |
Default `false`. |
| ... | ... |
@@ -2420,6 +2431,7 @@ Sets up an exec instance in a running container `id` |
| 2420 | 2420 |
"AttachStdin": false, |
| 2421 | 2421 |
"AttachStdout": true, |
| 2422 | 2422 |
"AttachStderr": true, |
| 2423 |
+ "DetachKeys": "ctrl-p,ctrl-q", |
|
| 2423 | 2424 |
"Tty": false, |
| 2424 | 2425 |
"Cmd": [ |
| 2425 | 2426 |
"date" |
| ... | ... |
@@ -2441,6 +2453,9 @@ Json Parameters: |
| 2441 | 2441 |
- **AttachStdin** - Boolean value, attaches to `stdin` of the `exec` command. |
| 2442 | 2442 |
- **AttachStdout** - Boolean value, attaches to `stdout` of the `exec` command. |
| 2443 | 2443 |
- **AttachStderr** - Boolean value, attaches to `stderr` of the `exec` command. |
| 2444 |
+- **Detacheys** – Override the key sequence for detaching a |
|
| 2445 |
+ container. Format is a single character `[a-Z]` or `ctrl-<value>` |
|
| 2446 |
+ where `<value>` is one of: `a-z`, `@`, `^`, `[`, `,` or `_`. |
|
| 2444 | 2447 |
- **Tty** - Boolean value to allocate a pseudo-TTY. |
| 2445 | 2448 |
- **Cmd** - Command to run specified as a string or an array of strings. |
| 2446 | 2449 |
|
| ... | ... |
@@ -14,9 +14,10 @@ parent = "smn_cli" |
| 14 | 14 |
|
| 15 | 15 |
Attach to a running container |
| 16 | 16 |
|
| 17 |
- --help Print usage |
|
| 18 |
- --no-stdin Do not attach STDIN |
|
| 19 |
- --sig-proxy=true Proxy all received signals to the process |
|
| 17 |
+ --detach-keys="<sequence>" Set up escape key sequence |
|
| 18 |
+ --help Print usage |
|
| 19 |
+ --no-stdin Do not attach STDIN |
|
| 20 |
+ --sig-proxy=true Proxy all received signals to the process |
|
| 20 | 21 |
|
| 21 | 22 |
The `docker attach` command allows you to attach to a running container using |
| 22 | 23 |
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 |
| 24 | 24 |
simultaneously, screen sharing style, or quickly view the progress of your |
| 25 | 25 |
detached process. |
| 26 | 26 |
|
| 27 |
-You can detach from the container and leave it running with `CTRL-p CTRL-q` |
|
| 28 |
-(for a quiet exit) or with `CTRL-c` if `--sig-proxy` is false. |
|
| 29 |
- |
|
| 30 |
-If `--sig-proxy` is true (the default),`CTRL-c` sends a `SIGINT` to the |
|
| 31 |
-container. |
|
| 27 |
+To stop a container, use `CTRL-c`. This key sequence sends `SIGKILL` to the |
|
| 28 |
+container. If `--sig-proxy` is true (the default),`CTRL-c` sends a `SIGINT` to |
|
| 29 |
+the container. You can detach from a container and leave it running using the |
|
| 30 |
+using `CTRL-p CTRL-q` key sequence. |
|
| 32 | 31 |
|
| 33 | 32 |
> **Note:** |
| 34 | 33 |
> A process running as PID 1 inside a container is treated specially by |
| ... | ... |
@@ -39,6 +39,31 @@ container. |
| 39 | 39 |
It is forbidden to redirect the standard input of a `docker attach` command |
| 40 | 40 |
while attaching to a tty-enabled container (i.e.: launched with `-t`). |
| 41 | 41 |
|
| 42 |
+ |
|
| 43 |
+## Override the detach sequence |
|
| 44 |
+ |
|
| 45 |
+If you want, you can configure a override the Docker key sequence for detach. |
|
| 46 |
+This is is useful if the Docker default sequence conflicts with key squence you |
|
| 47 |
+use for other applications. There are two ways to defines a your own detach key |
|
| 48 |
+sequence, as a per-container override or as a configuration property on your |
|
| 49 |
+entire configuration. |
|
| 50 |
+ |
|
| 51 |
+To override the sequence for an individual container, use the |
|
| 52 |
+`--detach-keys="<sequence>"` flag with the `docker attach` command. The format of |
|
| 53 |
+the `<sequence>` is either a letter [a-Z], or the `ctrl-` combined with any of |
|
| 54 |
+the following: |
|
| 55 |
+ |
|
| 56 |
+* `a-z` (a single lowercase alpha character ) |
|
| 57 |
+* `@` (ampersand) |
|
| 58 |
+* `[` (left bracket) |
|
| 59 |
+* `\\` (two backward slashes) |
|
| 60 |
+* `_` (underscore) |
|
| 61 |
+* `^` (caret) |
|
| 62 |
+ |
|
| 63 |
+These `a`, `ctrl-a`, `X`, or `ctrl-\\` values are all examples of valid key |
|
| 64 |
+sequences. To configure a different configuration default key sequence for all |
|
| 65 |
+containers, see [**Configuration file** section](cli.md#configuration-files). |
|
| 66 |
+ |
|
| 42 | 67 |
#### Examples |
| 43 | 68 |
|
| 44 | 69 |
$ docker run -d --name topdemo ubuntu /usr/bin/top -b |
| ... | ... |
@@ -101,7 +101,26 @@ The property `psFormat` specifies the default format for `docker ps` output. |
| 101 | 101 |
When the `--format` flag is not provided with the `docker ps` command, |
| 102 | 102 |
Docker's client uses this property. If this property is not set, the client |
| 103 | 103 |
falls back to the default table format. For a list of supported formatting |
| 104 |
-directives, see the [**Formatting** section in the `docker ps` documentation](ps.md) |
|
| 104 |
+directives, see the |
|
| 105 |
+[**Formatting** section in the `docker ps` documentation](ps.md) |
|
| 106 |
+ |
|
| 107 |
+Once attached to a container, users detach from it and leave it running using |
|
| 108 |
+the using `CTRL-p CTRL-q` key sequence. This detach key sequence is customizable |
|
| 109 |
+using the `detachKeys` property. Specify a `<sequence>` value for the |
|
| 110 |
+property. The format of the `<sequence>` is either a letter [a-Z], or the `ctrl-` |
|
| 111 |
+combined with any of the following: |
|
| 112 |
+ |
|
| 113 |
+* `a-z` (a single lowercase alpha character ) |
|
| 114 |
+* `@` (ampersand) |
|
| 115 |
+* `[` (left bracket) |
|
| 116 |
+* `\\` (two backward slashes) |
|
| 117 |
+* `_` (underscore) |
|
| 118 |
+* `^` (caret) |
|
| 119 |
+ |
|
| 120 |
+Your customization applies to all containers started in with your Docker client. |
|
| 121 |
+Users can override your custom or the default key sequence on a per-container |
|
| 122 |
+basis. To do this, the user specifies the `--detach-keys` flag with the `docker |
|
| 123 |
+attach`, `docker exec`, `docker run` or `docker start` command. |
|
| 105 | 124 |
|
| 106 | 125 |
The property `imagesFormat` specifies the default format for `docker images` output. |
| 107 | 126 |
When the `--format` flag is not provided with the `docker images` command, |
| ... | ... |
@@ -115,8 +134,9 @@ Following is a sample `config.json` file: |
| 115 | 115 |
"HttpHeaders": {
|
| 116 | 116 |
"MyHeader": "MyValue" |
| 117 | 117 |
}, |
| 118 |
- "psFormat": "table {{.ID}}\\t{{.Image}}\\t{{.Command}}\\t{{.Labels}}"
|
|
| 119 |
- "imagesFormat": "table {{.ID}}\\t{{.Repository}}\\t{{.Tag}}\\t{{.CreatedAt}}"
|
|
| 118 |
+ "psFormat": "table {{.ID}}\\t{{.Image}}\\t{{.Command}}\\t{{.Labels}}",
|
|
| 119 |
+ "imagesFormat": "table {{.ID}}\\t{{.Repository}}\\t{{.Tag}}\\t{{.CreatedAt}}",
|
|
| 120 |
+ "detachKeys": "ctrl-e,e" |
|
| 120 | 121 |
} |
| 121 | 122 |
|
| 122 | 123 |
### Notary |
| ... | ... |
@@ -15,6 +15,7 @@ parent = "smn_cli" |
| 15 | 15 |
Run a command in a running container |
| 16 | 16 |
|
| 17 | 17 |
-d, --detach Detached mode: run command in the background |
| 18 |
+ --detach-keys Specify the escape key sequence used to detach a container |
|
| 18 | 19 |
--help Print usage |
| 19 | 20 |
-i, --interactive Keep STDIN open even if not attached |
| 20 | 21 |
--privileged Give extended Linux capabilities to the command |
| ... | ... |
@@ -28,6 +28,7 @@ parent = "smn_cli" |
| 28 | 28 |
--cpuset-cpus="" CPUs in which to allow execution (0-3, 0,1) |
| 29 | 29 |
--cpuset-mems="" Memory nodes (MEMs) in which to allow execution (0-3, 0,1) |
| 30 | 30 |
-d, --detach Run container in background and print container ID |
| 31 |
+ --detach-keys Specify the escape key sequence used to detach a container |
|
| 31 | 32 |
--device=[] Add a host device to the container |
| 32 | 33 |
--device-read-bps=[] Limit read rate (bytes per second) from a device (e.g., --device-read-bps=/dev/sda:1mb) |
| 33 | 34 |
--device-read-iops=[] Limit read rate (IO per second) from a device (e.g., --device-read-iops=/dev/sda:1000) |
| ... | ... |
@@ -14,6 +14,7 @@ import ( |
| 14 | 14 |
"strings" |
| 15 | 15 |
"time" |
| 16 | 16 |
|
| 17 |
+ "github.com/docker/docker/pkg/homedir" |
|
| 17 | 18 |
"github.com/docker/docker/pkg/integration/checker" |
| 18 | 19 |
"github.com/docker/docker/pkg/mount" |
| 19 | 20 |
"github.com/docker/docker/pkg/parsers" |
| ... | ... |
@@ -87,10 +88,13 @@ func (s *DockerSuite) TestRunDeviceDirectory(c *check.C) {
|
| 87 | 87 |
c.Assert(strings.Trim(out, "\r\n"), checker.Contains, "seq", check.Commentf("expected output /dev/othersnd/seq"))
|
| 88 | 88 |
} |
| 89 | 89 |
|
| 90 |
-// TestRunDetach checks attaching and detaching with the escape sequence. |
|
| 90 |
+// TestRunDetach checks attaching and detaching with the default escape sequence. |
|
| 91 | 91 |
func (s *DockerSuite) TestRunAttachDetach(c *check.C) {
|
| 92 | 92 |
name := "attach-detach" |
| 93 |
- cmd := exec.Command(dockerBinary, "run", "--name", name, "-it", "busybox", "cat") |
|
| 93 |
+ |
|
| 94 |
+ dockerCmd(c, "run", "--name", name, "-itd", "busybox", "cat") |
|
| 95 |
+ |
|
| 96 |
+ cmd := exec.Command(dockerBinary, "attach", name) |
|
| 94 | 97 |
stdout, err := cmd.StdoutPipe() |
| 95 | 98 |
c.Assert(err, checker.IsNil) |
| 96 | 99 |
cpty, tty, err := pty.Open() |
| ... | ... |
@@ -120,21 +124,248 @@ func (s *DockerSuite) TestRunAttachDetach(c *check.C) {
|
| 120 | 120 |
ch <- struct{}{}
|
| 121 | 121 |
}() |
| 122 | 122 |
|
| 123 |
+ select {
|
|
| 124 |
+ case <-ch: |
|
| 125 |
+ case <-time.After(10 * time.Second): |
|
| 126 |
+ c.Fatal("timed out waiting for container to exit")
|
|
| 127 |
+ } |
|
| 128 |
+ |
|
| 123 | 129 |
running, err := inspectField(name, "State.Running") |
| 124 | 130 |
c.Assert(err, checker.IsNil) |
| 125 | 131 |
c.Assert(running, checker.Equals, "true", check.Commentf("expected container to still be running"))
|
| 132 |
+} |
|
| 133 |
+ |
|
| 134 |
+// TestRunDetach checks attaching and detaching with the escape sequence specified via flags. |
|
| 135 |
+func (s *DockerSuite) TestRunAttachDetachFromFlag(c *check.C) {
|
|
| 136 |
+ name := "attach-detach" |
|
| 137 |
+ keyCtrlA := []byte{1}
|
|
| 138 |
+ keyA := []byte{97}
|
|
| 139 |
+ |
|
| 140 |
+ dockerCmd(c, "run", "--name", name, "-itd", "busybox", "cat") |
|
| 141 |
+ |
|
| 142 |
+ cmd := exec.Command(dockerBinary, "attach", "--detach-keys='ctrl-a,a'", name) |
|
| 143 |
+ stdout, err := cmd.StdoutPipe() |
|
| 144 |
+ if err != nil {
|
|
| 145 |
+ c.Fatal(err) |
|
| 146 |
+ } |
|
| 147 |
+ cpty, tty, err := pty.Open() |
|
| 148 |
+ if err != nil {
|
|
| 149 |
+ c.Fatal(err) |
|
| 150 |
+ } |
|
| 151 |
+ defer cpty.Close() |
|
| 152 |
+ cmd.Stdin = tty |
|
| 153 |
+ if err := cmd.Start(); err != nil {
|
|
| 154 |
+ c.Fatal(err) |
|
| 155 |
+ } |
|
| 156 |
+ c.Assert(waitRun(name), check.IsNil) |
|
| 157 |
+ |
|
| 158 |
+ if _, err := cpty.Write([]byte("hello\n")); err != nil {
|
|
| 159 |
+ c.Fatal(err) |
|
| 160 |
+ } |
|
| 161 |
+ |
|
| 162 |
+ out, err := bufio.NewReader(stdout).ReadString('\n')
|
|
| 163 |
+ if err != nil {
|
|
| 164 |
+ c.Fatal(err) |
|
| 165 |
+ } |
|
| 166 |
+ if strings.TrimSpace(out) != "hello" {
|
|
| 167 |
+ c.Fatalf("expected 'hello', got %q", out)
|
|
| 168 |
+ } |
|
| 169 |
+ |
|
| 170 |
+ // escape sequence |
|
| 171 |
+ if _, err := cpty.Write(keyCtrlA); err != nil {
|
|
| 172 |
+ c.Fatal(err) |
|
| 173 |
+ } |
|
| 174 |
+ time.Sleep(100 * time.Millisecond) |
|
| 175 |
+ if _, err := cpty.Write(keyA); err != nil {
|
|
| 176 |
+ c.Fatal(err) |
|
| 177 |
+ } |
|
| 126 | 178 |
|
| 179 |
+ ch := make(chan struct{})
|
|
| 127 | 180 |
go func() {
|
| 128 |
- exec.Command(dockerBinary, "kill", name).Run() |
|
| 181 |
+ cmd.Wait() |
|
| 182 |
+ ch <- struct{}{}
|
|
| 129 | 183 |
}() |
| 130 | 184 |
|
| 131 | 185 |
select {
|
| 132 | 186 |
case <-ch: |
| 133 |
- case <-time.After(10 * time.Millisecond): |
|
| 187 |
+ case <-time.After(10 * time.Second): |
|
| 134 | 188 |
c.Fatal("timed out waiting for container to exit")
|
| 135 | 189 |
} |
| 190 |
+ |
|
| 191 |
+ running, err := inspectField(name, "State.Running") |
|
| 192 |
+ c.Assert(err, checker.IsNil) |
|
| 193 |
+ c.Assert(running, checker.Equals, "true", check.Commentf("expected container to still be running"))
|
|
| 136 | 194 |
} |
| 137 | 195 |
|
| 196 |
+// TestRunDetach checks attaching and detaching with the escape sequence specified via config file. |
|
| 197 |
+func (s *DockerSuite) TestRunAttachDetachFromConfig(c *check.C) {
|
|
| 198 |
+ keyCtrlA := []byte{1}
|
|
| 199 |
+ keyA := []byte{97}
|
|
| 200 |
+ |
|
| 201 |
+ // Setup config |
|
| 202 |
+ homeKey := homedir.Key() |
|
| 203 |
+ homeVal := homedir.Get() |
|
| 204 |
+ tmpDir, err := ioutil.TempDir("", "fake-home")
|
|
| 205 |
+ c.Assert(err, checker.IsNil) |
|
| 206 |
+ defer os.RemoveAll(tmpDir) |
|
| 207 |
+ |
|
| 208 |
+ dotDocker := filepath.Join(tmpDir, ".docker") |
|
| 209 |
+ os.Mkdir(dotDocker, 0600) |
|
| 210 |
+ tmpCfg := filepath.Join(dotDocker, "config.json") |
|
| 211 |
+ |
|
| 212 |
+ defer func() { os.Setenv(homeKey, homeVal) }()
|
|
| 213 |
+ os.Setenv(homeKey, tmpDir) |
|
| 214 |
+ |
|
| 215 |
+ data := `{
|
|
| 216 |
+ "detachKeys": "ctrl-a,a" |
|
| 217 |
+ }` |
|
| 218 |
+ |
|
| 219 |
+ err = ioutil.WriteFile(tmpCfg, []byte(data), 0600) |
|
| 220 |
+ c.Assert(err, checker.IsNil) |
|
| 221 |
+ |
|
| 222 |
+ // Then do the work |
|
| 223 |
+ name := "attach-detach" |
|
| 224 |
+ dockerCmd(c, "run", "--name", name, "-itd", "busybox", "cat") |
|
| 225 |
+ |
|
| 226 |
+ cmd := exec.Command(dockerBinary, "attach", name) |
|
| 227 |
+ stdout, err := cmd.StdoutPipe() |
|
| 228 |
+ if err != nil {
|
|
| 229 |
+ c.Fatal(err) |
|
| 230 |
+ } |
|
| 231 |
+ cpty, tty, err := pty.Open() |
|
| 232 |
+ if err != nil {
|
|
| 233 |
+ c.Fatal(err) |
|
| 234 |
+ } |
|
| 235 |
+ defer cpty.Close() |
|
| 236 |
+ cmd.Stdin = tty |
|
| 237 |
+ if err := cmd.Start(); err != nil {
|
|
| 238 |
+ c.Fatal(err) |
|
| 239 |
+ } |
|
| 240 |
+ c.Assert(waitRun(name), check.IsNil) |
|
| 241 |
+ |
|
| 242 |
+ if _, err := cpty.Write([]byte("hello\n")); err != nil {
|
|
| 243 |
+ c.Fatal(err) |
|
| 244 |
+ } |
|
| 245 |
+ |
|
| 246 |
+ out, err := bufio.NewReader(stdout).ReadString('\n')
|
|
| 247 |
+ if err != nil {
|
|
| 248 |
+ c.Fatal(err) |
|
| 249 |
+ } |
|
| 250 |
+ if strings.TrimSpace(out) != "hello" {
|
|
| 251 |
+ c.Fatalf("expected 'hello', got %q", out)
|
|
| 252 |
+ } |
|
| 253 |
+ |
|
| 254 |
+ // escape sequence |
|
| 255 |
+ if _, err := cpty.Write(keyCtrlA); err != nil {
|
|
| 256 |
+ c.Fatal(err) |
|
| 257 |
+ } |
|
| 258 |
+ time.Sleep(100 * time.Millisecond) |
|
| 259 |
+ if _, err := cpty.Write(keyA); err != nil {
|
|
| 260 |
+ c.Fatal(err) |
|
| 261 |
+ } |
|
| 262 |
+ |
|
| 263 |
+ ch := make(chan struct{})
|
|
| 264 |
+ go func() {
|
|
| 265 |
+ cmd.Wait() |
|
| 266 |
+ ch <- struct{}{}
|
|
| 267 |
+ }() |
|
| 268 |
+ |
|
| 269 |
+ select {
|
|
| 270 |
+ case <-ch: |
|
| 271 |
+ case <-time.After(10 * time.Second): |
|
| 272 |
+ c.Fatal("timed out waiting for container to exit")
|
|
| 273 |
+ } |
|
| 274 |
+ |
|
| 275 |
+ running, err := inspectField(name, "State.Running") |
|
| 276 |
+ c.Assert(err, checker.IsNil) |
|
| 277 |
+ c.Assert(running, checker.Equals, "true", check.Commentf("expected container to still be running"))
|
|
| 278 |
+} |
|
| 279 |
+ |
|
| 280 |
+// TestRunDetach checks attaching and detaching with the detach flags, making sure it overrides config file |
|
| 281 |
+func (s *DockerSuite) TestRunAttachDetachKeysOverrideConfig(c *check.C) {
|
|
| 282 |
+ keyCtrlA := []byte{1}
|
|
| 283 |
+ keyA := []byte{97}
|
|
| 284 |
+ |
|
| 285 |
+ // Setup config |
|
| 286 |
+ homeKey := homedir.Key() |
|
| 287 |
+ homeVal := homedir.Get() |
|
| 288 |
+ tmpDir, err := ioutil.TempDir("", "fake-home")
|
|
| 289 |
+ c.Assert(err, checker.IsNil) |
|
| 290 |
+ defer os.RemoveAll(tmpDir) |
|
| 291 |
+ |
|
| 292 |
+ dotDocker := filepath.Join(tmpDir, ".docker") |
|
| 293 |
+ os.Mkdir(dotDocker, 0600) |
|
| 294 |
+ tmpCfg := filepath.Join(dotDocker, "config.json") |
|
| 295 |
+ |
|
| 296 |
+ defer func() { os.Setenv(homeKey, homeVal) }()
|
|
| 297 |
+ os.Setenv(homeKey, tmpDir) |
|
| 298 |
+ |
|
| 299 |
+ data := `{
|
|
| 300 |
+ "detachKeys": "ctrl-e,e" |
|
| 301 |
+ }` |
|
| 302 |
+ |
|
| 303 |
+ err = ioutil.WriteFile(tmpCfg, []byte(data), 0600) |
|
| 304 |
+ c.Assert(err, checker.IsNil) |
|
| 305 |
+ |
|
| 306 |
+ // Then do the work |
|
| 307 |
+ name := "attach-detach" |
|
| 308 |
+ dockerCmd(c, "run", "--name", name, "-itd", "busybox", "cat") |
|
| 309 |
+ |
|
| 310 |
+ cmd := exec.Command(dockerBinary, "attach", "--detach-keys='ctrl-a,a'", name) |
|
| 311 |
+ stdout, err := cmd.StdoutPipe() |
|
| 312 |
+ if err != nil {
|
|
| 313 |
+ c.Fatal(err) |
|
| 314 |
+ } |
|
| 315 |
+ cpty, tty, err := pty.Open() |
|
| 316 |
+ if err != nil {
|
|
| 317 |
+ c.Fatal(err) |
|
| 318 |
+ } |
|
| 319 |
+ defer cpty.Close() |
|
| 320 |
+ cmd.Stdin = tty |
|
| 321 |
+ if err := cmd.Start(); err != nil {
|
|
| 322 |
+ c.Fatal(err) |
|
| 323 |
+ } |
|
| 324 |
+ c.Assert(waitRun(name), check.IsNil) |
|
| 325 |
+ |
|
| 326 |
+ if _, err := cpty.Write([]byte("hello\n")); err != nil {
|
|
| 327 |
+ c.Fatal(err) |
|
| 328 |
+ } |
|
| 329 |
+ |
|
| 330 |
+ out, err := bufio.NewReader(stdout).ReadString('\n')
|
|
| 331 |
+ if err != nil {
|
|
| 332 |
+ c.Fatal(err) |
|
| 333 |
+ } |
|
| 334 |
+ if strings.TrimSpace(out) != "hello" {
|
|
| 335 |
+ c.Fatalf("expected 'hello', got %q", out)
|
|
| 336 |
+ } |
|
| 337 |
+ |
|
| 338 |
+ // escape sequence |
|
| 339 |
+ if _, err := cpty.Write(keyCtrlA); err != nil {
|
|
| 340 |
+ c.Fatal(err) |
|
| 341 |
+ } |
|
| 342 |
+ time.Sleep(100 * time.Millisecond) |
|
| 343 |
+ if _, err := cpty.Write(keyA); err != nil {
|
|
| 344 |
+ c.Fatal(err) |
|
| 345 |
+ } |
|
| 346 |
+ |
|
| 347 |
+ ch := make(chan struct{})
|
|
| 348 |
+ go func() {
|
|
| 349 |
+ cmd.Wait() |
|
| 350 |
+ ch <- struct{}{}
|
|
| 351 |
+ }() |
|
| 352 |
+ |
|
| 353 |
+ select {
|
|
| 354 |
+ case <-ch: |
|
| 355 |
+ case <-time.After(10 * time.Second): |
|
| 356 |
+ c.Fatal("timed out waiting for container to exit")
|
|
| 357 |
+ } |
|
| 358 |
+ |
|
| 359 |
+ running, err := inspectField(name, "State.Running") |
|
| 360 |
+ c.Assert(err, checker.IsNil) |
|
| 361 |
+ c.Assert(running, checker.Equals, "true", check.Commentf("expected container to still be running"))
|
|
| 362 |
+} |
|
| 363 |
+ |
|
| 364 |
+// "test" should be printed |
|
| 138 | 365 |
func (s *DockerSuite) TestRunWithCPUQuota(c *check.C) {
|
| 139 | 366 |
testRequires(c, cpuCfsQuota) |
| 140 | 367 |
|
| ... | ... |
@@ -6,6 +6,7 @@ docker-attach - Attach to a running container |
| 6 | 6 |
|
| 7 | 7 |
# SYNOPSIS |
| 8 | 8 |
**docker attach** |
| 9 |
+[**--detach-keys**[=*[]*]] |
|
| 9 | 10 |
[**--help**] |
| 10 | 11 |
[**--no-stdin**] |
| 11 | 12 |
[**--sig-proxy**[=*true*]] |
| ... | ... |
@@ -18,15 +19,19 @@ interactively. You can attach to the same contained process multiple times |
| 18 | 18 |
simultaneously, screen sharing style, or quickly view the progress of your |
| 19 | 19 |
detached process. |
| 20 | 20 |
|
| 21 |
-You can detach from the container (and leave it running) with `CTRL-p CTRL-q` |
|
| 22 |
-(for a quiet exit) or `CTRL-c` which will send a `SIGKILL` to the container. |
|
| 23 |
-When you are attached to a container, and exit its main process, the process's |
|
| 24 |
-exit code will be returned to the client. |
|
| 21 |
+To stop a container, use `CTRL-c`. This key sequence sends `SIGKILL` to the |
|
| 22 |
+container. You can detach from the container (and leave it running) using a |
|
| 23 |
+configurable key sequence. The default sequence is `CTRL-p CTRL-q`. You |
|
| 24 |
+configure the key sequence using the **--detach-keys** option or a configuration |
|
| 25 |
+file. See **config-json(5)** for documentation on using a configuration file. |
|
| 25 | 26 |
|
| 26 | 27 |
It is forbidden to redirect the standard input of a `docker attach` command while |
| 27 | 28 |
attaching to a tty-enabled container (i.e.: launched with `-t`). |
| 28 | 29 |
|
| 29 | 30 |
# OPTIONS |
| 31 |
+**--detach-keys**="" |
|
| 32 |
+ 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 `_`. |
|
| 33 |
+ |
|
| 30 | 34 |
**--help** |
| 31 | 35 |
Print usage statement |
| 32 | 36 |
|
| ... | ... |
@@ -36,6 +41,30 @@ attaching to a tty-enabled container (i.e.: launched with `-t`). |
| 36 | 36 |
**--sig-proxy**=*true*|*false* |
| 37 | 37 |
Proxy all received signals to the process (non-TTY mode only). SIGCHLD, SIGKILL, and SIGSTOP are not proxied. The default is *true*. |
| 38 | 38 |
|
| 39 |
+# Override the detach sequence |
|
| 40 |
+ |
|
| 41 |
+If you want, you can configure a override the Docker key sequence for detach. |
|
| 42 |
+This is is useful if the Docker default sequence conflicts with key squence you |
|
| 43 |
+use for other applications. There are two ways to defines a your own detach key |
|
| 44 |
+sequence, as a per-container override or as a configuration property on your |
|
| 45 |
+entire configuration. |
|
| 46 |
+ |
|
| 47 |
+To override the sequence for an individual container, use the |
|
| 48 |
+`--detach-keys="<sequence>"` flag with the `docker attach` command. The format of |
|
| 49 |
+the `<sequence>` is either a letter [a-Z], or the `ctrl-` combined with any of |
|
| 50 |
+the following: |
|
| 51 |
+ |
|
| 52 |
+* `a-z` (a single lowercase alpha character ) |
|
| 53 |
+* `@` (ampersand) |
|
| 54 |
+* `[` (left bracket) |
|
| 55 |
+* `\\` (two backward slashes) |
|
| 56 |
+* `_` (underscore) |
|
| 57 |
+* `^` (caret) |
|
| 58 |
+ |
|
| 59 |
+These `a`, `ctrl-a`, `X`, or `ctrl-\\` values are all examples of valid key |
|
| 60 |
+sequences. To configure a different configuration default key sequence for all |
|
| 61 |
+containers, see **docker(1)**. |
|
| 62 |
+ |
|
| 39 | 63 |
# EXAMPLES |
| 40 | 64 |
|
| 41 | 65 |
## Attaching to a container |
| ... | ... |
@@ -7,6 +7,7 @@ docker-exec - Run a command in a running container |
| 7 | 7 |
# SYNOPSIS |
| 8 | 8 |
**docker exec** |
| 9 | 9 |
[**-d**|**--detach**] |
| 10 |
+[**--detach-keys**[=*[]*]] |
|
| 10 | 11 |
[**--help**] |
| 11 | 12 |
[**-i**|**--interactive**] |
| 12 | 13 |
[**--privileged**] |
| ... | ... |
@@ -26,7 +27,10 @@ container is unpaused, and then run |
| 26 | 26 |
|
| 27 | 27 |
# OPTIONS |
| 28 | 28 |
**-d**, **--detach**=*true*|*false* |
| 29 |
- Detached mode: run command in the background. The default is *false*. |
|
| 29 |
+ 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 `_`. |
|
| 30 |
+ |
|
| 31 |
+**--detach-keys**="" |
|
| 32 |
+ Define the key sequence which detaches the container. |
|
| 30 | 33 |
|
| 31 | 34 |
**--help** |
| 32 | 35 |
Print usage statement |
| ... | ... |
@@ -20,6 +20,7 @@ docker-run - Run a command in a new container |
| 20 | 20 |
[**--cpuset-cpus**[=*CPUSET-CPUS*]] |
| 21 | 21 |
[**--cpuset-mems**[=*CPUSET-MEMS*]] |
| 22 | 22 |
[**-d**|**--detach**] |
| 23 |
+[**--detach-keys**[=*[]*]] |
|
| 23 | 24 |
[**--device**[=*[]*]] |
| 24 | 25 |
[**--device-read-bps**[=*[]*]] |
| 25 | 26 |
[**--device-read-iops**[=*[]*]] |
| ... | ... |
@@ -190,8 +191,13 @@ the other shell to view a list of the running containers. You can reattach to a |
| 190 | 190 |
detached container with **docker attach**. If you choose to run a container in |
| 191 | 191 |
the detached mode, then you cannot use the **-rm** option. |
| 192 | 192 |
|
| 193 |
- When attached in the tty mode, you can detach from a running container without |
|
| 194 |
-stopping the process by pressing the keys CTRL-P CTRL-Q. |
|
| 193 |
+ When attached in the tty mode, you can detach from the container (and leave it |
|
| 194 |
+running) using a configurable key sequence. The default sequence is `CTRL-p CTRL-q`. |
|
| 195 |
+You configure the key sequence using the **--detach-keys** option or a configuration file. |
|
| 196 |
+See **config-json(5)** for documentation on using a configuration file. |
|
| 197 |
+ |
|
| 198 |
+**--detach-keys**="" |
|
| 199 |
+ 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 `_`. |
|
| 195 | 200 |
|
| 196 | 201 |
**--device**=[] |
| 197 | 202 |
Add a host device to the container (e.g. --device=/dev/sdc:/dev/xvdc:rwm) |
| ... | ... |
@@ -7,6 +7,7 @@ docker-start - Start one or more containers |
| 7 | 7 |
# SYNOPSIS |
| 8 | 8 |
**docker start** |
| 9 | 9 |
[**-a**|**--attach**] |
| 10 |
+[**--detach-keys**[=*[]*]] |
|
| 10 | 11 |
[**--help**] |
| 11 | 12 |
[**-i**|**--interactive**] |
| 12 | 13 |
CONTAINER [CONTAINER...] |
| ... | ... |
@@ -17,7 +18,11 @@ Start one or more containers. |
| 17 | 17 |
|
| 18 | 18 |
# OPTIONS |
| 19 | 19 |
**-a**, **--attach**=*true*|*false* |
| 20 |
- Attach container's STDOUT and STDERR and forward all signals to the process. The default is *false*. |
|
| 20 |
+ Attach container's STDOUT and STDERR and forward all signals to the |
|
| 21 |
+ process. The default is *false*. |
|
| 22 |
+ |
|
| 23 |
+**--detach-keys**="" |
|
| 24 |
+ 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 `_`. |
|
| 21 | 25 |
|
| 22 | 26 |
**--help** |
| 23 | 27 |
Print usage statement |
| ... | ... |
@@ -223,6 +223,7 @@ inside it) |
| 223 | 223 |
Block until a container stops, then print its exit code |
| 224 | 224 |
See **docker-wait(1)** for full documentation on the **wait** command. |
| 225 | 225 |
|
| 226 |
+ |
|
| 226 | 227 |
# EXEC DRIVER OPTIONS |
| 227 | 228 |
|
| 228 | 229 |
Use the **--exec-opt** flags to specify options to the execution driver. The only |
| 229 | 230 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,66 @@ |
| 0 |
+package term |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "fmt" |
|
| 4 |
+ "strings" |
|
| 5 |
+) |
|
| 6 |
+ |
|
| 7 |
+// ASCII list the possible supported ASCII key sequence |
|
| 8 |
+var ASCII = []string{
|
|
| 9 |
+ "ctrl-@", |
|
| 10 |
+ "ctrl-a", |
|
| 11 |
+ "ctrl-b", |
|
| 12 |
+ "ctrl-c", |
|
| 13 |
+ "ctrl-d", |
|
| 14 |
+ "ctrl-e", |
|
| 15 |
+ "ctrl-f", |
|
| 16 |
+ "ctrl-g", |
|
| 17 |
+ "ctrl-h", |
|
| 18 |
+ "ctrl-i", |
|
| 19 |
+ "ctrl-j", |
|
| 20 |
+ "ctrl-k", |
|
| 21 |
+ "ctrl-l", |
|
| 22 |
+ "ctrl-m", |
|
| 23 |
+ "ctrl-n", |
|
| 24 |
+ "ctrl-o", |
|
| 25 |
+ "ctrl-p", |
|
| 26 |
+ "ctrl-q", |
|
| 27 |
+ "ctrl-r", |
|
| 28 |
+ "ctrl-s", |
|
| 29 |
+ "ctrl-t", |
|
| 30 |
+ "ctrl-u", |
|
| 31 |
+ "ctrl-v", |
|
| 32 |
+ "ctrl-w", |
|
| 33 |
+ "ctrl-x", |
|
| 34 |
+ "ctrl-y", |
|
| 35 |
+ "ctrl-z", |
|
| 36 |
+ "ctrl-[", |
|
| 37 |
+ "ctrl-\\", |
|
| 38 |
+ "ctrl-]", |
|
| 39 |
+ "ctrl-^", |
|
| 40 |
+ "ctrl-_", |
|
| 41 |
+} |
|
| 42 |
+ |
|
| 43 |
+// ToBytes converts a string representing a suite of key-sequence to the corresponding ASCII code. |
|
| 44 |
+func ToBytes(keys string) ([]byte, error) {
|
|
| 45 |
+ codes := []byte{}
|
|
| 46 |
+next: |
|
| 47 |
+ for _, key := range strings.Split(keys, ",") {
|
|
| 48 |
+ if len(key) != 1 {
|
|
| 49 |
+ for code, ctrl := range ASCII {
|
|
| 50 |
+ if ctrl == key {
|
|
| 51 |
+ codes = append(codes, byte(code)) |
|
| 52 |
+ continue next |
|
| 53 |
+ } |
|
| 54 |
+ } |
|
| 55 |
+ if key == "DEL" {
|
|
| 56 |
+ codes = append(codes, 127) |
|
| 57 |
+ } else {
|
|
| 58 |
+ return nil, fmt.Errorf("Unknown character: '%s'", key)
|
|
| 59 |
+ } |
|
| 60 |
+ } else {
|
|
| 61 |
+ codes = append(codes, byte(key[0])) |
|
| 62 |
+ } |
|
| 63 |
+ } |
|
| 64 |
+ return codes, nil |
|
| 65 |
+} |
| 0 | 66 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,43 @@ |
| 0 |
+package term |
|
| 1 |
+ |
|
| 2 |
+import "testing" |
|
| 3 |
+ |
|
| 4 |
+func TestToBytes(t *testing.T) {
|
|
| 5 |
+ codes, err := ToBytes("ctrl-a,a")
|
|
| 6 |
+ if err != nil {
|
|
| 7 |
+ t.Fatal(err) |
|
| 8 |
+ } |
|
| 9 |
+ if len(codes) != 2 {
|
|
| 10 |
+ t.Fatalf("Expected 2 codes, got %d", len(codes))
|
|
| 11 |
+ } |
|
| 12 |
+ if codes[0] != 1 || codes[1] != 97 {
|
|
| 13 |
+ t.Fatalf("Expected '1' '97', got '%d' '%d'", codes[0], codes[1])
|
|
| 14 |
+ } |
|
| 15 |
+ |
|
| 16 |
+ codes, err = ToBytes("shift-z")
|
|
| 17 |
+ if err == nil {
|
|
| 18 |
+ t.Fatalf("Expected error, got none")
|
|
| 19 |
+ } |
|
| 20 |
+ |
|
| 21 |
+ codes, err = ToBytes("ctrl-@,ctrl-[,~,ctrl-o")
|
|
| 22 |
+ if err != nil {
|
|
| 23 |
+ t.Fatal(err) |
|
| 24 |
+ } |
|
| 25 |
+ if len(codes) != 4 {
|
|
| 26 |
+ t.Fatalf("Expected 4 codes, got %d", len(codes))
|
|
| 27 |
+ } |
|
| 28 |
+ if codes[0] != 0 || codes[1] != 27 || codes[2] != 126 || codes[3] != 15 {
|
|
| 29 |
+ t.Fatalf("Expected '0' '27' '126', '15', got '%d' '%d' '%d' '%d'", codes[0], codes[1], codes[2], codes[3])
|
|
| 30 |
+ } |
|
| 31 |
+ |
|
| 32 |
+ codes, err = ToBytes("DEL,+")
|
|
| 33 |
+ if err != nil {
|
|
| 34 |
+ t.Fatal(err) |
|
| 35 |
+ } |
|
| 36 |
+ if len(codes) != 2 {
|
|
| 37 |
+ t.Fatalf("Expected 2 codes, got %d", len(codes))
|
|
| 38 |
+ } |
|
| 39 |
+ if codes[0] != 127 || codes[1] != 43 {
|
|
| 40 |
+ t.Fatalf("Expected '127 '43'', got '%d' '%d'", codes[0], codes[1])
|
|
| 41 |
+ } |
|
| 42 |
+} |