mesh-controller's try asked build-agent, which leaves the checking at R4e. A try is now a key in the checker's state tries: the checker judges the head merged onto its trunk as it judges a pull request's head, through the same judge, places and holds, writes its end in the key and owes the forge nothing (no status, no preview, no verdict). One try per head, at most four at once; hq and a repository whose trunk the gate does not name are refused with "no level of <repo> is checked by the checker".
196 lines
9.9 KiB
Go
196 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 (hq, 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; for an hq pull request, that hq is judged by " +
|
|
"merge-check.sh until issue 1296. 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 holds the checker back and what runs: the capacity per node (the setting, the default one step per node), whether the controller's " +
|
|
"delivery plans in their build phase hold a key (no new check starts while one does), the steps in flight on the build modules by node, and the " +
|
|
"candidates without a verdict for their heads. The steps the checker holds waiting for a place are in its process, not in a bucket, and the " +
|
|
"answer says so. Read-only.",
|
|
Input: map[string]any{"type": "object", "properties": map[string]any{}},
|
|
Run: func(map[string]any) (any, error) { return r.Status() }},
|
|
}
|
|
}
|
|
|
|
func capacity() (string, error) {
|
|
path := os.Getenv("MESH_CHECKER_SETTINGS")
|
|
if path == "" {
|
|
return os.Getenv("MESH_CHECKER_CAPACITY"), nil
|
|
}
|
|
raw, err := os.ReadFile(path)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
var s struct {
|
|
Capacity string `json:"capacity"`
|
|
}
|
|
return s.Capacity, json.Unmarshal(raw, &s)
|
|
}
|
|
|
|
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")), PlansState: read(stdio.State("mesh-controller.plans-building")),
|
|
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"))},
|
|
Capacity: capacity}
|
|
tries := stdio.State(check.TriesState)
|
|
source := stdio.State("mesh-delivery-gate.source")
|
|
trying := Trying{
|
|
Tries: stateStore{StateRead: read(tries), create: func(key string, value []byte) error {
|
|
_, err := tries.Create(key, json.RawMessage(value))
|
|
return err
|
|
}},
|
|
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
|
|
},
|
|
}
|
|
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)
|
|
}
|
|
}
|