A given own secret the module reads at start is held by nobody but that module, so the mesh need not read it to replace it: secret rotate now works on it, and a value given through secret accept is replaced on its own after the module's first good start under the mesh. Only a value an outside party issues (own-secrets "issued-by": "outside") or one the module applies stays as given, refused with the reason.
91 lines
3.8 KiB
Go
91 lines
3.8 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"log"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/novox/mesh-controller/internal/link"
|
|
)
|
|
|
|
// A value given by hand lives only until the module's first good start (novox/hq ADR 0228).
|
|
//
|
|
// The serving controller hears every report; a clean one about the declaration a machine was last
|
|
// sent is the signal the store asks about (inventory.ReplaceGivenAfterStart). What it replaced is
|
|
// sent at once, said in the log and stated on the bus as the controller seat's `secret-replaced`,
|
|
// so a replacement is never silent. **Not in the hand-act log**: nobody acted by hand, and that log
|
|
// is read as the count of repairs a healer is wanted for.
|
|
|
|
// SecretReplaced is the controller seat's fact that a value given by hand was replaced
|
|
// (link.KeySecretReplaced). Never the value: neither the old one, which the mesh cannot read,
|
|
// nor the new one, sealed to the machine as it was made.
|
|
type SecretReplaced struct {
|
|
Node string `json:"node"`
|
|
Module string `json:"module"`
|
|
Name string `json:"name"`
|
|
Given time.Time `json:"given"`
|
|
// Sent are the machines sent so the module starts again on the new value; Unsent says why
|
|
// they could not be, in which case the next push carries it.
|
|
Sent []string `json:"sent"`
|
|
Unsent string `json:"unsent,omitempty"`
|
|
Why string `json:"why"`
|
|
}
|
|
|
|
// givenEvents is where the serving controller states it; nil in a command.
|
|
var givenEvents link.Bus
|
|
|
|
// replacing keeps one replacement per machine at a time: two reports arriving together find the
|
|
// same rows, and the store's claim makes one of them the replacer, but the sends need not race.
|
|
var replacing sync.Map
|
|
|
|
// startedWell says a report is a machine's clean account of a declaration: everything applied,
|
|
// nothing failed or refused. Whether it is the declaration last sent is the store's to answer.
|
|
func startedWell(report link.Report) bool {
|
|
return report.Declared != "" && report.Refused == "" && len(report.Failed) == 0 && report.Applied != nil
|
|
}
|
|
|
|
const givenWhy = "a value given by hand lives only until its module's first good start under the mesh (novox/hq ADR 0228)"
|
|
|
|
// replaceGiven replaces what the report makes due, sends the machines, and says so.
|
|
func replaceGiven(ctx context.Context, open *stores, report link.Report) {
|
|
if _, busy := replacing.LoadOrStore(report.Node, true); busy {
|
|
return
|
|
}
|
|
defer replacing.Delete(report.Node)
|
|
replaced, err := open.inventory.ReplaceGivenAfterStart(ctx, report.Node, report.Declared)
|
|
if err != nil {
|
|
log.Printf("a value given by hand on %s could not be replaced after its module started: %v", report.Node, err)
|
|
}
|
|
for _, r := range replaced {
|
|
said := SecretReplaced{Node: report.Node, Module: r.Module, Name: r.Name, Given: r.Given.UTC(),
|
|
Sent: r.Machines, Why: givenWhy}
|
|
log.Printf("replaced %q of %s on %s, given %s, with a value the mesh made: %s; sending %s",
|
|
r.Name, r.Module, report.Node, r.Given.UTC().Format(time.RFC3339), givenWhy, strings.Join(r.Machines, ", "))
|
|
if err := sendTo(ctx, open, r.Machines); err != nil {
|
|
said.Sent, said.Unsent = nil, err.Error()
|
|
log.Printf("the new %q of %s is sealed and not yet delivered to %s — the next push carries it: %v",
|
|
r.Name, r.Module, strings.Join(r.Machines, ", "), err)
|
|
}
|
|
stateReplaced(ctx, said)
|
|
}
|
|
}
|
|
|
|
func stateReplaced(ctx context.Context, said SecretReplaced) {
|
|
if givenEvents == nil {
|
|
return
|
|
}
|
|
body, err := json.Marshal(said)
|
|
if err != nil {
|
|
log.Printf("could not say that %s's %q was replaced: %v", said.Module, said.Name, err)
|
|
return
|
|
}
|
|
stating, cancel := context.WithTimeout(ctx, 10*time.Second)
|
|
defer cancel()
|
|
if err := givenEvents.PublishSeatEvent(stating, link.MeshControllerSeat, link.KeySecretReplaced, body); err != nil {
|
|
log.Printf("could not say that %s's %q was replaced: %v", said.Module, said.Name, err)
|
|
}
|
|
}
|