diff --git a/api/server/router/container/container_routes.go b/api/server/router/container/container_routes.go index 287611ad86..db821942db 100644 --- a/api/server/router/container/container_routes.go +++ b/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 != "" { diff --git a/api/swagger.yaml b/api/swagger.yaml index 2d7adca84d..19053217a4 100644 --- a/api/swagger.yaml +++ b/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: | diff --git a/api/types/container/host_config.go b/api/types/container/host_config.go index 9a5aa28f9a..100f434ce7 100644 --- a/api/types/container/host_config.go +++ b/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 diff --git a/client/container_create.go b/client/container_create.go index 754e7c195d..79a150466f 100644 --- a/client/container_create.go +++ b/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 } diff --git a/daemon/oci_linux.go b/daemon/oci_linux.go index c24f217230..ecc57a31b9 100644 --- a/daemon/oci_linux.go +++ b/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)) diff --git a/daemon/oci_opts.go b/daemon/oci_opts.go new file mode 100644 index 0000000000..c824999d50 --- /dev/null +++ b/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 + } +} diff --git a/docs/api/version-history.md b/docs/api/version-history.md index cbc5bd63e7..7cdcaeea98 100644 --- a/docs/api/version-history.md +++ b/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 diff --git a/integration/container/run_linux_test.go b/integration/container/run_linux_test.go index 11873e8e02..bd1fc6e932 100644 --- a/integration/container/run_linux_test.go +++ b/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") +} diff --git a/integration/internal/container/ops.go b/integration/internal/container/ops.go index 0a600361aa..0d94a4ebf7 100644 --- a/integration/internal/container/ops.go +++ b/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} + } +}