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

103 lines
3.6 KiB
Go

package main
import (
"fmt"
"regexp"
"strconv"
"strings"
)
// A config value held or dropped for a pull request asks its check again (novox/hq ADR 0502 clause 5, issue 1826).
//
// The controller's composed check lays the approved values held for a pull request (mesh-config's `held`) when it
// composes, so a check run before a value was held, or after it was dropped, judged another mesh. mesh-config
// raises `config-changed` with the action hold or drop and the pull request; this owner then asks that pull
// request's check, or its group's composed check, again, the way it asks a moved head: its own path, with the
// reason in the delivery's transition, and not the `recheck` verb, which stays an exception (ADR 0421 §12).
// ConfigChangedEvent is the event this owner follows from mesh-config.
const ConfigChangedEvent = "mesh-config.config-changed"
// ConfigChange is mesh-config's `config-changed` as far as this owner reads it.
type ConfigChange struct {
Action string `json:"action"`
Pull string `json:"pull,omitempty"`
Head string `json:"head,omitempty"`
Key string `json:"key,omitempty"`
Level struct {
Node string `json:"node,omitempty"`
Module string `json:"module,omitempty"`
} `json:"level"`
}
var pullNamed = regexp.MustCompile(`^([A-Za-z0-9_.-]+)/([A-Za-z0-9_.-]+)#([0-9]+)$`)
// ConfigChanged takes a hold or a drop of a value for a pull request: every live delivery of that pull request has its
// check asked again. Anything else mesh-config says (a value set, a resolution, the merge's set) asks nothing.
func (h *Holder) ConfigChanged(c ConfigChange) {
if c.Action != "hold" && c.Action != "drop" {
return
}
m := pullNamed.FindStringSubmatch(strings.TrimSpace(c.Pull))
if m == nil {
return
}
number, _ := strconv.Atoi(m[3])
repository := strings.ToLower(m[1] + "/" + m[2])
verb := "held for"
if c.Action == "drop" {
verb = "dropped from"
}
why := fmt.Sprintf("a config value was %s this pull request: %s of %s on %s (mesh-config)", verb, c.Key,
orWord(c.Level.Module, "a module"), orWord(c.Level.Node, "a node"))
h.mu.Lock()
var ids []string
for id, d := range h.deliveries {
if d.Number == number && strings.EqualFold(d.Repository, repository) && d.MergedAs == "" && !d.State.Final() {
ids = append(ids, id)
}
}
h.mu.Unlock()
for _, id := range ids {
h.askAgainForConfig(id, why)
}
}
func orWord(s, word string) string {
if s == "" {
return word
}
return s
}
// askAgainForConfig asks one delivery's check again: a rejected or ready one is proposed again, as any judged again
// head is; a proposed one has its pending ask replaced. Its group's composed check goes out of date, and is asked
// once a member is judged again.
func (h *Holder) askAgainForConfig(id, why string) {
h.mu.Lock()
d := h.deliveries[id]
if d == nil {
h.mu.Unlock()
return
}
probe := *d
probe.Transitions = append([]Transition(nil), d.Transitions...)
_, refused := Apply(&probe, EvRecheck, true, Facts{Now: h.Now(), Why: why, By: byOwner})
proposed := d.State == Proposed
h.mu.Unlock()
switch {
case refused == nil:
if _, err := h.Recheck(id, why, byOwner); err != nil {
h.Logf("[mesh-delivery] %s: its check could not be asked again for a config value: %v", id, err)
}
case proposed:
h.askProposedAgain(id, why)
}
}
// askProposedAgain says why a proposed delivery's check is not asked again for a config value: the checker's (issue 1302), and
// whether a held config value is weighed is its composition's. Nothing is asked of the controller (issue 1318).
func (h *Holder) askProposedAgain(id, why string) {
h.Logf("[mesh-delivery] %s: a held config value changed; %v", id, checkerRefusal(id))
}