grafana's data source and Node-RED's influxdb nodes reached ace's InfluxDB by a LAN IP or a public name nobody routes, with a credential somebody made by hand. Now a consumer requires influxdb-api and is told where it is, which org and default bucket it serves, and signs in with the password the mesh minted for the pair. The credential is a v1-compatibility authorization, made per grant by the new provisioner: InfluxDB 2.x generates API tokens itself and ignores one the caller sends, so a v2 token could only be accepted by hand per pair; a v1 authorization takes a caller-chosen password (8-72 characters, the mesh mints 40) and reads/writes every bucket as a database of its name over InfluxQL and line protocol. A consumer contributes `access` (read, write, read-write) and, for writing, the buckets; a missing bucket is made and never deleted. Only authorizations named mesh_* and marked [mesh] are ever changed or removed; anything else of that name is refused and left alone. The org and default bucket are served facts the assignment's settings set, reaching both the consumers and the provisioner's config.json.
55 lines
2.6 KiB
TypeScript
55 lines
2.6 KiB
TypeScript
// influxdb's provisioner — the adapter that makes influxdb a provider of the mesh `influxdb-api`
|
|
// interface. The reconcile loop, the contributions file and reading the mesh's minted secret are the
|
|
// sdk harness's; this writes only the per-service half: how InfluxDB creates, checks and removes a
|
|
// consumer's credential (novox/hq ADR 0039/0040/0048). What that credential is, and why it is a v1
|
|
// authorization, is in ../grants.ts.
|
|
//
|
|
// The `influxdb-api` interface: a consumer reaches `${bound:influxdb-api:scheme}://…:at:…:port`,
|
|
// signs in as `${bound:influxdb-api:as}` with the password the mesh minted for the pair, and reads
|
|
// or writes the org's buckets as databases of the same name — `${bound:influxdb-api:bucket}` being
|
|
// the one this instance serves by default. The org and the default bucket are the assignment's
|
|
// settings, which reach both what is served and this module's config.json, so the org a consumer is
|
|
// told and the org its credential is made in cannot disagree.
|
|
|
|
import { runProvisioner, type Provision } from "@novox/mesh-sdk/provisioner";
|
|
import { InfluxDBClient } from "../client.js";
|
|
import { ApiGrants } from "../grants.js";
|
|
|
|
let grants: ApiGrants | undefined;
|
|
try {
|
|
const influx = InfluxDBClient.fromEnv();
|
|
grants = new ApiGrants(influx, influx.org);
|
|
} catch (err) {
|
|
// No admin token: nothing can be provisioned, and the tools loaded beside this must still serve.
|
|
console.error(`[provisioner:influxdb-api] not started: ${err instanceof Error ? err.message : err}`);
|
|
}
|
|
|
|
if (grants) serve(grants);
|
|
|
|
function serve(grants: ApiGrants): void {
|
|
runProvisioner("influxdb-api", {
|
|
async create(p: Provision): Promise<void> {
|
|
const done = await grants.ensure(p);
|
|
if (done !== "unchanged") {
|
|
console.log(`[provisioner:influxdb-api] ${done} v1 authorization ${p.as} in org ${grants.org}`);
|
|
}
|
|
},
|
|
|
|
async remove(p: { as: string }): Promise<void> {
|
|
const done = await grants.remove(p.as);
|
|
if (done === "not ours") {
|
|
console.error(`[provisioner:influxdb-api] ${p.as}: an authorization of that name exists that the mesh did not make — left alone`);
|
|
} else if (done === "removed") {
|
|
console.log(`[provisioner:influxdb-api] removed v1 authorization ${p.as}; its buckets and their data stay`);
|
|
}
|
|
},
|
|
|
|
// Asked every minute by the harness: whether InfluxDB still holds this consumer's authorization
|
|
// exactly as the mesh gave it, so one deleted, disabled or re-passworded behind the mesh's back is
|
|
// made whole again (hq issue 120).
|
|
async holds(p: Provision): Promise<boolean> {
|
|
return grants.holds(p);
|
|
},
|
|
});
|
|
}
|