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 }