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 }