Files
mesh-controller/internal/catalogue/ssh_client_test.go
T
jschoubben bafc31bf85
mesh/merge-gate head bafc31bf8568 merged into trunk b0d489a86c23 — pass: builds build-agent, mesh-controller, route-proxy → ace, g14, novox, shanks; no bus…
mesh/repo-check head bafc31bf8568 merged into trunk b0d489a86c23 — pass: its merge-check.sh passed
mesh/delivery delivered
The ssh-client test offers every key ssh-client's files ask of forge-account, read from them (issue 985)
2026-10-17 12:43:21 +02:00

118 lines
4.6 KiB
Go

package catalogue
import (
"io/fs"
"os"
"path/filepath"
"regexp"
"strings"
"testing"
"github.com/novox/mesh-controller/internal/beside"
)
// boundKeys are every key the files of a module ask of ${bound:forge-account:<key>}, read from the module's own
// tree on the shelf, so the test offers what ssh-client asks however often it asks one more (issue 985).
func boundKeys(t *testing.T, module, provision string) map[string]bool {
t.Helper()
ask := regexp.MustCompile(`\$\{bound:` + regexp.QuoteMeta(provision) + `:([A-Za-z0-9_.-]+)\}`)
keys := map[string]bool{}
root := filepath.Join(beside.Catalogue(t), module)
err := filepath.WalkDir(root, func(path string, d fs.DirEntry, err error) error {
if err != nil || d.IsDir() || strings.HasSuffix(path, "_test.go") {
return err
}
body, err := os.ReadFile(path)
if err != nil {
return err
}
for _, m := range ask.FindAllStringSubmatch(string(body), -1) {
keys[m[1]] = true
}
return nil
})
if err != nil {
t.Fatalf("reading what %s asks of %s: %v", module, provision, err)
}
return keys
}
// The ssh-client module, composed as a machine receives it (novox/hq to-be 29, research 027/03): a
// region at the START of the operator's ~/.ssh/config that includes ~/.ssh/config.d/* — first,
// because ssh takes the first value it finds for each option — and the mesh's Host blocks as the
// whole of ~/.ssh/config.d/00-mesh, every other node with its account. ~/.ssh and ~/.ssh/config.d
// are created 0700 and owned by the account; the operator's own config below the region is kept.
func TestSSHClientOwnsTheOperatorsSSHConfig(t *testing.T) {
shelf := shelf(catalogueManifest(t, "ssh-client"), catalogueManifest(t, "gitea"))
// ssh-client requires forge-account since mesh-catalog #340 (novox/hq issue 955), answered by the forge's
// holder on another node; this test is about the operator's config, so the forge is offered from there
// (issue 985).
// Every key ssh-client's files ask is offered, read from them (boundKeys); a few have a realistic value.
serves := map[string]any{}
for key := range boundKeys(t, "ssh-client", "forge-account") {
serves[key] = "sample-" + key
}
for key, value := range map[string]any{"ssh_host": "marge.internal", "ssh_port": 222,
"known_host": "[marge.internal]:222 ssh-ed25519 AAAA", "ssh_base": "ssh://git@marge.internal:222/",
"https_base": "https://git.example.org/"} {
serves[key] = value
}
forge := World{Offered: map[string][]Provider{
"forge-account": {{Node: "marge", At: "marge.internal", Module: "gitea", Serves: serves}}}}
got, err := Resolve(shelf, []string{"ssh-client"},
Node{Name: "homer", At: "homer.internal", Account: "jo", AccountHome: "/home/jo"}, forge)
if err != nil {
t.Fatal(err)
}
names := map[string]string{"homer.internal": "10.10.0.1", "marge.internal": "10.10.0.2",
"bart.internal": "10.10.0.3"}
out, err := got.Declaration(Rendering{
Names: names, Machines: names,
Accounts: map[string]string{"homer": "jo", "marge": "jo", "bart": "op"},
Suffix: "internal",
})
if err != nil {
t.Fatal(err)
}
by := map[string]map[string]any{}
for _, r := range out {
by[r["id"].(string)] = r
}
for id, path := range map[string]string{
"ssh-client.ssh-dir": "/home/jo/.ssh", "ssh-client.config-d": "/home/jo/.ssh/config.d"} {
dir := by[id]
if dir == nil || dir["type"] != "directory" || dir["path"] != path || dir["owner"] != "jo" || dir["mode"] != "0700" {
t.Fatalf("%s is not created 0700 owned by the account: %v", path, dir)
}
}
cfg := by["ssh-client.config"]
if cfg == nil || cfg["path"] != "/home/jo/.ssh/config" || cfg["owner"] != "jo" ||
cfg["into"] != "block" || cfg["at"] != "start" {
t.Fatalf("the mesh's region is not the first thing in the operator's ~/.ssh/config: %v", cfg)
}
if body := cfg["content"].(string); !strings.Contains(body, "\nInclude ~/.ssh/config.d/*\n") || strings.Contains(body, "\nHost ") {
t.Fatalf("the region does not just include config.d:\n%s", body)
}
var hosts map[string]any
for _, r := range out {
if r["path"] == "/home/jo/.ssh/config.d/00-mesh" {
hosts = r
}
}
if hosts == nil || hosts["type"] != "file" || hosts["into"] != nil {
t.Fatalf("the mesh's hosts are not the whole of ~/.ssh/config.d/00-mesh: %v", hosts)
}
body := hosts["content"].(string)
for _, want := range []string{"Host marge marge.internal", "Host bart bart.internal", "User jo", "User op"} {
if !strings.Contains(body, want) {
t.Fatalf("00-mesh does not say %q — every other node with its account:\n%s", want, body)
}
}
if strings.Contains(body, "Host homer ") {
t.Fatalf("00-mesh names the machine itself, not only its peers:\n%s", body)
}
}