Feature 57 (config and provisioning): modules declare config and provisions, their files rendered from templates; synced with main (issues of feature 57, 874)
This commit is contained in:
@@ -0,0 +1,46 @@
|
||||
# The nats module's own server settings. Declared by the module, because a port, a TLS path
|
||||
# and a store directory are properties of the container this module raises: they live in its
|
||||
# image and its mounts and change when it does.
|
||||
#
|
||||
# The mesh writes accounts.conf beside this one and nothing else. A controller that wrote the
|
||||
# whole file would have to be kept in step with a Dockerfile it never sees.
|
||||
|
||||
port: 4222
|
||||
# Monitoring on every interface *inside* the container, so the publish below can reach it; the publish
|
||||
# is 127.0.0.1:8222 on the machine, so nothing beyond the machine reaches it, and the module guards 8222
|
||||
# too. Bound to the container's own loopback until 2026-10-04, the published port answered nothing
|
||||
# (connection reset), and the only way in was `docker exec`.
|
||||
http: 0.0.0.0:8222
|
||||
|
||||
# The mesh's own broker certificate — the one every machine already pins by fingerprint and the
|
||||
# controller already trusts (MESH_BROKER_CERTIFICATE). Serving the new bus with it means no
|
||||
# machine's pin changes when it moves, and no second certificate exists to be wrong about.
|
||||
tls {
|
||||
cert_file: "/tls/tls.crt"
|
||||
key_file: "/tls/tls.key"
|
||||
}
|
||||
|
||||
# **No `verify`, deliberately, and it was `verify: true` until a probe ran this image.** That
|
||||
# setting makes the server demand a *client* certificate, and nothing in the mesh presents one: a
|
||||
# host pins this server's exact certificate and authenticates with the password the mesh minted
|
||||
# (novox/hq ADR 0004, design 25 §4), and so does a module's runtime. With it on, every connection
|
||||
# in the mesh is refused at the TLS handshake, before any password is looked at — and the error is
|
||||
# "client didn't provide a certificate", which reads as a client fault.
|
||||
#
|
||||
# TLS is still required: a tls block is what makes it required, and verify only decides whether
|
||||
# client certificates are checked. What is given up is a second factor the mesh has no machinery
|
||||
# to issue or rotate — a certificate per module per node — and what is kept is stronger than a
|
||||
# name check in both directions: an exact pin outward, a per-user password inward.
|
||||
|
||||
jetstream {
|
||||
store_dir: "/data"
|
||||
}
|
||||
|
||||
# Every user of the mesh, composed by the controller and rewritten whenever a module is
|
||||
# assigned, a node enrols or a person's access changes.
|
||||
#
|
||||
# **Relative, and in this same directory, because it has to be.** An absolute include path is
|
||||
# resolved relative to the including file's directory, not from the root: nats-server given
|
||||
# `include /etc/nats/accounts.conf` from /etc/nats-server/nats.conf looks for
|
||||
# /etc/nats-server/etc/nats/accounts.conf and refuses to start. Verified against the server.
|
||||
include accounts.conf
|
||||
@@ -98,7 +98,7 @@
|
||||
"id": "server-conf",
|
||||
"type": "file",
|
||||
"path": "/var/lib/nats-module/conf/nats.conf",
|
||||
"content": "# The nats module's own server settings. Declared by the module, because a port, a TLS path\n# and a store directory are properties of the container this module raises: they live in its\n# image and its mounts and change when it does.\n#\n# The mesh writes accounts.conf beside this one and nothing else. A controller that wrote the\n# whole file would have to be kept in step with a Dockerfile it never sees.\n\nport: 4222\n# Monitoring on every interface *inside* the container, so the publish below can reach it; the publish\n# is 127.0.0.1:8222 on the machine, so nothing beyond the machine reaches it, and the module guards 8222\n# too. Bound to the container's own loopback until 2026-10-04, the published port answered nothing\n# (connection reset), and the only way in was `docker exec`.\nhttp: 0.0.0.0:8222\n\n# The mesh's own broker certificate — the one every machine already pins by fingerprint and the\n# controller already trusts (MESH_BROKER_CERTIFICATE). Serving the new bus with it means no\n# machine's pin changes when it moves, and no second certificate exists to be wrong about.\ntls {\n cert_file: \"/tls/tls.crt\"\n key_file: \"/tls/tls.key\"\n}\n\n# **No `verify`, deliberately, and it was `verify: true` until a probe ran this image.** That\n# setting makes the server demand a *client* certificate, and nothing in the mesh presents one: a\n# host pins this server's exact certificate and authenticates with the password the mesh minted\n# (novox/hq ADR 0004, design 25 §4), and so does a module's runtime. With it on, every connection\n# in the mesh is refused at the TLS handshake, before any password is looked at — and the error is\n# \"client didn't provide a certificate\", which reads as a client fault.\n#\n# TLS is still required: a tls block is what makes it required, and verify only decides whether\n# client certificates are checked. What is given up is a second factor the mesh has no machinery\n# to issue or rotate — a certificate per module per node — and what is kept is stronger than a\n# name check in both directions: an exact pin outward, a per-user password inward.\n\njetstream {\n store_dir: \"/data\"\n}\n\n# Every user of the mesh, composed by the controller and rewritten whenever a module is\n# assigned, a node enrols or a person's access changes.\n#\n# **Relative, and in this same directory, because it has to be.** An absolute include path is\n# resolved relative to the including file's directory, not from the root: nats-server given\n# `include /etc/nats/accounts.conf` from /etc/nats-server/nats.conf looks for\n# /etc/nats-server/etc/nats/accounts.conf and refuses to start. Verified against the server.\ninclude accounts.conf\n",
|
||||
"template": "files/nats.conf",
|
||||
"mode": "0644"
|
||||
},
|
||||
{
|
||||
@@ -156,6 +156,11 @@
|
||||
"MESH_NATS_CONTAINER": "mesh-broker-nats",
|
||||
"MESH_NATS_USERS_FILE": "/etc/nats/accounts.conf"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "files",
|
||||
"kind": "archive",
|
||||
"from": "files"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// The module's directory, where its manifest and its files/ are.
|
||||
const moduleDir = ".."
|
||||
|
||||
// templateText is the text a file resource places on the machine: its template's, in the module's files/
|
||||
// (novox/hq ADR 0349 §2). A resource that names no template, or a template that is not there, fails the test.
|
||||
func templateText(t *testing.T, r map[string]any) string {
|
||||
t.Helper()
|
||||
name, _ := r["template"].(string)
|
||||
if name == "" {
|
||||
t.Fatalf("resource %v names no template", r["id"])
|
||||
}
|
||||
raw, err := os.ReadFile(filepath.Join(moduleDir, filepath.FromSlash(name)))
|
||||
if err != nil {
|
||||
t.Fatalf("resource %v names %s: %v", r["id"], name, err)
|
||||
}
|
||||
return string(raw)
|
||||
}
|
||||
|
||||
// No file resource carries its text inline: each names its template in files/, and the build packs files/
|
||||
// into the one archive the controller reads the templates back from (novox/hq ADR 0349 §2).
|
||||
func TestEveryFileTakesItsTextFromATemplate(t *testing.T) {
|
||||
raw, err := os.ReadFile(filepath.Join(moduleDir, "module.json"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var m struct {
|
||||
Resources []map[string]any `json:"resources"`
|
||||
Build struct {
|
||||
Artifacts []map[string]any `json:"artifacts"`
|
||||
} `json:"build"`
|
||||
}
|
||||
if err := json.Unmarshal(raw, &m); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, r := range m.Resources {
|
||||
if _, inline := r["content"]; inline {
|
||||
t.Errorf("resource %v keeps its text inline: keep it in files/ and name it as the template", r["id"])
|
||||
}
|
||||
if r["type"] == "file" {
|
||||
templateText(t, r)
|
||||
}
|
||||
}
|
||||
packs := 0
|
||||
for _, a := range m.Build.Artifacts {
|
||||
if a["kind"] == "archive" && a["from"] == "files" {
|
||||
packs++
|
||||
if a["name"] != "files" {
|
||||
t.Errorf("the templates archive is named %v, not files", a["name"])
|
||||
}
|
||||
}
|
||||
}
|
||||
if packs != 1 {
|
||||
t.Errorf("the build packs files/ into %d archives, not one", packs)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user