image_history.go 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163
  1. package containerd
  2. import (
  3. "context"
  4. "sort"
  5. "github.com/containerd/containerd/images"
  6. cplatforms "github.com/containerd/containerd/platforms"
  7. "github.com/containerd/log"
  8. "github.com/distribution/reference"
  9. imagetype "github.com/docker/docker/api/types/image"
  10. "github.com/docker/docker/errdefs"
  11. "github.com/docker/docker/pkg/platforms"
  12. "github.com/opencontainers/image-spec/identity"
  13. ocispec "github.com/opencontainers/image-spec/specs-go/v1"
  14. "github.com/pkg/errors"
  15. )
  16. // ImageHistory returns a slice of HistoryResponseItem structures for the
  17. // specified image name by walking the image lineage.
  18. func (i *ImageService) ImageHistory(ctx context.Context, name string) ([]*imagetype.HistoryResponseItem, error) {
  19. img, err := i.resolveImage(ctx, name)
  20. if err != nil {
  21. return nil, err
  22. }
  23. cs := i.client.ContentStore()
  24. // TODO: pass platform in from the CLI
  25. platform := platforms.AllPlatformsWithPreference(cplatforms.Default())
  26. var presentImages []ocispec.Image
  27. err = i.walkImageManifests(ctx, img, func(img *ImageManifest) error {
  28. conf, err := img.Config(ctx)
  29. if err != nil {
  30. return err
  31. }
  32. var ociimage ocispec.Image
  33. if err := readConfig(ctx, cs, conf, &ociimage); err != nil {
  34. return err
  35. }
  36. presentImages = append(presentImages, ociimage)
  37. return nil
  38. })
  39. if err != nil {
  40. return nil, err
  41. }
  42. if len(presentImages) == 0 {
  43. return nil, errdefs.NotFound(errors.New("failed to find image manifest"))
  44. }
  45. sort.SliceStable(presentImages, func(i, j int) bool {
  46. return platform.Less(presentImages[i].Platform, presentImages[j].Platform)
  47. })
  48. ociimage := presentImages[0]
  49. var (
  50. history []*imagetype.HistoryResponseItem
  51. sizes []int64
  52. )
  53. s := i.client.SnapshotService(i.snapshotter)
  54. diffIDs := ociimage.RootFS.DiffIDs
  55. for i := range diffIDs {
  56. chainID := identity.ChainID(diffIDs[0 : i+1]).String()
  57. use, err := s.Usage(ctx, chainID)
  58. if err != nil {
  59. return nil, err
  60. }
  61. sizes = append(sizes, use.Size)
  62. }
  63. for _, h := range ociimage.History {
  64. size := int64(0)
  65. if !h.EmptyLayer {
  66. if len(sizes) == 0 {
  67. return nil, errors.New("unable to find the size of the layer")
  68. }
  69. size = sizes[0]
  70. sizes = sizes[1:]
  71. }
  72. var created int64
  73. if h.Created != nil {
  74. created = h.Created.Unix()
  75. }
  76. history = append([]*imagetype.HistoryResponseItem{{
  77. ID: "<missing>",
  78. Comment: h.Comment,
  79. CreatedBy: h.CreatedBy,
  80. Created: created,
  81. Size: size,
  82. Tags: nil,
  83. }}, history...)
  84. }
  85. findParents := func(img images.Image) []images.Image {
  86. imgs, err := i.getParentsByBuilderLabel(ctx, img)
  87. if err != nil {
  88. log.G(ctx).WithFields(log.Fields{
  89. "error": err,
  90. "image": img,
  91. }).Warn("failed to list parent images")
  92. return nil
  93. }
  94. return imgs
  95. }
  96. is := i.client.ImageService()
  97. currentImg := img
  98. for _, h := range history {
  99. dgst := currentImg.Target.Digest.String()
  100. h.ID = dgst
  101. imgs, err := is.List(ctx, "target.digest=="+dgst)
  102. if err != nil {
  103. return nil, err
  104. }
  105. tags := getImageTags(ctx, imgs)
  106. h.Tags = append(h.Tags, tags...)
  107. parents := findParents(currentImg)
  108. foundNext := false
  109. for _, img := range parents {
  110. _, hasLabel := img.Labels[imageLabelClassicBuilderParent]
  111. if !foundNext || hasLabel {
  112. currentImg = img
  113. foundNext = true
  114. }
  115. }
  116. if !foundNext {
  117. break
  118. }
  119. }
  120. return history, nil
  121. }
  122. func getImageTags(ctx context.Context, imgs []images.Image) []string {
  123. var tags []string
  124. for _, img := range imgs {
  125. if isDanglingImage(img) {
  126. continue
  127. }
  128. name, err := reference.ParseNamed(img.Name)
  129. if err != nil {
  130. log.G(ctx).WithFields(log.Fields{
  131. "name": name,
  132. "error": err,
  133. }).Warn("image with a name that's not a valid named reference")
  134. continue
  135. }
  136. tags = append(tags, reference.FamiliarString(name))
  137. }
  138. return tags
  139. }