Browse code

container: Add `ImagePlatform` field and deprecate `OS`

Change the persistent container metadata to store the whole platform
(as defined by OCI) instead of only the operating system.

Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>

Paweł Gronowski authored on 2024/11/07 23:33:43
Showing 10 changed files
... ...
@@ -15,6 +15,7 @@ import (
15 15
 
16 16
 	"github.com/containerd/containerd/cio"
17 17
 	"github.com/containerd/log"
18
+	"github.com/containerd/platforms"
18 19
 	containertypes "github.com/docker/docker/api/types/container"
19 20
 	"github.com/docker/docker/api/types/events"
20 21
 	mounttypes "github.com/docker/docker/api/types/mount"
... ...
@@ -84,7 +85,12 @@ type Container struct {
84 84
 	LogPath         string
85 85
 	Name            string
86 86
 	Driver          string
87
-	OS              string
87
+
88
+	// Deprecated: use [ImagePlatform.OS] instead.
89
+	// TODO: Remove, see https://github.com/moby/moby/issues/48892
90
+	OS string
91
+
92
+	ImagePlatform ocispec.Platform
88 93
 
89 94
 	RestartCount             int
90 95
 	HasBeenStartedBefore     bool
... ...
@@ -162,11 +168,17 @@ func (container *Container) FromDisk() error {
162 162
 		return err
163 163
 	}
164 164
 
165
-	// Ensure the operating system is set if blank. Assume it is the OS of the
166
-	// host OS if not, to ensure containers created before multiple-OS
167
-	// support are migrated
168
-	if container.OS == "" {
169
-		container.OS = runtime.GOOS
165
+	if container.OS != "" {
166
+		// OS was deprecated in favor of ImagePlatform
167
+		// Make sure we migrate the OS to ImagePlatform.OS.
168
+		if container.ImagePlatform.OS == "" {
169
+			container.ImagePlatform.OS = container.OS //nolint:staticcheck // ignore SA1019: field is deprecated
170
+		}
171
+	} else {
172
+		// Pre multiple-OS support containers have no OS set.
173
+		// Assume it is the host platform.
174
+		container.ImagePlatform = platforms.DefaultSpec()
175
+		container.OS = container.ImagePlatform.OS //nolint:staticcheck // ignore SA1019: field is deprecated
170 176
 	}
171 177
 
172 178
 	return container.readHostConfig()
... ...
@@ -752,7 +764,7 @@ func getConfigTargetPath(r *swarmtypes.ConfigReference) string {
752 752
 // CreateDaemonEnvironment creates a new environment variable slice for this container.
753 753
 func (container *Container) CreateDaemonEnvironment(tty bool, linkedEnv []string) []string {
754 754
 	// Setup environment
755
-	ctrOS := container.OS
755
+	ctrOS := container.ImagePlatform.OS
756 756
 	if ctrOS == "" {
757 757
 		ctrOS = runtime.GOOS
758 758
 	}
... ...
@@ -151,7 +151,7 @@ func (daemon *Daemon) CreateImageFromContainer(ctx context.Context, name string,
151 151
 	if c.Config == nil {
152 152
 		c.Config = container.Config
153 153
 	}
154
-	newConfig, err := dockerfile.BuildFromConfig(ctx, c.Config, c.Changes, container.OS)
154
+	newConfig, err := dockerfile.BuildFromConfig(ctx, c.Config, c.Changes, container.ImagePlatform.OS)
155 155
 	if err != nil {
156 156
 		return "", err
157 157
 	}
... ...
@@ -166,7 +166,7 @@ func (daemon *Daemon) CreateImageFromContainer(ctx context.Context, name string,
166 166
 		ContainerConfig:     container.Config,
167 167
 		ContainerID:         container.ID,
168 168
 		ContainerMountLabel: container.MountLabel,
169
-		ContainerOS:         container.OS,
169
+		ContainerOS:         container.ImagePlatform.OS,
170 170
 		ParentImageID:       string(container.ImageID),
171 171
 	})
172 172
 	if err != nil {
... ...
@@ -23,6 +23,7 @@ import (
23 23
 	volumemounts "github.com/docker/docker/volume/mounts"
24 24
 	"github.com/docker/go-connections/nat"
25 25
 	"github.com/moby/sys/signal"
26
+	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
26 27
 	"github.com/opencontainers/selinux/go-selinux"
27 28
 	"github.com/pkg/errors"
28 29
 )
... ...
@@ -119,7 +120,7 @@ func (daemon *Daemon) register(ctx context.Context, c *container.Container) erro
119 119
 	return c.CheckpointTo(ctx, daemon.containersReplica)
120 120
 }
121 121
 
122
-func (daemon *Daemon) newContainer(name string, operatingSystem string, config *containertypes.Config, hostConfig *containertypes.HostConfig, imgID image.ID, managed bool) (*container.Container, error) {
122
+func (daemon *Daemon) newContainer(name string, platform ocispec.Platform, config *containertypes.Config, hostConfig *containertypes.HostConfig, imgID image.ID, managed bool) (*container.Container, error) {
123 123
 	var (
124 124
 		id  string
125 125
 		err error
... ...
@@ -153,8 +154,9 @@ func (daemon *Daemon) newContainer(name string, operatingSystem string, config *
153 153
 	base.NetworkSettings = &network.Settings{}
154 154
 	base.Name = name
155 155
 	base.Driver = daemon.imageService.StorageDriver()
156
-	base.OS = operatingSystem
157
-	return base, nil
156
+	base.ImagePlatform = platform
157
+	base.OS = platform.OS //nolint:staticcheck // ignore SA1019: field is deprecated, but still set for compatibility
158
+	return base, err
158 159
 }
159 160
 
160 161
 // GetByName returns a container given a name.
... ...
@@ -325,7 +325,7 @@ func (i *ImageService) CommitBuildStep(ctx context.Context, c backend.CommitConf
325 325
 		return "", fmt.Errorf("container not found: %s", c.ContainerID)
326 326
 	}
327 327
 	c.ContainerMountLabel = ctr.MountLabel
328
-	c.ContainerOS = ctr.OS
328
+	c.ContainerOS = ctr.ImagePlatform.OS
329 329
 	c.ParentImageID = string(ctr.ImageID)
330 330
 	return i.CommitImage(ctx, c)
331 331
 }
... ...
@@ -3,7 +3,6 @@ package daemon // import "github.com/docker/docker/daemon"
3 3
 import (
4 4
 	"context"
5 5
 	"fmt"
6
-	"runtime"
7 6
 	"strings"
8 7
 	"time"
9 8
 
... ...
@@ -23,7 +22,7 @@ import (
23 23
 	"github.com/docker/docker/runconfig"
24 24
 	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
25 25
 	"github.com/opencontainers/selinux/go-selinux"
26
-	archvariant "github.com/tonistiigi/go-archvariant"
26
+	"github.com/tonistiigi/go-archvariant"
27 27
 )
28 28
 
29 29
 type createOpts struct {
... ...
@@ -130,7 +129,7 @@ func (daemon *Daemon) create(ctx context.Context, daemonCfg *config.Config, opts
130 130
 		imgManifest *ocispec.Descriptor
131 131
 		imgID       image.ID
132 132
 		err         error
133
-		os          = runtime.GOOS
133
+		platform    = platforms.DefaultSpec()
134 134
 	)
135 135
 
136 136
 	if opts.params.Config.Image != "" {
... ...
@@ -148,17 +147,17 @@ func (daemon *Daemon) create(ctx context.Context, daemonCfg *config.Config, opts
148 148
 				return nil, err
149 149
 			}
150 150
 		}
151
-		os = img.OperatingSystem()
151
+		platform = img.Platform()
152 152
 		imgID = img.ID()
153 153
 	} else if isWindows {
154
-		os = "linux" // 'scratch' case.
154
+		platform.OS = "linux" // 'scratch' case.
155 155
 	}
156 156
 
157 157
 	// On WCOW, if are not being invoked by the builder to create this container (where
158 158
 	// ignoreImagesArgEscaped will be true) - if the image already has its arguments escaped,
159 159
 	// ensure that this is replicated across to the created container to avoid double-escaping
160 160
 	// of the arguments/command line when the runtime attempts to run the container.
161
-	if os == "windows" && !opts.ignoreImagesArgsEscaped && img != nil && img.RunConfig().ArgsEscaped {
161
+	if platform.OS == "windows" && !opts.ignoreImagesArgsEscaped && img != nil && img.RunConfig().ArgsEscaped {
162 162
 		opts.params.Config.ArgsEscaped = true
163 163
 	}
164 164
 
... ...
@@ -170,7 +169,7 @@ func (daemon *Daemon) create(ctx context.Context, daemonCfg *config.Config, opts
170 170
 		return nil, errdefs.InvalidParameter(err)
171 171
 	}
172 172
 
173
-	if ctr, err = daemon.newContainer(opts.params.Name, os, opts.params.Config, opts.params.HostConfig, imgID, opts.managed); err != nil {
173
+	if ctr, err = daemon.newContainer(opts.params.Name, platform, opts.params.Config, opts.params.HostConfig, imgID, opts.managed); err != nil {
174 174
 		return nil, err
175 175
 	}
176 176
 	defer func() {
... ...
@@ -9,7 +9,7 @@ import (
9 9
 )
10 10
 
11 11
 func (daemon *Daemon) execSetPlatformOpt(ctx context.Context, daemonCfg *config.Config, ec *container.ExecConfig, p *specs.Process) error {
12
-	if ec.Container.OS == "windows" {
12
+	if ec.Container.ImagePlatform.OS == "windows" {
13 13
 		p.User.Username = ec.User
14 14
 	}
15 15
 	return nil
... ...
@@ -20,7 +20,7 @@ func (daemon *Daemon) ContainerExport(ctx context.Context, name string, out io.W
20 20
 		return err
21 21
 	}
22 22
 
23
-	if isWindows && ctr.OS == "windows" {
23
+	if isWindows && ctr.ImagePlatform.OS == "windows" {
24 24
 		return fmt.Errorf("the daemon on this operating system does not support exporting Windows containers")
25 25
 	}
26 26
 
... ...
@@ -457,7 +457,7 @@ func getShell(cntr *container.Container) []string {
457 457
 	if runtime.GOOS != "windows" {
458 458
 		return []string{"/bin/sh", "-c"}
459 459
 	}
460
-	if cntr.OS != runtime.GOOS {
460
+	if cntr.ImagePlatform.OS != runtime.GOOS {
461 461
 		return []string{"/bin/sh", "-c"}
462 462
 	}
463 463
 	return []string{"cmd", "/S", "/C"}
... ...
@@ -128,7 +128,7 @@ func (i *ImageService) CommitBuildStep(ctx context.Context, c backend.CommitConf
128 128
 		return "", errors.Errorf("container not found: %s", c.ContainerID)
129 129
 	}
130 130
 	c.ContainerMountLabel = ctr.MountLabel
131
-	c.ContainerOS = ctr.OS
131
+	c.ContainerOS = ctr.ImagePlatform.OS
132 132
 	c.ParentImageID = string(ctr.ImageID)
133 133
 	return i.CommitImage(ctx, c)
134 134
 }
... ...
@@ -145,7 +145,7 @@ func (daemon *Daemon) getInspectData(daemonCfg *config.Config, container *contai
145 145
 		Name:         container.Name,
146 146
 		RestartCount: container.RestartCount,
147 147
 		Driver:       container.Driver,
148
-		Platform:     container.OS,
148
+		Platform:     container.ImagePlatform.OS,
149 149
 		MountLabel:   container.MountLabel,
150 150
 		ProcessLabel: container.ProcessLabel,
151 151
 		ExecIDs:      container.GetExecIDs(),