зеркало из https://github.com/microsoft/docker.git
Merge pull request #8233 from tiborvass/pr-7658
Fix Interactive container hangs when redirecting stdout
This commit is contained in:
Коммит
0bb5f98731
|
@ -22,10 +22,16 @@ type DockerCli struct {
|
||||||
in io.ReadCloser
|
in io.ReadCloser
|
||||||
out io.Writer
|
out io.Writer
|
||||||
err io.Writer
|
err io.Writer
|
||||||
isTerminal bool
|
|
||||||
terminalFd uintptr
|
|
||||||
tlsConfig *tls.Config
|
tlsConfig *tls.Config
|
||||||
scheme string
|
scheme string
|
||||||
|
// inFd holds file descriptor of the client's STDIN, if it's a valid file
|
||||||
|
inFd uintptr
|
||||||
|
// outFd holds file descriptor of the client's STDOUT, if it's a valid file
|
||||||
|
outFd uintptr
|
||||||
|
// isTerminalIn describes if client's STDIN is a TTY
|
||||||
|
isTerminalIn bool
|
||||||
|
// isTerminalOut describes if client's STDOUT is a TTY
|
||||||
|
isTerminalOut bool
|
||||||
}
|
}
|
||||||
|
|
||||||
var funcMap = template.FuncMap{
|
var funcMap = template.FuncMap{
|
||||||
|
@ -94,8 +100,10 @@ func (cli *DockerCli) LoadConfigFile() (err error) {
|
||||||
|
|
||||||
func NewDockerCli(in io.ReadCloser, out, err io.Writer, proto, addr string, tlsConfig *tls.Config) *DockerCli {
|
func NewDockerCli(in io.ReadCloser, out, err io.Writer, proto, addr string, tlsConfig *tls.Config) *DockerCli {
|
||||||
var (
|
var (
|
||||||
isTerminal = false
|
inFd uintptr
|
||||||
terminalFd uintptr
|
outFd uintptr
|
||||||
|
isTerminalIn = false
|
||||||
|
isTerminalOut = false
|
||||||
scheme = "http"
|
scheme = "http"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -104,23 +112,33 @@ func NewDockerCli(in io.ReadCloser, out, err io.Writer, proto, addr string, tlsC
|
||||||
}
|
}
|
||||||
|
|
||||||
if in != nil {
|
if in != nil {
|
||||||
|
if file, ok := in.(*os.File); ok {
|
||||||
|
inFd = file.Fd()
|
||||||
|
isTerminalIn = term.IsTerminal(inFd)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if out != nil {
|
||||||
if file, ok := out.(*os.File); ok {
|
if file, ok := out.(*os.File); ok {
|
||||||
terminalFd = file.Fd()
|
outFd = file.Fd()
|
||||||
isTerminal = term.IsTerminal(terminalFd)
|
isTerminalOut = term.IsTerminal(outFd)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
err = out
|
err = out
|
||||||
}
|
}
|
||||||
|
|
||||||
return &DockerCli{
|
return &DockerCli{
|
||||||
proto: proto,
|
proto: proto,
|
||||||
addr: addr,
|
addr: addr,
|
||||||
in: in,
|
in: in,
|
||||||
out: out,
|
out: out,
|
||||||
err: err,
|
err: err,
|
||||||
isTerminal: isTerminal,
|
inFd: inFd,
|
||||||
terminalFd: terminalFd,
|
outFd: outFd,
|
||||||
|
isTerminalIn: isTerminalIn,
|
||||||
|
isTerminalOut: isTerminalOut,
|
||||||
tlsConfig: tlsConfig,
|
tlsConfig: tlsConfig,
|
||||||
scheme: scheme,
|
scheme: scheme,
|
||||||
}
|
}
|
||||||
|
|
|
@ -277,14 +277,14 @@ func (cli *DockerCli) CmdLogin(args ...string) error {
|
||||||
// the password or email from the config file, so prompt them
|
// the password or email from the config file, so prompt them
|
||||||
if username != authconfig.Username {
|
if username != authconfig.Username {
|
||||||
if password == "" {
|
if password == "" {
|
||||||
oldState, _ := term.SaveState(cli.terminalFd)
|
oldState, _ := term.SaveState(cli.inFd)
|
||||||
fmt.Fprintf(cli.out, "Password: ")
|
fmt.Fprintf(cli.out, "Password: ")
|
||||||
term.DisableEcho(cli.terminalFd, oldState)
|
term.DisableEcho(cli.inFd, oldState)
|
||||||
|
|
||||||
password = readInput(cli.in, cli.out)
|
password = readInput(cli.in, cli.out)
|
||||||
fmt.Fprint(cli.out, "\n")
|
fmt.Fprint(cli.out, "\n")
|
||||||
|
|
||||||
term.RestoreTerminal(cli.terminalFd, oldState)
|
term.RestoreTerminal(cli.inFd, oldState)
|
||||||
if password == "" {
|
if password == "" {
|
||||||
return fmt.Errorf("Error : Password Required")
|
return fmt.Errorf("Error : Password Required")
|
||||||
}
|
}
|
||||||
|
@ -669,7 +669,7 @@ func (cli *DockerCli) CmdStart(args ...string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
if *openStdin || *attach {
|
if *openStdin || *attach {
|
||||||
if tty && cli.isTerminal {
|
if tty && cli.isTerminalOut {
|
||||||
if err := cli.monitorTtySize(cmd.Arg(0), false); err != nil {
|
if err := cli.monitorTtySize(cmd.Arg(0), false); err != nil {
|
||||||
log.Errorf("Error monitoring TTY size: %s", err)
|
log.Errorf("Error monitoring TTY size: %s", err)
|
||||||
}
|
}
|
||||||
|
@ -1821,7 +1821,7 @@ func (cli *DockerCli) CmdAttach(args ...string) error {
|
||||||
tty = config.GetBool("Tty")
|
tty = config.GetBool("Tty")
|
||||||
)
|
)
|
||||||
|
|
||||||
if tty && cli.isTerminal {
|
if tty && cli.isTerminalOut {
|
||||||
if err := cli.monitorTtySize(cmd.Arg(0), false); err != nil {
|
if err := cli.monitorTtySize(cmd.Arg(0), false); err != nil {
|
||||||
log.Debugf("Error monitoring TTY size: %s", err)
|
log.Debugf("Error monitoring TTY size: %s", err)
|
||||||
}
|
}
|
||||||
|
@ -2241,7 +2241,7 @@ func (cli *DockerCli) CmdRun(args ...string) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config.AttachStdin || config.AttachStdout || config.AttachStderr) && config.Tty && cli.isTerminal {
|
if (config.AttachStdin || config.AttachStdout || config.AttachStderr) && config.Tty && cli.isTerminalOut {
|
||||||
if err := cli.monitorTtySize(runResult.Get("Id"), false); err != nil {
|
if err := cli.monitorTtySize(runResult.Get("Id"), false); err != nil {
|
||||||
log.Errorf("Error monitoring TTY size: %s", err)
|
log.Errorf("Error monitoring TTY size: %s", err)
|
||||||
}
|
}
|
||||||
|
@ -2490,7 +2490,7 @@ func (cli *DockerCli) CmdExec(args ...string) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if execConfig.Tty && cli.isTerminal {
|
if execConfig.Tty && cli.isTerminalIn {
|
||||||
if err := cli.monitorTtySize(execID, true); err != nil {
|
if err := cli.monitorTtySize(execID, true); err != nil {
|
||||||
log.Errorf("Error monitoring TTY size: %s", err)
|
log.Errorf("Error monitoring TTY size: %s", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -69,20 +69,20 @@ func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in io.Rea
|
||||||
|
|
||||||
var oldState *term.State
|
var oldState *term.State
|
||||||
|
|
||||||
if in != nil && setRawTerminal && cli.isTerminal && os.Getenv("NORAW") == "" {
|
if in != nil && setRawTerminal && cli.isTerminalIn && os.Getenv("NORAW") == "" {
|
||||||
oldState, err = term.SetRawTerminal(cli.terminalFd)
|
oldState, err = term.SetRawTerminal(cli.inFd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer term.RestoreTerminal(cli.terminalFd, oldState)
|
defer term.RestoreTerminal(cli.inFd, oldState)
|
||||||
}
|
}
|
||||||
|
|
||||||
if stdout != nil || stderr != nil {
|
if stdout != nil || stderr != nil {
|
||||||
receiveStdout = utils.Go(func() (err error) {
|
receiveStdout = utils.Go(func() (err error) {
|
||||||
defer func() {
|
defer func() {
|
||||||
if in != nil {
|
if in != nil {
|
||||||
if setRawTerminal && cli.isTerminal {
|
if setRawTerminal && cli.isTerminalIn {
|
||||||
term.RestoreTerminal(cli.terminalFd, oldState)
|
term.RestoreTerminal(cli.inFd, oldState)
|
||||||
}
|
}
|
||||||
// For some reason this Close call blocks on darwin..
|
// For some reason this Close call blocks on darwin..
|
||||||
// As the client exists right after, simply discard the close
|
// As the client exists right after, simply discard the close
|
||||||
|
@ -129,7 +129,7 @@ func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in io.Rea
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !cli.isTerminal {
|
if !cli.isTerminalIn {
|
||||||
if err := <-sendStdin; err != nil {
|
if err := <-sendStdin; err != nil {
|
||||||
log.Debugf("Error sendStdin: %s", err)
|
log.Debugf("Error sendStdin: %s", err)
|
||||||
return err
|
return err
|
||||||
|
|
|
@ -168,7 +168,7 @@ func (cli *DockerCli) streamHelper(method, path string, setRawTerminal bool, in
|
||||||
}
|
}
|
||||||
|
|
||||||
if api.MatchesContentType(resp.Header.Get("Content-Type"), "application/json") || api.MatchesContentType(resp.Header.Get("Content-Type"), "application/x-json-stream") {
|
if api.MatchesContentType(resp.Header.Get("Content-Type"), "application/json") || api.MatchesContentType(resp.Header.Get("Content-Type"), "application/x-json-stream") {
|
||||||
return utils.DisplayJSONMessagesStream(resp.Body, stdout, cli.terminalFd, cli.isTerminal)
|
return utils.DisplayJSONMessagesStream(resp.Body, stdout, cli.outFd, cli.isTerminalOut)
|
||||||
}
|
}
|
||||||
if stdout != nil || stderr != nil {
|
if stdout != nil || stderr != nil {
|
||||||
// When TTY is ON, use regular copy
|
// When TTY is ON, use regular copy
|
||||||
|
@ -252,10 +252,10 @@ func (cli *DockerCli) monitorTtySize(id string, isExec bool) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cli *DockerCli) getTtySize() (int, int) {
|
func (cli *DockerCli) getTtySize() (int, int) {
|
||||||
if !cli.isTerminal {
|
if !cli.isTerminalOut {
|
||||||
return 0, 0
|
return 0, 0
|
||||||
}
|
}
|
||||||
ws, err := term.GetWinsize(cli.terminalFd)
|
ws, err := term.GetWinsize(cli.outFd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Debugf("Error getting size: %s", err)
|
log.Debugf("Error getting size: %s", err)
|
||||||
if ws == nil {
|
if ws == nil {
|
||||||
|
|
|
@ -1,12 +1,18 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bufio"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
"unicode"
|
||||||
|
|
||||||
|
"github.com/kr/pty"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestEventsUntag(t *testing.T) {
|
func TestEventsUntag(t *testing.T) {
|
||||||
|
@ -166,3 +172,46 @@ func TestEventsImageUntagDelete(t *testing.T) {
|
||||||
}
|
}
|
||||||
logDone("events - image untag, delete is logged")
|
logDone("events - image untag, delete is logged")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// #5979
|
||||||
|
func TestEventsRedirectStdout(t *testing.T) {
|
||||||
|
|
||||||
|
since := time.Now().Unix()
|
||||||
|
|
||||||
|
cmd(t, "run", "busybox", "true")
|
||||||
|
|
||||||
|
defer deleteAllContainers()
|
||||||
|
|
||||||
|
file, err := ioutil.TempFile("", "")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("could not create temp file: %v", err)
|
||||||
|
}
|
||||||
|
defer os.Remove(file.Name())
|
||||||
|
|
||||||
|
command := fmt.Sprintf("%s events --since=%d --until=%d > %s", dockerBinary, since, time.Now().Unix(), file.Name())
|
||||||
|
_, tty, err := pty.Open()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Could not open pty: %v", err)
|
||||||
|
}
|
||||||
|
cmd := exec.Command("sh", "-c", command)
|
||||||
|
cmd.Stdin = tty
|
||||||
|
cmd.Stdout = tty
|
||||||
|
cmd.Stderr = tty
|
||||||
|
if err := cmd.Run(); err != nil {
|
||||||
|
t.Fatalf("run err for command %q: %v", command, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
scanner := bufio.NewScanner(file)
|
||||||
|
for scanner.Scan() {
|
||||||
|
for _, c := range scanner.Text() {
|
||||||
|
if unicode.IsControl(c) {
|
||||||
|
t.Fatalf("found control character %v", []byte(string(c)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := scanner.Err(); err != nil {
|
||||||
|
t.Fatalf("Scan err for command %q: %v", command, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
logDone("events - redirect stdout")
|
||||||
|
}
|
||||||
|
|
|
@ -19,6 +19,7 @@ import (
|
||||||
|
|
||||||
"github.com/docker/docker/pkg/mount"
|
"github.com/docker/docker/pkg/mount"
|
||||||
"github.com/docker/docker/pkg/networkfs/resolvconf"
|
"github.com/docker/docker/pkg/networkfs/resolvconf"
|
||||||
|
"github.com/kr/pty"
|
||||||
)
|
)
|
||||||
|
|
||||||
// "test123" should be printed by docker run
|
// "test123" should be printed by docker run
|
||||||
|
@ -2182,3 +2183,41 @@ func TestRunExecDir(t *testing.T) {
|
||||||
|
|
||||||
logDone("run - check execdriver dir behavior")
|
logDone("run - check execdriver dir behavior")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// #6509
|
||||||
|
func TestRunRedirectStdout(t *testing.T) {
|
||||||
|
|
||||||
|
defer deleteAllContainers()
|
||||||
|
|
||||||
|
checkRedirect := func(command string) {
|
||||||
|
_, tty, err := pty.Open()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Could not open pty: %v", err)
|
||||||
|
}
|
||||||
|
cmd := exec.Command("sh", "-c", command)
|
||||||
|
cmd.Stdin = tty
|
||||||
|
cmd.Stdout = tty
|
||||||
|
cmd.Stderr = tty
|
||||||
|
ch := make(chan struct{})
|
||||||
|
if err := cmd.Start(); err != nil {
|
||||||
|
t.Fatalf("start err: %v", err)
|
||||||
|
}
|
||||||
|
go func() {
|
||||||
|
if err := cmd.Wait(); err != nil {
|
||||||
|
t.Fatalf("wait err=%v", err)
|
||||||
|
}
|
||||||
|
close(ch)
|
||||||
|
}()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-time.After(time.Second):
|
||||||
|
t.Fatal("command timeout")
|
||||||
|
case <-ch:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
checkRedirect(dockerBinary + " run -i busybox cat /etc/passwd | grep -q root")
|
||||||
|
checkRedirect(dockerBinary + " run busybox cat /etc/passwd | grep -q root")
|
||||||
|
|
||||||
|
logDone("run - redirect stdout")
|
||||||
|
}
|
||||||
|
|
Загрузка…
Ссылка в новой задаче