Providers read the seat's plans and retire only on the operator's yes, removing the credential
ADR 0570 clauses 2 and 5: gitea, keycloak, postgres and route-adapter read mesh-provisioner.plans and .provisions instead of the controller's assignments. postgres hands the database to a keeper and drops the login; keycloak replaces the client's secret and keeps the client, so the data stays (issue 1954, V4).
This commit is contained in:
@@ -192,17 +192,14 @@ func (r *teamRig) pass() []provider.Outcome {
|
||||
return out
|
||||
}
|
||||
|
||||
// bind makes module on node bind gitea's forge-account in the assignments mirror.
|
||||
// bind makes module on node bind gitea's forge-account in the seat mesh-provisioner's plans.
|
||||
func (r *teamRig) bind(node, module string) {
|
||||
r.bus.set(provider.AssignmentsBucket, node+"."+module, []byte(fmt.Sprintf(
|
||||
`{"node":%q,"module":%q,"provisioning":{"provisions":[{"provision":"forge-account","provider":"novox","provider-module":"gitea","as":"mesh_%s"}]}}`,
|
||||
node, module, strings.ReplaceAll(node+"_"+module, "-", "_"))))
|
||||
r.bus.bindPlan(node, module, "mesh_"+strings.ReplaceAll(node+"_"+module, "-", "_"))
|
||||
}
|
||||
|
||||
// unbind is the operator's yes to retiring module's forge-account on node (ADR 0570 clause 5).
|
||||
func (r *teamRig) unbind(node, module string) {
|
||||
r.bus.mu.Lock()
|
||||
delete(r.bus.buckets[provider.AssignmentsBucket], node+"."+module)
|
||||
r.bus.mu.Unlock()
|
||||
r.bus.retire(node, module)
|
||||
}
|
||||
|
||||
// key reports a key in module's state keys, of the kind and for the account given.
|
||||
|
||||
@@ -30,6 +30,7 @@ import (
|
||||
"github.com/nats-io/nats.go/jetstream"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/provider"
|
||||
"git.novox.be/novox/mesh-sdk/go/provisions"
|
||||
)
|
||||
|
||||
const bundleVar = "MESH_FORGE_ACCOUNT_TEST_BUNDLE"
|
||||
@@ -80,7 +81,7 @@ func startBus(t *testing.T) (*runtime, context.CancelFunc) {
|
||||
rt := &runtime{t: t, js: js, kvs: map[string]jetstream.KeyValue{}, ctx: ctx, wait: map[string]chan json.RawMessage{}}
|
||||
// The controller's job: a bucket per state the module declares (bound) or reads (the others).
|
||||
for state, bucket := range map[string]string{"bound": "gitea_bound", "ssh-client.keys": "ssh-client_keys",
|
||||
"mesh-controller.assignments": "mesh-controller_assignments"} {
|
||||
"mesh-provisioner.plans": "mesh-provisioner_plans", "mesh-provisioner.provisions": "mesh-provisioner_provisions"} {
|
||||
kv, err := js.CreateKeyValue(ctx, jetstream.KeyValueConfig{Bucket: bucket, History: 1})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -296,9 +297,11 @@ func TestTheProviderWholeAnswersAMachineThatReportedItsKey(t *testing.T) {
|
||||
}
|
||||
rt, cancel := startBus(t)
|
||||
t.Cleanup(cancel)
|
||||
rt.put("mesh-controller.assignments", "g14.ssh-client", map[string]any{"node": "g14", "module": "ssh-client",
|
||||
"provisioning": map[string]any{"provisions": []map[string]any{{"provision": "forge-account", "provider": "novox",
|
||||
rt.put("mesh-provisioner.plans", "g14.ssh-client", map[string]any{"v": 1, "node": "g14", "module": "ssh-client",
|
||||
"plan": map[string]any{"provisions": []map[string]any{{"provision": "forge-account", "provider": "novox",
|
||||
"provider-module": "gitea", "as": "mesh_g14_ssh_client"}}}})
|
||||
rt.put("mesh-provisioner.provisions", "g14.ssh-client.forge-account",
|
||||
json.RawMessage(provisionKey("g14", "ssh-client", provisions.Asked, nil)))
|
||||
rt.put("ssh-client.keys", "g14.nox", map[string]string{"node": "g14", "account": "nox", "kind": "machine",
|
||||
"public": pubA + " nox@g14 (mesh)", "fingerprint": "SHA256:x", "at": "2026-10-16T10:00:00Z"})
|
||||
rt.start([]string{"MESH_NODE=novox", "MESH_MODULE=gitea", "MESH_SERVED_MODULE=gitea",
|
||||
|
||||
@@ -25,7 +25,7 @@ func TestConfigFromEnvNamesWhatIsMissing(t *testing.T) {
|
||||
}
|
||||
|
||||
// Issue 955: the runtime grants a module only the states its manifest `reads`, and the provider watches the machines'
|
||||
// keys and, through the SDK's provider, the controller's assignments (where it learns its consumers). Run as the
|
||||
// keys and, through the SDK's provider, the seat mesh-provisioner's plans and provisions (where it learns its consumers, ADR 0570). Run as the
|
||||
// bus test does, with every bucket made, a missing grant passes unseen; the manifest is read here instead.
|
||||
func TestTheManifestReadsEveryStateTheProviderWatches(t *testing.T) {
|
||||
raw, err := os.ReadFile("../../module.json")
|
||||
@@ -38,7 +38,7 @@ func TestTheManifestReadsEveryStateTheProviderWatches(t *testing.T) {
|
||||
if err := json.Unmarshal(raw, &m); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, state := range []string{"mesh-controller.assignments", "ssh-client.keys"} {
|
||||
for _, state := range []string{"mesh-provisioner.plans", "mesh-provisioner.provisions", "ssh-client.keys"} {
|
||||
if !slices.Contains(m.Reads, state) {
|
||||
t.Errorf("gitea's reads %v lack %q, so the runtime refuses its watch", m.Reads, state)
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ import (
|
||||
"time"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/provider"
|
||||
"git.novox.be/novox/mesh-sdk/go/provisions"
|
||||
|
||||
"gitea/internal/forgehttp"
|
||||
)
|
||||
@@ -78,6 +79,17 @@ func (b *fakeBus) entry(t *testing.T, key string) provider.Entry {
|
||||
}
|
||||
return e
|
||||
}
|
||||
|
||||
// serves is whether gitea's entry under key serves its consumer: there, provided and with values (ADR 0570 clause 3:
|
||||
// an entry the provider could not make is kept, failed, serving nothing).
|
||||
func (b *fakeBus) serves(key string) bool {
|
||||
b.mu.Lock()
|
||||
raw, ok := b.buckets[provider.BucketOf("gitea")][key]
|
||||
b.mu.Unlock()
|
||||
var e provider.Entry
|
||||
return ok && json.Unmarshal(raw, &e) == nil && e.State == provisions.Provided && len(e.Values) > 0
|
||||
}
|
||||
|
||||
func (b *fakeBus) has(key string) bool {
|
||||
b.mu.Lock()
|
||||
defer b.mu.Unlock()
|
||||
@@ -85,11 +97,41 @@ func (b *fakeBus) has(key string) bool {
|
||||
return ok
|
||||
}
|
||||
|
||||
// bindClient makes ssh-client on node bind gitea's forge-account in the assignments mirror.
|
||||
// bindClient makes ssh-client on node bind gitea's forge-account in the seat mesh-provisioner's plans.
|
||||
func (b *fakeBus) bindClient(node string) {
|
||||
b.set(provider.AssignmentsBucket, node+".ssh-client", []byte(fmt.Sprintf(
|
||||
`{"node":%q,"module":"ssh-client","provisioning":{"provisions":[{"provision":"forge-account","provider":"novox","provider-module":"gitea","as":"mesh_%s_ssh_client"}]}}`,
|
||||
node, node)))
|
||||
b.bindPlan(node, "ssh-client", "mesh_"+node+"_ssh_client")
|
||||
}
|
||||
|
||||
// bindPlan makes module on node bind gitea's forge-account as the seat mesh-provisioner keeps it: its plan, and the
|
||||
// provision key asked of gitea on novox (novox/hq ADR 0570 clauses 1 to 3).
|
||||
func (b *fakeBus) bindPlan(node, module, as string) {
|
||||
b.set(provisions.PlansBucket, node+"."+module, []byte(fmt.Sprintf(
|
||||
`{"v":1,"node":%q,"module":%q,"plan":{"provisions":[{"provision":"forge-account","provider":"novox","provider-module":"gitea","as":%q}]}}`,
|
||||
node, module, as)))
|
||||
b.set(provisions.ProvisionsBucket, node+"."+module+".forge-account", provisionKey(node, module, provisions.Asked, nil))
|
||||
}
|
||||
|
||||
// retire is the operator's yes to the retiring of module's forge-account on node: its plan gone, its key retiring
|
||||
// with the warrant (ADR 0570 clause 5), the only way the provider lets an entry go.
|
||||
func (b *fakeBus) retire(node, module string) {
|
||||
b.mu.Lock()
|
||||
delete(b.buckets[provisions.PlansBucket], node+"."+module)
|
||||
b.mu.Unlock()
|
||||
b.set(provisions.ProvisionsBucket, node+"."+module+".forge-account", provisionKey(node, module, provisions.Retiring,
|
||||
&provisions.Warrant{Ask: "ask-1", Option: "yes", By: "operator", At: time.Date(2026, 10, 29, 12, 0, 0, 0, time.UTC)}))
|
||||
}
|
||||
|
||||
func provisionKey(node, module, state string, w *provisions.Warrant) []byte {
|
||||
pr := provisions.Provision{Provision: "forge-account", State: state, Warrant: w,
|
||||
Consumer: provisions.End{Node: node, Module: module}, Provider: provisions.End{Node: "novox", Module: "gitea"}}
|
||||
if state == provisions.Retiring {
|
||||
pr.Why = "no want needs it"
|
||||
}
|
||||
raw, err := provisions.Encode(pr)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return raw
|
||||
}
|
||||
|
||||
// fakeForge is gitea's API as far as the provider uses it.
|
||||
@@ -343,9 +385,7 @@ func TestAConsumerThatNoLongerBindsLosesItsMeshKeysAndKeepsItsAccount(t *testing
|
||||
r.forge.mu.Lock()
|
||||
r.forge.held["nox-agent"] = append(r.forge.held["nox-agent"], map[string]any{"id": int64(99), "title": "my own", "key": pubB})
|
||||
r.forge.mu.Unlock()
|
||||
r.bus.mu.Lock()
|
||||
delete(r.bus.buckets[provider.AssignmentsBucket], "g14.ssh-client")
|
||||
r.bus.mu.Unlock()
|
||||
r.bus.retire("g14", "ssh-client")
|
||||
r.pass()
|
||||
if r.bus.has("g14.ssh-client.forge-account") {
|
||||
t.Fatal("the entry stayed")
|
||||
@@ -460,7 +500,7 @@ func TestADeletedKeyEntryRemovesTheMeshKeyAndTheBoundEntryButNotTheOperatorsKey(
|
||||
if got := r.forge.titles("nox-agent"); len(got) != 1 || got[0] != "my own" {
|
||||
t.Fatalf("keys = %v", got)
|
||||
}
|
||||
if r.bus.has("g14.ssh-client.forge-account") {
|
||||
if r.bus.serves("g14.ssh-client.forge-account") {
|
||||
t.Fatal("the entry still serves a machine whose key is gone")
|
||||
}
|
||||
}
|
||||
@@ -493,7 +533,7 @@ func TestAPassBeforeTheKeysAreLoadedRemovesAndDeletesNothing(t *testing.T) {
|
||||
r.svc.loaded = true
|
||||
r.svc.mu.Unlock()
|
||||
r.pass()
|
||||
if got := strings.Join(r.forge.titles("nox-agent"), ","); got != "my own" || r.bus.has("g14.ssh-client.forge-account") {
|
||||
if got := strings.Join(r.forge.titles("nox-agent"), ","); got != "my own" || r.bus.serves("g14.ssh-client.forge-account") {
|
||||
t.Fatalf("keys = %s", got)
|
||||
}
|
||||
}
|
||||
@@ -515,7 +555,7 @@ func TestKeepRunningStartsTheProviderAgainAfterARefusedWatch(t *testing.T) {
|
||||
r.svc.KeepRunning(ctx, func(ctx context.Context) error {
|
||||
calls++
|
||||
if calls <= 2 {
|
||||
return errors.New(`watching mesh-controller_assignments: gitea reads no state called "mesh-controller.assignments"`)
|
||||
return errors.New(`watching mesh-provisioner_plans: gitea reads no state called "mesh-provisioner.plans"`)
|
||||
}
|
||||
close(serving)
|
||||
<-ctx.Done()
|
||||
@@ -535,7 +575,7 @@ func TestKeepRunningStartsTheProviderAgainAfterARefusedWatch(t *testing.T) {
|
||||
if len(pauses) != 2 || pauses[0] != 5*time.Second || pauses[1] != 10*time.Second {
|
||||
t.Fatalf("the backoff is %v", pauses)
|
||||
}
|
||||
if len(said) == 0 || !strings.Contains(said[0], "mesh-controller.assignments") {
|
||||
if len(said) == 0 || !strings.Contains(said[0], "mesh-provisioner.plans") {
|
||||
t.Fatalf("a refusal is said loudly: %v", said)
|
||||
}
|
||||
loud := 0
|
||||
|
||||
@@ -142,7 +142,7 @@ func TestABuildModulesStateThatNeverAnswersHoldsBackOnlyItsOwnEntries(t *testing
|
||||
}
|
||||
|
||||
// 1482/AC4 (the provider's half): the manifest reads the states of both build modules beside ssh-client's and the
|
||||
// controller's assignments; the provider watches exactly the modules the code names as builders.
|
||||
// seat mesh-provisioner's plans; the provider watches exactly the modules the code names as builders.
|
||||
func TestTheManifestReadsTheBuildModulesKeys(t *testing.T) {
|
||||
raw, err := os.ReadFile("../../module.json")
|
||||
if err != nil {
|
||||
@@ -154,7 +154,7 @@ func TestTheManifestReadsTheBuildModulesKeys(t *testing.T) {
|
||||
if err := json.Unmarshal(raw, &m); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, state := range []string{"mesh-controller.assignments", "ssh-client.keys", "mesh-build-go.keys", "mesh-build-node.keys", "mesh-build-image.keys"} {
|
||||
for _, state := range []string{"mesh-provisioner.plans", "mesh-provisioner.provisions", "ssh-client.keys", "mesh-build-go.keys", "mesh-build-node.keys", "mesh-build-image.keys"} {
|
||||
if !slices.Contains(m.Reads, state) {
|
||||
t.Errorf("gitea's reads %v lack %q", m.Reads, state)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user