Files
mesh-delivery/modules/mesh-delivery-assigner/internal/puts/puts.go
T

157 lines
5.7 KiB
Go

// Package puts is the assigner's put: one version of a module put on one node by its tool `assigner_put` (novox/hq
// ADR 0546), kept under `<node>.<module>` in its state puts. It is the assigner's own value, which no other module reads
// (the earlier form lived in mesh-sdk's go/delivery; ADR 0634 §5), so the check and the codec live here.
package puts
import (
"encoding/json"
"fmt"
"regexp"
"time"
nec "git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient"
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient"
"git.novox.be/novox/mesh-sdk/go/keys"
)
// Each put is the key `<node>.<module>` of the assigner's state puts, which holds the version asked, the hold, why, and each
// step the assigner took or waits on, so a reader watches the key to follow it. The want the put writes carries Put (and
// Hold), so the assigner's rules pass over that key while the put holds it.
// History is the revisions the puts state keeps per key: each step of a put is one.
const History = 20
// A put's steps, in the order they are taken (ADR 0546 clauses 3 to 9). A put stands at the last step written.
const (
// Asked is a put written by the tool, not taken up by a pass yet.
Asked = "asked"
// Build waits for the builder's build at the fingerprint.
Build = "build"
// Compose waits for the composition (and grant) at the fingerprint.
Compose = "compose"
// Person waits for the operator's answer to the ask about the move.
Person = "person"
// Snapshot waits for the bus's streams to be snapshotted after the operator's yes.
Snapshot = "snapshot"
// Wanted is the want written: the node's have has not reached it yet.
Wanted = "wanted"
// Holding is a held put whose have runs it healthy: the want stays until released.
Holding = "holding"
// Done is a put without hold whose have ran it healthy: the rules write the want again.
Done = "done"
// Released is a put released by the tool `assigner_release`.
Released = "released"
// Refused is a put that cannot go on: a build that failed, an answer no, a snapshot not taken. Its words say why.
Refused = "refused"
)
// Ended says a put at this step is over: nothing more is done for it and the rules own the want.
func Ended(step string) bool {
return step == Done || step == Released || step == Refused
}
// Step is one step a put took: when, which, and what was done or waits, in a sentence.
type Step struct {
At time.Time `json:"at"`
Step string `json:"step"`
Words string `json:"words"`
}
// StepsKept is how many steps a put keeps, newest last.
const StepsKept = 16
// Put is the value of a key of the state puts.
type Put struct {
V int `json:"v"`
// FP is the fingerprint put, of the new form.
FP string `json:"fp"`
// Hold keeps the want at FP until released.
Hold bool `json:"hold,omitempty"`
Why string `json:"why"`
// By is who asked, as the bus named the caller; "unproven" when it named none.
By string `json:"by,omitempty"`
At time.Time `json:"at"`
// Step is where the put stands now, one of the steps above; Words say it in a sentence.
Step string `json:"step"`
Words string `json:"words,omitempty"`
// Composed is the digest of the composition the want names, once known.
Composed string `json:"composed,omitempty"`
// Ask is the key, in the asks state, of the ask about the move, once asked.
Ask string `json:"ask,omitempty"`
// Snapshot says where the bus's streams were snapshotted, once they were.
Snapshot string `json:"snapshot,omitempty"`
// Steps are the steps taken, oldest first, at most StepsKept.
Steps []Step `json:"steps,omitempty"`
}
// Said is p at step with words, the step added to its steps when it differs from the last one.
func (p Put) Said(at time.Time, step, words string) Put {
if p.Step == step && p.Words == words {
return p
}
p.Step, p.Words = step, words
p.Steps = append(append([]Step(nil), p.Steps...), Step{At: at.UTC(), Step: step, Words: words})
if len(p.Steps) > StepsKept {
p.Steps = p.Steps[len(p.Steps)-StepsKept:]
}
return p
}
var digestForm = regexp.MustCompile(`^sha256:[0-9a-f]{64}$`)
func (p Put) check() error {
if _, err := meshcatalogclient.KeyPart(p.FP); err != nil {
return fmt.Errorf("puts: a put's fingerprint: %w", err)
}
if p.Why == "" {
return fmt.Errorf("puts: a put says why")
}
switch p.Step {
case Asked, Build, Compose, Person, Snapshot, Wanted, Holding, Done, Released, Refused:
default:
return fmt.Errorf("puts: %q is not a step of a put", p.Step)
}
if p.Composed != "" && !digestForm.MatchString(p.Composed) {
return fmt.Errorf("puts: a put's composition digest %q is not sha256:<64 lowercase hex digits>", p.Composed)
}
return nil
}
// Key is the key, in the state puts, of the put of module on node: `<node>.<module>`.
func Key(node, module string) (string, error) {
return nec.HaveKey(node, module)
}
// ParseKey reads a Key.
func ParseKey(key string) (node, module string, err error) {
node, module, isNode, err := nec.ParseHaveKey(key)
if err == nil && isNode {
err = fmt.Errorf("puts: %q is a node's own key, and no put", key)
}
return node, module, err
}
// Encode is p as JSON; it refuses a fingerprint not of the new form, a put that says no why and an unknown step.
func Encode(p Put) ([]byte, error) {
if err := p.check(); err != nil {
return nil, err
}
p.V = keys.Version
return json.Marshal(p)
}
// Decode reads a put; it refuses an unknown version (keys.ErrUnknownVersion) and what Encode refuses.
func Decode(data []byte) (Put, error) {
if err := keys.CheckVersion(data); err != nil {
return Put{}, err
}
var p Put
if err := json.Unmarshal(data, &p); err != nil {
return Put{}, fmt.Errorf("puts: value does not fit its shape: %w", err)
}
if err := p.check(); err != nil {
return Put{}, err
}
return p, nil
}