120 lines
6.1 KiB
Go
120 lines
6.1 KiB
Go
package provisions
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
|
)
|
|
|
|
// ADR 0570 clauses 1, 2 and 5 (issue 1954 L6-P2): a provision's key is its consumer's node and module, the provision and
|
|
// its local name, the provider's entry's key; its value round-trips, names its state and why, and refuses any field the
|
|
// layout does not name, so no value rides in it; a plan's key is its node and module.
|
|
func TestAProvisionIsAReferenceUnderItsConsumersKey(t *testing.T) {
|
|
at := time.Date(2026, 10, 29, 22, 0, 0, 0, time.UTC)
|
|
db := Provision{Provision: "postgres-database", Consumer: End{Module: "keycloak", Node: "novox"},
|
|
Provider: End{Module: "postgres", Node: "novox"}, DependsOn: []string{"novox.keycloak.secret.db"}, State: Asked,
|
|
Reference: &Reference{ID: "provision/postgres/postgres-database", Generation: 2}, At: at}
|
|
for _, c := range []struct {
|
|
name string
|
|
edit func(*Provision)
|
|
key string
|
|
refused string
|
|
}{
|
|
{name: "asked, depending on the vault's", key: "novox.keycloak.postgres-database"},
|
|
{name: "under a local name", edit: func(p *Provision) { p.Local = "replica" }, key: "novox.keycloak.postgres-database.replica"},
|
|
{name: "retiring with the operator's warrant", edit: func(p *Provision) {
|
|
p.State, p.Why, p.Ask = Retiring, "keycloak left novox", "a-1"
|
|
p.Warrant = &Warrant{Ask: "a-1", Option: "yes", By: "the operator", At: at}
|
|
}, key: "novox.keycloak.postgres-database"},
|
|
{name: "an unknown state", edit: func(p *Provision) { p.State = "done" }, refused: "one of"},
|
|
{name: "failed without why", edit: func(p *Provision) { p.State = Failed }, refused: "says why"},
|
|
{name: "a warrant on a provided one", edit: func(p *Provision) { p.State, p.Warrant = Provided, &Warrant{Ask: "a"} }, refused: "only a retiring"},
|
|
{name: "no provider", edit: func(p *Provision) { p.Provider = End{} }, refused: "no provider"},
|
|
{name: "a dotted consumer", edit: func(p *Provision) { p.Consumer.Module = "a.b" }, refused: "cannot be part"},
|
|
{name: "a dependency that is no key", edit: func(p *Provision) { p.DependsOn = []string{"x"} }, refused: "is not"},
|
|
} {
|
|
p := db
|
|
if c.edit != nil {
|
|
c.edit(&p)
|
|
}
|
|
raw, err := Encode(p)
|
|
if c.refused != "" {
|
|
if err == nil || !strings.Contains(err.Error(), c.refused) {
|
|
t.Errorf("%s: %v, want refused saying %q", c.name, err, c.refused)
|
|
}
|
|
continue
|
|
}
|
|
back, err := Decode(raw)
|
|
key, kerr := back.Key()
|
|
if err != nil || kerr != nil || key != c.key || back.V != Version || back.Reference.Generation != 2 {
|
|
t.Errorf("%s: read back as %+v under %q, %v %v", c.name, back, key, err, kerr)
|
|
}
|
|
if node, consumer, provision, local, err := ParseKey(key); err != nil || node != "novox" || consumer != "keycloak" ||
|
|
provision != "postgres-database" || local != p.Local {
|
|
t.Errorf("%s: %s parses as %s %s %s %s, %v", c.name, key, node, consumer, provision, local, err)
|
|
}
|
|
}
|
|
// A value never rides in a provision: a field the layout does not name is refused.
|
|
if _, err := Decode([]byte(`{"v":1,"provision":"p","consumer":{"module":"c","node":"n"},"provider":{"module":"p","node":"n"},` +
|
|
`"state":"provided","at":"2026-10-29T22:00:00Z","password":"hunter2"}`)); err == nil {
|
|
t.Error("a provision carrying a password was read")
|
|
}
|
|
plan := Plan{Node: "novox", Module: "restic", Plan: catalogue.Plan{}}
|
|
raw, err := EncodePlan(plan)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if back, err := DecodePlan(raw); err != nil || back.Node != "novox" || back.Module != "restic" {
|
|
t.Errorf("a plan read back as %+v, %v", back, err)
|
|
}
|
|
if k, err := PlanKey("novox", "restic"); k != "novox.restic" || err != nil {
|
|
t.Errorf("a plan's key is %q, %v", k, err)
|
|
}
|
|
// A reference's id is the vault's name as its verb own names it, in each form (the host agent, 2026-10-29).
|
|
for form, want := range map[string]string{FormPair: "pair/novox/postgres-database/novox/keycloak", FormShared: "provision/novox/postgres-database",
|
|
FormIssued: "provision/novox/postgres-database/novox/keycloak", FormNone: ""} {
|
|
if got := VaultName(form, db); got != want {
|
|
t.Errorf("the %s name is %q", form, got)
|
|
}
|
|
}
|
|
if got := VaultName(FormPair, Provision{Provision: "p", Local: "r", Provider: End{Node: "a"}, Consumer: End{Node: "b", Module: "m"}}); got != "pair/a/p/b/m/r" {
|
|
t.Errorf("a local name's pair is %q", got)
|
|
}
|
|
// A provision waits for its credential, the vault's provision `secret-<provision>`, and for what its provider itself
|
|
// requires on its node (clause 3; the key form the lead settled on 2026-10-29).
|
|
keycloak, postgres := End{Module: "keycloak", Node: "novox"}, End{Module: "postgres", Node: "ace"}
|
|
providerPlan := &catalogue.Plan{Provisions: []catalogue.PlanProvision{{Provision: "backup-target", Provider: "ace", ProviderModule: "restic"}}}
|
|
for _, c := range []struct {
|
|
form string
|
|
plan *catalogue.Plan
|
|
want string
|
|
}{
|
|
{FormPair, nil, "novox.keycloak.secret-postgres-database"},
|
|
{FormShared, nil, "ace.postgres.secret-postgres-database"},
|
|
{FormPair, providerPlan, "ace.postgres.backup-target,novox.keycloak.secret-postgres-database"},
|
|
{"", nil, ""},
|
|
// An offer with no credential (the lead, 2026-10-29): nothing minted, nothing to wait for.
|
|
{FormNone, nil, ""},
|
|
} {
|
|
entry := catalogue.PlanProvision{Provision: "postgres-database", Provider: postgres.Node, ProviderModule: postgres.Module,
|
|
CredentialForm: c.form}
|
|
if got := strings.Join(DependsOn(keycloak, entry, c.plan), ","); got != c.want {
|
|
t.Errorf("%q depends on %q, want %q", c.form, got, c.want)
|
|
}
|
|
}
|
|
// The vault's own offer is the value: no secret-secret beside it.
|
|
vaultEntry := catalogue.PlanProvision{Provision: "secret", Provider: "novox", ProviderModule: VaultModule, CredentialForm: FormPair}
|
|
if got := DependsOn(keycloak, vaultEntry, nil); len(got) != 0 {
|
|
t.Errorf("the vault's secret depends on %v", got)
|
|
}
|
|
if _, err := catalogue.ParseManifest([]byte(`{"module":"x","version":"1","provides":[{"name":"secret-db","scope":"mesh"}]}`)); err == nil ||
|
|
!strings.Contains(err.Error(), "secret-") {
|
|
t.Errorf("a provision named secret-*: %v", err)
|
|
}
|
|
if ProvisionsBucket != "mesh-provisioner_provisions" || PlansRead != "mesh-provisioner.plans" {
|
|
t.Errorf("the states are %s and %s", ProvisionsBucket, PlansRead)
|
|
}
|
|
}
|