302 lines
10 KiB
Go
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
|
|
}
|