package versions import ( "context" "encoding/json" "errors" "fmt" "strings" "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/builds" ) // 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" // OldFormClosedEvent is, with builds.VersionNumberedEvent, 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. OldFormClosedEvent = "version.old-form-closed" ) // NumberedOf is the version.numbered body of an entry (builds.VersionNumbered, the contract a build module reads). func NumberedOf(e Entry, head string) builds.VersionNumbered { return builds.VersionNumbered{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, or kept: its seat kept it whole on remove, its digest shared, with the reason and the other tags naming it, kept_with) 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."