192 lines
9.9 KiB
Go
192 lines
9.9 KiB
Go
// Command mesh-delivery-checker-tools serves the checker's read tools (novox/hq ADR 0484 clause 13, issue 1686): the runtime
|
|
// launches it for a call, it reads the checker's verdicts and kept states and the indexer's candidates through the checker's
|
|
// state and reads, and writes nothing. The checker is the other program: it serves no tool (ADR 0437 §4).
|
|
package main
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"os"
|
|
"time"
|
|
|
|
stdio "git.novox.be/novox/mesh-sdk/go"
|
|
"git.novox.be/novox/mesh-sdk/go/delivery"
|
|
|
|
"mesh-delivery-checker/internal/check"
|
|
)
|
|
|
|
func read(s stdio.KeptState) check.StateRead {
|
|
return check.StateRead{Keys: s.Keys, Get: func(key string) (json.RawMessage, error) {
|
|
e, err := s.Get(key)
|
|
if err != nil || e == nil {
|
|
return nil, err
|
|
}
|
|
return e.Value, nil
|
|
}}
|
|
}
|
|
|
|
// stateStore is a state the tools write: the tries.
|
|
type stateStore struct {
|
|
check.StateRead
|
|
create func(key string, value []byte) error
|
|
}
|
|
|
|
func (s stateStore) Keys() ([]string, error) { return s.StateRead.Keys() }
|
|
func (s stateStore) Get(key string) (json.RawMessage, error) { return s.StateRead.Get(key) }
|
|
func (s stateStore) Create(key string, value []byte) error { return s.create(key, value) }
|
|
|
|
// Trying is what the try's tools work on: the tries, and the gate's trunk heads.
|
|
type Trying struct {
|
|
Tries check.TryStore
|
|
Trunks func() (map[string]string, error)
|
|
Now func() time.Time
|
|
}
|
|
|
|
// TryTools are the try's two tools (ADR 0501 §5): ask one, read one. mesh-controller's try calls them.
|
|
func TryTools(tr Trying) []stdio.Tool {
|
|
str := func(args map[string]any, k string) string { s, _ := args[k].(string); return s }
|
|
return []stdio.Tool{
|
|
{Name: check.TryTool,
|
|
Description: "Try a pushed head before its pull request is opened: the checker judges it as it judges a pull request's head, merged onto its trunk's head, " +
|
|
"its unit and integration levels through the build modules, and owes the forge nothing. Answers the try's id at once; " + check.TryReadTool + " reads " +
|
|
"where it stands. A try of the same head that waits or runs is the answer to a second ask; at most 4 wait or run at once, mesh-wide. A repository " +
|
|
"the checker checks no level of (one whose trunk the gate does not name) is refused.",
|
|
Input: map[string]any{"type": "object", "required": []string{"repository", "head"}, "properties": map[string]any{
|
|
"repository": map[string]any{"type": "string", "description": "<owner>/<name>"},
|
|
"head": map[string]any{"type": "string", "description": "the full commit to try, 40 or 64 hex digits"},
|
|
"trunk": map[string]any{"type": "string", "description": "the trunk commit to merge it onto, or the trunk's branch for its head now; the trunk's head when absent"},
|
|
"tests": map[string]any{"type": "string", "description": "a go test -run pattern: only those tests run, and nothing is kept"},
|
|
"level": map[string]any{"type": "string", "enum": []string{"unit", "integration"}, "description": "only this level; both when absent"}}},
|
|
Run: func(args map[string]any) (any, error) {
|
|
if tr.Tries == nil || tr.Trunks == nil {
|
|
return nil, fmt.Errorf("the tries state cannot be written here")
|
|
}
|
|
trunks, err := tr.Trunks()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("the trunk heads cannot be read: %w", err)
|
|
}
|
|
now := time.Now
|
|
if tr.Now != nil {
|
|
now = tr.Now
|
|
}
|
|
id, err := check.AskTry(tr.Tries, trunks, check.TryAsk{Repository: str(args, "repository"), Head: str(args, "head"),
|
|
Trunk: str(args, "trunk"), Tests: str(args, "tests"), Level: str(args, "level")}, now())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return map[string]any{"id": id}, nil
|
|
}},
|
|
{Name: check.TryReadTool,
|
|
Description: "Where a try stands, as stored: queued, running, or its end (pass, fail, or error when it could not run), with what was said of it, " +
|
|
"the head and the trunk commit it was merged onto, the time it took and what failed first. Read-only.",
|
|
Input: map[string]any{"type": "object", "required": []string{"id"}, "properties": map[string]any{
|
|
"id": map[string]any{"type": "string", "description": "the try's id, try-<digits>"}}},
|
|
Run: func(args map[string]any) (any, error) {
|
|
if tr.Tries == nil {
|
|
return nil, fmt.Errorf("the tries state cannot be read here")
|
|
}
|
|
return check.ReadTry(tr.Tries, str(args, "id"))
|
|
}},
|
|
}
|
|
}
|
|
|
|
// Tools are the read tools over the checker's states.
|
|
func Tools(r check.Reader) []stdio.Tool {
|
|
return []stdio.Tool{
|
|
{Name: check.VerdictsReadTool,
|
|
Description: "The verdicts the checker holds, each as stored under verdicts.<group>.<heads>: its state, what failed first, the tests run, kept and skipped per level, the time of each part, the trunk heads it judged against and the composition, with the full " +
|
|
"report of the status each pull request carries (the line the forge shows is cut at 140 characters, this is not). With a group, that group's only. Read-only.",
|
|
Input: map[string]any{"type": "object", "properties": map[string]any{
|
|
"group": map[string]any{"type": "string", "description": "the delivery group's branch name"}}},
|
|
Run: func(args map[string]any) (any, error) {
|
|
group, _ := args["group"].(string)
|
|
vs, err := r.Verdicts(group)
|
|
return map[string]any{"verdicts": vs}, err
|
|
}},
|
|
{Name: check.PullReadTool,
|
|
Description: "The verdict that judges one pull request and the full report of its status mesh/check. Read-only.",
|
|
Input: map[string]any{"type": "object", "required": []string{"repository", "number"}, "properties": map[string]any{
|
|
"repository": map[string]any{"type": "string", "description": "<owner>/<name>"},
|
|
"number": map[string]any{"type": "integer", "description": "the pull request's number"}}},
|
|
Run: func(args map[string]any) (any, error) {
|
|
repository, _ := args["repository"].(string)
|
|
n, _ := args["number"].(float64)
|
|
return r.Pull(repository, int(n))
|
|
}},
|
|
{Name: check.KeptReadTool,
|
|
Description: "The kept verdicts of passing packages, each as stored under kept.<level>.<digest>: the package, its tests, the time they took, the toolchain " +
|
|
"and when it was kept. With a level, that level's; with a level and a digest, that one. Read-only.",
|
|
Input: map[string]any{"type": "object", "properties": map[string]any{
|
|
"level": map[string]any{"type": "string", "enum": []string{"unit", "integration"}, "description": "the level"},
|
|
"digest": map[string]any{"type": "string", "description": "the package's digest, with a level"}}},
|
|
Run: func(args map[string]any) (any, error) {
|
|
level, _ := args["level"].(string)
|
|
digest, _ := args["digest"].(string)
|
|
kept, err := r.Kept(level, digest)
|
|
return map[string]any{"kept": kept}, err
|
|
}},
|
|
{Name: check.PreviewsReadTool,
|
|
Description: "The merge previews the checker holds, each as stored under previews.<repository>.<head>.<trunk>: the merge commit, per module directory " +
|
|
"its tree, manifest digest and lock hashes, or the files in conflict. With a repository, that repository's; with a head (7 or more characters) " +
|
|
"only that head's. Keys that are no preview key are listed as unreadable. Read-only.",
|
|
Input: map[string]any{"type": "object", "properties": map[string]any{
|
|
"repository": map[string]any{"type": "string", "description": "<owner>/<name>"},
|
|
"head": map[string]any{"type": "string", "description": "a pull request head commit, or a prefix of at least 7 characters"}}},
|
|
Run: func(args map[string]any) (any, error) {
|
|
repository, _ := args["repository"].(string)
|
|
head, _ := args["head"].(string)
|
|
if head != "" && len(head) < 7 {
|
|
return nil, fmt.Errorf("the head %q is too short: give at least 7 characters", head)
|
|
}
|
|
ps, bad, err := r.Previews(repository, head)
|
|
return map[string]any{"previews": ps, "unreadable": bad}, err
|
|
}},
|
|
{Name: check.StatusReadTool,
|
|
Description: "What runs: the steps in flight on the build modules by node, the candidates without a verdict for their heads, " +
|
|
"and the candidates whose check is owed by kind (waiting_by_kind: new, nodes, and trunk, a re-judge pulled after the other two). A key a " +
|
|
"build module left running (started past the step bound, or its build module not running on its node) is listed apart under " +
|
|
"left_running with why, and not counted. Nothing holds the checker and it has no capacity of its own: a check is an ask on the " +
|
|
"build seats' streams, whose backlog the delivery view shows (novox/hq ADR 0525). Read-only.",
|
|
Input: map[string]any{"type": "object", "properties": map[string]any{}},
|
|
Run: func(map[string]any) (any, error) { return r.Status() }},
|
|
}
|
|
}
|
|
|
|
// stepBound is the checker's step bound as the checker reads it (MESH_CHECKER_STEP_BOUND), its default when unset or not a
|
|
// duration.
|
|
func stepBound() time.Duration {
|
|
if d, err := time.ParseDuration(os.Getenv("MESH_CHECKER_STEP_BOUND")); err == nil && d > 0 {
|
|
return d
|
|
}
|
|
return check.DefaultStepBound
|
|
}
|
|
|
|
func main() {
|
|
r := check.Reader{VerdictState: read(stdio.State("verdicts")), KeptState: read(stdio.State("kept")), CandidateState: read(stdio.State("mesh-delivery-indexer.candidates")),
|
|
PreviewState: read(stdio.State("previews")),
|
|
StepStates: map[string]check.StateRead{"mesh-build-go": read(stdio.State("mesh-build-go.steps")), "mesh-build-node": read(stdio.State("mesh-build-node.steps"))},
|
|
HaveState: read(stdio.State("node-engine.have")), StepBound: stepBound()}
|
|
tries := stdio.State(check.TriesState)
|
|
source := stdio.State("mesh-delivery-gate.source")
|
|
trunks := func() (map[string]string, error) {
|
|
e, err := source.Get(delivery.SourceMainKey)
|
|
if err != nil || e == nil {
|
|
return nil, err
|
|
}
|
|
s, err := delivery.DecodeSource(e.Value)
|
|
return s.Heads, err
|
|
}
|
|
r.Trunks = trunks
|
|
trying := Trying{
|
|
Tries: stateStore{StateRead: read(tries), create: func(key string, value []byte) error {
|
|
_, err := tries.Create(key, json.RawMessage(value))
|
|
return err
|
|
}},
|
|
Trunks: trunks,
|
|
}
|
|
if err := stdio.Serve("", append(Tools(r), TryTools(trying)...)); err != nil {
|
|
fmt.Fprintf(os.Stderr, "[mesh-delivery-checker-tools] %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
}
|