Browse Source

Merge pull request #44809 from rumpl/containerd-image-history

daemon/c8d: Implement image history
Tianon Gravi 2 years ago
parent
commit
2b9de2e24a
1 changed files with 95 additions and 5 deletions
  1. 95 5
      daemon/containerd/image_history.go

+ 95 - 5
daemon/containerd/image_history.go

@@ -2,14 +2,104 @@ package containerd
 
 
 import (
 import (
 	"context"
 	"context"
-	"errors"
+	"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"
 	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 ImageHistory structures for the specified
-// image name by walking the image lineage.
+// 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) {
 func (i *ImageService) ImageHistory(ctx context.Context, name string) ([]*imagetype.HistoryResponseItem, error) {
-	return nil, errdefs.NotImplemented(errors.New("not implemented"))
+	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
+		}
+
+		tags := make([]string, len(tagged))
+		for i, t := range tagged {
+			name, err := reference.ParseNamed(t.Name)
+			if err != nil {
+				return nil, err
+			}
+			tags[i] = reference.FamiliarString(name)
+		}
+		history[0].Tags = tags
+	}
+
+	return history, nil
 }
 }