Files
mesh-delivery/modules/mesh-grants/internal/grants/records.go
T

302 lines
10 KiB
Go

package grants
import (
"fmt"
"slices"
"strings"
"git.novox.be/novox/mesh-sdk/go/buscompose"
"git.novox.be/novox/mesh-sdk/go/catalogue"
)
// Records is what the bus composer composes from, rebuilt from the bus (mesh-controller internal/inventory busrecords.go,
// BusRecords, read the same way from Inputs instead of the controller's store). Pure. An assigned module whose manifest
// the indexer does not hold is left out and named, where the controller refuses the whole composition: the shadow
// composes what it can and says what it could not.
func Records(in Inputs) (buscompose.Records, []Gap) {
var gaps []Gap
seats := map[string]catalogue.SeatDeclaration{}
declarers := map[string]string{}
for _, m := range in.Manifests {
for _, s := range m.DefinesSeats {
seats[s.Name] = s
declarers[s.Name] = m.Module
}
}
for _, own := range catalogue.SeatsWithAProtocol() {
seats[own.Name] = catalogue.SeatDeclaration{Name: own.Name, Scope: own.Scope, Accepts: own.Accepts, Emits: own.Emits, Serves: own.Serves}
}
out := buscompose.Records{Assigned: map[string][]buscompose.Declared{}, People: map[string][]string{},
Interchangeable: map[string]bool{}}
values := func(provider, setting string) []any {
var vs []any
for _, node := range in.Nodes {
if v, ok := in.Values[node+"/"+provider+"/"+setting]; ok && slices.Contains(in.Assigned[node], provider) {
vs = append(vs, v)
}
}
return vs
}
for _, node := range in.Nodes {
out.Nodes = append(out.Nodes, node)
manifestOf := func(module string) (catalogue.Manifest, bool) {
if m, ok := in.Running[node+"."+module]; ok {
return m, true
}
m, ok := in.Manifests[module]
return m, ok
}
var here []catalogue.Manifest
for _, module := range in.Assigned[node] {
if m, ok := manifestOf(module); ok {
here = append(here, m)
}
}
for _, module := range in.Assigned[node] {
m, known := manifestOf(module)
if !known {
gaps = append(gaps, Gap{Input: module + " on " + node, Why: "left out of the shadow list: no manifest to derive what it may say from"})
continue
}
d := declaredFor(m, seats, declarers)
for _, sr := range catalogue.ReadsGranted(m, here, node) {
d.KeyedReads = append(d.KeyedReads, buscompose.KeyedRead{Read: sr.Read, Key: sr.Key})
}
d.Hears, d.HearsAStream, d.Consumes = hearsOf(m, in.Manifests, node, values, d.Consumes)
out.Assigned[node] = append(out.Assigned[node], d)
if m.Instances == catalogue.InstancesInterchangeable {
out.Interchangeable[m.Module] = true
}
}
}
return out, gaps
}
// declaredFor is mesh-controller's declaredFor (internal/inventory/busrecords.go), copied: one module's manifest as the
// composer needs it.
func declaredFor(m catalogue.Manifest, seats map[string]catalogue.SeatDeclaration, declarers map[string]string) buscompose.Declared {
var fromModules []string
var watches []buscompose.Seat
for _, c := range m.Consumes {
emitter, event, named := strings.Cut(c, ".")
if named {
if s, isASeat := seats[emitter]; isASeat && s.Kinded {
ev := strings.TrimSuffix(event, ".*")
if catalogue.SeatSays(s.Emits, ev) {
watches = append(watches, buscompose.Seat{Name: s.Name, Scope: s.Scope, Emits: []string{ev},
Kinded: true, Proofs: s.Proofs, DeclaredBy: declarers[s.Name]})
continue
}
}
if s, isASeat := seats[emitter]; isASeat && catalogue.SeatSays(s.Emits, event) {
watches = append(watches, buscompose.Seat{Name: s.Name, Emits: []string{event}})
continue
}
}
fromModules = append(fromModules, c)
}
d := buscompose.Declared{
Module: m.Module, Emits: m.EmitsAll(), Consumes: fromModules, Watches: watches, Serves: m.Tools,
Invokes: m.Invokes, Waits: waitsOf(m, seats), State: bucketsOf(m), Reads: m.Reads, Logs: logsOf(m),
Streams: streamsOf(m), Queues: queueOwnersOf(m), Processes: processesOf(m), Checks: catalogue.HealthChecks(m),
RunsAs: m.RunsAs, Issued: issuedTools(m),
}
d.Loads = loadsABundle(m)
if _, reads := m.OwnSecrets["broker"]; !reads {
d.NoAccount = true
}
if _, reads := m.OwnSecrets[buscompose.ProvisionerSecret]; reads && m.ClaimsSeat(buscompose.NodeProvisionerSeat) {
d.ProvisionsNode = true
}
d.SnapshotsTheBus = m.ClaimsSeat(catalogue.BrokerSeat)
for _, c := range m.Claims {
if s, hasAProtocol := seats[c.Name]; hasAProtocol {
held := asSeat(s, declarers[s.Name])
held.Kind = c.Kind
held.Capabilities = c.Capabilities
d.Holds = append(d.Holds, held)
}
}
for _, name := range m.Uses {
if s, declaredSomewhere := seats[name]; declaredSomewhere {
d.Uses = append(d.Uses, asSeat(s, declarers[s.Name]))
}
}
return d
}
// loadsABundle says the node's tool runtime loads a bundle of the module. The controller reads it from the resolved
// manifest's Bundles, which a build derives and the indexer's manifest does not carry (the builder's build record names
// the repository's manifest digest), so the same answer is derived here from `build.artifacts` by mesh-sdk's rule
// (the build's own rule for what a bundle loads): a bundle that says `loads`, else every bundle of a module that declares tools and that no
// resource of the module runs. Without it a module whose only runtime-loaded part is a helper (route-proxy's
// route-provider) was left out of node-tools' grants (issue 1945).
func loadsABundle(m catalogue.Manifest) bool {
for _, b := range m.Bundles {
if len(b.Loads) > 0 {
return true
}
}
if m.Build == nil {
return false
}
run := map[string]bool{}
for _, r := range m.Resources {
if a, ok := r["artifact"].(string); ok && a != "" {
run[a] = true
}
}
for _, a := range m.Build.Artifacts {
if a.Kind != catalogue.ArtifactBundle {
continue
}
if len(a.Loads) > 0 || (a.Loads == nil && len(m.Tools) > 0 && !run[a.Name]) {
return true
}
}
return false
}
func waitsOf(m catalogue.Manifest, seats map[string]catalogue.SeatDeclaration) []buscompose.Wait {
var out []buscompose.Wait
for _, w := range m.Waits {
emitter, event, _ := strings.Cut(w, ".")
first, _, _ := strings.Cut(event, ".")
wait := buscompose.Wait{Name: w, Emitter: emitter, Event: event}
if s, isASeat := seats[emitter]; isASeat {
switch {
case s.Kinded && catalogue.SeatSays(s.Emits, first):
wait.Seat, wait.Last = true, "kind"
case s.NamedByCaller(first) && catalogue.SeatSays(s.Emits, first):
wait.Seat, wait.Last = true, "asker"
case catalogue.SeatSays(s.Emits, event):
wait.Seat = true
}
}
out = append(out, wait)
}
return out
}
func issuedTools(m catalogue.Manifest) []string {
var out []string
for _, o := range m.Provides {
if tool, issued := m.IssuedCredentialOf(o.Name); issued && !slices.Contains(out, tool) {
out = append(out, tool)
}
}
return out
}
func bucketsOf(m catalogue.Manifest) []buscompose.Bucket {
var out []buscompose.Bucket
for _, s := range m.State {
out = append(out, buscompose.Bucket{Module: m.Module, Name: s.Name, History: s.History, TTLSeconds: s.TTLSeconds,
PerMachine: s.PerMachine, KeyTTL: s.KeyTTL})
}
return out
}
func logsOf(m catalogue.Manifest) []buscompose.Log {
var out []buscompose.Log
for _, l := range m.Logs {
out = append(out, buscompose.Log{Module: m.Module, Name: l.Name, MaxMiB: l.Cap()})
}
return out
}
func asSeat(s catalogue.SeatDeclaration, declarer string) buscompose.Seat {
seat := buscompose.Seat{Name: s.Name, Scope: s.Scope, Accepts: s.Accepts, Emits: s.Emits,
Serves: catalogue.VerbNames(s.Serves), Kinded: s.Kinded, ByCaller: s.ByCaller, Proofs: s.Proofs, DeclaredBy: declarer}
if own, ok := catalogue.SeatNamed(s.Name); ok && own.PulledByKind {
seat.PulledByKind = true
}
for _, r := range s.Records {
if declarer != "" {
seat.Records = append(seat.Records, buscompose.BucketName(declarer, r))
}
}
return seat
}
func streamsOf(m catalogue.Manifest) []buscompose.ModuleStream {
var out []buscompose.ModuleStream
for _, s := range m.Streams {
events := make([]string, 0, len(s.Events))
for _, e := range s.Events {
events = append(events, buscompose.WirePattern(e))
}
out = append(out, buscompose.ModuleStream{Module: m.Module, Name: s.BusName(), Events: events,
MaxAgeDays: s.Days(), MaxMiB: s.Cap(), Restricted: s.Grant != nil,
WorkQueue: s.Retention == catalogue.StreamRetentionWorkQueue, Duplicates: s.Duplicates})
}
return out
}
func queueOwnersOf(m catalogue.Manifest) []buscompose.QueueOwner {
buses := m.ProcessBuses()
if len(buses) == 0 {
return nil
}
byName := map[string]catalogue.StreamDeclaration{}
for _, s := range m.Streams {
byName[s.Name] = s
}
var out []buscompose.QueueOwner
for _, r := range m.Resources {
id := fmt.Sprint(r["id"])
b, declared := buses[id]
if !declared {
continue
}
for _, q := range b.Queues {
s, known := byName[q]
if !known || s.Retention != catalogue.StreamRetentionWorkQueue {
continue
}
out = append(out, buscompose.QueueOwner{Process: id, Stream: s.BusName(), MaxDeliver: s.MaxDeliveries})
}
}
return out
}
func processesOf(m catalogue.Manifest) []buscompose.ProcessPrincipal {
buses := m.ProcessBuses()
if len(buses) == 0 {
return nil
}
queues := queueOwnersOf(m)
var out []buscompose.ProcessPrincipal
for _, r := range m.Resources {
id := fmt.Sprint(r["id"])
b, declared := buses[id]
if !declared {
continue
}
pr := buscompose.ProcessPrincipal{Process: id, Emits: b.Emits, State: b.State, Asks: b.Asks, Invokes: b.Invokes}
for _, q := range queues {
if q.Process == id {
pr.Queues = append(pr.Queues, q)
}
}
out = append(out, pr)
}
return out
}
func hearsOf(m catalogue.Manifest, declared map[string]catalogue.Manifest, node string, values func(provider, setting string) []any,
consumes []string) ([]buscompose.Heard, bool, []string) {
reading := m
reading.Consumes = consumes
parts, touches, onEvents := catalogue.Hearing(reading, declared, node, values)
var out []buscompose.Heard
for _, p := range parts {
h := buscompose.Heard{Stream: p.Stream.BusName(), Emitter: p.Emitter, Restricted: p.Stream.Grant != nil}
for _, pattern := range p.Patterns {
h.Filters = append(h.Filters, buscompose.ModuleStreamSubject(p.Emitter, buscompose.WirePattern(pattern)))
}
out = append(out, h)
}
return out, touches, onEvents
}