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
}

The log has additional information about the context (imageID, tag) - which might be useful when debugging failures. The only caveat is that these will only appear in the daemon log.
Should we drop the logging and just include these in the returned error?