daemon/c8d: Implement save and load
Upstreams: 
- https://github.com/rumpl/moby/pull/33
- https://github.com/rumpl/moby/pull/112

**- What I did**
This makes the `docker save` and `docker load` work with the containerd image store. The archive is both OCI and Docker compatible.

Saved archive will only contain content which is available locally.  In case the saved image is a multi-platform manifest list, the behavior depends on the local availability of the content. This is to be reconsidered when we have the `--platform` option in the CLI.

- If all manifests and their contents, referenced by the manifest list are present, then the manifest-list is exported directly and the ID will be the same..
- If only one platform manifest is present, only that manifest is exported (the image id will change and will be the id of platform-specific manifest, instead of the full manifest list).
- If multiple, but not all, platform manifests are available, a new manifest list will be created which will be a subset of the original manifest list.


**- How I did it**

**- How to verify it**
<details>

<summary>Save</summary>

```bash
$ docker pull alpine
Using default tag: latest
f271e74b17ce: Download complete
41d876d4e443: Download complete
04eeaa5f8c35: Download complete
a9eaa45ef418: Download complete
docker.io/library/alpine:latest
$ docker save alpine >alpine-c8d.tar
$ docker image ls
REPOSITORY   TAG       IMAGE ID       CREATED          SIZE
alpine       latest    f271e74b17ce   29 seconds ago   3.26MB $ docker save alpine >alpine-c8d.^Cr
$ tar  -f alpine-c8d.tar --to-stdout -x manifest.json | jq
[
  {
    "Config": "blobs/sha256/04eeaa5f8c35b8206a15c12425498c37ecd2181e0ef202bea24a2382e4dd240f",
    "RepoTags": [
      "alpine:latest"
    ],
    "Layers": [
      "blobs/sha256/a9eaa45ef418e883481a13c7d84fa9904f2ec56789c52a87ba5a9e6483f2b74f"
    ]
  }
]
$ tar  -f alpine-c8d.tar --to-stdout -x index.json | jq
{
  "schemaVersion": 2,
  "manifests": [
    {
      "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
      "digest": "sha256:41d876d4e44348d1c27445fdb0e64592e0eb926d4dbbcf09a3526dee7e628329",
      "size": 528,
      "annotations": {
        "io.containerd.image.name": "docker.io/library/alpine:latest",
        "org.opencontainers.image.ref.name": "latest"
      },
      "platform": {
        "architecture": "arm64",
        "os": "linux",
        "variant": "v8"
      }
    }
  ]
}
$ docker pull --platform linux/amd64 alpine
Using default tag: latest
93d5a28ff72d: Download complete
f271e74b17ce: Exists
042a816809aa: Download complete
8921db27df28: Downloading [===============================>                   ]  2.097MB/3.371MB
docker.io/library/alpine:latest
$ docker save alpine >alpine2-c8d.tar
$ tar  -f alpine2-c8d.tar --to-stdout -x index.json | jq
{
  "schemaVersion": 2,
  "manifests": [
    {
      "mediaType": "application/vnd.oci.image.index.v1+json",
      "digest": "sha256:f67bf9932060788baef3f3681cac3396127820a1b8768c49b7ea9c6aff366fff",
      "size": 526,
      "annotations": {
        "io.containerd.image.name": "docker.io/library/alpine:latest",
        "org.opencontainers.image.ref.name": "latest"
      }
    }
  ]
}
$ tar  -f alpine2-c8d.tar --to-stdout -x manifest.json | jq
[
  {
    "Config": "blobs/sha256/04eeaa5f8c35b8206a15c12425498c37ecd2181e0ef202bea24a2382e4dd240f",
    "RepoTags": [
      "alpine:latest"
    ],
    "Layers": [
      "blobs/sha256/a9eaa45ef418e883481a13c7d84fa9904f2ec56789c52a87ba5a9e6483f2b74f"
    ]
  }
]
$ tar  -f alpine2-c8d.tar --to-stdout -x blobs/sha256/f67bf9932060788baef3f3681cac3396127820a1b8768c49b7ea9c6aff366fff | jq
{
  "schemaVersion": 2,
  "mediaType": "application/vnd.oci.image.index.v1+json",
  "manifests": [
    {
      "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
      "digest": "sha256:93d5a28ff72d288d69b5997b8ba47396d2cbb62a72b5d87cd3351094b5d578a0",
      "size": 528,
      "platform": {
        "architecture": "amd64",
        "os": "linux"
      }
    },
    {
      "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
      "digest": "sha256:41d876d4e44348d1c27445fdb0e64592e0eb926d4dbbcf09a3526dee7e628329",
      "size": 528,
      "platform": {
        "architecture": "arm64",
        "os": "linux",
        "variant": "v8"
      }
    }
  ]
}


$ docker pull --platform linux/386 alpine
Using default tag: latest
f271e74b17ce: Exists
14d4381342be: Download complete
f106dae6f363: Download complete
40e5b0b2e2bd: Download complete
docker.io/library/alpine:latest
$ docker pull --platform linux/arm/v6 alpine
Using default tag: latest
f271e74b17ce: Exists
01a4cdaebc9c: Download complete
c1aae4382455: Download complete
0269c10e600f: Download complete
docker.io/library/alpine:latest
$ docker pull --platform linux/arm/v7 alpine
Using default tag: latest
f271e74b17ce: Exists
1c34b3cb760a: Download complete
7cd16f17a19f: Download complete
c527615e4ffa: Download complete
docker.io/library/alpine:latest
$ docker pull --platform linux/ppc64le alpine
Using default tag: latest
f271e74b17ce: Exists
145b24ad7f65: Download complete
d641b7bad7e9: Download complete
f45bfda3aa14: Downloading [==============================================>    ]  3.146MB/3.385MB
docker.io/library/alpine:latest
$ docker pull --platform linux/s390x alpine
Using default tag: latest
f271e74b17ce: Exists
176bc6c6e935: Download complete
8604c24e0b81: Download complete
ae982806674c: Download complete
docker.io/library/alpine:latest
$ docker save alpine >alpine-full-c8d.tar
$ tar  -f alpine-full-c8d.tar --to-stdout -x index.json | jq
{
  "schemaVersion": 2,
  "manifests": [
    {
      "mediaType": "application/vnd.docker.distribution.manifest.list.v2+json",
      "digest": "sha256:f271e74b17ced29b915d351685fd4644785c6d1559dd1f2d4189a5e851ef753a",
      "size": 1638,
      "annotations": {
        "io.containerd.image.name": "docker.io/library/alpine:latest",
        "org.opencontainers.image.ref.name": "latest"
      }
    }
  ]
}
```


