blobs_linux.go 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130
  1. //go:build linux
  2. // +build linux
  3. package cache
  4. import (
  5. "bufio"
  6. "context"
  7. "io"
  8. "github.com/containerd/containerd/content"
  9. "github.com/containerd/containerd/errdefs"
  10. labelspkg "github.com/containerd/containerd/labels"
  11. "github.com/containerd/containerd/mount"
  12. "github.com/moby/buildkit/util/bklog"
  13. "github.com/moby/buildkit/util/compression"
  14. "github.com/moby/buildkit/util/overlay"
  15. digest "github.com/opencontainers/go-digest"
  16. ocispecs "github.com/opencontainers/image-spec/specs-go/v1"
  17. "github.com/pkg/errors"
  18. )
  19. var emptyDesc = ocispecs.Descriptor{}
  20. // computeOverlayBlob provides overlayfs-specialized method to compute
  21. // diff between lower and upper snapshot. If the passed mounts cannot
  22. // be computed (e.g. because the mounts aren't overlayfs), it returns
  23. // an error.
  24. func (sr *immutableRef) tryComputeOverlayBlob(ctx context.Context, lower, upper []mount.Mount, mediaType string, ref string, compressorFunc compression.Compressor) (_ ocispecs.Descriptor, ok bool, err error) {
  25. // Get upperdir location if mounts are overlayfs that can be processed by this differ.
  26. upperdir, err := overlay.GetUpperdir(lower, upper)
  27. if err != nil {
  28. // This is not an overlayfs snapshot. This is not an error so don't return error here
  29. // and let the caller fallback to another differ.
  30. return emptyDesc, false, nil
  31. }
  32. cw, err := sr.cm.ContentStore.Writer(ctx,
  33. content.WithRef(ref),
  34. content.WithDescriptor(ocispecs.Descriptor{
  35. MediaType: mediaType, // most contentstore implementations just ignore this
  36. }))
  37. if err != nil {
  38. return emptyDesc, false, errors.Wrap(err, "failed to open writer")
  39. }
  40. defer func() {
  41. if cw != nil {
  42. // after commit success cw will be set to nil, if cw isn't nil, error
  43. // happened before commit, we should abort this ingest, and because the
  44. // error may incured by ctx cancel, use a new context here. And since
  45. // cm.Close will unlock this ref in the content store, we invoke abort
  46. // to remove the ingest root in advance.
  47. if aerr := sr.cm.ContentStore.Abort(context.Background(), ref); aerr != nil {
  48. bklog.G(ctx).WithError(aerr).Warnf("failed to abort writer %q", ref)
  49. }
  50. if cerr := cw.Close(); cerr != nil {
  51. bklog.G(ctx).WithError(cerr).Warnf("failed to close writer %q", ref)
  52. }
  53. }
  54. }()
  55. if err = cw.Truncate(0); err != nil {
  56. return emptyDesc, false, errors.Wrap(err, "failed to truncate writer")
  57. }
  58. bufW := bufio.NewWriterSize(cw, 128*1024)
  59. var labels map[string]string
  60. if compressorFunc != nil {
  61. dgstr := digest.SHA256.Digester()
  62. compressed, err := compressorFunc(bufW, mediaType)
  63. if err != nil {
  64. return emptyDesc, false, errors.Wrap(err, "failed to get compressed stream")
  65. }
  66. // Close ensure compressorFunc does some finalization works.
  67. defer compressed.Close()
  68. if err := overlay.WriteUpperdir(ctx, io.MultiWriter(compressed, dgstr.Hash()), upperdir, lower); err != nil {
  69. return emptyDesc, false, errors.Wrap(err, "failed to write compressed diff")
  70. }
  71. if err := compressed.Close(); err != nil {
  72. return emptyDesc, false, errors.Wrap(err, "failed to close compressed diff writer")
  73. }
  74. if labels == nil {
  75. labels = map[string]string{}
  76. }
  77. labels[labelspkg.LabelUncompressed] = dgstr.Digest().String()
  78. } else {
  79. if err = overlay.WriteUpperdir(ctx, bufW, upperdir, lower); err != nil {
  80. return emptyDesc, false, errors.Wrap(err, "failed to write diff")
  81. }
  82. }
  83. if err := bufW.Flush(); err != nil {
  84. return emptyDesc, false, errors.Wrap(err, "failed to flush diff")
  85. }
  86. var commitopts []content.Opt
  87. if labels != nil {
  88. commitopts = append(commitopts, content.WithLabels(labels))
  89. }
  90. dgst := cw.Digest()
  91. if err := cw.Commit(ctx, 0, dgst, commitopts...); err != nil {
  92. if !errdefs.IsAlreadyExists(err) {
  93. return emptyDesc, false, errors.Wrap(err, "failed to commit")
  94. }
  95. }
  96. if err := cw.Close(); err != nil {
  97. return emptyDesc, false, err
  98. }
  99. cw = nil
  100. cinfo, err := sr.cm.ContentStore.Info(ctx, dgst)
  101. if err != nil {
  102. return emptyDesc, false, errors.Wrap(err, "failed to get info from content store")
  103. }
  104. if cinfo.Labels == nil {
  105. cinfo.Labels = make(map[string]string)
  106. }
  107. // Set uncompressed label if digest already existed without label
  108. if _, ok := cinfo.Labels[labelspkg.LabelUncompressed]; !ok {
  109. cinfo.Labels[labelspkg.LabelUncompressed] = labels[labelspkg.LabelUncompressed]
  110. if _, err := sr.cm.ContentStore.Update(ctx, cinfo, "labels."+labelspkg.LabelUncompressed); err != nil {
  111. return emptyDesc, false, errors.Wrap(err, "error setting uncompressed label")
  112. }
  113. }
  114. return ocispecs.Descriptor{
  115. MediaType: mediaType,
  116. Size: cinfo.Size,
  117. Digest: cinfo.Digest,
  118. }, true, nil
  119. }