c8d/images: handle images without manifests for default platform
<!--
Please make sure you've read and understood our contributing guidelines;
https://github.com/moby/moby/blob/master/CONTRIBUTING.md

** Make sure all your commits include a signature generated with `git commit -s` **

For additional information on our contributing process, read our contributing
guide https://docs.docker.com/opensource/code/

If this is a bug fix, make sure your description includes "fixes #xxxx", or
"closes #xxxx"

Please provide the following information:
-->

**- What I did**
Fix https://github.com/moby/moby/issues/44578

**- How I did it**

For clarity: what was previously not working was the following block:

https://github.com/moby/moby/blob/b1569b9c185b9d5371d30fb59c14f4694239a4f3/daemon/containerd/image.go#L41-L50

passing 
```go
platform := platforms.AllPlatformsWithPreference(cplatforms.Default())
```
into
```go
containerdimages.Config(ctx, cs, desc, platform)
```
breaks because, after listing all the manifests in the index for the passed-in descriptor in:
https://github.com/moby/moby/blob/b1569b9c185b9d5371d30fb59c14f4694239a4f3/vendor/github.com/containerd/containerd/images/image.go#L215
they get sorted according to the `platform := platforms.AllPlatformsWithPreference(cplatforms.Default())` in:
https://github.com/moby/moby/blob/b1569b9c185b9d5371d30fb59c14f4694239a4f3/vendor/github.com/containerd/containerd/images/image.go#L221-L229
and then, in order of sorting, this block gets called:
https://github.com/moby/moby/blob/b1569b9c185b9d5371d30fb59c14f4694239a4f3/vendor/github.com/containerd/containerd/images/image.go#L150-L156

Which breaks since we don't have the manifests for the other platforms and the platform we do have isn't the first on the list.

**- How to verify it**
- `docker pull --platform=[non-default-platform] alpine`
- `docker inspect alpine`
- `docker history alpine`

**- Description for the changelog**
<!--
Write a short (one line) summary that describes the changes in this
pull request for inclusion in the changelog:
-->


**- A picture of a cute animal (not mandatory but encouraged)**

<img width="336" alt="image" src="https://github.com/moby/moby/assets/70572044/e1f16485-96ad-4274-815d-9b864a045bf0">

daemon/containerd/image_history.go
package containerd

import (
	"context"
	"encoding/json"

	"github.com/containerd/containerd/content"
	containerdimages "github.com/containerd/containerd/images"
	cplatforms "github.com/containerd/containerd/platforms"
	"github.com/docker/distribution/reference"
	imagetype "github.com/docker/docker/api/types/image"
	"github.com/docker/docker/pkg/platforms"
	"github.com/opencontainers/image-spec/identity"
	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
	"github.com/pkg/errors"
)

// ImageHistory returns a slice of HistoryResponseItem structures for the
// specified image name by walking the image lineage.
func (i *ImageService) ImageHistory(ctx context.Context, name string) ([]*imagetype.HistoryResponseItem, error) {
	desc, err := i.resolveDescriptor(ctx, name)
	if err != nil {
		return nil, err
	}

	cs := i.client.ContentStore()
	// TODO: pass the platform from the cli
	conf, err := containerdimages.Config(ctx, cs, desc, platforms.AllPlatformsWithPreference(cplatforms.Default()))
	if err != nil {
		return nil, err
	}

	diffIDs, err := containerdimages.RootFS(ctx, cs, conf)
	if err != nil {
		return nil, err
	}

	blob, err := content.ReadBlob(ctx, cs, conf)
	if err != nil {
		return nil, err
	}

	var image ocispec.Image
	if err := json.Unmarshal(blob, &image); err != nil {
		return nil, err
	}

	var (
		history []*imagetype.HistoryResponseItem
		sizes   []int64
	)
	s := i.client.SnapshotService(i.snapshotter)

	for i := range diffIDs {
		chainID := identity.ChainID(diffIDs[0 : i+1]).String()

		use, err := s.Usage(ctx, chainID)
		if err != nil {
			return nil, err
		}

		sizes = append(sizes, use.Size)
	}

	for _, h := range image.History {
		size := int64(0)
		if !h.EmptyLayer {
			if len(sizes) == 0 {
				return nil, errors.New("unable to find the size of the layer")
			}
			size = sizes[0]
			sizes = sizes[1:]
		}

		history = append([]*imagetype.HistoryResponseItem{{
			ID:        "<missing>",
			Comment:   h.Comment,
			CreatedBy: h.CreatedBy,
			Created:   h.Created.Unix(),
			Size:      size,
			Tags:      nil,
		}}, history...)
	}

	if len(history) != 0 {
		history[0].ID = desc.Digest.String()

		tagged, err := i.client.ImageService().List(ctx, "target.digest=="+desc.Digest.String())
		if err != nil {
			return nil, err
		}

		var tags []string
		for _, t := range tagged {
			if isDanglingImage(t) {
				continue
			}
			name, err := reference.ParseNamed(t.Name)
			if err != nil {
				return nil, err
			}
			tags = append(tags, reference.FamiliarString(name))
		}
		history[0].Tags = tags
	}

	return history, nil
}

