image_history.go 4.5 KB

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