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 @@
|
||||
${secret:admin}
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"user": "admin",
|
||||
"password": "${secret:admin}"
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
${secret:influxdb-api}
|
||||
@@ -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}
|
||||
@@ -0,0 +1 @@
|
||||
${secret:oidc-client}
|
||||
@@ -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
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -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`);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user