A holder that failed leaves a version to publish again by hand: said numbered again while the build is held, built again under the same number when its file is gone, numbered anew after a published; refused for other bytes. The floor is asked again by hand; a branch's tagged versions are removed through each seat's remove, per repository. Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig
129 lines
4.9 KiB
Go
129 lines
4.9 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"`
|
|
// Again is when builder_publish_again said it once more, so the bus's dedup does not drop it.
|
|
Again string `json:"again,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
|
|
}
|
|
if filter == AllFilter {
|
|
return all, nil
|
|
}
|
|
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."
|