</details>




<details>
<summary>Load</summary>

```bash

$ docker load <alpine-c8d.tar
$ docker image ls
REPOSITORY   TAG       IMAGE ID       CREATED         SIZE
alpine       latest    41d876d4e443   2 seconds ago   3.26MB
$ ctr --address /run/docker/containerd/containerd.sock -n moby content ls
DIGEST                                                                  SIZE    AGE             LABELS
sha256:04eeaa5f8c35b8206a15c12425498c37ecd2181e0ef202bea24a2382e4dd240f 1.485kB 14 seconds      containerd.io/gc.ref.snapshot.overlayfs=sha256:0a24540c1e5a47e5a8116c43151cb45c985f7a65afe5d4d65127d9c6fea2ffcf
sha256:41d876d4e44348d1c27445fdb0e64592e0eb926d4dbbcf09a3526dee7e628329 528B    14 seconds      containerd.io/gc.ref.content.config=sha256:04eeaa5f8c35b8206a15c12425498c37ecd2181e0ef202bea24a2382e4dd240f,containerd.io/gc.ref.content.l.0=sha256:a9eaa45ef418e883481a13c7d84fa9904f2ec56789c52a87ba5a9e6483f2b74f
sha256:a9eaa45ef418e883481a13c7d84fa9904f2ec56789c52a87ba5a9e6483f2b74f 3.259MB 14 seconds      containerd.io/uncompressed=sha256:0a24540c1e5a47e5a8116c43151cb45c985f7a65afe5d4d65127d9c6fea2ffcf
$ ctr --address /run/docker/containerd/containerd.sock -n moby content get sha256:41d876d4e44348d1c27445fdb0e64592e0eb926d4dbbcf09a3526dee7e628329 | jq
{
  "schemaVersion": 2,
  "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
  "config": {
    "mediaType": "application/vnd.docker.container.image.v1+json",
    "size": 1485,
    "digest": "sha256:04eeaa5f8c35b8206a15c12425498c37ecd2181e0ef202bea24a2382e4dd240f"
  },
  "layers": [
    {
      "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
      "size": 3259241,
      "digest": "sha256:a9eaa45ef418e883481a13c7d84fa9904f2ec56789c52a87ba5a9e6483f2b74f"
    }
  ]
}



$ docker load <alpine2-c8d.tar
$ docker image ls
REPOSITORY   TAG       IMAGE ID       CREATED          SIZE
alpine       latest    f67bf9932060   46 seconds ago   3.26MB
$ ctr --address /run/docker/containerd/containerd.sock -n moby content ls
DIGEST                                                                  SIZE    AGE             LABELS
sha256:042a816809aac8d0f7d7cacac7965782ee2ecac3f21bcf9f24b1de1a7387b769 1.472kB 10 seconds      containerd.io/gc.ref.snapshot.overlayfs=sha256:8e012198eea15b2554b07014081c85fec4967a1b9cc4b65bd9a4bce3ae1c0c88
sha256:04eeaa5f8c35b8206a15c12425498c37ecd2181e0ef202bea24a2382e4dd240f 1.485kB 52 seconds      containerd.io/gc.ref.snapshot.overlayfs=sha256:0a24540c1e5a47e5a8116c43151cb45c985f7a65afe5d4d65127d9c6fea2ffcf
sha256:41d876d4e44348d1c27445fdb0e64592e0eb926d4dbbcf09a3526dee7e628329 528B    52 seconds      containerd.io/gc.ref.content.l.0=sha256:a9eaa45ef418e883481a13c7d84fa9904f2ec56789c52a87ba5a9e6483f2b74f,containerd.io/gc.ref.content.config=sha256:04eeaa5f8c35b8206a15c12425498c37ecd2181e0ef202bea24a2382e4dd240f
sha256:8921db27df2831fa6eaa85321205a2470c669b855f3ec95d5a3c2b46de0442c9 3.371MB 10 seconds      containerd.io/uncompressed=sha256:8e012198eea15b2554b07014081c85fec4967a1b9cc4b65bd9a4bce3ae1c0c88
sha256:93d5a28ff72d288d69b5997b8ba47396d2cbb62a72b5d87cd3351094b5d578a0 528B    10 seconds      containerd.io/gc.ref.content.config=sha256:042a816809aac8d0f7d7cacac7965782ee2ecac3f21bcf9f24b1de1a7387b769,containerd.io/gc.ref.content.l.0=sha256:8921db27df2831fa6eaa85321205a2470c669b855f3ec95d5a3c2b46de0442c9
sha256:a9eaa45ef418e883481a13c7d84fa9904f2ec56789c52a87ba5a9e6483f2b74f 3.259MB 52 seconds      containerd.io/uncompressed=sha256:0a24540c1e5a47e5a8116c43151cb45c985f7a65afe5d4d65127d9c6fea2ffcf
sha256:f67bf9932060788baef3f3681cac3396127820a1b8768c49b7ea9c6aff366fff 526B    10 seconds      containerd.io/gc.ref.content.m.0=sha256:93d5a28ff72d288d69b5997b8ba47396d2cbb62a72b5d87cd3351094b5d578a0,containerd.io/gc.ref.content.m.1=sha256:41d876d4e44348d1c27445fdb0e64592e0eb926d4dbbcf09a3526dee7e628329
$ ctrr^C
$ ctr --address /run/docker/containerd/containerd.sock -n moby content get sha256:f67bf9932060788baef3f3681cac3396127820a1b8768c49b7ea9c6aff366fff | jq
{
  "schemaVersion": 2,
  "mediaType": "application/vnd.oci.image.index.v1+json",
  "manifests": [
    {
      "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
      "digest": "sha256:93d5a28ff72d288d69b5997b8ba47396d2cbb62a72b5d87cd3351094b5d578a0",
      "size": 528,
      "platform": {
        "architecture": "amd64",
        "os": "linux"
      }
    },
    {
      "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
      "digest": "sha256:41d876d4e44348d1c27445fdb0e64592e0eb926d4dbbcf09a3526dee7e628329",
      "size": 528,
      "platform": {
        "architecture": "arm64",
        "os": "linux",
        "variant": "v8"
      }
    }
  ]
}
$ docker load <alpine-full-c8d.tar
$ docker image ls
REPOSITORY   TAG       IMAGE ID       CREATED          SIZE
alpine       latest    f271e74b17ce   21 minutes ago   3.26MB
$ ctr --address /run/docker/containerd/containerd.sock -n moby content ls
DIGEST                                                                  SIZE    AGE             LABELS
sha256:01a4cdaebc9c6af607753cc538c507d0867897cdf9a1caa70bbab2eb1506c964 528B    10 seconds      containerd.io/gc.ref.content.config=sha256:c1aae4382455d858fc7dc11997cb34c5aae2da6a768619c16b2a7c70b3c12b43,containerd.io/gc.ref.content.l.0=sha256:0269c10e600f3a375f36ddabdbd264ce9503a455f0d0969ce8a00f24eaecc032
sha256:0269c10e600f3a375f36ddabdbd264ce9503a455f0d0969ce8a00f24eaecc032 3.107MB 10 seconds      containerd.io/uncompressed=sha256:86ddd1858dda5de0cc1e3cc31a93fddc627a28136b08db8cda7460de39f657a3
sha256:042a816809aac8d0f7d7cacac7965782ee2ecac3f21bcf9f24b1de1a7387b769 1.472kB 21 minutes      containerd.io/gc.ref.snapshot.overlayfs=sha256:8e012198eea15b2554b07014081c85fec4967a1b9cc4b65bd9a4bce3ae1c0c88
sha256:04eeaa5f8c35b8206a15c12425498c37ecd2181e0ef202bea24a2382e4dd240f 1.485kB 21 minutes      containerd.io/gc.ref.snapshot.overlayfs=sha256:0a24540c1e5a47e5a8116c43151cb45c985f7a65afe5d4d65127d9c6fea2ffcf
sha256:145b24ad7f65c5368b1cae556cec31da472a135d429b0082a611616579f2752d 528B    10 seconds      containerd.io/gc.ref.content.config=sha256:d641b7bad7e90e03bc539f8365c90a8aa5bc3bfd275d88fe51aa2fecec097c30,containerd.io/gc.ref.content.l.0=sha256:f45bfda3aa14e255d9eb4c9a108eb3d8c6721946b4aa2e5808e5092242344a1c
sha256:14d4381342be1cab4adc3d5288a279a8a2fc665907363e1dce510bec52f29af3 528B    10 seconds      containerd.io/gc.ref.content.config=sha256:f106dae6f3630d726e4097289b182175aa475b850a042b785bc088381f3d641d,containerd.io/gc.ref.content.l.0=sha256:40e5b0b2e2bde18974628cadecd8a2f190f45f06c32846c16885d69b2908bf68
sha256:176bc6c6e93528f4b729fae1f8dbd70b73861264dba3a3f64c49c92e1f42a5aa 528B    10 seconds      containerd.io/gc.ref.content.config=sha256:8604c24e0b81c89dda8fd64e3609796b450567e92f1e348ef2dd73fab4d83fb6,containerd.io/gc.ref.content.l.0=sha256:ae982806674c51a962c0fdd6e19f464ebd673df529c5cfb7c1d049e0b618d384
sha256:1c34b3cb760a98c23361d919357b99fa497074576c898e7289425d45ef67b46a 528B    10 seconds      containerd.io/gc.ref.content.config=sha256:7cd16f17a19f36043f7337dc35fd160da1326bc99c7338a451c37ead222f54d0,containerd.io/gc.ref.content.l.0=sha256:c527615e4ffa2d5b9b777fd469b3b5ba7c1b1e9201c065be2c43569de48a3754
sha256:40e5b0b2e2bde18974628cadecd8a2f190f45f06c32846c16885d69b2908bf68 3.408MB 10 seconds      containerd.io/uncompressed=sha256:253f1efcceb575705106468bdc1c875a7c9b2a734d4bcca3b1335946656d7acb
sha256:41d876d4e44348d1c27445fdb0e64592e0eb926d4dbbcf09a3526dee7e628329 528B    21 minutes      containerd.io/gc.ref.content.config=sha256:04eeaa5f8c35b8206a15c12425498c37ecd2181e0ef202bea24a2382e4dd240f,containerd.io/gc.ref.content.l.0=sha256:a9eaa45ef418e883481a13c7d84fa9904f2ec56789c52a87ba5a9e6483f2b74f
sha256:7cd16f17a19f36043f7337dc35fd160da1326bc99c7338a451c37ead222f54d0 1.484kB 10 seconds      containerd.io/gc.ref.snapshot.overlayfs=sha256:383269fd0d7d9a5f547ad63131df9c00938dbbb23170a498357bc9fb5cd0948a
sha256:8604c24e0b81c89dda8fd64e3609796b450567e92f1e348ef2dd73fab4d83fb6 1.47kB  10 seconds      containerd.io/gc.ref.snapshot.overlayfs=sha256:b5902734b682781f128d816229ad15ec369201ddec7177fc18f3a8dcfeb00697
sha256:8921db27df2831fa6eaa85321205a2470c669b855f3ec95d5a3c2b46de0442c9 3.371MB 21 minutes      containerd.io/uncompressed=sha256:8e012198eea15b2554b07014081c85fec4967a1b9cc4b65bd9a4bce3ae1c0c88
sha256:93d5a28ff72d288d69b5997b8ba47396d2cbb62a72b5d87cd3351094b5d578a0 528B    21 minutes      containerd.io/gc.ref.content.l.0=sha256:8921db27df2831fa6eaa85321205a2470c669b855f3ec95d5a3c2b46de0442c9,containerd.io/gc.ref.content.config=sha256:042a816809aac8d0f7d7cacac7965782ee2ecac3f21bcf9f24b1de1a7387b769
sha256:a9eaa45ef418e883481a13c7d84fa9904f2ec56789c52a87ba5a9e6483f2b74f 3.259MB 21 minutes      containerd.io/uncompressed=sha256:0a24540c1e5a47e5a8116c43151cb45c985f7a65afe5d4d65127d9c6fea2ffcf
sha256:ae982806674c51a962c0fdd6e19f464ebd673df529c5cfb7c1d049e0b618d384 3.171MB 10 seconds      containerd.io/uncompressed=sha256:b5902734b682781f128d816229ad15ec369201ddec7177fc18f3a8dcfeb00697
sha256:c1aae4382455d858fc7dc11997cb34c5aae2da6a768619c16b2a7c70b3c12b43 1.485kB 10 seconds      containerd.io/gc.ref.snapshot.overlayfs=sha256:86ddd1858dda5de0cc1e3cc31a93fddc627a28136b08db8cda7460de39f657a3
sha256:c527615e4ffa2d5b9b777fd469b3b5ba7c1b1e9201c065be2c43569de48a3754 2.865MB 10 seconds      containerd.io/uncompressed=sha256:383269fd0d7d9a5f547ad63131df9c00938dbbb23170a498357bc9fb5cd0948a
sha256:d641b7bad7e90e03bc539f8365c90a8aa5bc3bfd275d88fe51aa2fecec097c30 1.474kB 10 seconds      containerd.io/gc.ref.snapshot.overlayfs=sha256:0e3395c9c4d69931dec5029f4c593bdabd1bf4cc1c06b7d568ee72ea29783132
sha256:f106dae6f3630d726e4097289b182175aa475b850a042b785bc088381f3d641d 1.47kB  10 seconds      containerd.io/gc.ref.snapshot.overlayfs=sha256:253f1efcceb575705106468bdc1c875a7c9b2a734d4bcca3b1335946656d7acb
sha256:f271e74b17ced29b915d351685fd4644785c6d1559dd1f2d4189a5e851ef753a 1.638kB 10 seconds      containerd.io/gc.ref.content.m.6=sha256:176bc6c6e93528f4b729fae1f8dbd70b73861264dba3a3f64c49c92e1f42a5aa,containerd.io/gc.ref.content.m.0=sha256:93d5a28ff72d288d69b5997b8ba47396d2cbb62a72b5d87cd3351094b5d578a0,containerd.io/gc.ref.content.m.1=sha256:01a4cdaebc9c6af607753cc538c507d0867897cdf9a1caa70bbab2eb1506c964,containerd.io/gc.ref.content.m.2=sha256:1c34b3cb760a98c23361d919357b99fa497074576c898e7289425d45ef67b46a,containerd.io/gc.ref.content.m.3=sha256:41d876d4e44348d1c27445fdb0e64592e0eb926d4dbbcf09a3526dee7e628329,containerd.io/gc.ref.content.m.4=sha256:14d4381342be1cab4adc3d5288a279a8a2fc665907363e1dce510bec52f29af3,containerd.io/gc.ref.content.m.5=sha256:145b24ad7f65c5368b1cae556cec31da472a135d429b0082a611616579f2752d
sha256:f45bfda3aa14e255d9eb4c9a108eb3d8c6721946b4aa2e5808e5092242344a1c 3.385MB 10 seconds      containerd.io/uncompressed=sha256:0e3395c9c4d69931dec5029f4c593bdabd1bf4cc1c06b7d568ee72ea29783132
$ ctr --address /run/docker/containerd/containerd.sock -n moby content get sha256:f271e74b17ced29b915d351685fd4644785c6d1559dd1f2d4189a5e851ef753a | jq
{
  "manifests": [
    {
      "digest": "sha256:93d5a28ff72d288d69b5997b8ba47396d2cbb62a72b5d87cd3351094b5d578a0",
      "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
      "platform": {
        "architecture": "amd64",
        "os": "linux"
      },
      "size": 528
    },
    {
      "digest": "sha256:01a4cdaebc9c6af607753cc538c507d0867897cdf9a1caa70bbab2eb1506c964",
      "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
      "platform": {
        "architecture": "arm",
        "os": "linux",
        "variant": "v6"
      },
      "size": 528
    },
    {
      "digest": "sha256:1c34b3cb760a98c23361d919357b99fa497074576c898e7289425d45ef67b46a",
      "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
      "platform": {
        "architecture": "arm",
        "os": "linux",
        "variant": "v7"
      },
      "size": 528
    },
    {
      "digest": "sha256:41d876d4e44348d1c27445fdb0e64592e0eb926d4dbbcf09a3526dee7e628329",
      "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
      "platform": {
        "architecture": "arm64",
        "os": "linux",
        "variant": "v8"
      },
      "size": 528
    },
    {
      "digest": "sha256:14d4381342be1cab4adc3d5288a279a8a2fc665907363e1dce510bec52f29af3",
      "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
      "platform": {
        "architecture": "386",
        "os": "linux"
      },
      "size": 528
    },
    {
      "digest": "sha256:145b24ad7f65c5368b1cae556cec31da472a135d429b0082a611616579f2752d",
      "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
      "platform": {
        "architecture": "ppc64le",
        "os": "linux"
      },
      "size": 528
    },
    {
      "digest": "sha256:176bc6c6e93528f4b729fae1f8dbd70b73861264dba3a3f64c49c92e1f42a5aa",
      "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
      "platform": {
        "architecture": "s390x",
        "os": "linux"
      },
      "size": 528
    }
  ],
  "mediaType": "application/vnd.docker.distribution.manifest.list.v2+json",
  "schemaVersion": 2
}

```

