Bladeren bron

daemon/linux: Set console size on creation

On Linux the daemon was not respecting the HostConfig.ConsoleSize
property and relied on cli initializing the tty size after the container
was created. This caused a delay between container creation and
the tty actually being resized.

This is also a small change to the api description, because
HostConfig.ConsoleSize is no longer Windows-only.

Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
Paweł Gronowski 3 jaren geleden
bovenliggende
commit
85a7f5a09a

+ 6 - 0
api/server/router/container/container_routes.go

@@ -6,6 +6,7 @@ import (
 	"fmt"
 	"io"
 	"net/http"
+	"runtime"
 	"strconv"
 
 	"github.com/containerd/containerd/platforms"
@@ -517,6 +518,11 @@ func (s *containerRouter) postContainersCreate(ctx context.Context, w http.Respo
 		hostConfig.KernelMemory = 0
 	}
 
+	if hostConfig != nil && runtime.GOOS == "linux" && versions.LessThan(version, "1.42") {
+		// ConsoleSize is not respected by Linux daemon before API 1.42
+		hostConfig.ConsoleSize = [2]uint{0, 0}
+	}
+
 	var platform *specs.Platform
 	if versions.GreaterThanOrEqualTo(version, "1.41") {
 		if v := r.Form.Get("platform"); v != "" {

+ 9 - 9
api/swagger.yaml

@@ -955,6 +955,15 @@ definitions:
             type: "array"
             items:
               $ref: "#/definitions/Mount"
+          ConsoleSize:
+            type: "array"
+            description: |
+              Initial console size, as an `[height, width]` array.
+            minItems: 2
+            maxItems: 2
+            items:
+              type: "integer"
+              minimum: 0
 
           # Applicable to UNIX platforms
           CapAdd:
@@ -1119,15 +1128,6 @@ definitions:
             type: "string"
             description: "Runtime to use with this container."
           # Applicable to Windows
-          ConsoleSize:
-            type: "array"
-            description: |
-              Initial console size, as an `[height, width]` array. (Windows only)
-            minItems: 2
-            maxItems: 2
-            items:
-              type: "integer"
-              minimum: 0
           Isolation:
             type: "string"
             description: |

+ 2 - 2
api/types/container/host_config.go

@@ -417,6 +417,7 @@ type HostConfig struct {
 	AutoRemove      bool          // Automatically remove container when it exits
 	VolumeDriver    string        // Name of the volume driver used to mount volumes
 	VolumesFrom     []string      // List of volumes to take from other container
+	ConsoleSize     [2]uint       // Initial console size (height,width)
 
 	// Applicable to UNIX platforms
 	CapAdd          strslice.StrSlice // List of kernel capabilities to add to the container
@@ -445,8 +446,7 @@ type HostConfig struct {
 	Runtime         string            `json:",omitempty"` // Runtime to use with this container
 
 	// Applicable to Windows
-	ConsoleSize [2]uint   // Initial console size (height,width)
-	Isolation   Isolation // Isolation technology of the container (e.g. default, hyperv)
+	Isolation Isolation // Isolation technology of the container (e.g. default, hyperv)
 
 	// Contains container's resources (cgroups, ulimits)
 	Resources

+ 8 - 1
client/container_create.go

@@ -27,11 +27,18 @@ func (cli *Client) ContainerCreate(ctx context.Context, config *container.Config
 		return response, err
 	}
 
+	clientVersion := cli.ClientVersion()
+
 	// When using API 1.24 and under, the client is responsible for removing the container
-	if hostConfig != nil && versions.LessThan(cli.ClientVersion(), "1.25") {
+	if hostConfig != nil && versions.LessThan(clientVersion, "1.25") {
 		hostConfig.AutoRemove = false
 	}
 
+	// When using API under 1.42, the Linux daemon doesn't respect the ConsoleSize
+	if hostConfig != nil && platform != nil && platform.OS == "linux" && versions.LessThan(clientVersion, "1.42") {
+		hostConfig.ConsoleSize = [2]uint{0, 0}
+	}
+
 	if err := cli.NewVersionError("1.41", "specify container image platform"); platform != nil && err != nil {
 		return response, err
 	}

+ 3 - 1
daemon/oci_linux.go

@@ -1023,7 +1023,9 @@ func (daemon *Daemon) createSpec(c *container.Container) (retSpec *specs.Spec, e
 	if c.NoNewPrivileges {
 		opts = append(opts, coci.WithNoNewPrivileges)
 	}
-
+	if c.Config.Tty {
+		opts = append(opts, WithConsoleSize(c))
+	}
 	// Set the masked and readonly paths with regard to the host config options if they are set.
 	if c.HostConfig.MaskedPaths != nil {
 		opts = append(opts, coci.WithMaskedPaths(c.HostConfig.MaskedPaths))

+ 23 - 0
daemon/oci_opts.go

@@ -0,0 +1,23 @@
+package daemon
+
+import (
+	"context"
+
+	"github.com/containerd/containerd/containers"
+	coci "github.com/containerd/containerd/oci"
+	"github.com/docker/docker/container"
+	specs "github.com/opencontainers/runtime-spec/specs-go"
+)
+
+// WithConsoleSize sets the initial console size
+func WithConsoleSize(c *container.Container) coci.SpecOpts {
+	return func(ctx context.Context, _ coci.Client, _ *containers.Container, s *coci.Spec) error {
+		if c.HostConfig.ConsoleSize[0] > 0 || c.HostConfig.ConsoleSize[1] > 0 {
+			s.Process.ConsoleSize = &specs.Box{
+				Height: c.HostConfig.ConsoleSize[0],
+				Width:  c.HostConfig.ConsoleSize[1],
+			}
+		}
+		return nil
+	}
+}

+ 3 - 0
docs/api/version-history.md

@@ -94,6 +94,9 @@ keywords: "API, Docker, rcli, REST, documentation"
   actually supported. API versions before v1.42 continue to ignore these parameters
   and default to attaching to all streams. To preserve the pre-v1.42 behavior,
   set all three query parameters (`?stdin=1,stdout=1,stderr=1`).
+* `POST /containers/create` on Linux now respects the `HostConfig.ConsoleSize` property.
+  Container is immediately created with the desired terminal size and clients no longer
+  need to set the desired size on their own.
 
 ## v1.41 API changes
 

+ 31 - 0
integration/container/run_linux_test.go

@@ -1,13 +1,16 @@
 package container // import "github.com/docker/docker/integration/container"
 
 import (
+	"bytes"
 	"context"
+	"io"
 	"os"
 	"path/filepath"
 	"strings"
 	"testing"
 	"time"
 
+	"github.com/docker/docker/api/types"
 	containertypes "github.com/docker/docker/api/types/container"
 	"github.com/docker/docker/api/types/versions"
 	"github.com/docker/docker/integration/internal/container"
@@ -183,3 +186,31 @@ func TestPrivilegedHostDevices(t *testing.T) {
 		assert.Check(t, is.Equal(strings.TrimSpace(res.Stdout()), devRootOnlyTest))
 	}
 }
+
+func TestConsoleSize(t *testing.T) {
+	skip.If(t, testEnv.DaemonInfo.OSType != "linux")
+	skip.If(t, versions.LessThan(testEnv.DaemonAPIVersion(), "1.42"), "skip test from new feature")
+
+	defer setupTest(t)()
+	client := testEnv.APIClient()
+	ctx := context.Background()
+
+	cID := container.Run(ctx, t, client,
+		container.WithTty(true),
+		container.WithImage("busybox"),
+		container.WithCmd("stty", "size"),
+		container.WithConsoleSize(57, 123),
+	)
+
+	poll.WaitOn(t, container.IsStopped(ctx, client, cID), poll.WithDelay(100*time.Millisecond))
+
+	out, err := client.ContainerLogs(ctx, cID, types.ContainerLogsOptions{ShowStdout: true})
+	assert.NilError(t, err)
+	defer out.Close()
+
+	var b bytes.Buffer
+	_, err = io.Copy(&b, out)
+	assert.NilError(t, err)
+
+	assert.Equal(t, strings.TrimSpace(b.String()), "123 57")
+}

+ 7 - 0
integration/internal/container/ops.go

@@ -227,3 +227,10 @@ func WithIsolation(isolation containertypes.Isolation) func(*TestContainerConfig
 		c.HostConfig.Isolation = isolation
 	}
 }
+
+// WithConsoleSize sets the initial console size of the container
+func WithConsoleSize(width, height uint) func(*TestContainerConfig) {
+	return func(c *TestContainerConfig) {
+		c.HostConfig.ConsoleSize = [2]uint{height, width}
+	}
+}