Files
buildx/vendor/github.com/moby/buildkit/util/contentutil/cache.go
T
2026-02-24 14:19:34 -08:00

248 lines
5.6 KiB
Go

package contentutil
import (
"context"
"encoding/json"
"slices"
"strings"
"sync"
"github.com/containerd/containerd/v2/core/content"
"github.com/containerd/containerd/v2/core/remotes"
cerrdefs "github.com/containerd/errdefs"
digest "github.com/opencontainers/go-digest"
ocispecs "github.com/opencontainers/image-spec/specs-go/v1"
)
func ReferrersProviderWithBuffer(p ReferrersProvider, buffer Buffer, name string) *ReferrersProviderBuffer {
return &ReferrersProviderBuffer{
p: p,
cache: buffer,
name: name,
}
}
var _ ReferrersProvider = &ReferrersProviderBuffer{}
type ReferrersProviderBuffer struct {
p ReferrersProvider
cache Buffer
name string
mu sync.Mutex
blobs map[digest.Digest]ocispecs.Descriptor
refs map[digest.Digest][]ocispecs.Descriptor
}
func (p *ReferrersProviderBuffer) ReaderAt(ctx context.Context, desc ocispecs.Descriptor) (content.ReaderAt, error) {
cw, err := content.OpenWriter(ctx, p.cache, content.WithDescriptor(desc), content.WithRef(desc.Digest.String()))
if err != nil {
if cerrdefs.IsAlreadyExists(err) {
ra, err := p.cache.ReaderAt(ctx, desc)
if err != nil {
return nil, err
}
p.mu.Lock()
if p.blobs == nil {
p.blobs = make(map[digest.Digest]ocispecs.Descriptor)
}
p.blobs[desc.Digest] = desc
p.mu.Unlock()
return ra, nil
}
return nil, err
}
if st, err := cw.Status(); err == nil {
if st.Offset > 0 {
if err := cw.Truncate(0); err != nil {
cw.Close()
return nil, err
}
}
}
abort := func() {
_ = p.cache.Abort(ctx, desc.Digest.String())
}
defer func() {
if abort != nil {
abort()
}
}()
ra, err := p.p.ReaderAt(ctx, desc)
if err != nil {
cw.Close()
return nil, err
}
defer ra.Close()
if err := content.CopyReaderAt(cw, ra, ra.Size()); err != nil {
cw.Close()
return nil, err
}
if err := cw.Commit(ctx, desc.Size, desc.Digest); err != nil {
cw.Close()
return nil, err
}
abort = nil
ra, err = p.cache.ReaderAt(ctx, desc)
if err != nil {
return nil, err
}
p.mu.Lock()
if p.blobs == nil {
p.blobs = make(map[digest.Digest]ocispecs.Descriptor)
}
p.blobs[desc.Digest] = desc
p.mu.Unlock()
return ra, nil
}
func (p *ReferrersProviderBuffer) FetchReferrers(ctx context.Context, dgst digest.Digest, opts ...remotes.FetchReferrersOpt) ([]ocispecs.Descriptor, error) {
cfg := remotes.FetchReferrersConfig{}
for _, o := range opts {
if err := o(ctx, &cfg); err != nil {
return nil, err
}
}
info, err := p.cache.Info(ctx, dgst)
if err == nil && len(info.Labels) != 0 {
refs := []ocispecs.Descriptor{}
for l, v := range info.Labels {
if !strings.HasPrefix(l, "containerd.io/gc.ref.content.buildkit.refs.") {
continue
}
dgst, err := digest.Parse(v)
if err != nil {
continue
}
dt, err := content.ReadBlob(ctx, p.cache, ocispecs.Descriptor{Digest: dgst})
if err != nil {
continue
}
desc := ocispecs.Descriptor{
Digest: dgst,
Size: int64(len(dt)),
ArtifactType: readArtifactType(dt),
}
refs = append(refs, desc)
}
refs = filterRefs(refs, &cfg)
if len(refs) > 0 {
return refs, nil
}
v, ok := info.Labels["buildkit/refs.null"]
if ok {
for name := range strings.SplitSeq(v, ",") {
if name == p.name {
return nil, nil
}
}
}
}
refs, err := p.p.FetchReferrers(ctx, dgst, opts...)
if err != nil {
return nil, err
}
refs = filterRefs(refs, &cfg)
p.mu.Lock()
if p.refs == nil {
p.refs = make(map[digest.Digest][]ocispecs.Descriptor)
}
p.refs[dgst] = append(p.refs[dgst], refs...)
p.mu.Unlock()
return refs, nil
}
func (p *ReferrersProviderBuffer) SetGCLabels(ctx context.Context, root ocispecs.Descriptor) error {
labels := map[string]string{}
fieldpaths := []string{}
p.mu.Lock()
for _, desc := range p.blobs {
shaPrefix := desc.Digest.Hex()[:12]
key := "containerd.io/gc.ref.content.buildkit." + shaPrefix
labels[key] = desc.Digest.String()
fieldpaths = append(fieldpaths, "labels."+key)
}
p.mu.Unlock()
_, err := p.cache.Update(ctx, content.Info{
Digest: root.Digest,
Labels: labels,
}, fieldpaths...)
if err != nil {
return err
}
for dgst, refs := range p.refs {
info, err := p.cache.Info(ctx, dgst)
if err != nil {
continue
}
labels := map[string]string{}
fieldpaths := []string{}
for _, ref := range refs {
shaPrefix := ref.Digest.Hex()[:12]
key := "containerd.io/gc.ref.content.buildkit.refs." + shaPrefix
labels[key] = ref.Digest.String()
fieldpaths = append(fieldpaths, "labels."+key)
}
if len(refs) == 0 {
key := "buildkit/refs.null"
labels[key] = addName(info.Labels[key], p.name)
fieldpaths = append(fieldpaths, "labels."+key)
}
if len(labels) == 0 {
continue
}
_, err = p.cache.Update(ctx, content.Info{
Digest: dgst,
Labels: labels,
}, fieldpaths...)
if err != nil {
return err
}
}
return nil
}
func filterRefs(refs []ocispecs.Descriptor, cfg *remotes.FetchReferrersConfig) []ocispecs.Descriptor {
if len(cfg.ArtifactTypes) == 0 {
return refs
}
out := []ocispecs.Descriptor{}
for _, ref := range refs {
if slices.Contains(cfg.ArtifactTypes, ref.ArtifactType) {
out = append(out, ref)
}
}
return out
}
func addName(existing, name string) string {
if existing == "" {
return name
}
m := map[string]struct{}{}
for n := range strings.SplitSeq(existing, ",") {
m[n] = struct{}{}
}
m[name] = struct{}{}
var names []string
for n := range m {
names = append(names, n)
}
slices.Sort(names)
return strings.Join(names, ",")
}
func readArtifactType(dt []byte) string {
var mfst ocispecs.Manifest
if err := json.Unmarshal(dt, &mfst); err != nil {
return ""
}
return mfst.ArtifactType
}