diff --git a/cmd/mesh-controller/acts.go b/cmd/mesh-controller/acts.go index 3989f0f8..9a8cd694 100644 --- a/cmd/mesh-controller/acts.go +++ b/cmd/mesh-controller/acts.go @@ -408,12 +408,21 @@ func issueOnAssign(ctx context.Context, open *stores, node, module string) strin return "" } user := broker.Principal{Kind: broker.KindModule, Node: node, Module: module}.Username() - if _, reads := m.OwnSecrets["broker"]; !reads { + _, reads := m.OwnSecrets["broker"] + if !reads && !runsTheProvisionerClient(m) { + // A module whose bus accounts are its processes' alone has none of its own: only the processes that + // are not issued yet are issued (issue 1226). + return processAccountsOnAssign(ctx, inv, m, node) + } + if !reads { // A module running the provisioner client alone is minted the client's account (issue 917). user = broker.Principal{Kind: broker.KindProvisioner, Node: node, Module: module}.Username() } - if _, minted, err := inv.BusUserHash(ctx, user); err != nil || minted { + if _, minted, err := inv.BusUserHash(ctx, user); err != nil { return "" + } else if minted { + // Its own account stands and is kept; a process it gained is not issued yet (issue 1226). + return processAccountsOnAssign(ctx, inv, m, node) } busAddress, err := broker.BusAddress() if err == nil { @@ -426,6 +435,16 @@ func issueOnAssign(ctx context.Context, open *stores, node, module string) strin return fmt.Sprintf("its bus credential is issued and sealed to %s, and arrives with the push", node) } +// processAccountsOnAssign issues the process accounts of an assigned module not issued yet, and says what it did. +func processAccountsOnAssign(ctx context.Context, inv *inventory.Inventory, m catalogue.Manifest, node string) string { + line, err := issueMissingProcessAccounts(ctx, inv, m, node) + if err != nil { + return fmt.Sprintf("its process accounts are not issued (%v): `module issue %s --node %s` first — "+ + "`push %s` refuses to send %s until it is", err, m.Module, node, node, m.Module) + } + return line +} + // refusedMovingTheController refuses assigning or unassigning the controller's module through a verb (review of // novox/hq ADR 0272): the node it is assigned to is the control-node, and the control-node's operator account is // the controller's terminal, so whoever moves the module chooses the terminal. At the terminal alone, as every diff --git a/cmd/mesh-controller/build.go b/cmd/mesh-controller/build.go index accc7e29..b7077f1b 100644 --- a/cmd/mesh-controller/build.go +++ b/cmd/mesh-controller/build.go @@ -768,6 +768,9 @@ func takeIn(ctx context.Context, inv *inventory.Inventory, result link.BuildResu } return manifest, kept, err } + // A bus process the new definition gains gets its account now, on each machine assigned the module, so the + // next push composes a real account (issue 1226). + issueGainedProcessAccounts(ctx, inv, manifest) // Which repository it is registered from, by the forge's own id (novox/hq ADR 0266). if err := inv.SetSourceIdentity(ctx, manifest.Module, repoID); err != nil { return manifest, kept, err diff --git a/cmd/mesh-controller/process_accounts_integration_test.go b/cmd/mesh-controller/process_accounts_integration_test.go new file mode 100644 index 00000000..983fbf78 --- /dev/null +++ b/cmd/mesh-controller/process_accounts_integration_test.go @@ -0,0 +1,64 @@ +//go:build integration + +package main + +import ( + "path/filepath" + "strings" + "testing" + + "git.novox.be/novox/mesh-sdk/go/catalogue" + + "github.com/novox/mesh-controller/internal/broker" +) + +// 1226/AC2: given an assigned module whose new definition adds a bus process, when the delivery records it, +// that process's account is issued and the next compose carries a real account; and (AC1) before it, the +// composition refuses the module naming issue; (AC3) a later record does not replace the account. +func TestAGainedProcessIsIssuedWhenTheDeliveryRecordsIt(t *testing.T) { + open := aMesh(t) + ctx := t.Context() + inv := open.inventory + + dir := t.TempDir() + if err := busCertificate([]string{"--into", dir}); err != nil { + t.Fatal(err) + } + t.Setenv(broker.AddressVar, "127.0.0.1:4222") + t.Setenv(broker.CertificateVar, filepath.Join(dir, "tls.crt")) + + // Assigned without the process, then a definition that gains it. + plain, err := catalogue.ParseManifest([]byte(`{"module":"mesh-mail","version":"1", + "own-secrets":{"broker-send":{"path":"/var/lib/mesh-mail/broker-send","owner":"mesh-mail-send"}}, + "resources":[{"id":"account","type":"user","name":"mesh-mail-send"}]}`)) + if err != nil { + t.Fatal(err) + } + register(t, open, plain) + if _, err := assign(ctx, open, "laptop", "mesh-mail"); err != nil { + t.Fatal(err) + } + gained := mailSenderWithProcess() + register(t, open, gained) + + // 1226/AC1 on the store: nothing issued, so the compose refuses, naming issue. + if _, err := prepareSend(ctx, open, "laptop"); err == nil || !strings.Contains(err.Error(), "mesh-controller.issue laptop mesh-mail") { + t.Fatalf("1226/AC1: the composition did not refuse the unissued account naming issue: %v", err) + } + + issueGainedProcessAccounts(ctx, inv, gained) + hash, held, err := inv.BusUserHash(ctx, "laptop.mesh-mail.send") + if err != nil || !held { + t.Fatalf("1226/AC2: the gained process's account was not issued: held %v, %v", held, err) + } + if _, err := prepareSend(ctx, open, "laptop"); err != nil { + t.Fatalf("1226/AC2: the next compose does not carry the account: %v", err) + } + + // 1226/AC3: recording the definition again, and composing, replaces nothing. + issueGainedProcessAccounts(ctx, inv, gained) + again, _, err := inv.BusUserHash(ctx, "laptop.mesh-mail.send") + if err != nil || again != hash { + t.Fatalf("1226/AC3: the issued account was replaced (%v)", err) + } +}