No client names the holder of the seat mesh-provisioner: each reader keeps its own constant, held to its manifest (ADR 0625 section 4, issue 1966)
Claude-Session: https://claude.ai/code/session_01NcbgfFSnn24M3GcqttoU7i
This commit is contained in:
@@ -16,19 +16,26 @@ import (
|
||||
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
||||
)
|
||||
|
||||
// The seat, its holder and its two states, and the names a reader declares in its `reads`.
|
||||
// The seat and its two states, by their local names. The module that holds the seat, and so the addresses and buckets of the
|
||||
// states (`<module>.<state>`, `<module>_<state>`), is each reader's own, taken from its manifest's `reads` (novox/hq ADR 0625 §4).
|
||||
const (
|
||||
Seat = "mesh-provisioner"
|
||||
Module = "mesh-provisioner"
|
||||
ProvisionsState = "provisions"
|
||||
PlansState = "plans"
|
||||
ProvisionsRead = Module + "." + ProvisionsState
|
||||
PlansRead = Module + "." + PlansState
|
||||
// ProvisionsBucket and PlansBucket are the states' buckets.
|
||||
ProvisionsBucket = Module + "_" + ProvisionsState
|
||||
PlansBucket = Module + "_" + PlansState
|
||||
)
|
||||
|
||||
// The states' buckets and `reads` addresses for the module holding the seat, which a reader names from its own manifest.
|
||||
func ProvisionsBucketOf(holder string) string { return holder + "_" + ProvisionsState }
|
||||
|
||||
// PlansBucketOf is the bucket of the state plans in the holder's name.
|
||||
func PlansBucketOf(holder string) string { return holder + "_" + PlansState }
|
||||
|
||||
// ProvisionsReadOf is the address a manifest's `reads` names the state provisions by.
|
||||
func ProvisionsReadOf(holder string) string { return holder + "." + ProvisionsState }
|
||||
|
||||
// PlansReadOf is the address a manifest's `reads` names the state plans by.
|
||||
func PlansReadOf(holder string) string { return holder + "." + PlansState }
|
||||
|
||||
// The states of a provision (clause 1).
|
||||
const (
|
||||
// Asked is a provision its consumer needs that its provider has not answered yet, or whose dependencies are not
|
||||
|
||||
@@ -118,7 +118,10 @@ func TestAProvisionIsAReferenceUnderItsConsumersKey(t *testing.T) {
|
||||
t.Errorf("the secret provided by %s depends on %v", holder, got)
|
||||
}
|
||||
}
|
||||
if ProvisionsBucket != "mesh-provisioner_provisions" || PlansRead != "mesh-provisioner.plans" {
|
||||
t.Errorf("the states are %s and %s", ProvisionsBucket, PlansRead)
|
||||
if ProvisionsBucketOf(testProvisioner) != "mesh-provisioner_provisions" || PlansReadOf(testProvisioner) != "mesh-provisioner.plans" {
|
||||
t.Errorf("the states are %s and %s", ProvisionsBucketOf(testProvisioner), PlansReadOf(testProvisioner))
|
||||
}
|
||||
}
|
||||
|
||||
// testProvisioner is the module holding the seat in these tests: a reader names it from its own manifest's `reads`.
|
||||
const testProvisioner = "mesh-provisioner"
|
||||
|
||||
@@ -32,6 +32,8 @@ import (
|
||||
type MovedSpec struct {
|
||||
// Module and Provision are the provider module's name and what it provides, as its manifest says.
|
||||
Module, Provision string
|
||||
// Provisioner is the module that holds the seat mesh-provisioner, as the provider's manifest's `reads` name it.
|
||||
Provisioner string
|
||||
// Offer is what it serves: its credential form and served values.
|
||||
Offer provider.Offer
|
||||
// Login is the identity a consumer is made under at the provider's end (postgres: the entry's User; keycloak: its
|
||||
@@ -234,7 +236,7 @@ func (m *Moved) retire(ctx context.Context) {
|
||||
|
||||
// watch keeps the plans as the bus holds them, from the first read on.
|
||||
func (m *Moved) watch(ctx context.Context, bus provider.Bus) error {
|
||||
err := bus.Watch(ctx, provisions.PlansBucket, func(key string, value []byte) {
|
||||
err := bus.Watch(ctx, provisions.PlansBucketOf(m.spec.Provisioner), func(key string, value []byte) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if value == nil {
|
||||
@@ -297,10 +299,10 @@ func NewMoved(consumersFile string, h *Harness, spec MovedSpec) (*Moved, *provid
|
||||
return nil, nil, nil, errors.New("no consumers' file")
|
||||
}
|
||||
run := newMoved(h, spec)
|
||||
bus := provider.StateBus(spec.Module)
|
||||
bus := provider.StateBus(spec.Module, spec.Provisioner)
|
||||
return run, provider.New(provider.Options{
|
||||
Node: h.Node,
|
||||
Module: spec.Module,
|
||||
Module: spec.Module, Provisioner: spec.Provisioner,
|
||||
Offers: map[string]provider.Offer{spec.Provision: spec.Offer},
|
||||
Bus: bus,
|
||||
Vault: provider.ConsumersFile{Paths: []string{consumersFile}},
|
||||
|
||||
@@ -22,7 +22,7 @@ const (
|
||||
)
|
||||
|
||||
// postgresSpec is what a postgres-like provider says: a login per consumer, and the grant file's logins.
|
||||
var postgresSpec = MovedSpec{Module: "postgres", Provision: ProvisionName,
|
||||
var postgresSpec = MovedSpec{Module: "postgres", Provisioner: testProvisioner, Provision: ProvisionName,
|
||||
Offer: provider.Offer{Credential: provider.PerConsumer, Serves: map[string]any{"port": servedPort}},
|
||||
Login: func(e provider.Entry) string { return e.User },
|
||||
Noun: "login", Made: "login", Backend: "the backend", WithGrants: true}
|
||||
@@ -75,13 +75,13 @@ func (b *bucketBus) putPlan(node, module string, entries ...map[string]any) {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
_ = b.Put(context.Background(), provisions.ProvisionsBucket, key, value)
|
||||
_ = b.Put(context.Background(), provisions.ProvisionsBucketOf(testProvisioner), key, value)
|
||||
}
|
||||
plan, err := provisions.EncodePlan(provisions.Plan{Node: node, Module: module, Plan: catalogue.Plan{Provisions: ps}})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
_ = b.Put(context.Background(), provisions.PlansBucket, node+"."+module, plan)
|
||||
_ = b.Put(context.Background(), provisions.PlansBucketOf(testProvisioner), node+"."+module, plan)
|
||||
}
|
||||
|
||||
// retireOn is a consumer's provision retiring with the operator's yes (ADR 0570 clause 5), its plan gone: the one way
|
||||
@@ -92,8 +92,8 @@ func (b *bucketBus) retireOn(node, module, provides string) {
|
||||
Warrant: &provisions.Warrant{Ask: "a-1", Option: "yes", By: "the operator"}}
|
||||
key, _ := pr.Key()
|
||||
value, _ := provisions.Encode(pr)
|
||||
_ = b.Delete(context.Background(), provisions.PlansBucket, node+"."+module)
|
||||
_ = b.Put(context.Background(), provisions.ProvisionsBucket, key, value)
|
||||
_ = b.Delete(context.Background(), provisions.PlansBucketOf(testProvisioner), node+"."+module)
|
||||
_ = b.Put(context.Background(), provisions.ProvisionsBucketOf(testProvisioner), key, value)
|
||||
}
|
||||
|
||||
// bind is one consumer's assignment, whose plan names this machine's postgres as the provider and carries what the
|
||||
@@ -160,11 +160,11 @@ func (w *movedWorld) pass() []provider.Outcome {
|
||||
w.now = w.now.Add(5 * time.Second)
|
||||
// the plans as the bus holds them now, as the watch would hand them
|
||||
w.run.plans = map[string][]byte{}
|
||||
for k, v := range w.bus.buckets[provisions.PlansBucket] {
|
||||
for k, v := range w.bus.buckets[provisions.PlansBucketOf(testProvisioner)] {
|
||||
w.run.plans[k] = v
|
||||
}
|
||||
// the helper reads its buckets from the in-memory bus
|
||||
w.p = provider.New(provider.Options{Node: "novox", Module: w.spec.Module, Bus: w.bus,
|
||||
w.p = provider.New(provider.Options{Provisioner: testProvisioner, Node: "novox", Module: w.spec.Module, Bus: w.bus,
|
||||
Vault: provider.ConsumersFile{Paths: []string{w.file}},
|
||||
Offers: map[string]provider.Offer{ProvisionName: w.spec.Offer},
|
||||
Make: w.run.Make, Gone: w.run.Gone, Logf: func(string, ...any) {}})
|
||||
@@ -248,7 +248,7 @@ func testAConsumerMissingFromOneReadRetiresNothing(t *testing.T) {
|
||||
w.bus.bind("ace", "app1", nil)
|
||||
w.bus.bind("ace", "app2", nil)
|
||||
w.pass()
|
||||
_ = w.bus.Delete(ctx, provisions.PlansBucket, "ace.app2")
|
||||
_ = w.bus.Delete(ctx, provisions.PlansBucketOf(testProvisioner), "ace.app2")
|
||||
for i := 0; i < 8; i++ {
|
||||
w.pass()
|
||||
}
|
||||
@@ -276,7 +276,7 @@ func testNoPlansReadRetiresNothing(t *testing.T) {
|
||||
w := newMovedWorld(t, bound)
|
||||
w.bus.bind("ace", "app1", nil)
|
||||
w.pass()
|
||||
w.bus.Delete(ctx, provisions.PlansBucket, "ace.app1")
|
||||
w.bus.Delete(ctx, provisions.PlansBucketOf(testProvisioner), "ace.app1")
|
||||
for i := 0; i < 8; i++ {
|
||||
w.pass()
|
||||
}
|
||||
@@ -334,7 +334,7 @@ func testASpecWithoutGrantsMakesUnderThePlansAsAndIgnoresTheGrantFile(t *testing
|
||||
now := time.Date(2026, 10, 15, 12, 0, 0, 0, time.UTC)
|
||||
h := &Harness{Resource: ProvisionName, Adapter: a, Node: "novox", Now: func() time.Time { return now },
|
||||
Receives: filepath.Join(t.TempDir(), "unreadable-or-missing.json"), Log: func(string, ...any) {}}
|
||||
spec := MovedSpec{Module: "keycloak", Provision: ProvisionName,
|
||||
spec := MovedSpec{Module: "keycloak", Provisioner: testProvisioner, Provision: ProvisionName,
|
||||
Offer: provider.Offer{Credential: provider.PerConsumer},
|
||||
Login: func(e provider.Entry) string { return e.As }, Noun: "client id", Made: "client", Backend: "Keycloak",
|
||||
Values: func(e provider.Entry) map[string]any { return map[string]any{"label": e.Contributes["label"]} }}
|
||||
@@ -348,7 +348,7 @@ func testASpecWithoutGrantsMakesUnderThePlansAsAndIgnoresTheGrantFile(t *testing
|
||||
if err := run.watch(ctx, bus); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
p := provider.New(provider.Options{Node: "novox", Module: "keycloak", Bus: bus,
|
||||
p := provider.New(provider.Options{Provisioner: testProvisioner, Node: "novox", Module: "keycloak", Bus: bus,
|
||||
Vault: provider.ConsumersFile{Paths: []string{file}},
|
||||
Offers: map[string]provider.Offer{ProvisionName: spec.Offer}, Make: run.Make, Gone: run.Gone,
|
||||
Logf: func(string, ...any) {}})
|
||||
@@ -372,7 +372,7 @@ var (
|
||||
|
||||
// keycloakSpec is what a keycloak-like provider says: the client id is the plan's `as`, the values are what the
|
||||
// consumer contributes with the controller's names over them, and no grant file is consulted.
|
||||
var keycloakSpec = MovedSpec{Module: "keycloak", Provision: ProvisionName,
|
||||
var keycloakSpec = MovedSpec{Module: "keycloak", Provisioner: testProvisioner, Provision: ProvisionName,
|
||||
Offer: provider.Offer{Credential: provider.PerConsumer, Serves: map[string]any{"issuer": testIssuer,
|
||||
"token-path": "/protocol/openid-connect/token"}},
|
||||
Login: func(e provider.Entry) string { return e.As },
|
||||
@@ -462,3 +462,6 @@ func testAConsumerNamedByItsPlanKeepsItsClientId(t *testing.T) {
|
||||
t.Fatalf("retired %v: the plan's client id was counted unasked", w.a.removed)
|
||||
}
|
||||
}
|
||||
|
||||
// testProvisioner is the module holding the seat mesh-provisioner in these tests.
|
||||
const testProvisioner = "mesh-provisioner"
|
||||
|
||||
@@ -25,7 +25,7 @@ func TestHarnessRuns(t *testing.T) {
|
||||
w.run.FirstRetry = time.Millisecond
|
||||
w.run.loaded = false
|
||||
bus := &earlyBus{bucketBus: w.bus, refuse: 3}
|
||||
p := provider.New(provider.Options{Node: "novox", Module: "postgres", Bus: w.bus,
|
||||
p := provider.New(provider.Options{Provisioner: testProvisioner, Node: "novox", Module: "postgres", Bus: w.bus,
|
||||
Vault: provider.ConsumersFile{Paths: []string{w.file}}, Make: w.run.Make, Gone: w.run.Gone,
|
||||
Every: time.Hour, Logf: func(string, ...any) {}})
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
|
||||
@@ -162,6 +162,9 @@ type Consumer struct {
|
||||
type Options struct {
|
||||
// Node and Module are this provider: the machine it runs on and the module serving.
|
||||
Node, Module string
|
||||
// Provisioner is the module that holds the seat mesh-provisioner, whose states plans and provisions the provider reads: its
|
||||
// own manifest's `reads` name it (`<module>.plans`); the client names no holder (ADR 0625 §4).
|
||||
Provisioner string
|
||||
// Offers are the provisions it serves, by name.
|
||||
Offers map[string]Offer
|
||||
Bus Bus
|
||||
@@ -238,12 +241,15 @@ type Outcome struct {
|
||||
// Run watches the binds and the provider's own entries, and passes once at start, on every change and every
|
||||
// Every, until ctx ends.
|
||||
func (p *Provider) Run(ctx context.Context) error {
|
||||
if p.opts.Provisioner == "" {
|
||||
return errors.New("the provider names no module holding the seat mesh-provisioner (Options.Provisioner), so it reads no plans")
|
||||
}
|
||||
if err := p.watch(ctx, BucketOf(p.opts.Module), p.entries); err != nil {
|
||||
return fmt.Errorf("watching %s: %w", BucketOf(p.opts.Module), err)
|
||||
}
|
||||
for _, bucket := range []string{provisions.PlansBucket, provisions.ProvisionsBucket} {
|
||||
for _, bucket := range []string{provisions.PlansBucketOf(p.opts.Provisioner), provisions.ProvisionsBucketOf(p.opts.Provisioner)} {
|
||||
into := p.plans
|
||||
if bucket == provisions.ProvisionsBucket {
|
||||
if bucket == provisions.ProvisionsBucketOf(p.opts.Provisioner) {
|
||||
into = p.provisions
|
||||
}
|
||||
if err := p.watch(ctx, bucket, into); err != nil {
|
||||
@@ -317,13 +323,13 @@ func (p *Provider) Pass(ctx context.Context) ([]Outcome, error) {
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("reading %s: %w", BucketOf(p.opts.Module), err)
|
||||
}
|
||||
plans, err := read(provisions.PlansBucket)
|
||||
plans, err := read(provisions.PlansBucketOf(p.opts.Provisioner))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("reading %s: %w", provisions.PlansBucket, err)
|
||||
return nil, fmt.Errorf("reading %s: %w", provisions.PlansBucketOf(p.opts.Provisioner), err)
|
||||
}
|
||||
provs, err := read(provisions.ProvisionsBucket)
|
||||
provs, err := read(provisions.ProvisionsBucketOf(p.opts.Provisioner))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("reading %s: %w", provisions.ProvisionsBucket, err)
|
||||
return nil, fmt.Errorf("reading %s: %w", provisions.ProvisionsBucketOf(p.opts.Provisioner), err)
|
||||
}
|
||||
return p.passIn(ctx, Inputs{Entries: entries, Plans: plans, Provisions: provs})
|
||||
}
|
||||
@@ -705,26 +711,28 @@ func sortedKeys[V any](m map[string]V) []string {
|
||||
}
|
||||
|
||||
// StateBus is the Bus of a provider that runs as a module bundle over the runtime's stdio: the provider's own bucket,
|
||||
// BucketOf(module), is the module's state `bound`, and provisions.PlansBucket and provisions.ProvisionsBucket are the
|
||||
// states it reads, `mesh-provisioner.plans` and `mesh-provisioner.provisions` (each declared in the module's manifest).
|
||||
// BucketOf(module), is the module's state `bound`, and the provisioner holder's plans and provisions are the
|
||||
// states it reads, `<holder>.plans` and `<holder>.provisions` (each declared in the module's manifest).
|
||||
// Any other bucket is refused.
|
||||
//
|
||||
// A stdio state watch lasts as long as the process and cannot be ended (stdio.KeptState.Watch), so Watch honours the
|
||||
// Bus contract by silencing instead: once ctx has ended the handler is never called again, though the runtime keeps
|
||||
// sending changes that are dropped. A Pass, which watches twice per call, is therefore correct, only the dropped
|
||||
// changes of its two spent watches remain.
|
||||
func StateBus(module string) Bus { return stateBus{module: module} }
|
||||
func StateBus(module, provisioner string) Bus {
|
||||
return stateBus{module: module, provisioner: provisioner}
|
||||
}
|
||||
|
||||
type stateBus struct{ module string }
|
||||
type stateBus struct{ module, provisioner string }
|
||||
|
||||
func (b stateBus) state(bucket string) (stdio.KeptState, error) {
|
||||
switch bucket {
|
||||
case BucketOf(b.module):
|
||||
return stdio.State("bound"), nil
|
||||
case provisions.PlansBucket:
|
||||
return stdio.State(provisions.PlansRead), nil
|
||||
case provisions.ProvisionsBucket:
|
||||
return stdio.State(provisions.ProvisionsRead), nil
|
||||
case provisions.PlansBucketOf(b.provisioner):
|
||||
return stdio.State(provisions.PlansReadOf(b.provisioner)), nil
|
||||
case provisions.ProvisionsBucketOf(b.provisioner):
|
||||
return stdio.State(provisions.ProvisionsReadOf(b.provisioner)), nil
|
||||
}
|
||||
return stdio.KeptState{}, fmt.Errorf("%s is not a bucket this provider keeps or reads", bucket)
|
||||
}
|
||||
|
||||
@@ -194,13 +194,13 @@ func planned(t *testing.T, bus *standIn, node, module string, ps ...catalogue.Pl
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_ = bus.Put(context.Background(), provisions.ProvisionsBucket, key, value)
|
||||
_ = bus.Put(context.Background(), provisions.ProvisionsBucketOf(testProvisioner), key, value)
|
||||
}
|
||||
raw, err := provisions.EncodePlan(provisions.Plan{Node: node, Module: module, Plan: catalogue.Plan{Provisions: ps}})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := bus.Put(context.Background(), provisions.PlansBucket, node+"."+module, raw); err != nil {
|
||||
if err := bus.Put(context.Background(), provisions.PlansBucketOf(testProvisioner), node+"."+module, raw); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
@@ -217,8 +217,8 @@ func retire(t *testing.T, bus *standIn, node, module, provision, provider, provi
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_ = bus.Delete(context.Background(), provisions.PlansBucket, node+"."+module)
|
||||
_ = bus.Put(context.Background(), provisions.ProvisionsBucket, key, raw)
|
||||
_ = bus.Delete(context.Background(), provisions.PlansBucketOf(testProvisioner), node+"."+module)
|
||||
_ = bus.Put(context.Background(), provisions.ProvisionsBucketOf(testProvisioner), key, raw)
|
||||
}
|
||||
|
||||
func bind(provision, local, provider, providerModule string) map[string]string {
|
||||
@@ -245,7 +245,7 @@ func entryOf(t *testing.T, bus *standIn, key string) Entry {
|
||||
// postgres is a provider of postgres-database on novox with a login per consumer (ADR 0048, ADR 0158 §11), its
|
||||
// served values derived from each consumer's identity (ADR 0202 §1).
|
||||
func postgres(bus *standIn, v *vault, m *maker) *Provider {
|
||||
return New(Options{
|
||||
return New(Options{Provisioner: testProvisioner,
|
||||
Node: "novox", Module: "postgres", Bus: bus, Vault: v, Make: m.Make, Gone: m.Gone,
|
||||
Offers: map[string]Offer{
|
||||
"postgres-database": {Credential: PerConsumer, Serves: map[string]any{
|
||||
@@ -292,3 +292,15 @@ func keysOf(v any, into *[]string) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// testProvisioner is the module holding the seat mesh-provisioner in these tests (a reader names it from its manifest).
|
||||
const testProvisioner = "mesh-provisioner"
|
||||
|
||||
// A provider that does not say which module holds the seat mesh-provisioner reads no plans, and says so at its start: the
|
||||
// client names no holder, so a reader that forgot to give it is loud (ADR 0625 §4).
|
||||
func TestAProviderWithoutTheHoldersNameDoesNotRun(t *testing.T) {
|
||||
p := New(Options{Node: "novox", Module: "postgres", Bus: &standIn{}, Logf: func(string, ...any) {}})
|
||||
if err := p.Run(context.Background()); err == nil || !strings.Contains(err.Error(), "mesh-provisioner") {
|
||||
t.Errorf("Run = %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,7 +73,7 @@ func TestEntries(t *testing.T) {
|
||||
v := &vault{values: map[string]string{"provision/novox/sonarr-api": "one-value"}}
|
||||
assign(t, bus, "ace", "overseerr", bind("sonarr-api", "", "novox", "sonarr"))
|
||||
assign(t, bus, "g14", "homepage", bind("sonarr-api", "", "novox", "sonarr"))
|
||||
p := New(Options{Node: "novox", Module: "sonarr", Bus: bus, Vault: v, Make: m.Make, Logf: func(string, ...any) {},
|
||||
p := New(Options{Provisioner: testProvisioner, Node: "novox", Module: "sonarr", Bus: bus, Vault: v, Make: m.Make, Logf: func(string, ...any) {},
|
||||
Offers: map[string]Offer{"sonarr-api": {Credential: Shared, User: "sonarr",
|
||||
Serves: map[string]any{"url": "http://sonarr.novox.mesh:8989"}}}})
|
||||
if _, err := p.Pass(context.Background()); err != nil {
|
||||
@@ -129,7 +129,7 @@ func TestEntries(t *testing.T) {
|
||||
bus, m := newStandIn(), &maker{}
|
||||
v := &vault{values: map[string]string{"pair/novox/redis-cache/ace/letta/queue": "q-secret"}}
|
||||
assign(t, bus, "ace", "letta", bind("redis-cache", "queue", "novox", "redis"))
|
||||
p := New(Options{Node: "novox", Module: "redis", Bus: bus, Vault: v, Make: m.Make, Logf: func(string, ...any) {},
|
||||
p := New(Options{Provisioner: testProvisioner, Node: "novox", Module: "redis", Bus: bus, Vault: v, Make: m.Make, Logf: func(string, ...any) {},
|
||||
Offers: map[string]Offer{"redis-cache": {Credential: PerConsumer, Serves: map[string]any{"port": 6379}}}})
|
||||
if _, err := p.Pass(context.Background()); err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -255,7 +255,7 @@ func TestEntries(t *testing.T) {
|
||||
b[k] = val
|
||||
}
|
||||
assign(t, bus, "ace", "letta", b)
|
||||
p := New(Options{Node: "novox", Module: "postgres", Bus: bus, Vault: v, Make: m.Make, Logf: func(string, ...any) {},
|
||||
p := New(Options{Provisioner: testProvisioner, Node: "novox", Module: "postgres", Bus: bus, Vault: v, Make: m.Make, Logf: func(string, ...any) {},
|
||||
Offers: map[string]Offer{"postgres-database": {Credential: offer, User: "shared-user"}}})
|
||||
out, err := p.Pass(context.Background())
|
||||
if err != nil || len(out) != 1 || out[0].Status != Answered {
|
||||
@@ -288,7 +288,7 @@ func TestEntries(t *testing.T) {
|
||||
b := bind("route", "", "novox", "route-proxy")
|
||||
b["credential-form"] = "pair"
|
||||
assign(t, bus, "ace", "letta", b)
|
||||
p := New(Options{Node: "novox", Module: "route-proxy", Bus: bus, Logf: func(string, ...any) {},
|
||||
p := New(Options{Provisioner: testProvisioner, Node: "novox", Module: "route-proxy", Bus: bus, Logf: func(string, ...any) {},
|
||||
Offers: map[string]Offer{"route": {Credential: NoCredential}}})
|
||||
out, err := p.Pass(context.Background())
|
||||
if err != nil || len(out) != 1 || out[0].Status != Answered {
|
||||
@@ -318,12 +318,12 @@ func TestEntries(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_ = bus.Put(context.Background(), provisions.ProvisionsBucket, "ace.letta.postgres-database", raw)
|
||||
_ = bus.Put(context.Background(), provisions.ProvisionsBucketOf(testProvisioner), "ace.letta.postgres-database", raw)
|
||||
// And umami's key carries none: asked, but no plan names it.
|
||||
bare := provisions.Provision{Provision: "postgres-database", Consumer: provisions.End{Module: "umami", Node: "g14"},
|
||||
Provider: provisions.End{Module: "postgres", Node: "novox"}, State: provisions.Asked}
|
||||
raw, _ = provisions.Encode(bare)
|
||||
_ = bus.Put(context.Background(), provisions.ProvisionsBucket, "g14.umami.postgres-database", raw)
|
||||
_ = bus.Put(context.Background(), provisions.ProvisionsBucketOf(testProvisioner), "g14.umami.postgres-database", raw)
|
||||
|
||||
out, err := postgres(bus, v, m).Pass(context.Background())
|
||||
if err != nil {
|
||||
@@ -416,7 +416,7 @@ func TestBinds(t *testing.T) {
|
||||
if _, err := p.Pass(context.Background()); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_ = bus.Put(context.Background(), provisions.PlansBucket, "ace.letta", []byte(bad))
|
||||
_ = bus.Put(context.Background(), provisions.PlansBucketOf(testProvisioner), "ace.letta", []byte(bad))
|
||||
assign(t, bus, "g14", "umami", bind("postgres-database", "", "novox", "postgres"))
|
||||
var logged []string
|
||||
p.opts.Logf = func(f string, a ...any) { logged = append(logged, fmt.Sprintf(f, a...)) }
|
||||
@@ -628,7 +628,7 @@ func TestCredentialsAndKeys(t *testing.T) {
|
||||
v := &vault{values: map[string]string{"pair/novox/postgres-database/ace/letta": "s1",
|
||||
"provision/novox/postgres-database": "s2"}}
|
||||
assign(t, bus, "ace", "letta", bind("postgres-database", "", "novox", "postgres"))
|
||||
p := New(Options{Node: "novox", Module: "postgres", Bus: bus, Vault: v, Make: m.Make, Logf: func(string, ...any) {},
|
||||
p := New(Options{Provisioner: testProvisioner, Node: "novox", Module: "postgres", Bus: bus, Vault: v, Make: m.Make, Logf: func(string, ...any) {},
|
||||
Offers: map[string]Offer{"postgres-database": {Credential: form, User: "shared-user"}}})
|
||||
if _, err := p.Pass(context.Background()); err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -664,7 +664,7 @@ func TestCredentialsAndKeys(t *testing.T) {
|
||||
assign(t, bus, "ace", "letta", bind("postgres-database", "", "shanks", "postgres"))
|
||||
plan, _ := provisions.EncodePlan(provisions.Plan{Node: "ace", Module: "letta", Plan: catalogue.Plan{
|
||||
Provisions: []catalogue.PlanProvision{{Provision: "postgres-database", Provider: "novox", ProviderModule: "postgres"}}}})
|
||||
_ = bus.Put(ctx, provisions.PlansBucket, "ace.letta", plan)
|
||||
_ = bus.Put(ctx, provisions.PlansBucketOf(testProvisioner), "ace.letta", plan)
|
||||
key := "ace.letta.postgres-database"
|
||||
put := func(k string, state string, depends []string, warrant *provisions.Warrant) {
|
||||
pr := provisions.Provision{Provision: "postgres-database", Consumer: provisions.End{Module: "letta", Node: "ace"},
|
||||
@@ -680,7 +680,7 @@ func TestCredentialsAndKeys(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_ = bus.Put(ctx, provisions.ProvisionsBucket, k, raw)
|
||||
_ = bus.Put(ctx, provisions.ProvisionsBucketOf(testProvisioner), k, raw)
|
||||
}
|
||||
p := postgres(bus, v, m)
|
||||
steps := []struct {
|
||||
@@ -697,7 +697,7 @@ func TestCredentialsAndKeys(t *testing.T) {
|
||||
}, status: Waiting, says: "depends on ace.letta.secret, which is asked"},
|
||||
{name: "the dependency provided", before: func() { put("ace.letta.secret", provisions.Provided, nil, nil) },
|
||||
status: Answered, entry: true},
|
||||
{name: "its plan gone: nothing let go by itself", before: func() { _ = bus.Delete(ctx, provisions.PlansBucket, "ace.letta") },
|
||||
{name: "its plan gone: nothing let go by itself", before: func() { _ = bus.Delete(ctx, provisions.PlansBucketOf(testProvisioner), "ace.letta") },
|
||||
entry: true},
|
||||
{name: "retiring, not yet answered", before: func() { put(key, provisions.Retiring, nil, nil) }, entry: true},
|
||||
{name: "retiring, the operator's yes", before: func() {
|
||||
@@ -769,7 +769,7 @@ func TestConsumersFileAndStateBus(t *testing.T) {
|
||||
bus := newStandIn()
|
||||
assign(t, bus, "ace", "letta", bind("postgres-database", "", "novox", "postgres"))
|
||||
var made []string
|
||||
p := New(Options{Node: "novox", Module: "postgres", Bus: bus, Vault: v,
|
||||
p := New(Options{Provisioner: testProvisioner, Node: "novox", Module: "postgres", Bus: bus, Vault: v,
|
||||
Offers: map[string]Offer{"postgres-database": {Serves: map[string]any{"port": 5432}}},
|
||||
Make: func(_ context.Context, c Consumer) error {
|
||||
made = append(made, c.Entry.User+"="+string(c.Credential))
|
||||
@@ -814,7 +814,7 @@ func TestConsumersFileAndStateBus(t *testing.T) {
|
||||
// `mesh-provisioner.plans` (novox/hq ADR 0570 clause 2), writes only the own bucket's state, and refuses any other
|
||||
// bucket, the controller's assignments among them.
|
||||
t.Run("StateBusMapsTheProvidersBucketsToTheRuntimesStates", func(t *testing.T) {
|
||||
bus := StateBus("postgres")
|
||||
bus := StateBus("postgres", testProvisioner)
|
||||
rt := serve(t, []stdio.Tool{
|
||||
{Name: "watch", Run: func(a map[string]any) (any, error) {
|
||||
return nil, bus.Watch(context.Background(), a["bucket"].(string), func(string, []byte) {})
|
||||
@@ -828,7 +828,7 @@ func TestConsumersFileAndStateBus(t *testing.T) {
|
||||
})
|
||||
for i, c := range []struct{ bucket, state string }{
|
||||
{BucketOf("postgres"), "bound"},
|
||||
{provisions.PlansBucket, "mesh-provisioner.plans"},
|
||||
{provisions.PlansBucketOf(testProvisioner), "mesh-provisioner.plans"},
|
||||
} {
|
||||
rt.call(i+1, "watch", map[string]any{"bucket": c.bucket})
|
||||
w := rt.next()
|
||||
@@ -869,12 +869,12 @@ func TestConsumersFileAndStateBus(t *testing.T) {
|
||||
// A watch whose ctx has ended calls its handler no more, though the runtime keeps sending changes: the Bus contract
|
||||
// says a watch lasts until ctx ends, and a stdio state watch lasts as long as the process.
|
||||
t.Run("StateBusWatchCallsItsHandlerNoMoreOnceItsContextEnded", func(t *testing.T) {
|
||||
bus := StateBus("postgres")
|
||||
bus := StateBus("postgres", testProvisioner)
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
handled := make(chan string, 8)
|
||||
rt := serve(t, []stdio.Tool{
|
||||
{Name: "start", Run: func(map[string]any) (any, error) {
|
||||
return "started", bus.Watch(ctx, provisions.PlansBucket, func(key string, value []byte) {
|
||||
return "started", bus.Watch(ctx, provisions.PlansBucketOf(testProvisioner), func(key string, value []byte) {
|
||||
handled <- key + "=" + string(value)
|
||||
})
|
||||
}},
|
||||
|
||||
Reference in New Issue
Block a user