</details>


**- Description for the changelog**

**- A picture of a cute animal (not mandatory but encouraged)**
![image](https://user-images.githubusercontent.com/5046555/212901105-6185f842-57d3-4134-a82a-20799d16d2c1.png)
daemon/containerd/image_exporter.go
package containerd

import (
	"context"
	"io"

	"github.com/containerd/containerd"
	"github.com/containerd/containerd/images/archive"
	"github.com/containerd/containerd/platforms"
	"github.com/docker/distribution/reference"
	"github.com/pkg/errors"
	"github.com/sirupsen/logrus"
)

// ExportImage exports a list of images to the given output stream. The
// exported images are archived into a tar when written to the output
// stream. All images with the given tag and all versions containing
// the same tag are exported. names is the set of tags to export, and
// outStream is the writer which the images are written to.
//
// TODO(thaJeztah): produce JSON stream progress response and image events; see https://github.com/moby/moby/issues/43910
func (i *ImageService) ExportImage(ctx context.Context, names []string, outStream io.Writer) error {
	opts := []archive.ExportOpt{
		archive.WithPlatform(platforms.Ordered(platforms.DefaultSpec())),
		archive.WithSkipNonDistributableBlobs(),
	}
	is := i.client.ImageService()
	for _, imageRef := range names {
		named, err := reference.ParseDockerRef(imageRef)
		if err != nil {
			return err
		}
		opts = append(opts, archive.WithImage(is, named.String()))
	}
	return i.client.Export(ctx, outStream, opts...)
}

// LoadImage uploads a set of images into the repository. This is the
// complement of ExportImage.  The input stream is an uncompressed tar
// ball containing images and metadata.
//
// TODO(thaJeztah): produce JSON stream progress response and image events; see https://github.com/moby/moby/issues/43910
func (i *ImageService) LoadImage(ctx context.Context, inTar io.ReadCloser, outStream io.Writer, quiet bool) error {
	platform := platforms.All
	imgs, err := i.client.Import(ctx, inTar, containerd.WithImportPlatform(platform))

	if err != nil {
		// TODO(thaJeztah): remove this log or change to debug once we can; see https://github.com/moby/moby/pull/43822#discussion_r937502405
		logrus.WithError(err).Warn("failed to import image to containerd")
		return errors.Wrap(err, "failed to import image")
	}

	for _, img := range imgs {
		platformImg := containerd.NewImageWithPlatform(i.client, img, platform)

		unpacked, err := platformImg.IsUnpacked(ctx, i.snapshotter)
		if err != nil {
			// TODO(thaJeztah): remove this log or change to debug once we can; see https://github.com/moby/moby/pull/43822#discussion_r937502405
			logrus.WithError(err).WithField("image", img.Name).Debug("failed to check if image is unpacked")
			continue
		}

		if !unpacked {
			err := platformImg.Unpack(ctx, i.snapshotter)
			if err != nil {
				// TODO(thaJeztah): remove this log or change to debug once we can; see https://github.com/moby/moby/pull/43822#discussion_r937502405
				logrus.WithError(err).WithField("image", img.Name).Warn("failed to unpack image")
				return errors.Wrap(err, "failed to unpack image")
			}
		}
	}
	return nil
}

package containerd

import (
	"context"
	"fmt"
	"io"

	"github.com/containerd/containerd"
	cerrdefs "github.com/containerd/containerd/errdefs"
	containerdimages "github.com/containerd/containerd/images"
	"github.com/containerd/containerd/images/archive"
	cplatforms "github.com/containerd/containerd/platforms"
	"github.com/docker/distribution/reference"
	"github.com/docker/docker/errdefs"
	"github.com/docker/docker/pkg/platforms"
	"github.com/docker/docker/pkg/streamformatter"
	"github.com/opencontainers/image-spec/specs-go"
	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
	"github.com/sirupsen/logrus"
)

// ExportImage exports a list of images to the given output stream. The
// exported images are archived into a tar when written to the output
// stream. All images with the given tag and all versions containing
// the same tag are exported. names is the set of tags to export, and
// outStream is the writer which the images are written to.
//
// TODO(thaJeztah): produce JSON stream progress response and image events; see https://github.com/moby/moby/issues/43910
func (i *ImageService) ExportImage(ctx context.Context, names []string, outStream io.Writer) error {
	platform := platforms.AllPlatformsWithPreference(cplatforms.Default())
	opts := []archive.ExportOpt{
		archive.WithSkipNonDistributableBlobs(),

		// This makes the exported archive also include `manifest.json`
		// when the image is a manifest list. It is needed for backwards
		// compatibility with Docker image format.
		// The containerd will choose only one manifest for the `manifest.json`.
		// Our preference is to have it point to the default platform.
		// Example:
		//  Daemon is running on linux/arm64
		//  When we export linux/amd64 and linux/arm64, manifest.json will point to linux/arm64.
		//  When we export linux/amd64 only, manifest.json will point to linux/amd64.
		// Note: This is only applicable if importing this archive into non-containerd Docker.
		// Importing the same archive into containerd, will not restrict the platforms.
		archive.WithPlatform(platform),
	}

	ctx, release, err := i.client.WithLease(ctx)
	if err != nil {
		return errdefs.System(err)
	}
	defer release(ctx)

	for _, name := range names {
		target, err := i.resolveDescriptor(ctx, name)
		if err != nil {
			return err
		}

		// We may not have locally all the platforms that are specified in the index.
		// Export only those manifests that we have.
		// TODO(vvoland): Reconsider this when `--platform` is added.
		if containerdimages.IsIndexType(target.MediaType) {
			desc, err := i.getBestDescriptorForExport(ctx, target)
			if err != nil {
				return err
			}
			target = desc
		}

		if ref, err := reference.ParseNormalizedNamed(name); err == nil {
			ref = reference.TagNameOnly(ref)
			opts = append(opts, archive.WithManifest(target, ref.String()))

			logrus.WithFields(logrus.Fields{
				"target": target,
				"name":   ref.String(),
			}).Debug("export image")
		} else {
			opts = append(opts, archive.WithManifest(target))

			logrus.WithFields(logrus.Fields{
				"target": target,
			}).Debug("export image without name")
		}
	}

	return i.client.Export(ctx, outStream, opts...)
}

// LoadImage uploads a set of images into the repository. This is the
// complement of ExportImage.  The input stream is an uncompressed tar
// ball containing images and metadata.
//
// TODO(thaJeztah): produce JSON stream progress response and image events; see https://github.com/moby/moby/issues/43910
func (i *ImageService) LoadImage(ctx context.Context, inTar io.ReadCloser, outStream io.Writer, quiet bool) error {
	// TODO(vvoland): Allow user to pass platform
	platform := cplatforms.All
	imgs, err := i.client.Import(ctx, inTar, containerd.WithImportPlatform(platform))

	if err != nil {
		logrus.WithError(err).Debug("failed to import image to containerd")
		return errdefs.System(err)
	}

	store := i.client.ContentStore()
	progress := streamformatter.NewStdoutWriter(outStream)

	for _, img := range imgs {
		allPlatforms, err := containerdimages.Platforms(ctx, store, img.Target)
		if err != nil {
			logrus.WithError(err).WithField("image", img.Name).Debug("failed to get image platforms")
			return errdefs.Unknown(err)
		}

		name := img.Name
		if named, err := reference.ParseNormalizedNamed(img.Name); err == nil {
			name = reference.FamiliarName(named)
		}

		for _, platform := range allPlatforms {
			logger := logrus.WithFields(logrus.Fields{
				"platform": platform,
				"image":    name,
			})
			platformImg := containerd.NewImageWithPlatform(i.client, img, cplatforms.OnlyStrict(platform))

			unpacked, err := platformImg.IsUnpacked(ctx, i.snapshotter)
			if err != nil {
				logger.WithError(err).Debug("failed to check if image is unpacked")
				continue
			}

			if !unpacked {
				err = platformImg.Unpack(ctx, i.snapshotter)

				if err != nil {
					return errdefs.System(err)
				}
			}
			logger.WithField("alreadyUnpacked", unpacked).WithError(err).Debug("unpack")
		}

		fmt.Fprintf(progress, "Loaded image: %s\n", name)
	}
	return nil
}

// getBestDescriptorForExport returns a descriptor which only references content available locally.
// The returned descriptor can be:
// - The same index descriptor - if all content is available
// - Platform specific manifest - if only one manifest from the whole index is available
// - Reduced index descriptor - if not all, but more than one manifest is available
//
// The reduced index descriptor is stored in the content store and may be garbage collected.
// It's advised to pass a context with a lease that's long enough to cover usage of the blob.
func (i *ImageService) getBestDescriptorForExport(ctx context.Context, indexDesc ocispec.Descriptor) (ocispec.Descriptor, error) {
	none := ocispec.Descriptor{}

	if !containerdimages.IsIndexType(indexDesc.MediaType) {
		err := fmt.Errorf("index/manifest-list descriptor expected, got: %s", indexDesc.MediaType)
		return none, errdefs.InvalidParameter(err)
	}
	store := i.client.ContentStore()
	children, err := containerdimages.Children(ctx, store, indexDesc)
	if err != nil {
		if cerrdefs.IsNotFound(err) {
			return none, errdefs.NotFound(err)
		}
		return none, errdefs.System(err)
	}

	// Check which platform manifests have all their blobs available.
	hasMissingManifests := false
	var presentManifests []ocispec.Descriptor
	for _, mfst := range children {
		if containerdimages.IsManifestType(mfst.MediaType) {
			available, _, _, missing, err := containerdimages.Check(ctx, store, mfst, nil)
			if err != nil {
				hasMissingManifests = true
				logrus.WithField("manifest", mfst.Digest).Warn("failed to check manifest's blob availability, won't export")
				continue
			}

			if available && len(missing) == 0 {
				presentManifests = append(presentManifests, mfst)
				logrus.WithField("manifest", mfst.Digest).Debug("manifest content present, will export")
			} else {
				hasMissingManifests = true
				logrus.WithFields(logrus.Fields{
					"manifest": mfst.Digest,
					"missing":  missing,
				}).Debug("manifest is missing, won't export")
			}
		}
	}

	if !hasMissingManifests || len(children) == 0 {
		// If we have the full image, or it has no manifests, just export the original index.
		return indexDesc, nil
	} else if len(presentManifests) == 1 {
		// If only one platform is present, export that one manifest.
		return presentManifests[0], nil
	} else if len(presentManifests) == 0 {
		// Return error when none of the image's manifest is present.
		return none, errdefs.NotFound(fmt.Errorf("none of the manifests is fully present in the content store"))
	}

	// Create a new index which contains only the manifests we have in store.
	index := ocispec.Index{
		Versioned: specs.Versioned{
			SchemaVersion: 2,
		},
		MediaType:   ocispec.MediaTypeImageIndex,
		Manifests:   presentManifests,
		Annotations: indexDesc.Annotations,
	}

	reducedIndexDesc, err := storeJson(ctx, store, index.MediaType, index, nil)
	if err != nil {
		return none, err
	}

	return reducedIndexDesc, nil
}

No, the main consumer of the `manifest.json` is of course the "graphdriver Docker", but this one is actually about the containerd choosing only one manifest to include in the `manifest.json`:
https://github.com/containerd/containerd/blob/1c90a442489720eec95342e1789ee8a5e1b9536f/images/archive/exporter.go#L232-L234

It sorts all manifests by their platform, according to the platform matcher `Less` and chooses the "least" one.
In this case we prefer it to choose the default (host) platform, but if that one won't be exported, then let it choose whatever because for now we don't have any CLI flags for the user to communicate what is his intent.