Files
mesh-catalog/modules/distribution/internal/holder/list.go
T

110 lines
3.7 KiB
Go

package holder
// **`list`: what the store holds, through the seat** (novox/hq ADR 0632 §3). For one repository, or every repository a
// seat holds, each manifest's digest, whether it is an index and the manifests it names, the tags naming it, its size
// and whether a hold keeps it. Read from the store's own files (files.go), so a manifest no tag names is listed too. The
// two seats share one store and split it by what a manifest keeps: a manifest keeping one blob under an empty
// configuration is a bundle or an archive, oci-artifact-registry's, named by that blob as its references name it;
// every other manifest, an index included, is container-image-registry's.
import (
"context"
"errors"
"fmt"
"sort"
reg "git.novox.be/novox/mesh-delivery/contracts/go/seats/containerimageregistry"
)
// VerbList is the seats' verb list.
const VerbList = reg.VerbList
// StoreFiles is the store's own files as list reads them: Files in the program, a test's stand-in.
type StoreFiles interface {
List(ctx context.Context) (*Listing, error)
Manifests(ctx context.Context, l *Listing) (map[string]*Manifest, map[string]string)
}
// List is `list` on seat: the repositories it holds, or name alone; a repository the seat holds nothing of is not named,
// and name not held is an empty answer. A manifest whose content cannot be read fails the answer, by name: a listing
// missing one would read as the store not holding it.
func (v *Verbs) List(ctx context.Context, seat, caller, name string) ([]reg.Repository, error) {
if err := v.Callers.Allowed(VerbList, caller); err != nil {
return nil, err
}
if seat != ImageSeat && seat != ArtifactSeat {
return nil, fmt.Errorf("list: %s is no seat of distribution's", seat)
}
if v.Files == nil {
return nil, errors.New("list: this process reads no store files")
}
l, err := v.Files.List(ctx)
if err != nil {
return nil, fmt.Errorf("list: the store's files cannot be listed: %w", err)
}
if name != "" {
r, held := l.Repos[name]
l = &Listing{Blobs: l.Blobs, Repos: map[string]*Repo{}}
if held {
l.Repos[name] = r
}
}
docs, unread := v.Files.Manifests(ctx, l)
if len(unread) > 0 {
keys := make([]string, 0, len(unread))
for k := range unread {
keys = append(keys, k)
}
sort.Strings(keys)
return nil, fmt.Errorf("list: %d manifests cannot be read, the first %s: %s", len(keys), keys[0], unread[keys[0]])
}
holds, err := v.Holds.List()
if err != nil {
return nil, fmt.Errorf("list: the holds cannot be read: %w", err)
}
held := map[string]bool{}
for _, h := range holds {
held[h.Key()] = true
}
out := []reg.Repository{}
for _, repo := range l.RepoNames() {
r := l.Repos[repo]
tagsAt := map[string][]string{}
for t, d := range r.Tags {
tagsAt[d] = append(tagsAt[d], t)
}
digests := make([]string, 0, len(r.Revisions))
for d := range r.Revisions {
digests = append(digests, d)
}
sort.Strings(digests)
var manifests []reg.Listed
for _, d := range digests {
m := docs[Key(repo, d)]
artifact := ""
if m.Holder() {
artifact = m.Layers[0]
}
if (artifact != "") != (seat == ArtifactSeat) {
continue
}
e := reg.Listed{Digest: d, Index: len(m.Children) > 0, Platforms: m.Children, Artifact: artifact,
Tags: tagsAt[d], Size: l.Blobs[d]}
for _, b := range m.Refs {
e.Size += l.Blobs[b]
}
sort.Strings(e.Tags)
by := d
if artifact != "" {
by = artifact // a bundle or an archive is held by the blob its references name (HoldOne)
}
e.Held = held[Hold{Seat: seat, Name: repo, Digest: by}.Key()]
manifests = append(manifests, e)
}
if len(manifests) > 0 {
out = append(out, reg.Repository{Name: repo, Manifests: manifests})
}
}
return out, nil
}