package containerd

import (
	"context"
	"sort"

	cplatforms "github.com/containerd/containerd/platforms"
	"github.com/docker/distribution/reference"
	imagetype "github.com/docker/docker/api/types/image"
	"github.com/docker/docker/errdefs"
	"github.com/docker/docker/pkg/platforms"
	"github.com/opencontainers/image-spec/identity"
	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
	"github.com/pkg/errors"
)

// ImageHistory returns a slice of HistoryResponseItem structures for the
// specified image name by walking the image lineage.
func (i *ImageService) ImageHistory(ctx context.Context, name string) ([]*imagetype.HistoryResponseItem, error) {
	desc, err := i.resolveImage(ctx, name)
	if err != nil {
		return nil, err
	}

	cs := i.client.ContentStore()
	// TODO: pass platform in from the CLI
	platform := platforms.AllPlatformsWithPreference(cplatforms.Default())

	var presentImages []ocispec.Image
	err = i.walkImageManifests(ctx, desc, func(img *ImageManifest) error {
		conf, err := img.Config(ctx)
		if err != nil {
			return err
		}
		var ociimage ocispec.Image
		if err := readConfig(ctx, cs, conf, &ociimage); err != nil {
			return err
		}
		presentImages = append(presentImages, ociimage)
		return nil
	})
	if err != nil {
		return nil, err
	}
	if len(presentImages) == 0 {
		return nil, errdefs.NotFound(errors.New("failed to find image manifest"))
	}

	sort.SliceStable(presentImages, func(i, j int) bool {
		return platform.Less(presentImages[i].Platform, presentImages[j].Platform)
	})
	ociimage := presentImages[0]

	var (
		history []*imagetype.HistoryResponseItem
		sizes   []int64
	)
	s := i.client.SnapshotService(i.snapshotter)

	diffIDs := ociimage.RootFS.DiffIDs
	for i := range diffIDs {
		chainID := identity.ChainID(diffIDs[0 : i+1]).String()

		use, err := s.Usage(ctx, chainID)
		if err != nil {
			return nil, err
		}

		sizes = append(sizes, use.Size)
	}

	for _, h := range ociimage.History {
		size := int64(0)
		if !h.EmptyLayer {
			if len(sizes) == 0 {
				return nil, errors.New("unable to find the size of the layer")
			}
			size = sizes[0]
			sizes = sizes[1:]
		}

		history = append([]*imagetype.HistoryResponseItem{{
			ID:        "<missing>",
			Comment:   h.Comment,
			CreatedBy: h.CreatedBy,
			Created:   h.Created.Unix(),
			Size:      size,
			Tags:      nil,
		}}, history...)
	}

	if len(history) != 0 {
		history[0].ID = desc.Target.Digest.String()

		tagged, err := i.client.ImageService().List(ctx, "target.digest=="+desc.Target.Digest.String())
		if err != nil {
			return nil, err
		}

		var tags []string
		for _, t := range tagged {
			if isDanglingImage(t) {
				continue
			}
			name, err := reference.ParseNamed(t.Name)
			if err != nil {
				return nil, err
			}
			tags = append(tags, reference.FamiliarString(name))
		}
		history[0].Tags = tags
	}

	return history, nil
}

Yeah, I think it probably should! As we keep fixing all the c8d integration issues and writing the code that's needed, it's easier to see what we can factor out and what interfaces we should expose. I think after this it might make since to do a bigger review of `daemon/containerd/image_*.go` and see what makes since (but I can definitely factor this out for this PR already).