daemon/c8d: Implement `docker tag`
- Upstreams: https://github.com/rumpl/moby/pull/13


**- What I did**
Implement `docker tag` for containerd image store.

**- How I did it**
See individual commits.

**- How to verify it**
```bash
$ docker pull ubuntu
$ docker tag ubuntu test
$ docker pull alpine
$ docker tag alpine test
```
<details>
<summary>Tag replace</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 image ls
REPOSITORY   TAG       IMAGE ID       CREATED                  SIZE
alpine       latest    f271e74b17ce   Less than a second ago   3.26MB
$ docker image ls
REPOSITORY   TAG       IMAGE ID       CREATED         SIZE
alpine       latest    f271e74b17ce   6 seconds ago   3.26MB
$ docker pull busybox
Using default tag: latest
7b3ccabffc97: Download complete
5e42fbc46b17: Download complete
abaa813f94fd: Download complete
f78e6840ded1: Download complete
docker.io/library/busybox:latest
$ docker tag busybox myimage
$ docker image ls
REPOSITORY   TAG       IMAGE ID       CREATED         SIZE
alpine       latest    f271e74b17ce   8 seconds ago   3.26MB
busybox      latest    7b3ccabffc97   5 seconds ago   2.01MB
myimage      latest    7b3ccabffc97   1 second ago    2.01MB
$ docker rmi busybox
Untagged: busybox
$ docker image ls
REPOSITORY   TAG       IMAGE ID       CREATED          SIZE
alpine       latest    f271e74b17ce   13 seconds ago   3.26MB
myimage      latest    7b3ccabffc97   6 seconds ago    2.01MB
$ docker tag alpine myimage
$ docker image ls
REPOSITORY   TAG       IMAGE ID       CREATED          SIZE
<none>       <none>    7b3ccabffc97   2 seconds ago    2.01MB
alpine       latest    f271e74b17ce   24 seconds ago   3.26MB
myimage      latest    f271e74b17ce   2 seconds ago    3.26MB
$ docker tag 7b3ccabffc97 busybox # this needs https://github.com/moby/moby/pull/44842
$ docker image ls
REPOSITORY   TAG       IMAGE ID       CREATED              SIZE
alpine       latest    f271e74b17ce   About a minute ago   3.26MB
busybox      latest    7b3ccabffc97   2 seconds ago        2.01MB
myimage      latest    f271e74b17ce   52 seconds ago       3.26MB


```

</details>


**- 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)**
![image](https://user-images.githubusercontent.com/5046555/211568561-d168ab13-bdc0-4a0a-8a78-fef3d9cca2b7.png)


daemon/containerd/image_tag.go
package containerd

import (
	"errors"

	"github.com/docker/distribution/reference"
	"github.com/docker/docker/errdefs"
	"github.com/docker/docker/image"
)

// TagImage creates the tag specified by newTag, pointing to the image named
// imageName (alternatively, imageName can also be an image ID).
func (i *ImageService) TagImage(imageName, repository, tag string) (string, error) {
	return "", errdefs.NotImplemented(errors.New("not implemented"))
}

// TagImageWithReference adds the given reference to the image ID provided.
func (i *ImageService) TagImageWithReference(imageID image.ID, newTag reference.Named) error {
	return errdefs.NotImplemented(errors.New("not implemented"))
}

package containerd

import (
	"context"

	cerrdefs "github.com/containerd/containerd/errdefs"
	containerdimages "github.com/containerd/containerd/images"
	"github.com/docker/distribution/reference"
	"github.com/docker/docker/errdefs"
	"github.com/docker/docker/image"
	"github.com/pkg/errors"
	"github.com/sirupsen/logrus"
)

// TagImage creates an image named as newTag and targeting the given descriptor id.
func (i *ImageService) TagImage(ctx context.Context, imageID image.ID, newTag reference.Named) error {
	target, err := i.resolveDescriptor(ctx, imageID.String())
	if err != nil {
		return errors.Wrapf(err, "failed to resolve image id %q to a descriptor", imageID.String())
	}

	newImg := containerdimages.Image{
		Name:   newTag.String(),
		Target: target,
	}

	is := i.client.ImageService()
	_, createErr := is.Create(ctx, newImg)
	if createErr != nil {
		if !cerrdefs.IsAlreadyExists(err) {
			return errdefs.System(errors.Wrapf(err, "failed to create image with name %s and target %s", newImg.Name, newImg.Target.Digest.String()))
		}

		replacedImg, err := is.Get(ctx, newImg.Name)
		if err != nil {
			return errdefs.Unknown(errors.Wrapf(err, "creating image %s failed because it already exists, but accessing it also failed", newImg.Name))
		}

		// Check if image we would replace already resolves to the same target.
		// No need to do anything.
		if replacedImg.Target.Digest == target.Digest {
			return nil
		}

		// If there already exists an image with this tag, delete it
		if err := i.softImageDelete(ctx, replacedImg); err != nil {
			return errors.Wrapf(err, "failed to delete previous image %s", replacedImg.Name)
		}

		if _, err = is.Create(context.Background(), newImg); err != nil {
			return errdefs.System(errors.Wrapf(err, "failed to create an image %s with target %s after deleting the existing one",
				newImg.Name, imageID.String()))
		}
	}

	logger := logrus.WithFields(logrus.Fields{
		"imageID": imageID.String(),
		"tag":     newTag.String(),
	})
	logger.Info("image created")

	// The tag succeeded, check if the source image is dangling
	sourceDanglingImg, err := is.Get(context.Background(), danglingImageName(target.Digest))
	if err != nil {
		if !cerrdefs.IsNotFound(err) {
			logger.WithError(err).Warn("unexpected error when checking if source image is dangling")
		}
		return nil
	}

	// Delete the source dangling image, as it's no longer dangling.
	if err := is.Delete(context.Background(), sourceDanglingImg.Name); err != nil {
		logger.WithError(err).Warn("unexpected error when deleting dangling image")
	}

	return nil
}

Nit, `if err := i.softImageDelete(oldImg); err != nil { ... }`