With no send left nothing else writes a missed grant, so the take-in of a done key returns it and tries again every half minute under an urgent condition until it is written. plan-moved and sent go with the plans. The controller's first-start deletions are said on a granted subject, a node hears only its backup holder, and the operator's account writes the hand path, an object replaced included (ADR 0539 clauses 2, 7, 10 and 13).
166 lines
6.0 KiB
Go
166 lines
6.0 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"sort"
|
|
"strings"
|
|
|
|
sdkdelivery "git.novox.be/novox/mesh-sdk/go/delivery"
|
|
"github.com/nats-io/nats.go/jetstream"
|
|
|
|
"github.com/novox/mesh-controller/internal/broker"
|
|
"github.com/novox/mesh-controller/internal/inventory"
|
|
)
|
|
|
|
// The grants issued (novox/hq ADR 0528 clause 2, ADR 0429 §6 rule 6, issue 1394): what the assigner's sixth rule reads,
|
|
// that a module's bus credential for a fingerprint exists. A module's permissions on the bus are what the bus's user list
|
|
// composes from its registered manifest (inv.BusRecords), and a module's manifest is registered at a fingerprint when the
|
|
// controller takes it in from the builder's key; so once the list is sent to the machine that runs the bus, the
|
|
// credential of every module it carried is issued at the fingerprint of the build it is registered at. The controller
|
|
// writes `<module>.<fingerprint hex>` then, naming the accounts it composed for the module, never removed. A module one
|
|
// of whose accounts has no password yet (the list leaves it out) has no grant until a later send carries it.
|
|
|
|
// grantsFrom is the grant of every module whose accounts all have a password in hashes, at the fingerprint current names
|
|
// for it; a module whose current build names no fingerprint of the new form has none. principals are the bus's users
|
|
// as broker.Users composes them.
|
|
func grantsFrom(principals []broker.Principal, hashes map[string]string, current map[string]string) map[string]sdkdelivery.ControllerGrant {
|
|
accounts, missing := map[string][]string{}, map[string]bool{}
|
|
for _, p := range principals {
|
|
if p.Module == "" || (p.Kind != broker.KindModule && p.Kind != broker.KindProcess) {
|
|
continue
|
|
}
|
|
if hashes[p.Username()] == "" {
|
|
missing[p.Module] = true
|
|
continue
|
|
}
|
|
accounts[p.Module] = append(accounts[p.Module], p.Username())
|
|
}
|
|
out := map[string]sdkdelivery.ControllerGrant{}
|
|
for module, fp := range current {
|
|
if missing[module] || !strings.HasPrefix(fp, sdkdelivery.SourcePrefix2) {
|
|
continue
|
|
}
|
|
key, err := sdkdelivery.ControllerGrantKey(module, fp)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
names := accounts[module]
|
|
sort.Strings(names)
|
|
out[key] = sdkdelivery.ControllerGrant{Accounts: names}
|
|
}
|
|
return out
|
|
}
|
|
|
|
// currentFingerprints is, by module, the fingerprint of the build each registered module is registered at: its
|
|
// manifest's commit (built_from), that commit's newest successful build, and the fingerprint that build names.
|
|
func currentFingerprints(ctx context.Context, inv *inventory.Inventory) (map[string]string, error) {
|
|
builds, err := inv.CurrentBuilds(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
out := map[string]string{}
|
|
for module, b := range builds {
|
|
if b.Commit == "" {
|
|
continue
|
|
}
|
|
id, err := inv.BuildOf(ctx, module, b.Commit)
|
|
if err != nil || id == "" {
|
|
continue
|
|
}
|
|
build, found, err := inv.BuildByID(ctx, id)
|
|
if err != nil || !found {
|
|
continue
|
|
}
|
|
out[module] = build.SourceFingerprint
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
// grantsKV is what writing the grants needs of their bucket.
|
|
type grantsKV interface {
|
|
Get(ctx context.Context, key string) (jetstream.KeyValueEntry, error)
|
|
Create(ctx context.Context, key string, value []byte, opts ...jetstream.KVCreateOpt) (uint64, error)
|
|
Update(ctx context.Context, key string, value []byte, revision uint64) (uint64, error)
|
|
}
|
|
|
|
// keepGrants writes every grant the list just sent issued that the bucket does not hold as it is: a create for a new one,
|
|
// a compare-and-set for one whose accounts changed. It answers how many it wrote.
|
|
func keepGrants(ctx context.Context, kv grantsKV, grants map[string]sdkdelivery.ControllerGrant) (int, error) {
|
|
keys := make([]string, 0, len(grants))
|
|
for k := range grants {
|
|
keys = append(keys, k)
|
|
}
|
|
sort.Strings(keys)
|
|
wrote := 0
|
|
var errs []error
|
|
for _, key := range keys {
|
|
value, err := sdkdelivery.EncodeControllerGrant(grants[key])
|
|
if err != nil {
|
|
errs = append(errs, fmt.Errorf("%s: %w", key, err))
|
|
continue
|
|
}
|
|
e, err := kv.Get(ctx, key)
|
|
switch {
|
|
case errors.Is(err, jetstream.ErrKeyNotFound):
|
|
_, err = kv.Create(ctx, key, value)
|
|
case err != nil:
|
|
default:
|
|
if before, derr := sdkdelivery.DecodeControllerGrant(e.Value()); derr == nil && sdkdelivery.SameControllerGrant(before, withVersion(grants[key])) {
|
|
continue
|
|
}
|
|
_, err = kv.Update(ctx, key, value, e.Revision())
|
|
}
|
|
if err != nil {
|
|
errs = append(errs, fmt.Errorf("%s: %w", key, err))
|
|
continue
|
|
}
|
|
wrote++
|
|
}
|
|
return wrote, errors.Join(errs...)
|
|
}
|
|
|
|
func withVersion(g sdkdelivery.ControllerGrant) sdkdelivery.ControllerGrant {
|
|
g.V = sdkdelivery.Version
|
|
return g
|
|
}
|
|
|
|
// grantsIssued writes the grants issued for every module whose credentials all exist at the fingerprint it is
|
|
// registered at (ADR 0528 clause 2, ADR 0539 clause 7). Its error is the caller's: with no send left, nothing writes a
|
|
// missed grant later but the caller's retry (the done key's take-in, composed_keys.go), and the assigner's wants wait.
|
|
func grantsIssued(ctx context.Context, inv *inventory.Inventory, js jetstream.JetStream, indent string) error {
|
|
kv, err := js.KeyValue(ctx, broker.GrantsBucket)
|
|
if err != nil {
|
|
return fmt.Errorf("the grants issued are not written: the bucket %s: %w", broker.GrantsBucket, err)
|
|
}
|
|
records, err := inv.BusRecords(ctx)
|
|
if err != nil {
|
|
return fmt.Errorf("the grants issued are not written: %w", err)
|
|
}
|
|
principals, err := broker.Users(records)
|
|
if err != nil {
|
|
return fmt.Errorf("the grants issued are not written: %w", err)
|
|
}
|
|
kept, err := inv.BusUsers(ctx)
|
|
if err != nil {
|
|
return fmt.Errorf("the grants issued are not written: %w", err)
|
|
}
|
|
hashes := make(map[string]string, len(kept))
|
|
for name, u := range kept {
|
|
hashes[name] = u.PasswordHash
|
|
}
|
|
current, err := currentFingerprints(ctx, inv)
|
|
if err != nil {
|
|
return fmt.Errorf("the grants issued are not written: %w", err)
|
|
}
|
|
wrote, err := keepGrants(ctx, kv, grantsFrom(principals, hashes, current))
|
|
if wrote > 0 {
|
|
fmt.Printf("%s%d grant(s) issued written for the assigner\n", indent, wrote)
|
|
}
|
|
if err != nil {
|
|
return fmt.Errorf("some grants issued are not written: %w", err)
|
|
}
|
|
return nil
|
|
}
|