image_manifest.go 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151
  1. package containerd
  2. import (
  3. "context"
  4. "encoding/json"
  5. "github.com/containerd/containerd"
  6. "github.com/containerd/containerd/content"
  7. "github.com/containerd/containerd/images"
  8. containerdimages "github.com/containerd/containerd/images"
  9. cplatforms "github.com/containerd/containerd/platforms"
  10. "github.com/docker/docker/errdefs"
  11. "github.com/moby/buildkit/util/attestation"
  12. ocispec "github.com/opencontainers/image-spec/specs-go/v1"
  13. "github.com/pkg/errors"
  14. )
  15. var (
  16. errNotManifestOrIndex = errdefs.InvalidParameter(errors.New("descriptor is neither a manifest or index"))
  17. errNotManifest = errdefs.InvalidParameter(errors.New("descriptor isn't a manifest"))
  18. )
  19. // walkImageManifests calls the handler for each locally present manifest in
  20. // the image. The image implements the containerd.Image interface, but all
  21. // operations act on the specific manifest instead of the index.
  22. func (i *ImageService) walkImageManifests(ctx context.Context, img containerdimages.Image, handler func(img *ImageManifest) error) error {
  23. desc := img.Target
  24. handleManifest := func(ctx context.Context, d ocispec.Descriptor) error {
  25. platformImg, err := i.NewImageManifest(ctx, img, d)
  26. if err != nil {
  27. if err == errNotManifest {
  28. return nil
  29. }
  30. return err
  31. }
  32. return handler(platformImg)
  33. }
  34. if containerdimages.IsManifestType(desc.MediaType) {
  35. return handleManifest(ctx, desc)
  36. }
  37. if containerdimages.IsIndexType(desc.MediaType) {
  38. return i.walkPresentChildren(ctx, desc, handleManifest)
  39. }
  40. return errNotManifestOrIndex
  41. }
  42. type ImageManifest struct {
  43. containerd.Image
  44. // Parent of the manifest (index/manifest list)
  45. RealTarget ocispec.Descriptor
  46. manifest *ocispec.Manifest
  47. }
  48. func (i *ImageService) NewImageManifest(ctx context.Context, img containerdimages.Image, manifestDesc ocispec.Descriptor) (*ImageManifest, error) {
  49. if !containerdimages.IsManifestType(manifestDesc.MediaType) {
  50. return nil, errNotManifest
  51. }
  52. parent := img.Target
  53. img.Target = manifestDesc
  54. c8dImg := containerd.NewImageWithPlatform(i.client, img, cplatforms.All)
  55. return &ImageManifest{
  56. Image: c8dImg,
  57. RealTarget: parent,
  58. }, nil
  59. }
  60. func (im *ImageManifest) Metadata() containerdimages.Image {
  61. md := im.Image.Metadata()
  62. md.Target = im.RealTarget
  63. return md
  64. }
  65. // IsPseudoImage returns false if the manifest has no layers or any of its layers is a known image layer.
  66. // Some manifests use the image media type for compatibility, even if they are not a real image.
  67. func (im *ImageManifest) IsPseudoImage(ctx context.Context) (bool, error) {
  68. desc := im.Target()
  69. // Quick check for buildkit attestation manifests
  70. // https://github.com/moby/buildkit/blob/v0.11.4/docs/attestations/attestation-storage.md
  71. // This would have also been caught by the layer check below, but it requires
  72. // an additional content read and deserialization of Manifest.
  73. if _, has := desc.Annotations[attestation.DockerAnnotationReferenceType]; has {
  74. return true, nil
  75. }
  76. mfst, err := im.Manifest(ctx)
  77. if err != nil {
  78. return true, err
  79. }
  80. if len(mfst.Layers) == 0 {
  81. return false, nil
  82. }
  83. for _, l := range mfst.Layers {
  84. if images.IsLayerType(l.MediaType) {
  85. return false, nil
  86. }
  87. }
  88. return true, nil
  89. }
  90. func (im *ImageManifest) Manifest(ctx context.Context) (ocispec.Manifest, error) {
  91. if im.manifest != nil {
  92. return *im.manifest, nil
  93. }
  94. mfst, err := readManifest(ctx, im.ContentStore(), im.Target())
  95. if err != nil {
  96. return ocispec.Manifest{}, err
  97. }
  98. im.manifest = &mfst
  99. return mfst, nil
  100. }
  101. func (im *ImageManifest) CheckContentAvailable(ctx context.Context) (bool, error) {
  102. // The target is already a platform-specific manifest, so no need to match platform.
  103. pm := cplatforms.All
  104. available, _, _, missing, err := containerdimages.Check(ctx, im.ContentStore(), im.Target(), pm)
  105. if err != nil {
  106. return false, err
  107. }
  108. if !available || len(missing) > 0 {
  109. return false, nil
  110. }
  111. return true, nil
  112. }
  113. func readManifest(ctx context.Context, store content.Provider, desc ocispec.Descriptor) (ocispec.Manifest, error) {
  114. p, err := content.ReadBlob(ctx, store, desc)
  115. if err != nil {
  116. return ocispec.Manifest{}, err
  117. }
  118. var mfst ocispec.Manifest
  119. if err := json.Unmarshal(p, &mfst); err != nil {
  120. return ocispec.Manifest{}, err
  121. }
  122. return mfst, nil
  123. }