Files
mesh-catalog/modules/mesh-delivery-gate/cmd/mesh-delivery-gate/pullkey.go
T

116 lines
4.2 KiB
Go

package main
import (
"encoding/json"
"errors"
"fmt"
"strings"
stdio "git.novox.be/novox/mesh-sdk/go"
)
// The pull request's key (novox/hq ADR 0406 §4, issue 975): mesh-delivery keeps one key per pull request on the bus,
// holding its head, the trunk commit its current check is asked against and its verdict, written by compare-and-set.
// It writes a new head when the forge says one (`pull.updated`, which carries the head and the trunk commit it was
// announced against), and a new trunk commit only when the check is asked again against one — the merge tool's ask is
// that same announcement. A merge of a group's member, and a trunk move that leaves an inactive pull request unasked,
// are announced as nothing (the gitea module announces `pull.updated` only for a head and for the merge tool's ask), so
// they write nothing. A verdict for that head and trunk commit writes the verdict. The build seat reads the key.
// PullKey is what the bus keeps of one pull request.
type PullKey struct {
Head string `json:"head"`
Trunk string `json:"trunk,omitempty"`
Verdict string `json:"verdict,omitempty"`
}
// PullKeys keeps the pull requests' keys: Change reads the key, applies the change to what it read and writes the result
// by compare-and-set when the change says to write; a write another writer won reads the key again and applies again.
type PullKeys interface {
Change(key string, apply func(PullKey) (PullKey, bool)) error
}
// pullKeyName is a pull request's key: owner.repo.number, with any dot in a name made an underscore.
func pullKeyName(owner, repo string, number int) string {
clean := func(s string) string { return strings.NewReplacer(".", "_", "/", "_").Replace(s) }
return fmt.Sprintf("%s.%s.%d", clean(owner), clean(repo), number)
}
// KeepPull writes the key of a pull request whose head was announced, when the head or the trunk commit it was
// announced against is not what the key holds; a new head or trunk clears the verdict.
func (h *Holder) KeepPull(p PullEvent) {
if h.Pulls == nil || p.Number == 0 || p.HeadSHA == "" {
return
}
err := h.Pulls.Change(pullKeyName(p.Owner, p.Repo, p.Number), func(k PullKey) (PullKey, bool) {
if sameCommit(k.Head, p.HeadSHA) && k.Head != "" && (p.BaseSHA == "" || sameCommit(k.Trunk, p.BaseSHA)) {
return k, false
}
return PullKey{Head: p.HeadSHA, Trunk: p.BaseSHA}, true
})
if err != nil {
h.Logf("[mesh-delivery] the key of %s/%s#%d could not be written: %v", p.Owner, p.Repo, p.Number, err)
h.again("a pull request's key that could not be written")
}
}
// KeepVerdict writes a verdict into the key of its pull request, when the key holds that head and trunk commit.
func (h *Holder) KeepVerdict(c CheckedEvent) {
if h.Pulls == nil || c.Group != "" || c.Number == 0 || c.Commit == "" || c.Trunk == "" {
return
}
verdict := c.Verdict
if verdict == "" {
return
}
err := h.Pulls.Change(pullKeyName(c.Owner, c.Repo, c.Number), func(k PullKey) (PullKey, bool) {
if !sameCommit(k.Head, c.Commit) || !sameCommit(k.Trunk, c.Trunk) || k.Verdict == verdict {
return k, false
}
k.Verdict = verdict
return k, true
})
if err != nil {
h.Logf("[mesh-delivery] the verdict in the key of %s/%s#%d could not be written: %v", c.Owner, c.Repo, c.Number, err)
h.again("a pull request's key that could not be written")
}
}
// kvPulls keeps the keys in the module's own state `pulls`.
type kvPulls struct{}
// changeAttempts is how often a lost compare-and-set is read and applied again before it is said.
const changeAttempts = 10
func (kvPulls) Change(key string, apply func(PullKey) (PullKey, bool)) error {
state := stdio.State("pulls")
for range changeAttempts {
e, err := state.Get(key)
if err != nil {
return err
}
var k PullKey
if e != nil {
if err := json.Unmarshal(e.Value, &k); err != nil {
return err
}
}
next, write := apply(k)
if !write {
return nil
}
if e == nil {
_, err = state.Create(key, next)
} else {
_, err = state.Update(key, next, e.Revision)
}
if err == nil {
return nil
}
if !errors.Is(err, stdio.ErrChanged) {
return err
}
}
return fmt.Errorf("%s was written by another writer first %d times: %w", key, changeAttempts, stdio.ErrChanged)
}