image_children.go 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167
  1. package containerd
  2. import (
  3. "context"
  4. "github.com/containerd/containerd/content"
  5. cerrdefs "github.com/containerd/containerd/errdefs"
  6. containerdimages "github.com/containerd/containerd/images"
  7. "github.com/containerd/containerd/platforms"
  8. "github.com/containerd/log"
  9. "github.com/docker/docker/errdefs"
  10. "github.com/docker/docker/image"
  11. "github.com/opencontainers/go-digest"
  12. ocispec "github.com/opencontainers/image-spec/specs-go/v1"
  13. "github.com/pkg/errors"
  14. )
  15. // Children returns a slice of image IDs that are children of the `id` image
  16. func (i *ImageService) Children(ctx context.Context, id image.ID) ([]image.ID, error) {
  17. imgs, err := i.client.ImageService().List(ctx, "labels."+imageLabelClassicBuilderParent+"=="+string(id))
  18. if err != nil {
  19. return []image.ID{}, errdefs.System(errors.Wrap(err, "failed to list all images"))
  20. }
  21. var children []image.ID
  22. for _, img := range imgs {
  23. children = append(children, image.ID(img.Target.Digest))
  24. }
  25. return children, nil
  26. }
  27. // platformRootfs returns a rootfs for a specified platform.
  28. func platformRootfs(ctx context.Context, store content.Store, desc ocispec.Descriptor, platform ocispec.Platform) (ocispec.RootFS, error) {
  29. empty := ocispec.RootFS{}
  30. configDesc, err := containerdimages.Config(ctx, store, desc, platforms.OnlyStrict(platform))
  31. if err != nil {
  32. return empty, errors.Wrapf(err, "failed to get config for platform %s", platforms.Format(platform))
  33. }
  34. diffs, err := containerdimages.RootFS(ctx, store, configDesc)
  35. if err != nil {
  36. return empty, errors.Wrapf(err, "failed to obtain rootfs")
  37. }
  38. return ocispec.RootFS{
  39. Type: "layers",
  40. DiffIDs: diffs,
  41. }, nil
  42. }
  43. // isRootfsChildOf checks if all layers from parent rootfs are child's first layers
  44. // and child has at least one more layer (to make it not commutative).
  45. // Example:
  46. // A with layers [X, Y],
  47. // B with layers [X, Y, Z]
  48. // C with layers [Y, Z]
  49. //
  50. // Only isRootfsChildOf(B, A) is true.
  51. // Which means that B is considered a children of A. B and C has no children.
  52. // See more examples in TestIsRootfsChildOf.
  53. func isRootfsChildOf(child ocispec.RootFS, parent ocispec.RootFS) bool {
  54. childLen := len(child.DiffIDs)
  55. parentLen := len(parent.DiffIDs)
  56. if childLen <= parentLen {
  57. return false
  58. }
  59. for i := 0; i < parentLen; i++ {
  60. if child.DiffIDs[i] != parent.DiffIDs[i] {
  61. return false
  62. }
  63. }
  64. return true
  65. }
  66. // parents returns a slice of image IDs whose entire rootfs contents match,
  67. // in order, the childs first layers, excluding images with the exact same
  68. // rootfs.
  69. //
  70. // Called from image_delete.go to prune dangling parents.
  71. func (i *ImageService) parents(ctx context.Context, id image.ID) ([]imageWithRootfs, error) {
  72. target, err := i.resolveDescriptor(ctx, id.String())
  73. if err != nil {
  74. return nil, errors.Wrap(err, "failed to get child image")
  75. }
  76. cs := i.client.ContentStore()
  77. allPlatforms, err := containerdimages.Platforms(ctx, cs, target)
  78. if err != nil {
  79. return nil, errdefs.System(errors.Wrap(err, "failed to list platforms supported by image"))
  80. }
  81. var childRootFS []ocispec.RootFS
  82. for _, platform := range allPlatforms {
  83. rootfs, err := platformRootfs(ctx, cs, target, platform)
  84. if err != nil {
  85. if cerrdefs.IsNotFound(err) {
  86. continue
  87. }
  88. return nil, errdefs.System(errors.Wrap(err, "failed to get platform-specific rootfs"))
  89. }
  90. childRootFS = append(childRootFS, rootfs)
  91. }
  92. imgs, err := i.client.ImageService().List(ctx)
  93. if err != nil {
  94. return nil, errdefs.System(errors.Wrap(err, "failed to list all images"))
  95. }
  96. var parents []imageWithRootfs
  97. for _, img := range imgs {
  98. nextImage:
  99. for _, platform := range allPlatforms {
  100. rootfs, err := platformRootfs(ctx, cs, img.Target, platform)
  101. if err != nil {
  102. if cerrdefs.IsNotFound(err) {
  103. continue
  104. }
  105. return nil, errdefs.System(errors.Wrap(err, "failed to get platform-specific rootfs"))
  106. }
  107. for _, childRoot := range childRootFS {
  108. if isRootfsChildOf(childRoot, rootfs) {
  109. parents = append(parents, imageWithRootfs{
  110. img: img,
  111. rootfs: rootfs,
  112. })
  113. break nextImage
  114. }
  115. }
  116. }
  117. }
  118. return parents, nil
  119. }
  120. // getParentsByBuilderLabel finds images that were a base for the given image
  121. // by an image label set by the legacy builder.
  122. // NOTE: This only works for images built with legacy builder (not Buildkit).
  123. func (i *ImageService) getParentsByBuilderLabel(ctx context.Context, img containerdimages.Image) ([]containerdimages.Image, error) {
  124. parent, ok := img.Labels[imageLabelClassicBuilderParent]
  125. if !ok || parent == "" {
  126. return nil, nil
  127. }
  128. dgst, err := digest.Parse(parent)
  129. if err != nil {
  130. log.G(ctx).WithFields(log.Fields{
  131. "error": err,
  132. "value": parent,
  133. }).Warnf("invalid %s label value", imageLabelClassicBuilderParent)
  134. return nil, nil
  135. }
  136. return i.client.ImageService().List(ctx, "target.digest=="+dgst.String())
  137. }
  138. type imageWithRootfs struct {
  139. img containerdimages.Image
  140. rootfs ocispec.RootFS
  141. }