144 lines
5.3 KiB
Go
144 lines
5.3 KiB
Go
package catalogue
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// A node runs several modules that all want one database (novox/hq 04-ISSUES/022).
|
|
//
|
|
// **The ordinary arrangement, and it could not be planned at all.** The walk that resolves a node
|
|
// is a work-list over names, so a requirement three modules shared was visited once and produced
|
|
// one need — carrying whichever module mentioned it first. Everything downstream inherited that:
|
|
// one credential, keyed by machine, named after a machine by the provisioner.
|
|
//
|
|
// The symptom had two halves and only one was loud. The provider refused, naming the modules and
|
|
// saying they would share one credential, which reads as a decision. The consumer did not: it
|
|
// resolved cleanly, wrote one module's credential file, and left the other two absent — a service
|
|
// that starts and cannot authenticate, with nothing anywhere saying why. That is the shape of 021
|
|
// again, on a different axis.
|
|
|
|
func threeConsumers() map[string]Manifest {
|
|
return shelf(
|
|
Manifest{Module: "postgres", Version: "1", Provides: FromAnywhere("postgres-database")},
|
|
Manifest{Module: "gitea", Version: "1", Requires: []string{"postgres-database"},
|
|
Contributes: map[string]map[string]any{"postgres-database": {"name": "gitea"}},
|
|
Secrets: map[string]string{"postgres-database": "/var/lib/gitea/db.secret"}},
|
|
Manifest{Module: "keycloak", Version: "1", Requires: []string{"postgres-database"},
|
|
Contributes: map[string]map[string]any{"postgres-database": {"name": "keycloak"}},
|
|
Secrets: map[string]string{"postgres-database": "/var/lib/keycloak/db.secret"}},
|
|
Manifest{Module: "umami", Version: "1", Requires: []string{"postgres-database"},
|
|
Contributes: map[string]map[string]any{"postgres-database": {"name": "umami"}},
|
|
Secrets: map[string]string{"postgres-database": "/var/lib/umami/db.secret"}},
|
|
)
|
|
}
|
|
|
|
func TestEveryConsumerOnANodeGetsItsOwnCredential(t *testing.T) {
|
|
got, err := Resolve(threeConsumers(), []string{"gitea", "keycloak", "umami"},
|
|
reachable(), World{Offered: onNetwork("anchor")})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(got.Needs) != 3 {
|
|
t.Fatalf("three modules want a database and the node has %d need(s): %v",
|
|
len(got.Needs), got.Needs)
|
|
}
|
|
for _, want := range []string{"gitea", "keycloak", "umami"} {
|
|
var found bool
|
|
for _, n := range got.Needs {
|
|
if n.For == want {
|
|
found = true
|
|
}
|
|
}
|
|
if !found {
|
|
t.Errorf("%s wants a database and no credential is made for it", want)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Each consumer's own credential reaches its own file. Matching on the provision alone, every
|
|
// consumer took whichever need was last — a module handed somebody else's password, which is a
|
|
// valid credential and therefore fails in a way that looks like a configuration error.
|
|
func TestEachConsumerGetsItsOwnCredentialAndNotAnothersFile(t *testing.T) {
|
|
got, err := Resolve(threeConsumers(), []string{"gitea", "keycloak", "umami"},
|
|
reachable(), World{Offered: onNetwork("anchor")})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for i := range got.Needs {
|
|
got.Needs[i].Sealed = "sealed-for-" + got.Needs[i].For
|
|
}
|
|
out, err := got.Declaration(Rendering{})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for _, want := range []string{"gitea", "keycloak", "umami"} {
|
|
var seen bool
|
|
for _, r := range out {
|
|
if r["path"] != "/var/lib/"+want+"/db.secret" {
|
|
continue
|
|
}
|
|
seen = true
|
|
if r["sealed"] != "sealed-for-"+want {
|
|
t.Errorf("%s was given %v, which belongs to something else", want, r["sealed"])
|
|
}
|
|
}
|
|
if !seen {
|
|
t.Errorf("%s resolved and its credential file was never written", want)
|
|
}
|
|
}
|
|
}
|
|
|
|
// The provider is told about all three, separately, and names each grant after the module.
|
|
func TestAProviderIsToldAboutEveryConsumerOnOneMachine(t *testing.T) {
|
|
provider, err := Resolve(shelf(Manifest{
|
|
Module: "postgres", Version: "1", Provides: FromAnywhere("postgres-database"),
|
|
Grants: map[string]string{"postgres-database": "/var/lib/postgres/grants"},
|
|
Receives: map[string]string{"postgres-database": "/var/lib/postgres/grants/mesh.json"},
|
|
}), []string{"postgres"}, reachable(), World{})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
var grants []Grant
|
|
for _, who := range []string{"gitea", "keycloak", "umami"} {
|
|
grants = append(grants, Grant{
|
|
Provision: "postgres-database", Consumer: "anchor", From: who,
|
|
Values: map[string]any{"name": who}, Sealed: "sealed-for-" + who})
|
|
}
|
|
out, err := provider.Declaration(Rendering{Grants: grants})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
for _, who := range []string{"gitea", "keycloak", "umami"} {
|
|
path := "/var/lib/postgres/grants/anchor." + who + ".secret"
|
|
var found bool
|
|
for _, r := range out {
|
|
if r["path"] == path {
|
|
found = true
|
|
if r["sealed"] != "sealed-for-"+who {
|
|
t.Errorf("%s's grant holds %v", who, r["sealed"])
|
|
}
|
|
}
|
|
}
|
|
if !found {
|
|
t.Errorf("the provider was never told to create a login for %s", who)
|
|
}
|
|
}
|
|
|
|
// And all three appear in the readable manifest, so the provisioner sees three consumers
|
|
// where there are three. Named after one machine, they were one path and the last won.
|
|
for _, r := range out {
|
|
if r["path"] != "/var/lib/postgres/grants/mesh.json" {
|
|
continue
|
|
}
|
|
body := r["content"].(string)
|
|
for _, who := range []string{"gitea", "keycloak", "umami"} {
|
|
if !strings.Contains(body, `"`+who+`"`) {
|
|
t.Errorf("the provider's manifest never mentions %s: %s", who, body)
|
|
}
|
|
}
|
|
}
|
|
}
|