248 lines
5.6 KiB
Go
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
|
|
}
|