Merge pull request 'Mark grafana's and home-assistant's soft requirements optional (hq issue 166, ADR 0330)' (#259) from fix/166-optional-requirements into main
This commit was merged in pull request #259.
This commit is contained in:
@@ -71,20 +71,36 @@
|
||||
"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"
|
||||
},
|
||||
{
|
||||
"id": "influxdb-datasources",
|
||||
"type": "directory",
|
||||
"path": "${dir:state}/datasources",
|
||||
"mode": "0755",
|
||||
"owner": "472:472"
|
||||
},
|
||||
{
|
||||
"id": "influxdb-credential",
|
||||
"type": "directory",
|
||||
"path": "${dir:state}/influxdb",
|
||||
"mode": "0700",
|
||||
"owner": "472:472"
|
||||
},
|
||||
{
|
||||
"id": "influxdb-secret",
|
||||
"type": "file",
|
||||
"path": "${dir:state}/influxdb-api.secret",
|
||||
"path": "${dir:influxdb-credential}/influxdb-api",
|
||||
"mode": "0400",
|
||||
"owner": "472:472",
|
||||
"content": "${secret:influxdb-api}"
|
||||
"content": "${secret:influxdb-api}",
|
||||
"only-when-bound": "influxdb-api"
|
||||
},
|
||||
{
|
||||
"id": "influxdb-datasource",
|
||||
"type": "file",
|
||||
"path": "${dir:state}/datasource-influxdb.yaml",
|
||||
"path": "${dir:influxdb-datasources}/mesh-influxdb.yaml",
|
||||
"mode": "0644",
|
||||
"content": "apiVersion: 1\n# Written by the mesh from grafana's influxdb-api binding; 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-api}\n"
|
||||
"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",
|
||||
"only-when-bound": "influxdb-api"
|
||||
},
|
||||
{
|
||||
"id": "server",
|
||||
@@ -103,8 +119,8 @@
|
||||
"${dir:data}:/var/lib/grafana",
|
||||
"${dir:state}/admin.secret:/run/secrets/admin:ro",
|
||||
"${dir:state}/oidc-client.secret:/run/secrets/oidc-client:ro",
|
||||
"${dir:state}/influxdb-api.secret:/run/secrets/influxdb-api:ro",
|
||||
"${dir:state}/datasource-influxdb.yaml:/etc/grafana/provisioning/datasources/mesh-influxdb.yaml:ro"
|
||||
"${dir:influxdb-credential}:/run/secrets/influxdb:ro",
|
||||
"${dir:influxdb-datasources}:/etc/grafana/provisioning/datasources:ro"
|
||||
],
|
||||
"env": {
|
||||
"GF_SECURITY_ADMIN_PASSWORD__FILE": "/run/secrets/admin"
|
||||
@@ -131,7 +147,10 @@
|
||||
"requires": [
|
||||
"route",
|
||||
"oidc-client",
|
||||
"influxdb-api"
|
||||
{
|
||||
"name": "influxdb-api",
|
||||
"optional": true
|
||||
}
|
||||
],
|
||||
"contributes": {
|
||||
"route": {
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
// grafana runs without a data source, so its influxdb-api is optional (novox/hq ADR 0330, issue 166):
|
||||
// a mesh without influxdb can assign grafana, and influxdb is never installed for it. While absent, the
|
||||
// data source and its credential are not delivered, and grafana mounts their directories empty.
|
||||
|
||||
import { test } from "node:test";
|
||||
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 };
|
||||
const manifest = JSON.parse(readFileSync(new URL("../module.json", import.meta.url), "utf8")) as {
|
||||
requires: Requirement[];
|
||||
resources: Resource[];
|
||||
};
|
||||
|
||||
const optional = (name: string) =>
|
||||
manifest.requires.some((r) => typeof r !== "string" && r.name === name && r.optional === true);
|
||||
|
||||
test("influxdb-api is optional, and route and oidc-client stay hard", () => {
|
||||
assert.ok(optional("influxdb-api"));
|
||||
assert.ok(manifest.requires.includes("route"));
|
||||
assert.ok(manifest.requires.includes("oidc-client"));
|
||||
});
|
||||
|
||||
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 ?? "")) {
|
||||
assert.equal(r["only-when-bound"], "influxdb-api", `${r.id} names influxdb-api and is delivered while it is absent`);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
test("the container mounts the data source's directories, never a file that may be absent", () => {
|
||||
// A bind mount of a missing file makes a directory in its place, which the file could then never be written over.
|
||||
const server = manifest.resources.find((r) => r.id === "server");
|
||||
for (const v of server?.volumes ?? []) {
|
||||
assert.ok(!/influxdb.*\.(yaml|secret)|influxdb-api:/.test(v.split(":")[0]), `${v} mounts an only-when-bound file`);
|
||||
}
|
||||
});
|
||||
@@ -123,11 +123,23 @@
|
||||
}
|
||||
],
|
||||
"requires": [
|
||||
"lidarr-api",
|
||||
"mqtt-topic",
|
||||
"radarr-api",
|
||||
{
|
||||
"name": "lidarr-api",
|
||||
"optional": true
|
||||
},
|
||||
{
|
||||
"name": "mqtt-topic",
|
||||
"optional": true
|
||||
},
|
||||
{
|
||||
"name": "radarr-api",
|
||||
"optional": true
|
||||
},
|
||||
"route",
|
||||
"sonarr-api"
|
||||
{
|
||||
"name": "sonarr-api",
|
||||
"optional": true
|
||||
}
|
||||
],
|
||||
"contributes": {
|
||||
"mqtt-topic": {
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
// Home Assistant runs without the Servarr apps and without a broker, so those requirements are optional
|
||||
// (novox/hq ADR 0330, issue 166): a mesh without lidarr or an MQTT broker can assign home-assistant, and
|
||||
// nothing is installed for it. Its provisions step already reads a binding that is not there as absent.
|
||||
|
||||
import { test } from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import { readFileSync } from "node:fs";
|
||||
|
||||
type Requirement = string | { name: string; optional?: boolean };
|
||||
const manifest = JSON.parse(readFileSync(new URL("../module.json", import.meta.url), "utf8")) as { requires: Requirement[] };
|
||||
|
||||
test("the Servarr APIs and mqtt-topic are optional, and route stays hard", () => {
|
||||
for (const name of ["sonarr-api", "radarr-api", "lidarr-api", "mqtt-topic"]) {
|
||||
assert.ok(
|
||||
manifest.requires.some((r) => typeof r !== "string" && r.name === name && r.optional === true),
|
||||
`${name} is not optional`,
|
||||
);
|
||||
}
|
||||
assert.ok(manifest.requires.includes("route"));
|
||||
});
|
||||
Reference in New Issue
Block a user