157 lines
5.7 KiB
Go
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
|
|
}
|