blob: 34c4b06caf49171beb216d0f85de66aee3865e26 [file] [log] [blame]
package daemon
import (
"context"
"encoding/json"
"github.com/containerd/containerd/v2/core/content"
"github.com/containerd/log"
"github.com/containerd/platforms"
"github.com/moby/moby/v2/daemon/container"
"github.com/moby/moby/v2/daemon/internal/image"
"github.com/moby/moby/v2/daemon/internal/multierror"
"github.com/moby/moby/v2/daemon/server/imagebackend"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors"
)
func migrateContainerOS(ctx context.Context,
migration platformReader,
ctr *container.Container,
) {
deduced, err := deduceContainerPlatform(ctx, migration, ctr)
if err != nil {
log.G(ctx).WithFields(log.Fields{
"container": ctr.ID,
"error": err,
}).Warn("failed to deduce the container architecture")
ctr.ImagePlatform.OS = ctr.OS //nolint:staticcheck // ignore SA1019
return
}
ctr.ImagePlatform = deduced
}
type platformReader interface {
ReadPlatformFromConfigByImageManifest(ctx context.Context, desc ocispec.Descriptor) (ocispec.Platform, error)
ReadPlatformFromImage(ctx context.Context, id image.ID) (ocispec.Platform, error)
}
// deduceContainerPlatform tries to deduce `ctr`'s platform.
// If both `ctr.OS` and `ctr.ImageManifest` are empty, assume the image comes
// from a pre-OS times and use the host platform to match the behavior of
// [container.FromDisk].
// Otherwise:
// - `ctr.ImageManifest.Platform` is used, if it exists and is not empty.
// - The platform from the manifest's config is used, if `ctr.ImageManifest` exists
// and we're able to load its config from the content store.
// - The platform found by loading the image from the image service by ID (using
// `ctr.ImageID`) is used – this looks for the best *present* matching manifest in
// the store.
func deduceContainerPlatform(
ctx context.Context,
migration platformReader,
ctr *container.Container,
) (ocispec.Platform, error) {
if ctr.OS == "" && ctr.ImageManifest == nil { //nolint:staticcheck // ignore SA1019 because we are testing deprecated field migration
return platforms.DefaultSpec(), nil
}
var errs []error
isValidPlatform := func(p ocispec.Platform) bool {
return p.OS != "" && p.Architecture != ""
}
if ctr.ImageManifest != nil {
if ctr.ImageManifest.Platform != nil {
return *ctr.ImageManifest.Platform, nil
}
if ctr.ImageManifest != nil {
p, err := migration.ReadPlatformFromConfigByImageManifest(ctx, *ctr.ImageManifest)
if err != nil {
errs = append(errs, err)
} else {
if isValidPlatform(p) {
return p, nil
}
errs = append(errs, errors.New("malformed image config obtained by ImageManifestDescriptor"))
}
}
}
if ctr.ImageID != "" {
p, err := migration.ReadPlatformFromImage(ctx, ctr.ImageID)
if err != nil {
errs = append(errs, err)
} else {
if isValidPlatform(p) {
return p, nil
}
errs = append(errs, errors.New("malformed image config obtained by image id"))
}
}
return ocispec.Platform{}, errors.Wrap(multierror.Join(errs...), "cannot deduce the container platform")
}
type daemonPlatformReader struct {
imageService ImageService
content content.Provider
}
func (r daemonPlatformReader) ReadPlatformFromConfigByImageManifest(
ctx context.Context,
desc ocispec.Descriptor,
) (ocispec.Platform, error) {
if r.content == nil {
return ocispec.Platform{}, errors.New("not an containerd image store")
}
b, err := content.ReadBlob(ctx, r.content, desc)
if err != nil {
return ocispec.Platform{}, err
}
var mfst ocispec.Manifest
if err := json.Unmarshal(b, &mfst); err != nil {
return ocispec.Platform{}, err
}
b, err = content.ReadBlob(ctx, r.content, mfst.Config)
if err != nil {
return ocispec.Platform{}, err
}
var plat ocispec.Platform
if err := json.Unmarshal(b, &plat); err != nil {
return ocispec.Platform{}, err
}
return plat, nil
}
func (r daemonPlatformReader) ReadPlatformFromImage(ctx context.Context, id image.ID) (ocispec.Platform, error) {
img, err := r.imageService.GetImage(ctx, id.String(), imagebackend.GetImageOpts{})
if err != nil {
return ocispec.Platform{}, err
}
return img.Platform(), nil
}