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:
2026-10-16 07:37:43 +00:00
parent 1a2c917abf
commit 9652b2d5ce
771 changed files with 52063 additions and 1774 deletions
+1
View File
@@ -0,0 +1 @@
${secret:admin}
+4
View File
@@ -0,0 +1,4 @@
{
"user": "admin",
"password": "${secret:admin}"
}
+1
View File
@@ -0,0 +1 @@
${secret:influxdb-api}
+19
View File
@@ -0,0 +1,19 @@
apiVersion: 1
# Written by the mesh from grafana's influxdb-api binding, only while it is bound (novox/hq ADR 0330):
# without influxdb, grafana runs with no mesh data source. grafana reads it at start. Its own name and
# uid, so a data source somebody made in the UI is never overwritten, and read-only in the UI because
# the mesh resets it. The password is read from the file the mesh delivers, never written here.
datasources:
- name: InfluxDB (mesh)
uid: mesh-influxdb-api
type: influxdb
access: proxy
url: ${bound:influxdb-api:scheme}://${bound:influxdb-api:at}:${bound:influxdb-api:port}
user: ${bound:influxdb-api:as}
isDefault: false
editable: false
jsonData:
dbName: ${bound:influxdb-api:bucket}
httpMode: POST
secureJsonData:
password: $__file{/run/secrets/influxdb/influxdb-api}
+1
View File
@@ -0,0 +1 @@
${secret:oidc-client}
+13
View File
@@ -0,0 +1,13 @@
GF_SERVER_ROOT_URL=https://${bound:route:name}
GF_AUTH_GENERIC_OAUTH_ENABLED=true
GF_AUTH_GENERIC_OAUTH_NAME=Keycloak
GF_AUTH_GENERIC_OAUTH_CLIENT_ID=${bound:oidc-client:as}
GF_AUTH_GENERIC_OAUTH_CLIENT_SECRET__FILE=/run/secrets/oidc-client
GF_AUTH_GENERIC_OAUTH_SCOPES=openid email profile roles
GF_AUTH_GENERIC_OAUTH_AUTH_URL=${bound:oidc-client:issuer}${bound:oidc-client:authorization-path}
GF_AUTH_GENERIC_OAUTH_TOKEN_URL=${bound:oidc-client:issuer}${bound:oidc-client:token-path}
GF_AUTH_GENERIC_OAUTH_API_URL=${bound:oidc-client:issuer}${bound:oidc-client:userinfo-path}
GF_AUTH_GENERIC_OAUTH_ROLE_ATTRIBUTE_PATH=contains(roles[*], 'admin') && 'Admin' || contains(realm_access.roles[*], 'admin') && 'Admin' || 'Viewer'
GF_AUTH_GENERIC_OAUTH_USE_PKCE=true
GF_AUTH_GENERIC_OAUTH_ALLOW_SIGN_UP=true
GF_AUTH_GENERIC_OAUTH_ALLOW_ASSIGN_GRAFANA_ADMIN=true
+26 -6
View File
@@ -29,6 +29,20 @@
}
]
},
"config": {
"user": {
"type": "string",
"description": "The Grafana account the module's tools sign in as with the vault's password; the file's default is admin.",
"trusted": true,
"default": "admin"
},
"url": {
"type": "string",
"description": "The address the module's tools reach Grafana at, as a URL; unset, the code falls back to its environment, else this machine's Grafana on its port.",
"trusted": true,
"default": ""
}
},
"resources": [
{
"id": "mesh-state",
@@ -54,7 +68,7 @@
"path": "${dir:state}/admin.secret",
"mode": "0400",
"owner": "472:472",
"content": "${secret:admin}"
"template": "files/admin.secret"
},
{
"id": "oidc-secret",
@@ -62,14 +76,14 @@
"path": "${dir:state}/oidc-client.secret",
"mode": "0400",
"owner": "472:472",
"content": "${secret:oidc-client}"
"template": "files/oidc-client.secret"
},
{
"id": "oidc-env",
"type": "file",
"path": "${dir:state}/oidc.env",
"mode": "0644",
"content": "GF_SERVER_ROOT_URL=https://${bound:route:name}\nGF_AUTH_GENERIC_OAUTH_ENABLED=true\nGF_AUTH_GENERIC_OAUTH_NAME=Keycloak\nGF_AUTH_GENERIC_OAUTH_CLIENT_ID=${bound:oidc-client:as}\nGF_AUTH_GENERIC_OAUTH_CLIENT_SECRET__FILE=/run/secrets/oidc-client\nGF_AUTH_GENERIC_OAUTH_SCOPES=openid email profile roles\nGF_AUTH_GENERIC_OAUTH_AUTH_URL=${bound:oidc-client:issuer}${bound:oidc-client:authorization-path}\nGF_AUTH_GENERIC_OAUTH_TOKEN_URL=${bound:oidc-client:issuer}${bound:oidc-client:token-path}\nGF_AUTH_GENERIC_OAUTH_API_URL=${bound:oidc-client:issuer}${bound:oidc-client:userinfo-path}\nGF_AUTH_GENERIC_OAUTH_ROLE_ATTRIBUTE_PATH=contains(roles[*], 'admin') && 'Admin' || contains(realm_access.roles[*], 'admin') && 'Admin' || 'Viewer'\nGF_AUTH_GENERIC_OAUTH_USE_PKCE=true\nGF_AUTH_GENERIC_OAUTH_ALLOW_SIGN_UP=true\nGF_AUTH_GENERIC_OAUTH_ALLOW_ASSIGN_GRAFANA_ADMIN=true\n"
"template": "files/oidc.env"
},
{
"id": "influxdb-datasources",
@@ -91,7 +105,7 @@
"path": "${dir:influxdb-credential}/influxdb-api",
"mode": "0400",
"owner": "472:472",
"content": "${secret:influxdb-api}",
"template": "files/influxdb-api",
"only-when-bound": "influxdb-api"
},
{
@@ -99,7 +113,7 @@
"type": "file",
"path": "${dir:influxdb-datasources}/mesh-influxdb.yaml",
"mode": "0644",
"content": "apiVersion: 1\n# Written by the mesh from grafana's influxdb-api binding, only while it is bound (novox/hq ADR 0330):\n# without influxdb, grafana runs with no mesh data source. grafana reads it at start. Its own name and\n# uid, so a data source somebody made in the UI is never overwritten, and read-only in the UI because\n# the mesh resets it. The password is read from the file the mesh delivers, never written here.\ndatasources:\n - name: InfluxDB (mesh)\n uid: mesh-influxdb-api\n type: influxdb\n access: proxy\n url: ${bound:influxdb-api:scheme}://${bound:influxdb-api:at}:${bound:influxdb-api:port}\n user: ${bound:influxdb-api:as}\n isDefault: false\n editable: false\n jsonData:\n dbName: ${bound:influxdb-api:bucket}\n httpMode: POST\n secureJsonData:\n password: $__file{/run/secrets/influxdb/influxdb-api}\n",
"template": "files/mesh-influxdb.yaml",
"only-when-bound": "influxdb-api"
},
{
@@ -140,8 +154,9 @@
"type": "file",
"path": "${dir:mesh-state}/config.json",
"mode": "0600",
"content": "{\n \"user\": \"admin\",\n \"password\": \"${secret:admin}\"\n}\n",
"template": "files/config.json",
"merge": "json",
"takes-config": ["user", "url"],
"protected": ["password", "token"]
}
],
@@ -194,6 +209,11 @@
"MESH_GRAFANA_URL": "http://127.0.0.1:${port:3000}",
"MESH_GRAFANA_CONFIG_FILE": "${dir:mesh-state}/config.json"
}
},
{
"name": "files",
"kind": "archive",
"from": "files"
}
]
}
+17 -2
View File
@@ -7,11 +7,26 @@ import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
type Requirement = string | { name: string; optional?: boolean };
type Resource = { id: string; type: string; path?: string; content?: string; volumes?: string[]; "only-when-bound"?: string };
type Resource = { id: string; type: string; path?: string; content?: string; template?: string; volumes?: string[]; "only-when-bound"?: string };
const manifest = JSON.parse(readFileSync(new URL("../module.json", import.meta.url), "utf8")) as {
requires: Requirement[];
resources: Resource[];
build: { artifacts: { name: string; kind: string; from?: string }[] };
};
// A file's text is its template's, in the module's files/ (novox/hq ADR 0349 §2).
const text = (r: Resource): string => {
assert.ok(r.template, `${r.id} names no template`);
return readFileSync(new URL(`../${r.template}`, import.meta.url), "utf8");
};
test("no file carries its text inline: each names its template, and the build packs files/ into one archive", () => {
for (const r of manifest.resources) {
assert.equal(r.content, undefined, `${r.id} keeps its text inline`);
if (r.type === "file") text(r);
}
const packs = manifest.build.artifacts.filter((a) => a.kind === "archive" && a.from === "files");
assert.deepEqual(packs.map((a) => a.name), ["files"]);
});
const optional = (name: string) =>
manifest.requires.some((r) => typeof r !== "string" && r.name === name && r.optional === true);
@@ -24,7 +39,7 @@ test("influxdb-api is optional, and route and oidc-client stay hard", () => {
test("every file naming influxdb-api is delivered only while it is bound", () => {
for (const r of manifest.resources) {
if (r.type === "file" && /\$\{(bound:influxdb-api:|secret:influxdb-api\})/.test(r.content ?? "")) {
if (r.type === "file" && /\$\{(bound:influxdb-api:|secret:influxdb-api\})/.test(text(r))) {
assert.equal(r["only-when-bound"], "influxdb-api", `${r.id} names influxdb-api and is delivered while it is absent`);
}
}