Fix #21064
Let client print error message explicitly when user specifies wrong
detach keys.
Signed-off-by: Zhang Wei <zhangwei555@huawei.com>
| ... | ... |
@@ -3,6 +3,7 @@ package client |
| 3 | 3 |
import ( |
| 4 | 4 |
"fmt" |
| 5 | 5 |
"io" |
| 6 |
+ "net/http/httputil" |
|
| 6 | 7 |
|
| 7 | 8 |
"golang.org/x/net/context" |
| 8 | 9 |
|
| ... | ... |
@@ -66,9 +67,12 @@ func (cli *DockerCli) CmdAttach(args ...string) error {
|
| 66 | 66 |
defer signal.StopCatch(sigc) |
| 67 | 67 |
} |
| 68 | 68 |
|
| 69 |
- resp, err := cli.client.ContainerAttach(context.Background(), options) |
|
| 70 |
- if err != nil {
|
|
| 71 |
- return err |
|
| 69 |
+ resp, errAttach := cli.client.ContainerAttach(context.Background(), options) |
|
| 70 |
+ if errAttach != nil && errAttach != httputil.ErrPersistEOF {
|
|
| 71 |
+ // ContainerAttach returns an ErrPersistEOF (connection closed) |
|
| 72 |
+ // means server met an error and put it in Hijacked connection |
|
| 73 |
+ // keep the error and read detailed error message from hijacked connection later |
|
| 74 |
+ return errAttach |
|
| 72 | 75 |
} |
| 73 | 76 |
defer resp.Close() |
| 74 | 77 |
|
| ... | ... |
@@ -90,6 +94,10 @@ func (cli *DockerCli) CmdAttach(args ...string) error {
|
| 90 | 90 |
return err |
| 91 | 91 |
} |
| 92 | 92 |
|
| 93 |
+ if errAttach != nil {
|
|
| 94 |
+ return errAttach |
|
| 95 |
+ } |
|
| 96 |
+ |
|
| 93 | 97 |
_, status, err := getExitCode(cli, options.ContainerID) |
| 94 | 98 |
if err != nil {
|
| 95 | 99 |
return err |
| ... | ... |
@@ -3,6 +3,7 @@ package client |
| 3 | 3 |
import ( |
| 4 | 4 |
"fmt" |
| 5 | 5 |
"io" |
| 6 |
+ "net/http/httputil" |
|
| 6 | 7 |
"os" |
| 7 | 8 |
"runtime" |
| 8 | 9 |
"strings" |
| ... | ... |
@@ -205,19 +206,26 @@ func (cli *DockerCli) CmdRun(args ...string) error {
|
| 205 | 205 |
DetachKeys: cli.configFile.DetachKeys, |
| 206 | 206 |
} |
| 207 | 207 |
|
| 208 |
- resp, err := cli.client.ContainerAttach(context.Background(), options) |
|
| 209 |
- if err != nil {
|
|
| 210 |
- return err |
|
| 208 |
+ resp, errAttach := cli.client.ContainerAttach(context.Background(), options) |
|
| 209 |
+ if errAttach != nil && errAttach != httputil.ErrPersistEOF {
|
|
| 210 |
+ // ContainerAttach returns an ErrPersistEOF (connection closed) |
|
| 211 |
+ // means server met an error and put it in Hijacked connection |
|
| 212 |
+ // keep the error and read detailed error message from hijacked connection later |
|
| 213 |
+ return errAttach |
|
| 211 | 214 |
} |
| 212 | 215 |
ctx, cancelFun = context.WithCancel(context.Background()) |
| 213 | 216 |
errCh = promise.Go(func() error {
|
| 214 |
- return cli.holdHijackedConnection(ctx, config.Tty, in, out, stderr, resp) |
|
| 217 |
+ errHijack := cli.holdHijackedConnection(ctx, config.Tty, in, out, stderr, resp) |
|
| 218 |
+ if errHijack == nil {
|
|
| 219 |
+ return errAttach |
|
| 220 |
+ } |
|
| 221 |
+ return errHijack |
|
| 215 | 222 |
}) |
| 216 | 223 |
} |
| 217 | 224 |
|
| 218 | 225 |
if *flAutoRemove {
|
| 219 | 226 |
defer func() {
|
| 220 |
- if err := cli.removeContainer(createResponse.ID, true, false, false); err != nil {
|
|
| 227 |
+ if err := cli.removeContainer(createResponse.ID, true, false, true); err != nil {
|
|
| 221 | 228 |
fmt.Fprintf(cli.err, "%v\n", err) |
| 222 | 229 |
} |
| 223 | 230 |
}() |
| ... | ... |
@@ -3,6 +3,7 @@ package client |
| 3 | 3 |
import ( |
| 4 | 4 |
"fmt" |
| 5 | 5 |
"io" |
| 6 |
+ "net/http/httputil" |
|
| 6 | 7 |
"os" |
| 7 | 8 |
"strings" |
| 8 | 9 |
|
| ... | ... |
@@ -94,14 +95,21 @@ func (cli *DockerCli) CmdStart(args ...string) error {
|
| 94 | 94 |
in = cli.in |
| 95 | 95 |
} |
| 96 | 96 |
|
| 97 |
- resp, err := cli.client.ContainerAttach(context.Background(), options) |
|
| 98 |
- if err != nil {
|
|
| 99 |
- return err |
|
| 97 |
+ resp, errAttach := cli.client.ContainerAttach(context.Background(), options) |
|
| 98 |
+ if errAttach != nil && errAttach != httputil.ErrPersistEOF {
|
|
| 99 |
+ // ContainerAttach return an ErrPersistEOF (connection closed) |
|
| 100 |
+ // means server met an error and put it in Hijacked connection |
|
| 101 |
+ // keep the error and read detailed error message from hijacked connection |
|
| 102 |
+ return errAttach |
|
| 100 | 103 |
} |
| 101 | 104 |
defer resp.Close() |
| 102 | 105 |
ctx, cancelFun := context.WithCancel(context.Background()) |
| 103 | 106 |
cErr := promise.Go(func() error {
|
| 104 |
- return cli.holdHijackedConnection(ctx, c.Config.Tty, in, cli.out, cli.err, resp) |
|
| 107 |
+ errHijack := cli.holdHijackedConnection(ctx, c.Config.Tty, in, cli.out, cli.err, resp) |
|
| 108 |
+ if errHijack == nil {
|
|
| 109 |
+ return errAttach |
|
| 110 |
+ } |
|
| 111 |
+ return errHijack |
|
| 105 | 112 |
}) |
| 106 | 113 |
|
| 107 | 114 |
// 3. Start the container. |
| ... | ... |
@@ -24,11 +24,11 @@ type inputValidationError interface {
|
| 24 | 24 |
IsValidationError() bool |
| 25 | 25 |
} |
| 26 | 26 |
|
| 27 |
-// WriteError decodes a specific docker error and sends it in the response. |
|
| 28 |
-func WriteError(w http.ResponseWriter, err error) {
|
|
| 29 |
- if err == nil || w == nil {
|
|
| 30 |
- logrus.WithFields(logrus.Fields{"error": err, "writer": w}).Error("unexpected HTTP error handling")
|
|
| 31 |
- return |
|
| 27 |
+// GetHTTPErrorStatusCode retrieve status code from error message |
|
| 28 |
+func GetHTTPErrorStatusCode(err error) int {
|
|
| 29 |
+ if err == nil {
|
|
| 30 |
+ logrus.WithFields(logrus.Fields{"error": err}).Error("unexpected HTTP error handling")
|
|
| 31 |
+ return http.StatusInternalServerError |
|
| 32 | 32 |
} |
| 33 | 33 |
|
| 34 | 34 |
var statusCode int |
| ... | ... |
@@ -65,5 +65,16 @@ func WriteError(w http.ResponseWriter, err error) {
|
| 65 | 65 |
statusCode = http.StatusInternalServerError |
| 66 | 66 |
} |
| 67 | 67 |
|
| 68 |
- http.Error(w, errMsg, statusCode) |
|
| 68 |
+ return statusCode |
|
| 69 |
+} |
|
| 70 |
+ |
|
| 71 |
+// WriteError decodes a specific docker error and sends it in the response. |
|
| 72 |
+func WriteError(w http.ResponseWriter, err error) {
|
|
| 73 |
+ if err == nil || w == nil {
|
|
| 74 |
+ logrus.WithFields(logrus.Fields{"error": err, "writer": w}).Error("unexpected HTTP error handling")
|
|
| 75 |
+ return |
|
| 76 |
+ } |
|
| 77 |
+ |
|
| 78 |
+ statusCode := GetHTTPErrorStatusCode(err) |
|
| 79 |
+ http.Error(w, err.Error(), statusCode) |
|
| 69 | 80 |
} |
| ... | ... |
@@ -15,7 +15,6 @@ import ( |
| 15 | 15 |
"github.com/docker/docker/api/types/backend" |
| 16 | 16 |
"github.com/docker/docker/pkg/ioutils" |
| 17 | 17 |
"github.com/docker/docker/pkg/signal" |
| 18 |
- "github.com/docker/docker/pkg/term" |
|
| 19 | 18 |
"github.com/docker/engine-api/types" |
| 20 | 19 |
"github.com/docker/engine-api/types/container" |
| 21 | 20 |
"github.com/docker/engine-api/types/filters" |
| ... | ... |
@@ -408,15 +407,7 @@ func (s *containerRouter) postContainersAttach(ctx context.Context, w http.Respo |
| 408 | 408 |
containerName := vars["name"] |
| 409 | 409 |
|
| 410 | 410 |
_, upgrade := r.Header["Upgrade"] |
| 411 |
- |
|
| 412 |
- keys := []byte{}
|
|
| 413 | 411 |
detachKeys := r.FormValue("detachKeys")
|
| 414 |
- if detachKeys != "" {
|
|
| 415 |
- keys, err = term.ToBytes(detachKeys) |
|
| 416 |
- if err != nil {
|
|
| 417 |
- logrus.Warnf("Invalid escape keys provided (%s) using default : ctrl-p ctrl-q", detachKeys)
|
|
| 418 |
- } |
|
| 419 |
- } |
|
| 420 | 412 |
|
| 421 | 413 |
hijacker, ok := w.(http.Hijacker) |
| 422 | 414 |
if !ok {
|
| ... | ... |
@@ -452,11 +443,24 @@ func (s *containerRouter) postContainersAttach(ctx context.Context, w http.Respo |
| 452 | 452 |
UseStderr: httputils.BoolValue(r, "stderr"), |
| 453 | 453 |
Logs: httputils.BoolValue(r, "logs"), |
| 454 | 454 |
Stream: httputils.BoolValue(r, "stream"), |
| 455 |
- DetachKeys: keys, |
|
| 455 |
+ DetachKeys: detachKeys, |
|
| 456 | 456 |
MuxStreams: true, |
| 457 | 457 |
} |
| 458 | 458 |
|
| 459 |
- return s.backend.ContainerAttach(containerName, attachConfig) |
|
| 459 |
+ if err = s.backend.ContainerAttach(containerName, attachConfig); err != nil {
|
|
| 460 |
+ logrus.Errorf("Handler for %s %s returned error: %v", r.Method, r.URL.Path, err)
|
|
| 461 |
+ // Remember to close stream if error happens |
|
| 462 |
+ conn, _, errHijack := hijacker.Hijack() |
|
| 463 |
+ if errHijack == nil {
|
|
| 464 |
+ statusCode := httputils.GetHTTPErrorStatusCode(err) |
|
| 465 |
+ statusText := http.StatusText(statusCode) |
|
| 466 |
+ fmt.Fprintf(conn, "HTTP/1.1 %d %s\r\nContent-Type: application/vnd.docker.raw-stream\r\n\r\n%s\r\n", statusCode, statusText, err.Error()) |
|
| 467 |
+ httputils.CloseStreams(conn) |
|
| 468 |
+ } else {
|
|
| 469 |
+ logrus.Errorf("Error Hijacking: %v", err)
|
|
| 470 |
+ } |
|
| 471 |
+ } |
|
| 472 |
+ return nil |
|
| 460 | 473 |
} |
| 461 | 474 |
|
| 462 | 475 |
func (s *containerRouter) wsContainersAttach(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
| ... | ... |
@@ -465,15 +469,8 @@ func (s *containerRouter) wsContainersAttach(ctx context.Context, w http.Respons |
| 465 | 465 |
} |
| 466 | 466 |
containerName := vars["name"] |
| 467 | 467 |
|
| 468 |
- var keys []byte |
|
| 469 | 468 |
var err error |
| 470 | 469 |
detachKeys := r.FormValue("detachKeys")
|
| 471 |
- if detachKeys != "" {
|
|
| 472 |
- keys, err = term.ToBytes(detachKeys) |
|
| 473 |
- if err != nil {
|
|
| 474 |
- logrus.Warnf("Invalid escape keys provided (%s) using default : ctrl-p ctrl-q", detachKeys)
|
|
| 475 |
- } |
|
| 476 |
- } |
|
| 477 | 470 |
|
| 478 | 471 |
done := make(chan struct{})
|
| 479 | 472 |
started := make(chan struct{})
|
| ... | ... |
@@ -499,7 +496,7 @@ func (s *containerRouter) wsContainersAttach(ctx context.Context, w http.Respons |
| 499 | 499 |
GetStreams: setupStreams, |
| 500 | 500 |
Logs: httputils.BoolValue(r, "logs"), |
| 501 | 501 |
Stream: httputils.BoolValue(r, "stream"), |
| 502 |
- DetachKeys: keys, |
|
| 502 |
+ DetachKeys: detachKeys, |
|
| 503 | 503 |
UseStdin: true, |
| 504 | 504 |
UseStdout: true, |
| 505 | 505 |
UseStderr: true, |
| ... | ... |
@@ -18,7 +18,7 @@ type ContainerAttachConfig struct {
|
| 18 | 18 |
UseStderr bool |
| 19 | 19 |
Logs bool |
| 20 | 20 |
Stream bool |
| 21 |
- DetachKeys []byte |
|
| 21 |
+ DetachKeys string |
|
| 22 | 22 |
|
| 23 | 23 |
// Used to signify that streams are multiplexed and therefore need a StdWriter to encode stdout/sderr messages accordingly. |
| 24 | 24 |
// TODO @cpuguy83: This shouldn't be needed. It was only added so that http and websocket endpoints can use the same function, and the websocket function was not using a stdwriter prior to this change... |
| ... | ... |
@@ -11,10 +11,20 @@ import ( |
| 11 | 11 |
"github.com/docker/docker/daemon/logger" |
| 12 | 12 |
"github.com/docker/docker/errors" |
| 13 | 13 |
"github.com/docker/docker/pkg/stdcopy" |
| 14 |
+ "github.com/docker/docker/pkg/term" |
|
| 14 | 15 |
) |
| 15 | 16 |
|
| 16 | 17 |
// ContainerAttach attaches to logs according to the config passed in. See ContainerAttachConfig. |
| 17 | 18 |
func (daemon *Daemon) ContainerAttach(prefixOrName string, c *backend.ContainerAttachConfig) error {
|
| 19 |
+ keys := []byte{}
|
|
| 20 |
+ var err error |
|
| 21 |
+ if c.DetachKeys != "" {
|
|
| 22 |
+ keys, err = term.ToBytes(c.DetachKeys) |
|
| 23 |
+ if err != nil {
|
|
| 24 |
+ return fmt.Errorf("Invalid escape keys (%s) provided", c.DetachKeys)
|
|
| 25 |
+ } |
|
| 26 |
+ } |
|
| 27 |
+ |
|
| 18 | 28 |
container, err := daemon.GetContainer(prefixOrName) |
| 19 | 29 |
if err != nil {
|
| 20 | 30 |
return err |
| ... | ... |
@@ -48,7 +58,7 @@ func (daemon *Daemon) ContainerAttach(prefixOrName string, c *backend.ContainerA |
| 48 | 48 |
stderr = errStream |
| 49 | 49 |
} |
| 50 | 50 |
|
| 51 |
- if err := daemon.containerAttach(container, stdin, stdout, stderr, c.Logs, c.Stream, c.DetachKeys); err != nil {
|
|
| 51 |
+ if err := daemon.containerAttach(container, stdin, stdout, stderr, c.Logs, c.Stream, keys); err != nil {
|
|
| 52 | 52 |
fmt.Fprintf(outStream, "Error attaching: %s\n", err) |
| 53 | 53 |
} |
| 54 | 54 |
return nil |
| ... | ... |
@@ -101,7 +101,8 @@ func (d *Daemon) ContainerExecCreate(config *types.ExecConfig) (string, error) {
|
| 101 | 101 |
if config.DetachKeys != "" {
|
| 102 | 102 |
keys, err = term.ToBytes(config.DetachKeys) |
| 103 | 103 |
if err != nil {
|
| 104 |
- logrus.Warnf("Wrong escape keys provided (%s, error: %s) using default : ctrl-p ctrl-q", config.DetachKeys, err.Error())
|
|
| 104 |
+ err = fmt.Errorf("Invalid escape keys (%s) provided", config.DetachKeys)
|
|
| 105 |
+ return "", err |
|
| 105 | 106 |
} |
| 106 | 107 |
} |
| 107 | 108 |
|
| ... | ... |
@@ -67,11 +67,18 @@ func (s *DockerSuite) TestGetContainersAttachWebsocket(c *check.C) {
|
| 67 | 67 |
|
| 68 | 68 |
// regression gh14320 |
| 69 | 69 |
func (s *DockerSuite) TestPostContainersAttachContainerNotFound(c *check.C) {
|
| 70 |
- status, body, err := sockRequest("POST", "/containers/doesnotexist/attach", nil)
|
|
| 71 |
- c.Assert(status, checker.Equals, http.StatusNotFound) |
|
| 70 |
+ req, client, err := newRequestClient("POST", "/containers/doesnotexist/attach", nil, "")
|
|
| 72 | 71 |
c.Assert(err, checker.IsNil) |
| 73 |
- expected := "No such container: doesnotexist\n" |
|
| 74 |
- c.Assert(string(body), checker.Contains, expected) |
|
| 72 |
+ |
|
| 73 |
+ resp, err := client.Do(req) |
|
| 74 |
+ // connection will shutdown, err should be "persistent connection closed" |
|
| 75 |
+ c.Assert(err, checker.NotNil) // Server shutdown connection |
|
| 76 |
+ |
|
| 77 |
+ body, err := readBody(resp.Body) |
|
| 78 |
+ c.Assert(err, checker.IsNil) |
|
| 79 |
+ c.Assert(resp.StatusCode, checker.Equals, http.StatusNotFound) |
|
| 80 |
+ expected := "No such container: doesnotexist\r\n" |
|
| 81 |
+ c.Assert(string(body), checker.Equals, expected) |
|
| 75 | 82 |
} |
| 76 | 83 |
|
| 77 | 84 |
func (s *DockerSuite) TestGetContainersWsAttachContainerNotFound(c *check.C) {
|
| ... | ... |
@@ -197,6 +197,38 @@ func (s *DockerSuite) TestRunAttachDetachFromFlag(c *check.C) {
|
| 197 | 197 |
c.Assert(running, checker.Equals, "true", check.Commentf("expected container to still be running"))
|
| 198 | 198 |
} |
| 199 | 199 |
|
| 200 |
+// TestRunDetach checks attaching and detaching with the escape sequence specified via flags. |
|
| 201 |
+func (s *DockerSuite) TestRunAttachDetachFromInvalidFlag(c *check.C) {
|
|
| 202 |
+ name := "attach-detach" |
|
| 203 |
+ dockerCmd(c, "run", "--name", name, "-itd", "busybox", "top") |
|
| 204 |
+ c.Assert(waitRun(name), check.IsNil) |
|
| 205 |
+ |
|
| 206 |
+ // specify an invalid detach key, container will ignore it and use default |
|
| 207 |
+ cmd := exec.Command(dockerBinary, "attach", "--detach-keys='ctrl-A,a'", name) |
|
| 208 |
+ stdout, err := cmd.StdoutPipe() |
|
| 209 |
+ if err != nil {
|
|
| 210 |
+ c.Fatal(err) |
|
| 211 |
+ } |
|
| 212 |
+ cpty, tty, err := pty.Open() |
|
| 213 |
+ if err != nil {
|
|
| 214 |
+ c.Fatal(err) |
|
| 215 |
+ } |
|
| 216 |
+ defer cpty.Close() |
|
| 217 |
+ cmd.Stdin = tty |
|
| 218 |
+ if err := cmd.Start(); err != nil {
|
|
| 219 |
+ c.Fatal(err) |
|
| 220 |
+ } |
|
| 221 |
+ |
|
| 222 |
+ bufReader := bufio.NewReader(stdout) |
|
| 223 |
+ out, err := bufReader.ReadString('\n')
|
|
| 224 |
+ if err != nil {
|
|
| 225 |
+ c.Fatal(err) |
|
| 226 |
+ } |
|
| 227 |
+ // it should print a warning to indicate the detach key flag is invalid |
|
| 228 |
+ errStr := "Invalid escape keys (ctrl-A,a) provided" |
|
| 229 |
+ c.Assert(strings.TrimSpace(out), checker.Equals, errStr) |
|
| 230 |
+} |
|
| 231 |
+ |
|
| 200 | 232 |
// TestRunDetach checks attaching and detaching with the escape sequence specified via config file. |
| 201 | 233 |
func (s *DockerSuite) TestRunAttachDetachFromConfig(c *check.C) {
|
| 202 | 234 |
keyCtrlA := []byte{1}
|