Files
mesh-controller/internal/inventory/provisioning_test.go
T
fix-ctl-pas df5f01bfa1
mesh/merge-gate head df5f01bfa1ee merged into trunk c7cb9f0a2cd3 beside mesh-catalog#337@d2992f81 — pass: builds build-agent, mesh-controller → ace, g14, n…
mesh/repo-check head df5f01bfa1ee merged into trunk c7cb9f0a2cd3 beside mesh-catalog#337@d2992f81 — pass: its merge-check.sh passed
mesh/delivery delivered
mesh/delivery-group group feat/providers-after-switch-on delivered: every member is delivered
Name the credential tool of a software-issued offer in the plan (issue 928)
2026-10-16 19:35:56 +02:00

394 lines
20 KiB
Go

package inventory
import (
"bytes"
"encoding/json"
"reflect"
"strings"
"testing"
"time"
"github.com/novox/mesh-controller/internal/catalogue"
)
// APlan is a whole plan, as design 57 P6 composes one for a module handed to the provisioner.
func aPlan() Provisioning {
return Provisioning{
Templates: TemplatesArchive{Source: "artifacts/dunst/files.tar.gz",
Digest: "sha256:" + strings.Repeat("ab", 32)},
Files: []ProvisionedFile{{ID: "dunstrc", Path: "/home/nox/.config/dunst/dunstrc",
Template: "dunstrc", OwnerAtRuntime: "owned", Takes: "reload-on"}},
Placement: map[string]string{"machine:account-home": "/home/nox", "dir:state": "/var/lib/dunst"},
}
}
// **An assignment with no plan is the bytes it was** (design 57 P5, issue 671): the plan joins the value of a
// module handed to the provisioner alone, so no other assignment's key is written again.
func TestAnAssignmentWithoutAPlanIsMirroredAsBefore(t *testing.T) {
at := time.Date(2026, 10, 14, 20, 0, 0, 0, time.UTC)
values, err := AssignmentValues([]AssignmentMirrored{{Node: "g14", Module: "zsh", Assigned: at}}, nil)
if err != nil {
t.Fatal(err)
}
if got, want := string(values["g14.zsh"]), `{"node":"g14","module":"zsh","assigned":"2026-10-14T20:00:00Z"}`; got != want {
t.Errorf("an assignment without a plan is mirrored as %s, want %s", got, want)
}
}
// **A handed module's assignment carries its plan**, whole, under `provisioning`, as the client reads it.
func TestAHandedModulesAssignmentCarriesItsPlan(t *testing.T) {
at := time.Date(2026, 10, 14, 20, 0, 0, 0, time.UTC)
values, err := AssignmentValues([]AssignmentMirrored{{Node: "g14", Module: "dunst", Assigned: at},
{Node: "g14", Module: "zsh", Assigned: at}}, map[string]Provisioning{"g14.dunst": aPlan()})
if err != nil {
t.Fatal(err)
}
var got AssignmentMirrored
if err := json.Unmarshal(values["g14.dunst"], &got); err != nil {
t.Fatal(err)
}
if got.Provisioning == nil || got.Provisioning.Files[0].Path != "/home/nox/.config/dunst/dunstrc" ||
got.Provisioning.Placement["dir:state"] != "/var/lib/dunst" {
t.Fatalf("the plan is mirrored as %s", values["g14.dunst"])
}
for _, field := range []string{`"templates":{"source":`, `"digest":"sha256:`, `"owner-at-runtime":"owned"`,
`"template":"dunstrc"`, `"takes":"reload-on"`} {
if !strings.Contains(string(values["g14.dunst"]), field) {
t.Errorf("the plan's value lacks %s: %s", field, values["g14.dunst"])
}
}
if strings.Contains(string(values["g14.zsh"]), "provisioning") {
t.Errorf("an assignment with no plan carries one: %s", values["g14.zsh"])
}
}
// **A plan the client would have to guess at is refused, by name**, and so is one for a module not assigned
// there: nothing is written rather than something wrong.
func TestAPlanWithAProblemIsRefusedByName(t *testing.T) {
at := time.Now().UTC()
assigned := []AssignmentMirrored{{Node: "g14", Module: "dunst", Assigned: at}}
cases := map[string]func(*Provisioning){
"templates.digest": func(p *Provisioning) { p.Templates.Digest = "sha256:short" },
"templates.source": func(p *Provisioning) { p.Templates.Source = "" },
"files and items are empty": func(p *Provisioning) { p.Files = nil },
`collection "Shell"`: func(p *Provisioning) { p.Items = []ProvisionedItem{{Collection: "Shell", Template: "x"}} },
"collection a is contributed to twice": func(p *Provisioning) {
p.Items = []ProvisionedItem{{Collection: "a", Template: "x"}, {Collection: "a", Template: "y"}}
},
`items[0]: template "/x"`: func(p *Provisioning) { p.Items = []ProvisionedItem{{Collection: "a", Template: "/x"}} },
"collections[1]: a is named twice": func(p *Provisioning) {
p.Collections = []ProvisionedCollection{{Name: "a"}, {Name: "a"}}
},
"the comment of a holds a line break": func(p *Provisioning) {
p.Collections = []ProvisionedCollection{{Name: "a", Comment: "#\n"}}
},
"not a clean": func(p *Provisioning) { p.Files[0].Path = "relative/dunstrc" },
"inside the templates": func(p *Provisioning) { p.Files[0].Template = "../dunstrc" },
"owner-at-runtime": func(p *Provisioning) { p.Files[0].OwnerAtRuntime = "mine" },
"takes": func(p *Provisioning) { p.Files[0].Takes = "now" },
`placement "secret`: func(p *Provisioning) { p.Placement["secret:token"] = "x" },
`placement "dir:"`: func(p *Provisioning) { p.Placement["dir:"] = "x" },
"is named twice": func(p *Provisioning) { p.Files = append(p.Files, p.Files[0]) },
"not a resource id": func(p *Provisioning) { p.Files[0].ID = "Dunst RC" },
`secret "Token"`: func(p *Provisioning) { p.Secrets = []ProvisionedSecret{{Name: "Token"}} },
"secret token is named twice": func(p *Provisioning) {
p.Secrets = []ProvisionedSecret{{Name: "token"}, {Name: "token"}}
},
`secret token: path "rel/token"`: func(p *Provisioning) {
p.Secrets = []ProvisionedSecret{{Name: "token", Path: "rel/token"}}
},
"secret token: path /home/nox/.config/dunst/dunstrc is named twice": func(p *Provisioning) {
p.Secrets = []ProvisionedSecret{{Name: "token", Path: "/home/nox/.config/dunst/dunstrc"}}
},
}
for want, spoil := range cases {
p := aPlan()
spoil(&p)
_, err := AssignmentValues(assigned, map[string]Provisioning{"g14.dunst": p})
if err == nil || !strings.Contains(err.Error(), want) || !strings.Contains(err.Error(), "g14.dunst") {
t.Errorf("%s: refused with %v", want, err)
}
}
if _, err := AssignmentValues(assigned, map[string]Provisioning{"shanks.dunst": aPlan()}); err == nil ||
!strings.Contains(err.Error(), "shanks.dunst: a plan for a module not assigned there") {
t.Errorf("a plan for a module not assigned there: %v", err)
}
}
// **A plan names the module's own secrets its declaration names there** (design 57 V4, issue 647): each by name, and
// a secret kept in a file of its own with that file's path; the vault hands a node's client a secret only when the
// plan names it. Names, never a value.
func TestAPlanNamesTheModulesSecretsAndNoValue(t *testing.T) {
p := aPlan()
p.Secrets = []ProvisionedSecret{{Name: "token"}, {Name: "db-password", Path: "/var/lib/dunst/db"}}
values, err := AssignmentValues([]AssignmentMirrored{{Node: "g14", Module: "dunst", Assigned: time.Now().UTC()}},
map[string]Provisioning{"g14.dunst": p})
if err != nil {
t.Fatal(err)
}
if want := `"secrets":[{"name":"token"},{"name":"db-password","path":"/var/lib/dunst/db"}]`; !strings.Contains(
string(values["g14.dunst"]), want) {
t.Errorf("the plan's value is %s, want it to carry %s", values["g14.dunst"], want)
}
plain := aPlan()
values, err = AssignmentValues([]AssignmentMirrored{{Node: "g14", Module: "dunst", Assigned: time.Now().UTC()}},
map[string]Provisioning{"g14.dunst": plain})
if err != nil {
t.Fatal(err)
}
if strings.Contains(string(values["g14.dunst"]), "secrets") {
t.Errorf("a plan naming no secret says secrets: %s", values["g14.dunst"])
}
}
// **A contributor's plan may fill no file** (design 57 L1, issue 684): its item is its part on the node; and a
// collector's names the collections its files read, the old namespaces' by their own names.
func TestAPlanMayContributeAnItemAndNameItsCollections(t *testing.T) {
p := aPlan()
p.Files = nil
p.Items = []ProvisionedItem{{Collection: "shell:zsh:normal", Template: "zsh"}}
p.Collections = []ProvisionedCollection{{Name: "widgets", Comment: "#"}, {Name: "contribution:node-hotkeys:trigger"}}
values, err := AssignmentValues([]AssignmentMirrored{{Node: "g14", Module: "prompt", Assigned: time.Now()}},
map[string]Provisioning{"g14.prompt": p})
if err != nil {
t.Fatal(err)
}
var back AssignmentMirrored
if err := json.Unmarshal(values["g14.prompt"], &back); err != nil || back.Provisioning == nil ||
len(back.Provisioning.Items) != 1 || len(back.Provisioning.Collections) != 2 {
t.Fatalf("mirrored %s (%v)", values["g14.prompt"], err)
}
}
// **A plan names the credentials of what the module requires that the vault keeps for it alone** (issue 757): each
// by the name its files call it, its provision, its provider and its file; never a value. Read as strictly as the
// rest of the plan.
func TestAPlanNamesTheCredentialsIssuedForTheModuleAndNoValue(t *testing.T) {
assigned := []AssignmentMirrored{{Node: "g14", Module: "dunst", Assigned: time.Now().UTC()}}
good := ProvisionedCredential{Name: "gitea-read", Provision: "gitea-read", Provider: "novox",
ProviderModule: "gitea", Path: "/var/lib/dunst/gitea-read.secret"}
p := aPlan()
p.Provisions = []ProvisionedCredential{good}
values, err := AssignmentValues(assigned, map[string]Provisioning{"g14.dunst": p})
if err != nil {
t.Fatal(err)
}
if want := `"provisions":[{"name":"gitea-read","provision":"gitea-read","provider":"novox",` +
`"provider-module":"gitea","path":"/var/lib/dunst/gitea-read.secret"}]`; !strings.Contains(string(values["g14.dunst"]), want) {
t.Errorf("the plan's value is %s, want it to carry %s", values["g14.dunst"], want)
}
plain, err := AssignmentValues(assigned, map[string]Provisioning{"g14.dunst": aPlan()})
if err != nil || strings.Contains(string(plain["g14.dunst"]), "provisions") {
t.Errorf("a plan naming no credential says provisions: %s (%v)", plain["g14.dunst"], err)
}
for want, spoil := range map[string]func(*ProvisionedCredential){
`provision credential "Gitea Read"`: func(c *ProvisionedCredential) { c.Name = "Gitea Read" },
`provision ""`: func(c *ProvisionedCredential) { c.Provision = "" },
`provider "../ace"`: func(c *ProvisionedCredential) { c.Provider = "../ace" },
`path "rel"`: func(c *ProvisionedCredential) { c.Path = "rel" },
"is named twice": func(c *ProvisionedCredential) { c.Path = "/home/nox/.config/dunst/dunstrc" },
} {
c := good
spoil(&c)
bad := aPlan()
bad.Provisions = []ProvisionedCredential{c}
if _, err := AssignmentValues(assigned, map[string]Provisioning{"g14.dunst": bad}); err == nil ||
!strings.Contains(err.Error(), want) {
t.Errorf("%s: refused with %v", want, err)
}
}
twice := aPlan()
twice.Secrets = []ProvisionedSecret{{Name: "gitea-read"}}
twice.Provisions = []ProvisionedCredential{good}
if _, err := AssignmentValues(assigned, map[string]Provisioning{"g14.dunst": twice}); err == nil ||
!strings.Contains(err.Error(), "secret gitea-read is named twice") {
t.Errorf("a credential named as an own secret too: %v", err)
}
}
// **A module only pulled in by another has a value of its own** (issue 765): where the machine runs a handed module
// that nothing assigned there, its plan is mirrored under `<node>.<module>` like an assigned one's, saying which
// module requires it and no time it was assigned. A plan for a module neither assigned nor required there is still
// refused, and a module assigned there is mirrored as an assignment whatever else requires it.
func TestAModulePulledInHasAValueOfItsOwnWithItsPlan(t *testing.T) {
at := time.Date(2026, 10, 14, 20, 0, 0, 0, time.UTC)
assigned := []AssignmentMirrored{{Node: "g14", Module: "zsh", Assigned: at}}
pulled := aPlan()
pulled.RequiredBy = "desk"
both := aPlan()
both.RequiredBy = "desk"
values, err := AssignmentValues(assigned, map[string]Provisioning{"g14.dunst": pulled, "g14.zsh": both})
if err != nil {
t.Fatal(err)
}
if got := string(values["g14.dunst"]); !strings.HasPrefix(got, `{"node":"g14","module":"dunst","required-by":"desk",`+
`"provisioning":{"templates":{"source":"artifacts/dunst/files.tar.gz",`) || strings.Contains(got, "assigned") {
t.Errorf("a pulled-in module is mirrored as %s", got)
}
var got AssignmentMirrored
if err := json.Unmarshal(values["g14.dunst"], &got); err != nil || got.Provisioning == nil ||
got.Provisioning.Files[0].Path != "/home/nox/.config/dunst/dunstrc" {
t.Fatalf("the pulled-in module's plan is mirrored as %s (%v)", values["g14.dunst"], err)
}
if zsh := string(values["g14.zsh"]); !strings.HasPrefix(zsh, `{"node":"g14","module":"zsh","assigned":"2026-10-14T20:00:00Z",`+
`"provisioning":`) || strings.Contains(zsh, "required-by") {
t.Errorf("an assigned module also required is mirrored as %s", zsh)
}
if len(values) != 2 {
t.Errorf("the mirror holds %d values, want 2", len(values))
}
spoilt := pulled
spoilt.Templates.Digest = "sha256:short"
if _, err := AssignmentValues(assigned, map[string]Provisioning{"g14.dunst": spoilt}); err == nil ||
!strings.Contains(err.Error(), "g14.dunst: templates.digest") {
t.Errorf("a pulled-in module's spoilt plan: %v", err)
}
if _, err := AssignmentValues(assigned, map[string]Provisioning{"shanks.dunst": aPlan()}); err == nil ||
!strings.Contains(err.Error(), "shanks.dunst: a plan for a module not assigned there") {
t.Errorf("a plan for a module neither assigned nor required there: %v", err)
}
}
// **A plan names the provider serving each provision its module requires** (design 57 V5a, issue 760): machine,
// module and local name, so the vault reading the mirror sees a bind and an unbind; an entry with no credential
// to fill has no name and no file. Read as strictly as the rest of the plan.
func TestAPlanNamesTheProviderOfEachProvision(t *testing.T) {
assigned := []AssignmentMirrored{{Node: "g14", Module: "dunst", Assigned: time.Now().UTC()}}
p := aPlan()
p.Provisions = []ProvisionedCredential{
{Provision: "postgres-database", Provider: "novox", ProviderModule: "postgres"},
{Provision: "s3-bucket", Local: "hot", Provider: "ace", ProviderModule: "minio"}}
values, err := AssignmentValues(assigned, map[string]Provisioning{"g14.dunst": p})
if err != nil {
t.Fatal(err)
}
if want := `"provisions":[{"provision":"postgres-database","provider":"novox","provider-module":"postgres"},` +
`{"provision":"s3-bucket","local":"hot","provider":"ace","provider-module":"minio"}]`; !strings.Contains(
string(values["g14.dunst"]), want) {
t.Errorf("the plan's value is %s, want it to carry %s", values["g14.dunst"], want)
}
for want, provisions := range map[string][]ProvisionedCredential{
`postgres-database: provider-module ""`: {{Provision: "postgres-database", Provider: "novox"}},
`postgres-database: provider ""`: {{Provision: "postgres-database", ProviderModule: "postgres"}},
"s3-bucket (hot): its provider is named twice": {
{Provision: "s3-bucket", Local: "hot", Provider: "ace", ProviderModule: "minio"},
{Provision: "s3-bucket", Local: "hot", Provider: "novox", ProviderModule: "minio"}},
`postgres-database: path "/var/lib/dunst/db" names a file and no credential`: {{Provision: "postgres-database",
Provider: "novox", ProviderModule: "postgres", Path: "/var/lib/dunst/db"}},
} {
bad := aPlan()
bad.Provisions = provisions
if _, err := AssignmentValues(assigned, map[string]Provisioning{"g14.dunst": bad}); err == nil ||
!strings.Contains(err.Error(), want) {
t.Errorf("%s: refused with %v", want, err)
}
}
}
// The plan a composition makes (catalogue.Plan) is the plan the mirror carries, its provisions whole: kept with the
// send as the one and read back as the other, nothing is dropped and nothing is unknown.
func TestAComposedPlansProvisionsAreTheMirroredPlans(t *testing.T) {
composed := catalogue.Plan{Templates: catalogue.PlanTemplates{Source: "s", Digest: "d"},
Provisions: []catalogue.PlanProvision{{Name: "db-hot", Provision: "postgres-database", Local: "hot",
Provider: "novox", ProviderModule: "postgres", CredentialForm: catalogue.CredentialFormPair,
Path: "/var/lib/x/hot"}}}
raw, err := json.Marshal(composed)
if err != nil {
t.Fatal(err)
}
var mirrored Provisioning
dec := json.NewDecoder(bytes.NewReader(raw))
dec.DisallowUnknownFields()
if err := dec.Decode(&mirrored); err != nil {
t.Fatal(err)
}
want := []ProvisionedCredential{{Name: "db-hot", Provision: "postgres-database", Local: "hot", Provider: "novox",
ProviderModule: "postgres", CredentialForm: "pair", Path: "/var/lib/x/hot"}}
if !reflect.DeepEqual(mirrored.Provisions, want) {
t.Fatalf("the mirrored plan's provisions are %+v, want %+v", mirrored.Provisions, want)
}
}
// **A plan names the credential form of each provider's offer** (issue 768), so the vault makes a first value in
// that form for a provider it holds no row of: carried as written, and refused when it is not a form.
func TestAPlanNamesTheCredentialFormOfEachProvidersOffer(t *testing.T) {
assigned := []AssignmentMirrored{{Node: "g14", Module: "dunst", Assigned: time.Now().UTC()}}
p := aPlan()
p.Provisions = []ProvisionedCredential{
{Provision: "postgres-database", Provider: "novox", ProviderModule: "postgres", CredentialForm: "pair"},
{Provision: "sonarr-api", Provider: "ace", ProviderModule: "sonarr", CredentialForm: "shared"}}
values, err := AssignmentValues(assigned, map[string]Provisioning{"g14.dunst": p})
if err != nil {
t.Fatal(err)
}
if want := `{"provision":"postgres-database","provider":"novox","provider-module":"postgres",` +
`"credential-form":"pair"},{"provision":"sonarr-api","provider":"ace","provider-module":"sonarr",` +
`"credential-form":"shared"}`; !strings.Contains(string(values["g14.dunst"]), want) {
t.Errorf("the plan's value is %s, want it to carry %s", values["g14.dunst"], want)
}
bad := aPlan()
bad.Provisions = []ProvisionedCredential{{Provision: "postgres-database", Provider: "novox",
ProviderModule: "postgres", CredentialForm: "one-for-all"}}
if _, err := AssignmentValues(assigned, map[string]Provisioning{"g14.dunst": bad}); err == nil ||
!strings.Contains(err.Error(), `credential-form "one-for-all"`) {
t.Errorf("a credential form that is not one was refused with %v", err)
}
}
func TestAPlanMayHoldSeveralNamedItemsOfOneCollectionAndRefusesABadOne(t *testing.T) {
p := aPlan()
p.Items = []ProvisionedItem{
{Collection: "blocks", Name: "a", Attributes: map[string]string{"bar": "top"}, Template: "a"},
{Collection: "blocks", Name: "b", Template: "b"}}
if problems := p.Problems(); len(problems) != 0 {
t.Fatalf("a plan with two named items is refused: %v", problems)
}
p.Items = append(p.Items, ProvisionedItem{Collection: "blocks", Name: "b", Template: "c"},
ProvisionedItem{Collection: "blocks", Name: "c", Attributes: map[string]string{"Bar": "x y"}, Template: "d"})
got := strings.Join(p.Problems(), "; ")
for _, want := range []string{"item blocks/b is contributed to twice", "attribute Bar"} {
if !strings.Contains(got, want) {
t.Errorf("the problems %q lack %q", got, want)
}
}
}
// **A kept plan's provisions keep every field the composition sets** (issue 923): the names, the place, the machine
// and the identity the provider's helper writes into the consumer's entry, and what the manifest contributes, are
// read back as they were sent, so the assignments mirror carries them to the provider.
func TestAKeptPlansProvisionsKeepContributesAtFromAsAndNames(t *testing.T) {
sent := catalogue.PlanProvision{Provision: "route", Provider: "novox", ProviderModule: "route-proxy",
Contributes: map[string]any{"database": "gitea"}, At: "10.0.0.1", From: "novox", As: "gitea@novox",
Names: map[string]string{"name-web": "git.example.org", "internal-name": "gitea.mesh"}}
raw, err := json.Marshal(map[string]any{"gitea": map[string]any{
"provisions": []catalogue.PlanProvision{sent}, "required-by": "forge"}})
if err != nil {
t.Fatal(err)
}
var kept map[string]keptPlan
if err := json.Unmarshal(raw, &kept); err != nil {
t.Fatal(err)
}
got := kept["gitea"].Provisions
if len(got) != 1 || !reflect.DeepEqual(got[0], sent) {
t.Fatalf("the kept plan's provisions are %+v, want %+v", got, sent)
}
}
// A kept plan's provision keeps the tool the vault asks for a software-issued credential (issue 928).
func TestAKeptPlansProvisionKeepsItsCredentialTool(t *testing.T) {
sent := catalogue.PlanProvision{Provision: "gitea-read", Provider: "novox", ProviderModule: "gitea",
CredentialForm: catalogue.CredentialFormIssued, CredentialTool: "gitea_agent_token"}
raw, err := json.Marshal(map[string]any{"claude-code": map[string]any{"provisions": []catalogue.PlanProvision{sent}}})
if err != nil {
t.Fatal(err)
}
var kept map[string]keptPlan
if err := json.Unmarshal(raw, &kept); err != nil {
t.Fatal(err)
}
if got := kept["claude-code"].Provisions; len(got) != 1 || got[0].CredentialTool != "gitea_agent_token" {
t.Fatalf("the kept plan's provisions are %+v, want the tool kept", got)
}
}