diff --git a/cli/command/container/client_test.go b/cli/command/container/client_test.go index 3ff1792d51..b883ad1db1 100644 --- a/cli/command/container/client_test.go +++ b/cli/command/container/client_test.go @@ -12,20 +12,24 @@ import ( type fakeClient struct { client.Client - inspectFunc func(string) (types.ContainerJSON, error) - execInspectFunc func(execID string) (types.ContainerExecInspect, error) - execCreateFunc func(container string, config types.ExecConfig) (types.IDResponse, error) - createContainerFunc func(config *container.Config, hostConfig *container.HostConfig, networkingConfig *network.NetworkingConfig, containerName string) (container.ContainerCreateCreatedBody, error) - containerStartFunc func(container string, options types.ContainerStartOptions) error - imageCreateFunc func(parentReference string, options types.ImageCreateOptions) (io.ReadCloser, error) - infoFunc func() (types.Info, error) - containerStatPathFunc func(container, path string) (types.ContainerPathStat, error) - containerCopyFromFunc func(container, srcPath string) (io.ReadCloser, types.ContainerPathStat, error) - logFunc func(string, types.ContainerLogsOptions) (io.ReadCloser, error) - waitFunc func(string) (<-chan container.ContainerWaitOKBody, <-chan error) - containerListFunc func(types.ContainerListOptions) ([]types.Container, error) - containerExportFunc func(string) (io.ReadCloser, error) - Version string + inspectFunc func(string) (types.ContainerJSON, error) + execInspectFunc func(execID string) (types.ContainerExecInspect, error) + execCreateFunc func(container string, config types.ExecConfig) (types.IDResponse, error) + createContainerFunc func(config *container.Config, + hostConfig *container.HostConfig, + networkingConfig *network.NetworkingConfig, + containerName string) (container.ContainerCreateCreatedBody, error) + containerStartFunc func(container string, options types.ContainerStartOptions) error + imageCreateFunc func(parentReference string, options types.ImageCreateOptions) (io.ReadCloser, error) + infoFunc func() (types.Info, error) + containerStatPathFunc func(container, path string) (types.ContainerPathStat, error) + containerCopyFromFunc func(container, srcPath string) (io.ReadCloser, types.ContainerPathStat, error) + logFunc func(string, types.ContainerLogsOptions) (io.ReadCloser, error) + waitFunc func(string) (<-chan container.ContainerWaitOKBody, <-chan error) + containerListFunc func(types.ContainerListOptions) ([]types.Container, error) + containerExportFunc func(string) (io.ReadCloser, error) + containerExecResizeFunc func(id string, options types.ResizeOptions) error + Version string } func (f *fakeClient) ContainerList(_ context.Context, options types.ContainerListOptions) ([]types.Container, error) { @@ -132,3 +136,10 @@ func (f *fakeClient) ContainerExport(_ context.Context, container string) (io.Re } return nil, nil } + +func (f *fakeClient) ContainerExecResize(_ context.Context, id string, options types.ResizeOptions) error { + if f.containerExecResizeFunc != nil { + return f.containerExecResizeFunc(id, options) + } + return nil +} diff --git a/cli/command/container/tty.go b/cli/command/container/tty.go index cb49ded8ef..b7003f1a04 100644 --- a/cli/command/container/tty.go +++ b/cli/command/container/tty.go @@ -16,9 +16,9 @@ import ( ) // resizeTtyTo resizes tty to specific height and width -func resizeTtyTo(ctx context.Context, client client.ContainerAPIClient, id string, height, width uint, isExec bool) { +func resizeTtyTo(ctx context.Context, client client.ContainerAPIClient, id string, height, width uint, isExec bool) error { if height == 0 && width == 0 { - return + return nil } options := types.ResizeOptions{ @@ -34,19 +34,42 @@ func resizeTtyTo(ctx context.Context, client client.ContainerAPIClient, id strin } if err != nil { - logrus.Debugf("Error resize: %s", err) + logrus.Debugf("Error resize: %s\r", err) + } + return err +} + +// resizeTty is to resize the tty with cli out's tty size +func resizeTty(ctx context.Context, cli command.Cli, id string, isExec bool) error { + height, width := cli.Out().GetTtySize() + return resizeTtyTo(ctx, cli.Client(), id, height, width, isExec) +} + +// initTtySize is to init the tty's size to the same as the window, if there is an error, it will retry 5 times. +func initTtySize(ctx context.Context, cli command.Cli, id string, isExec bool, resizeTtyFunc func(ctx context.Context, cli command.Cli, id string, isExec bool) error) { + rttyFunc := resizeTtyFunc + if rttyFunc == nil { + rttyFunc = resizeTty + } + if err := rttyFunc(ctx, cli, id, isExec); err != nil { + go func() { + var err error + for retry := 0; retry < 5; retry++ { + time.Sleep(10 * time.Millisecond) + if err = rttyFunc(ctx, cli, id, isExec); err == nil { + break + } + } + if err != nil { + fmt.Fprintln(cli.Err(), "failed to resize tty, using default size") + } + }() } } // MonitorTtySize updates the container tty size when the terminal tty changes size func MonitorTtySize(ctx context.Context, cli command.Cli, id string, isExec bool) error { - resizeTty := func() { - height, width := cli.Out().GetTtySize() - resizeTtyTo(ctx, cli.Client(), id, height, width, isExec) - } - - resizeTty() - + initTtySize(ctx, cli, id, isExec, resizeTty) if runtime.GOOS == "windows" { go func() { prevH, prevW := cli.Out().GetTtySize() @@ -55,7 +78,7 @@ func MonitorTtySize(ctx context.Context, cli command.Cli, id string, isExec bool h, w := cli.Out().GetTtySize() if prevW != w || prevH != h { - resizeTty() + resizeTty(ctx, cli, id, isExec) } prevH = h prevW = w @@ -66,7 +89,7 @@ func MonitorTtySize(ctx context.Context, cli command.Cli, id string, isExec bool gosignal.Notify(sigchan, signal.SIGWINCH) go func() { for range sigchan { - resizeTty() + resizeTty(ctx, cli, id, isExec) } }() } diff --git a/cli/command/container/tty_test.go b/cli/command/container/tty_test.go new file mode 100644 index 0000000000..eb2e0bfaab --- /dev/null +++ b/cli/command/container/tty_test.go @@ -0,0 +1,30 @@ +package container + +import ( + "context" + "testing" + "time" + + "github.com/docker/cli/cli/command" + "github.com/docker/cli/internal/test" + "github.com/docker/docker/api/types" + "github.com/pkg/errors" + "gotest.tools/assert" + is "gotest.tools/assert/cmp" +) + +func TestInitTtySizeErrors(t *testing.T) { + expectedError := "failed to resize tty, using default size\n" + fakeContainerExecResizeFunc := func(id string, options types.ResizeOptions) error { + return errors.Errorf("Error response from daemon: no such exec") + } + fakeResizeTtyFunc := func(ctx context.Context, cli command.Cli, id string, isExec bool) error { + height, width := uint(1024), uint(768) + return resizeTtyTo(ctx, cli.Client(), id, height, width, isExec) + } + ctx := context.Background() + cli := test.NewFakeCli(&fakeClient{containerExecResizeFunc: fakeContainerExecResizeFunc}) + initTtySize(ctx, cli, "8mm8nn8tt8bb", true, fakeResizeTtyFunc) + time.Sleep(100 * time.Millisecond) + assert.Check(t, is.Equal(expectedError, cli.ErrBuffer().String())) +}