package daemon import ( "context" "errors" "testing" "github.com/containerd/platforms" "github.com/moby/moby/v2/daemon/container" "github.com/moby/moby/v2/daemon/internal/image" ocispec "github.com/opencontainers/image-spec/specs-go/v1" "gotest.tools/v3/assert" ) type mockPlatformReader struct{} func (m mockPlatformReader) ReadPlatformFromImage(ctx context.Context, id image.ID) (ocispec.Platform, error) { switch id { case "multiplatform": // This image has multiple platforms, but GetImage will prefer the first one // because the ID points to the full image index, not a specific platform. return platforms.DefaultSpec(), nil case "linux/arm64/v8": return ocispec.Platform{ OS: "linux", Architecture: "arm64", Variant: "v8", }, nil case "linux/amd64": return ocispec.Platform{ OS: "linux", Architecture: "amd64", }, nil case "windows/amd64": return ocispec.Platform{ OS: "windows", Architecture: "amd64", }, nil default: return ocispec.Platform{}, errors.New("image not found") } } func (m mockPlatformReader) ReadPlatformFromConfigByImageManifest(ctx context.Context, desc ocispec.Descriptor) (ocispec.Platform, error) { return m.ReadPlatformFromImage(ctx, image.ID(desc.Digest)) } //nolint:staticcheck // ignore SA1019 because we are testing deprecated field migration func TestContainerMigrateOS(t *testing.T) { type Container = container.Container var mock mockPlatformReader // ImageManifest is nil for containers created with graphdrivers image store var graphdrivers *ocispec.Descriptor for _, tc := range []struct { name string ctr Container expected ocispec.Platform }{ { name: "gd pre-OS container", ctr: Container{ ImageManifest: graphdrivers, OS: "", }, expected: platforms.DefaultSpec(), }, { name: "gd with linux arm64 image", ctr: Container{ ImageManifest: graphdrivers, ImageID: "linux/arm64/v8", OS: "linux", }, expected: ocispec.Platform{ OS: "linux", Architecture: "arm64", Variant: "v8", }, }, { name: "gd with windows image", ctr: Container{ ImageManifest: graphdrivers, ImageID: "windows/amd64", OS: "windows", }, expected: ocispec.Platform{ OS: "windows", Architecture: "amd64", }, }, { name: "gd with an image thats no longer available", ctr: Container{ ImageManifest: graphdrivers, ImageID: "notfound", OS: "linux", }, expected: platforms.Platform{ OS: "linux", }, }, { name: "c8d with linux arm64 image", ctr: Container{ ImageManifest: &ocispec.Descriptor{ Digest: "linux/arm64/v8", }, OS: "linux", ImageID: "linux/arm64/v8", }, expected: ocispec.Platform{ OS: "linux", Architecture: "arm64", Variant: "v8", }, }, { name: "c8d with an image thats no longer available", ctr: Container{ ImageManifest: &ocispec.Descriptor{ Digest: "notfound", }, OS: "linux", ImageID: "notfound", }, expected: platforms.Platform{ OS: "linux", }, }, { name: "c8d with ImageManifest that is no longer available", ctr: Container{ ImageManifest: &ocispec.Descriptor{ Digest: "notfound", }, OS: "linux", ImageID: "multiplatform", }, // Note: This might produce unexpected results, because if the platform-specific manifest // is not available, and the ImageID points to a multi-platform image, then GetImage will // return any available platform with host platform being the priority. // So it will just use whatever platform is returned by GetImage (docker image inspect). expected: platforms.DefaultSpec(), }, { name: "ImageManifest has priority over ImageID migration", ctr: Container{ ImageManifest: &ocispec.Descriptor{ Digest: "linux/arm64/v8", }, OS: "linux", ImageID: "linux/amd64", }, expected: ocispec.Platform{ OS: "linux", Architecture: "arm64", Variant: "v8", }, }, } { ctr := tc.ctr t.Run(tc.name, func(t *testing.T) { migrateContainerOS(context.Background(), mock, &ctr) assert.DeepEqual(t, tc.expected, ctr.ImagePlatform) }) } }