Say whether a plan hands its module in one rule, Plan.Hands, so no reader takes a plan naming provisions alone for a handed module (issue 774)
mesh/delivery merged: source.main moved to novox/mesh-sdk@b9ef1e4fcb68
mesh/delivery-group group fix/plan-hands merged: every member is merged
mesh/check being carried to 17fc9b9b8115 on b9ef1e4fcb68 | from 125bb9177189 | novox/mesh-sdk#144 in group fix/plan-hands

This commit is contained in:
2026-10-30 23:50:09 +01:00
parent 125bb91771
commit 17fc9b9b81
2 changed files with 32 additions and 0 deletions
+10
View File
@@ -66,6 +66,16 @@ type Plan struct {
Provisions []PlanProvision `json:"provisions,omitempty"` Provisions []PlanProvision `json:"provisions,omitempty"`
} }
// Hands is whether the plan hands its module to the provisioner: it names something the node's provisioner client fills
// (a template archive, a file, an own secret or an item), and the node composes none of it. A plan naming provisions alone
// hands nothing (catalogue.Keyed, issue 774): the seat keys what a provider's helper answers of a module not handed, and
// that module's files and own secrets stay its node's composition. The one rule every reader of a plan asks; whether a
// plan exists is never it (on 2026-10-30 the vault read every route-only plan as handed and answered twenty modules'
// own secrets to nobody).
func (p Plan) Hands() bool {
return p.Templates.Source != "" || len(p.Files) > 0 || len(p.Secrets) > 0 || len(p.Items) > 0
}
// PlanItem is one item the module contributes: to which collection, and its template inside the archive. // PlanItem is one item the module contributes: to which collection, and its template inside the archive.
// //
// A named item has its Name and Attributes (design 57 L3, issue 777): the client records them with the item and // A named item has its Name and Attributes (design 57 L3, issue 777): the client records them with the item and
+22
View File
@@ -104,6 +104,28 @@ func TestAModuleNotHandedHasAPlanForWhatAHelperAnswers(t *testing.T) {
!strings.Contains(plan.Provisions[0].As, "notify") || plan.Provisions[0].Contributes["host"] != "notify.example.org" { !strings.Contains(plan.Provisions[0].As, "notify") || plan.Provisions[0].Contributes["host"] != "notify.example.org" {
t.Fatalf("the plan names %+v", plan) t.Fatalf("the plan names %+v", plan)
} }
// And it hands nothing: Plan.Hands, the one rule every reader of a plan asks, by each part a plan can name.
if plan.Hands() {
t.Fatalf("a plan naming the route alone hands its module: %+v", plan)
}
for _, c := range []struct {
name string
plan Plan
hands bool
}{
{"empty", Plan{}, false},
{"provisions alone", Plan{Provisions: plan.Provisions}, false},
{"a digest without an archive", Plan{Templates: PlanTemplates{Digest: "sha256:1"}}, false},
{"templates alone", Plan{Templates: PlanTemplates{Source: "notify.tar", Digest: "sha256:1"}}, true},
{"files alone", Plan{Files: []PlanFile{{ID: "conf", Path: "/etc/notify.conf"}}}, true},
{"secrets alone", Plan{Secrets: []PlanSecret{{Name: "admin"}}}, true},
{"items alone", Plan{Items: []PlanItem{{Collection: "routes", Template: "route.tmpl"}}}, true},
{"secrets and provisions", Plan{Secrets: []PlanSecret{{Name: "admin"}}, Provisions: plan.Provisions}, true},
} {
if got := c.plan.Hands(); got != c.hands {
t.Errorf("%s: Hands() is %v, want %v", c.name, got, c.hands)
}
}
if want := Awaited(consumer, func(p string) []Manifest { if want := Awaited(consumer, func(p string) []Manifest {
return map[string][]Manifest{"route": {proxy}, "postgres-database": {store}}[p] return map[string][]Manifest{"route": {proxy}, "postgres-database": {store}}[p]
}); !reflect.DeepEqual(want, []string{"route"}) { }); !reflect.DeepEqual(want, []string{"route"}) {