From 524cc2a3ecedcd36c33cec15f133f3cdd0aa8997 Mon Sep 17 00:00:00 2001 From: jochen Date: Thu, 24 Sep 2026 18:40:30 +0200 Subject: [PATCH] plan: show what a module would open and why MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Every module.json already declares a why for each port under listens, but plan only ever used it to build the firewall's rule set — nothing printed it. An operator deciding whether to assign a module had no way to see what it would open without reading the manifest by hand. plan now prints each assigned module's listens entries — port, protocol, source, and its why — right under the module line, so the same text that feeds the firewall is visible at the point someone is actually deciding whether to open it. --- cmd/mesh-controller/plan.go | 19 ++++++++++++++++ cmd/mesh-controller/plan_test.go | 37 ++++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+) create mode 100644 cmd/mesh-controller/plan_test.go diff --git a/cmd/mesh-controller/plan.go b/cmd/mesh-controller/plan.go index 23a97fa..b6bd92b 100644 --- a/cmd/mesh-controller/plan.go +++ b/cmd/mesh-controller/plan.go @@ -800,6 +800,22 @@ func grantsFor(ctx context.Context, open *stores, node string) ([]catalogue.Gran return out, nil } +// listensLines is what a person is told about what this module would open, and why — the same +// `why` every listens entry already carries for the firewall it also feeds (novox/hq ADR 0007), so +// deciding whether to assign a module can see what it would open before it opens it, not only +// after. A module with nothing to listen on prints nothing extra, same as today. +func listensLines(m catalogue.Manifest) []string { + var out []string + for _, l := range m.Listens { + if l.Why == "" { + out = append(out, fmt.Sprintf(" listens %d/%s from %s", l.Port, l.At(), l.From)) + continue + } + out = append(out, fmt.Sprintf(" listens %d/%s from %s — %s", l.Port, l.At(), l.From, l.Why)) + } + return out +} + func planCommand(ctx context.Context, args []string) error { set := flag.NewFlagSet("plan", flag.ContinueOnError) // Because "one resource" does not tell you whether the settings landed. Being able to read @@ -853,6 +869,9 @@ func planCommand(ctx context.Context, args []string) error { fmt.Printf("%s would run:\n", args[0]) for _, m := range plan.Modules { fmt.Printf(" %-20s %s\n", m.Module, plan.Because[m.Module]) + for _, line := range listensLines(m) { + fmt.Println(line) + } } // What was assigned here and cannot run here. Said with the rest rather than as a refusal: it is // one module on the wrong machine, the others still run, and the remedy is to move this one. diff --git a/cmd/mesh-controller/plan_test.go b/cmd/mesh-controller/plan_test.go new file mode 100644 index 0000000..35a86b8 --- /dev/null +++ b/cmd/mesh-controller/plan_test.go @@ -0,0 +1,37 @@ +package main + +import ( + "strings" + "testing" + + "github.com/novox/mesh-controller/internal/catalogue" +) + +// `plan` tells a person what a module would open and why, from the same `why` every listens +// entry already carries for the firewall (novox/hq ADR 0007) — so deciding whether to assign a +// module does not need reading its manifest first. +func TestListensLinesShowWhatAModuleWouldOpenAndWhy(t *testing.T) { + m := catalogue.Manifest{Module: "minio", Listens: []catalogue.Listening{ + {Port: 9000, From: catalogue.FromMesh, Why: "the S3 endpoint"}, + {Port: 9001, From: catalogue.FromMesh}, + }} + got := listensLines(m) + if len(got) != 2 { + t.Fatalf("two listens entries, got %d: %v", len(got), got) + } + if !strings.Contains(got[0], "9000/tcp") || !strings.Contains(got[0], "the S3 endpoint") { + t.Errorf("the port and its why did not both appear: %q", got[0]) + } + if strings.Contains(got[1], "—") { + t.Errorf("a listens entry with no why should not print a dash: %q", got[1]) + } + if !strings.Contains(got[1], "9001/tcp") { + t.Errorf("the port still appears without a why: %q", got[1]) + } +} + +func TestListensLinesAreEmptyForAModuleWithNothingToListenOn(t *testing.T) { + if got := listensLines(catalogue.Manifest{Module: "board"}); len(got) != 0 { + t.Errorf("a module with no listens should print nothing, got %v", got) + } +} -- 2.54.0