buildkit/cache/blobs_linux.go

106 lines
3.4 KiB
Go

//go:build linux
// +build linux
package cache
import (
"bufio"
"context"
"io"
"github.com/containerd/containerd/content"
"github.com/containerd/containerd/errdefs"
"github.com/containerd/containerd/mount"
"github.com/moby/buildkit/util/overlay"
digest "github.com/opencontainers/go-digest"
ocispecs "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors"
)
var emptyDesc = ocispecs.Descriptor{}
// computeOverlayBlob provides overlayfs-specialized method to compute
// diff between lower and upper snapshot. If the passed mounts cannot
// be computed (e.g. because the mounts aren't overlayfs), it returns
// an error.
func (sr *immutableRef) tryComputeOverlayBlob(ctx context.Context, lower, upper []mount.Mount, mediaType string, ref string, compressorFunc compressor) (_ ocispecs.Descriptor, ok bool, err error) {
// Get upperdir location if mounts are overlayfs that can be processed by this differ.
upperdir, err := overlay.GetUpperdir(lower, upper)
if err != nil {
// This is not an overlayfs snapshot. This is not an error so don't return error here
// and let the caller fallback to another differ.
return emptyDesc, false, nil
}
cw, err := sr.cm.ContentStore.Writer(ctx,
content.WithRef(ref),
content.WithDescriptor(ocispecs.Descriptor{
MediaType: mediaType, // most contentstore implementations just ignore this
}))
if err != nil {
return emptyDesc, false, errors.Wrap(err, "failed to open writer")
}
defer func() {
if err != nil {
cw.Close()
}
}()
bufW := bufio.NewWriterSize(cw, 128*1024)
var labels map[string]string
if compressorFunc != nil {
dgstr := digest.SHA256.Digester()
compressed, err := compressorFunc(bufW, mediaType)
if err != nil {
return emptyDesc, false, errors.Wrap(err, "failed to get compressed stream")
}
err = overlay.WriteUpperdir(ctx, io.MultiWriter(compressed, dgstr.Hash()), upperdir, lower)
compressed.Close()
if err != nil {
return emptyDesc, false, errors.Wrap(err, "failed to write compressed diff")
}
if labels == nil {
labels = map[string]string{}
}
labels[containerdUncompressed] = dgstr.Digest().String()
} else {
if err = overlay.WriteUpperdir(ctx, bufW, upperdir, lower); err != nil {
return emptyDesc, false, errors.Wrap(err, "failed to write diff")
}
}
if err := bufW.Flush(); err != nil {
return emptyDesc, false, errors.Wrap(err, "failed to flush diff")
}
var commitopts []content.Opt
if labels != nil {
commitopts = append(commitopts, content.WithLabels(labels))
}
dgst := cw.Digest()
if err := cw.Commit(ctx, 0, dgst, commitopts...); err != nil {
if !errdefs.IsAlreadyExists(err) {
return emptyDesc, false, errors.Wrap(err, "failed to commit")
}
}
cinfo, err := sr.cm.ContentStore.Info(ctx, dgst)
if err != nil {
return emptyDesc, false, errors.Wrap(err, "failed to get info from content store")
}
if cinfo.Labels == nil {
cinfo.Labels = make(map[string]string)
}
// Set uncompressed label if digest already existed without label
if _, ok := cinfo.Labels[containerdUncompressed]; !ok {
cinfo.Labels[containerdUncompressed] = labels[containerdUncompressed]
if _, err := sr.cm.ContentStore.Update(ctx, cinfo, "labels."+containerdUncompressed); err != nil {
return emptyDesc, false, errors.Wrap(err, "error setting uncompressed label")
}
}
return ocispecs.Descriptor{
MediaType: mediaType,
Size: cinfo.Size,
Digest: cinfo.Digest,
}, true, nil
}