The builder keeps one key per name, tag and version (design 51 *The versions*, ADR 0606), in its old state section (ADR 0619), history one, 64 MiB with the 80 % mark. A package is numbered per source revision and keeps a version held published, publishing nothing; one numbered but never stored is published again under it. An image, bundle or archive keeps its fingerprint's number. An unknown floor builds an image, bundle or archive unnumbered and makes a package wait (ADR 0617 §1). Merge order and tagged versions below the next version are the package's helpers. builder_versions answers a name's versions and the holder's question (ADR 0605 §4). Four claim tests merged into two, so the new table test adds no function (test budget). Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig
124 lines
4.8 KiB
Go
124 lines
4.8 KiB
Go
package versions
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
)
|
|
|
|
// The builder's tools of its versions (design 51 *Events of the builder*, *Tools of the builder*).
|
|
const (
|
|
VersionsTool = "builder_versions"
|
|
PublishAgainTool = "builder_publish_again"
|
|
FloorAgainTool = "builder_floor_again"
|
|
TagTool = "builder_tag"
|
|
TagAsksTool = "builder_tag_asks"
|
|
UntagTool = "builder_untag"
|
|
OldFormTool = "builder_old_form"
|
|
// NumberedEvent and OldFormClosedEvent are the builder's own family `version`, kept seven days, apart from its builds'
|
|
// ends, so a waiter on a build's end does not wake on them.
|
|
NumberedEvent = "version.numbered"
|
|
OldFormClosedEvent = "version.old-form-closed"
|
|
)
|
|
|
|
// NumberedBody is the body of NumberedEvent: what a build module consumes to publish a build's version with the held digest,
|
|
// which stores nothing (design 51 *The floor*).
|
|
type NumberedBody struct {
|
|
Name string `json:"name"`
|
|
Kind string `json:"kind"`
|
|
Version string `json:"version"`
|
|
Tag string `json:"tag,omitempty"`
|
|
Module string `json:"module"`
|
|
Fingerprint string `json:"fingerprint"`
|
|
Commit string `json:"commit,omitempty"`
|
|
Digest string `json:"digest,omitempty"`
|
|
Seat string `json:"seat,omitempty"`
|
|
// Head is the head the version answers, when it answers a tag ask.
|
|
Head string `json:"head,omitempty"`
|
|
}
|
|
|
|
// NumberedOf is the event of an entry.
|
|
func NumberedOf(e Entry, head string) NumberedBody {
|
|
return NumberedBody{Name: e.Name, Kind: e.Kind, Version: e.Version, Tag: e.Tag, Module: e.Module, Fingerprint: e.Fingerprint,
|
|
Commit: e.Commit, Digest: e.Digest, Seat: e.Seat, Head: head}
|
|
}
|
|
|
|
// Reader reads the state as the tools bundle may: every key, and one.
|
|
type Reader struct {
|
|
Keys func() ([]string, error)
|
|
Get func(key string) (json.RawMessage, bool, error)
|
|
}
|
|
|
|
// readStore is a Store over a Reader that refuses every write: the tools bundle only reads versions.
|
|
type readStore struct{ r Reader }
|
|
|
|
var errReadOnly = errors.New("the versions are written by the builder alone")
|
|
|
|
func (s readStore) Get(_ context.Context, key string) ([]byte, uint64, bool, error) {
|
|
v, ok, err := s.r.Get(key)
|
|
return v, 0, ok, err
|
|
}
|
|
func (readStore) Create(context.Context, string, []byte) (uint64, error) { return 0, errReadOnly }
|
|
func (readStore) Update(context.Context, string, []byte, uint64) (uint64, error) {
|
|
return 0, errReadOnly
|
|
}
|
|
func (s readStore) KeysOf(_ context.Context, filter string) ([]string, error) {
|
|
all, err := s.r.Keys()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
prefix := strings.TrimSuffix(filter, "*")
|
|
var out []string
|
|
for _, k := range all {
|
|
if strings.HasPrefix(k, prefix) && !strings.Contains(k[len(prefix):], ".") {
|
|
out = append(out, k)
|
|
}
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
// VersionsAnswer is what builder_versions answers.
|
|
type VersionsAnswer struct {
|
|
Name string `json:"name"`
|
|
Versions []Entry `json:"versions"`
|
|
// Asked, Recorded and Why answer a holder's question for one version and digest (ADR 0605 §4).
|
|
Asked string `json:"asked,omitempty"`
|
|
Recorded *bool `json:"recorded,omitempty"`
|
|
Why string `json:"why,omitempty"`
|
|
}
|
|
|
|
// AnswerVersions is builder_versions: a name's versions in order, in one call; with a version (and a digest), whether
|
|
// the builder numbered it with that digest, which the holder asks before it stores (ADR 0605 §4).
|
|
func AnswerVersions(ctx context.Context, r Reader, name, version, digest string) (VersionsAnswer, error) {
|
|
name = strings.TrimSpace(name)
|
|
if name == "" {
|
|
return VersionsAnswer{}, errors.New("name is required: the package, image, bundle or archive name")
|
|
}
|
|
v := Versions{Store: readStore{r}}
|
|
all, err := v.Of(ctx, name)
|
|
if err != nil {
|
|
return VersionsAnswer{}, err
|
|
}
|
|
out := VersionsAnswer{Name: name, Versions: all}
|
|
if out.Versions == nil {
|
|
out.Versions = []Entry{}
|
|
}
|
|
if version = strings.TrimSpace(version); version != "" {
|
|
_, ok, why, err := v.Recorded(ctx, name, version, strings.TrimSpace(digest))
|
|
if err != nil {
|
|
return VersionsAnswer{}, fmt.Errorf("%s@%s: %w", name, version, err)
|
|
}
|
|
out.Asked, out.Recorded, out.Why = version, &ok, why
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
// VersionsDescription is builder_versions' description.
|
|
const VersionsDescription = "A name's versions as the builder numbered them (its state versions, design 51): every trunk and tagged " +
|
|
"version of a package, image, bundle or archive in order, each with its kind, series, fingerprint, commit, source revision, digest, " +
|
|
"seat, state (numbered, published, refused with its reason, removed with its time, reason and the event that removed it) and when. " +
|
|
"With version, and digest, it also answers recorded: whether the builder numbered that version with that digest, and why not: the " +
|
|
"question a registry seat's holder asks before it stores a publish (ADR 0605 §4). Read-only."
|