@homeflare/alchemy 0.32.0 → 0.33.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/grafana/dashboard-model.d.ts +77 -0
- package/dist/grafana/dashboard-model.d.ts.map +1 -0
- package/dist/grafana/dashboard.d.ts +98 -0
- package/dist/grafana/dashboard.d.ts.map +1 -0
- package/dist/grafana/folder.d.ts +100 -0
- package/dist/grafana/folder.d.ts.map +1 -0
- package/dist/grafana/index.d.ts +9 -8
- package/dist/grafana/index.d.ts.map +1 -1
- package/dist/grafana/index.js +158 -6
- package/dist/grafana/index.js.map +8 -4
- package/dist/grafana/providers.d.ts +1 -1
- package/dist/grafana/providers.d.ts.map +1 -1
- package/dist/grafana/provisioned.d.ts +11 -0
- package/dist/grafana/provisioned.d.ts.map +1 -0
- package/dist/proxmox/index.js +76 -45
- package/dist/proxmox/index.js.map +6 -6
- package/dist/proxmox/zfs-pool-form.d.ts +124 -0
- package/dist/proxmox/zfs-pool-form.d.ts.map +1 -0
- package/dist/proxmox/zfs-pool-wire.d.ts +78 -0
- package/dist/proxmox/zfs-pool-wire.d.ts.map +1 -0
- package/dist/proxmox/zfs-pool.d.ts +27 -52
- package/dist/proxmox/zfs-pool.d.ts.map +1 -1
- package/docs/grafana-folder-dashboard.md +145 -0
- package/docs/grafana.md +11 -10
- package/docs/upstream-conformance.md +9 -4
- package/package.json +1 -1
- package/src/grafana/dashboard-model.test.ts +111 -0
- package/src/grafana/dashboard-model.ts +111 -0
- package/src/grafana/dashboard.test.ts +232 -0
- package/src/grafana/dashboard.ts +161 -0
- package/src/grafana/folder.test.ts +212 -0
- package/src/grafana/folder.ts +161 -0
- package/src/grafana/index.ts +21 -8
- package/src/grafana/providers.ts +7 -1
- package/src/grafana/provisioned.ts +49 -0
- package/src/proxmox/constraints-host-forms.test.ts +1 -1
- package/src/proxmox/zfs-pool-adopt.test.ts +26 -0
- package/src/proxmox/zfs-pool-form.ts +236 -0
- package/src/proxmox/zfs-pool-read-failure.test.ts +118 -0
- package/src/proxmox/zfs-pool-wire.ts +132 -0
- package/src/proxmox/zfs-pool.ts +106 -76
- package/dist/proxmox/zfs-pool-spec.d.ts +0 -27
- package/dist/proxmox/zfs-pool-spec.d.ts.map +0 -1
- package/dist/proxmox/zfs-pool-write.d.ts +0 -128
- package/dist/proxmox/zfs-pool-write.d.ts.map +0 -1
- package/src/proxmox/zfs-pool-spec.ts +0 -76
- package/src/proxmox/zfs-pool-write.ts +0 -196
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
|
-
"sources": ["../src/grafana/datasource.ts", "../src/grafana/resource.ts", "../src/grafana/credentials.ts", "../src/grafana/providers.ts"],
|
|
3
|
+
"sources": ["../src/grafana/datasource.ts", "../src/grafana/resource.ts", "../src/grafana/dashboard.ts", "../src/grafana/dashboard-model.ts", "../src/grafana/provisioned.ts", "../src/grafana/folder.ts", "../src/grafana/credentials.ts", "../src/grafana/providers.ts"],
|
|
4
4
|
"sourcesContent": [
|
|
5
5
|
"/**\n * `Grafana.Datasource` — one Grafana data source, keyed by `uid`.\n *\n * ★ WHY `uid` IS REQUIRED, NOT GENERATED. `getDataSourceByUID`/`updateDataSourceByUID`/\n * `deleteDataSourceByUID` are all UID-keyed, and `getDataSources` (list) takes no filter — so\n * locating a datasource this resource didn't create means listing the whole org and matching by\n * name, the same ambiguity netbox/resource.ts's `soleMatch` exists to catch. Requiring `uid` up\n * front (Grafana accepts a caller-chosen one on create — `AddDataSourceRequest.uid`) sidesteps\n * it entirely: `read`/`reconcile`/`destroy` all key on the one field the API itself is keyed on.\n * `teslamate-grafana`'s live datasource (provisioned from a file, not this resource) has a\n * Grafana-generated uid; the census in the handoff script records it so a first declaration can\n * name it explicitly.\n *\n * ⛔ `secureJsonData` (e.g. a datasource password) NEVER APPEARS AS A LITERAL PROP (S25).\n * `secureJsonDataRefs` takes env var NAMES instead; `resolveSecureJsonData` reads them from\n * `process.env` fresh inside `create`/`update`'s own effect, never at plan time and never\n * returned in `attributes` — Grafana's own read API agrees: `DataSource.secureJsonFields` on a\n * GET is a map of booleans (is-it-set), never the value.\n */\nimport { Resource } from 'alchemy';\nimport * as Provider from 'alchemy/Provider';\nimport * as grafana from '@distilled.cloud/grafana';\nimport * as Data from 'effect/Data';\nimport * as Effect from 'effect/Effect';\nimport { type GrafanaSpec, grafanaHandlers } from './resource.ts';\n\nexport interface DatasourceProps {\n uid: string;\n name: string;\n /** e.g. `postgres`, `prometheus`, `influxdb` — the Grafana plugin id. */\n type: string;\n url?: string;\n access?: 'proxy' | 'direct';\n database?: string;\n user?: string;\n basicAuth?: boolean;\n basicAuthUser?: string;\n isDefault?: boolean;\n withCredentials?: boolean;\n jsonData?: Record<string, unknown>;\n /** Secure field name (e.g. `password`) -> env var NAME holding its value. Never the value. */\n secureJsonDataRefs?: Record<string, string>;\n}\n\nexport interface DatasourceAttributes {\n uid: string;\n id: number;\n name: string;\n type: string;\n url: string;\n access: string;\n orgId: number;\n isDefault: boolean;\n readOnly: boolean;\n version: number;\n}\n\nexport interface GrafanaDatasource extends Resource<\n 'Grafana.Datasource',\n DatasourceProps,\n DatasourceAttributes,\n never\n> {}\n\nexport const GrafanaDatasource = Resource<GrafanaDatasource>('Grafana.Datasource');\n\n/** A named secure-field env var is unset — a domain refusal, not a distilled error. */\nexport class GrafanaSecretRefUnsetError extends Data.TaggedError('GrafanaSecretRefUnsetError')<{\n readonly message: string;\n}> {}\n\n/** Reads every named env var fresh, inside the caller's effect (S24) — never at plan time. */\nconst resolveSecureJsonData = (\n refs: Record<string, string> | undefined,\n): Effect.Effect<Record<string, string> | undefined, GrafanaSecretRefUnsetError> =>\n Effect.gen(function* () {\n if (refs === undefined) return undefined;\n const out: Record<string, string> = {};\n for (const [field, envVar] of Object.entries(refs)) {\n const raw = process.env[envVar];\n if (raw === undefined || raw.trim() === '') {\n return yield* Effect.fail(\n new GrafanaSecretRefUnsetError({\n message:\n `${envVar} is unset. Export the value of secureJsonData.${field} there — ` +\n 'never as an Alchemy prop.',\n }),\n );\n }\n out[field] = raw.trim();\n }\n return out;\n });\n\n/** `exactOptionalPropertyTypes` means an undeclared field must be OMITTED, never sent as `undefined`. */\nconst fields = (props: DatasourceProps) => ({\n name: props.name,\n type: props.type,\n ...(props.access === undefined ? {} : { access: props.access }),\n ...(props.basicAuth === undefined ? {} : { basicAuth: props.basicAuth }),\n ...(props.basicAuthUser === undefined ? {} : { basicAuthUser: props.basicAuthUser }),\n ...(props.database === undefined ? {} : { database: props.database }),\n ...(props.isDefault === undefined ? {} : { isDefault: props.isDefault }),\n ...(props.jsonData === undefined ? {} : { jsonData: props.jsonData }),\n ...(props.url === undefined ? {} : { url: props.url }),\n ...(props.user === undefined ? {} : { user: props.user }),\n ...(props.withCredentials === undefined ? {} : { withCredentials: props.withCredentials }),\n});\n\nexport const spec: GrafanaSpec<\n DatasourceProps,\n grafana.DataSource,\n DatasourceAttributes,\n | grafana.AddDataSourceError\n | grafana.GetDataSourceByUIDError\n | grafana.UpdateDataSourceByUIDError\n | grafana.DeleteDataSourceByUIDError\n | GrafanaSecretRefUnsetError\n> = {\n attributes: (live) => ({\n access: live.access ?? '',\n id: live.id ?? 0,\n isDefault: live.isDefault ?? false,\n name: live.name ?? '',\n orgId: live.orgId ?? 0,\n readOnly: live.readOnly ?? false,\n type: live.type ?? '',\n uid: live.uid ?? '',\n url: live.url ?? '',\n version: live.version ?? 0,\n }),\n create: (props) =>\n Effect.flatMap(resolveSecureJsonData(props.secureJsonDataRefs), (secureJsonData) =>\n grafana.addDataSource({\n ...fields(props),\n ...(secureJsonData === undefined ? {} : { secureJsonData }),\n uid: props.uid,\n }),\n ),\n destroy: (props) => grafana.deleteDataSourceByUID({ uid: props.uid }),\n fetchLive: (props) =>\n grafana\n .getDataSourceByUID({ uid: props.uid })\n .pipe(Effect.catchTag('NotFound', () => Effect.succeed(undefined))),\n /**\n * ⛔ AN UNDECLARED FIELD IS NEITHER COMPARED NOR SENT — the same rule every family in this\n * estate follows. `secureJsonDataRefs` is never compared: Grafana never returns the value to\n * diff against, only whether a field is set (`secureJsonFields`), so this resource cannot\n * detect drift in a secret's value — only in whether one is configured at all.\n */\n matches: (attributes, props) =>\n (props.name === undefined || attributes.name === props.name) &&\n (props.type === undefined || attributes.type === props.type) &&\n (props.url === undefined || attributes.url === props.url) &&\n (props.access === undefined || attributes.access === props.access) &&\n (props.isDefault === undefined || attributes.isDefault === props.isDefault),\n update: (props, live) =>\n Effect.flatMap(resolveSecureJsonData(props.secureJsonDataRefs), (secureJsonData) =>\n grafana.updateDataSourceByUID({\n ...fields(props),\n ...(secureJsonData === undefined ? {} : { secureJsonData }),\n ...(live.version === undefined ? {} : { version: live.version }),\n uid: props.uid,\n }),\n ),\n};\n\nexport const handlers = grafanaHandlers(spec);\n\nexport const GrafanaDatasourceProvider = () =>\n Provider.effect(GrafanaDatasource, Effect.succeed(GrafanaDatasource.Provider.of(handlers)));\n",
|
|
6
6
|
"/**\n * One shape for every Grafana object this family declares — mirroring `../forgejo/resource.ts`\n * and `../netbox/resource.ts` exactly, minus the credentials wrapping those two bake in.\n *\n * ★ CREDENTIALS ARE NOT PROVIDED HERE. Forgejo and NetBox each manage exactly one instance for\n * this house, so their engines close over a hardcoded `CredentialsFromEnv`. Grafana does not —\n * see `credentials.ts`'s note on `grafana.homeflare.dev` vs `teslamate-grafana` — so `Credentials`\n * stays a context requirement on every handler here, and `providers.ts`'s `grafanaProviders`\n * supplies it per target with `Layer.provide`, the same composition `litellm/providers.ts` uses.\n *\n * ⚠️ `NotFound` IS FOLDED TO \"ABSENT\" INSIDE EACH RESOURCE FILE'S OWN `fetchLive`, NOT HERE — the\n * same split forgejo's and netbox's engines use. `Effect.catchTag`'s tag-literal inference needs\n * a concrete error union to resolve `\"NotFound\"` against; a spec's `E` is only concrete at each\n * resource file's own call site.\n */\nimport { isResolved } from 'alchemy/Diff';\nimport type { Input } from 'alchemy/Input';\nimport type { GrafanaOpContext } from '@distilled.cloud/grafana/Protocol';\nimport * as Effect from 'effect/Effect';\n\n/**\n * One Grafana object's distilled calls. `Live` is whatever the SDK decodes (a `DataSource`, …) —\n * no untyped `Record<string, unknown>`. `E` is left to each resource file to declare (the union\n * of the distilled operations it actually calls plus any domain refusal, e.g. an unset secret-ref\n * env var), so a call site's precise error union flows straight through instead of being widened\n * by hand here.\n */\nexport type GrafanaSpec<Props extends object, Live, Attributes, E> = {\n /** Already folds the SDK's `NotFound` to `undefined` via its own `catchTag` — see the note above. */\n readonly fetchLive: (props: Props) => Effect.Effect<Live | undefined, E, GrafanaOpContext>;\n readonly attributes: (live: Live, props: Props) => Attributes | undefined;\n readonly matches: (attributes: Attributes, props: Props) => boolean;\n readonly create: (props: Props) => Effect.Effect<unknown, E, GrafanaOpContext>;\n readonly update?: (props: Props, live: Live) => Effect.Effect<unknown, E, GrafanaOpContext>;\n readonly destroy: (props: Props, live: Live) => Effect.Effect<unknown, E, GrafanaOpContext>;\n};\n\nexport const grafanaOperations = <Props extends object, Live, Attributes, E>(\n spec: GrafanaSpec<Props, Live, Attributes, E>,\n) => {\n const read = (props: Props) =>\n spec\n .fetchLive(props)\n .pipe(Effect.map((live) => (live === undefined ? undefined : spec.attributes(live, props))));\n\n return {\n read,\n\n diff: (news: Input<Props>, output: Attributes | undefined) =>\n Effect.gen(function* () {\n if (output === undefined || !isResolved(news)) return undefined;\n const live = yield* read(news);\n if (live === undefined) return { action: 'update' } as const;\n if (spec.matches(live, news)) return { action: 'noop' } as const;\n return spec.update === undefined\n ? ({ action: 'replace' } as const)\n : ({ action: 'update' } as const);\n }),\n\n /**\n * ★ OBSERVES BEFORE IT WRITES (S9, S10) — THE WHOLE OF \"ADOPT\" FOR THIS FAMILY. A `uid` this\n * Grafana instance already has is bound and, when it already matches the declaration,\n * left alone; only a genuinely absent `uid` is created. Declaring exactly what is live\n * changes nothing on `reconcile`, the same posture netbox/resource.ts documents as\n * \"adopt is the default posture, not a flag\".\n */\n reconcile: (news: Props) =>\n Effect.gen(function* () {\n const before = yield* spec.fetchLive(news);\n if (before === undefined) {\n yield* spec.create(news);\n } else if (spec.update !== undefined) {\n const mapped = spec.attributes(before, news);\n if (mapped === undefined || spec.matches(mapped, news)) {\n /* adoption or already converged — no write */\n } else {\n yield* spec.update(news, before);\n }\n }\n const after = yield* read(news);\n if (after === undefined) {\n return yield* Effect.die(\n new Error(\n 'grafanaOperations: the write returned no error but the object is still absent. ' +\n 'Read back rather than trusting the response status.',\n ),\n );\n }\n return after;\n }),\n\n destroy: (olds: Props) =>\n Effect.gen(function* () {\n const live = yield* spec.fetchLive(olds);\n if (live === undefined) return;\n yield* spec.destroy(olds, live);\n }),\n };\n};\n\n/**\n * The five provider handlers for a spec'd Grafana object, wired once. `Credentials` and\n * `HttpClient.HttpClient` stay ambient context requirements — see the file header.\n *\n * ⚠️ `list` answers empty — an org's full datasource list is not a page this family sweeps for\n * adoption; adoption stays explicit, the same reasoning forgejo's and netbox's engines give.\n */\nexport const grafanaHandlers = <Props extends object, Live, Attributes extends object, E>(\n spec: GrafanaSpec<Props, Live, Attributes, E>,\n) => {\n const ops = grafanaOperations(spec);\n return {\n list: () => Effect.succeed([]),\n read: ({ olds }: { olds: Props }) => ops.read(olds),\n diff: ({ news, output }: { news: Input<Props>; output: Attributes | undefined }) =>\n ops.diff(news, output),\n reconcile: ({ news }: { news: Props }) => ops.reconcile(news),\n delete: ({ olds }: { olds: Props }) => ops.destroy(olds),\n };\n};\n",
|
|
7
|
+
"/**\n * `Grafana.Dashboard` — one Grafana dashboard, keyed by `uid`. Same uid-required doctrine as\n * `datasource.ts`/`folder.ts` (`getDashboard`/`postDashboard`/`deleteDashboard` are all UID-keyed\n * on the wire — `postDashboard`'s own `uid` travels INSIDE the `dashboard` JSON body, not as a\n * request field, because Grafana's classic dashboard-save API has never had one; see\n * `dashboard-model.ts`'s `normalizeModel`, which is what puts it there).\n *\n * ★ THE DECLARED CONTENT IS THE DASHBOARD JSON MODEL — `DashboardProps.dashboard`, the same shape\n * Grafana's own \"Export as JSON\" produces. `dashboard-model.ts`'s `normalizeModel` strips the\n * volatile fields (`id`, `version`, `iteration`) and forces `uid` to this resource's own value\n * before ANY comparison, so a plan against an unchanged dashboard is a true noop despite those\n * fields moving on every live save — see that file for the full reasoning, including which\n * fields (`panels`, `targets`) are array-ORDER-significant and compared as such.\n *\n * ⛔ `matches` COMPARES \"IS THE DECLARATION A SUBSET OF WHAT IS LIVE\", NOT PLAIN EQUALITY —\n * `dashboard-model.ts`'s `declaredContentMatches`, added after an adversarial review of this PR\n * found plain `deepEqual` made an ordinary dashboard show `update` FOREVER: Grafana's own\n * schema-migration on save decorates any panel/target whose `datasource` is absent with an\n * explicit reference, so a declaration that never sets one would permanently disagree with what\n * it reads back. See that function's own header for the full reasoning and the trade-off it\n * accepts (an omitted field can no longer be used to CLEAR a previously-set one).\n *\n * ⛔ VERSION CONFLICTS (412) FAIL LOUDLY — NEVER OVERWRITTEN BLINDLY. `update` injects the\n * `version` this resource just read (`live.meta.version`, from the SAME `fetchLive` call\n * `resource.ts`'s `reconcile` already made before calling `update`) into the model it sends, and\n * `overwrite` is never set. If Grafana's own version check then still fails — someone else saved\n * in the gap between that read and this write — `postDashboard` answers `412` and\n * `PreconditionFailed` is left IN the error union below, uncaught: the whole reconcile fails, the\n * deploy stops, and nothing is written. The next run's `fetchLive` re-reads the new live version\n * fresh, which is the \"re-read\" half of \"re-read and fail loudly\" — there is no separate retry\n * loop in this file, because the next `plan`/`deploy` invocation already IS that re-read.\n *\n * ⛔ A PROVISIONED DASHBOARD (`meta.provisioned` — see provisioned.ts) REFUSES EVERY UPDATE AND\n * DESTROY, naming `meta.provisionedExternalId` (the file) when Grafana reports one. `matches`\n * still compares structurally regardless, so a mismatched declaration against a provisioned\n * dashboard shows `update` in a plan rather than a silent, permanently-hidden noop.\n *\n * ⚠️ NO RETAIN DEFAULT, UNLIKE `Grafana.Folder`. Deleting one dashboard is a single-object blast\n * radius — the same class `Grafana.Datasource` already accepts without a retain guard — not\n * folder.ts's multi-object cascade (a folder's delete takes its dashboards AND their alert rules\n * with it). See folder.ts's file header for the full contrast.\n */\nimport { Resource } from 'alchemy';\nimport * as Provider from 'alchemy/Provider';\nimport * as grafana from '@distilled.cloud/grafana';\nimport * as Effect from 'effect/Effect';\nimport {\n type DashboardModel,\n declaredContentMatches,\n modelTitle,\n normalizeModel,\n} from './dashboard-model.ts';\nimport { type GrafanaProvisionedObjectError, refuseIfProvisioned } from './provisioned.ts';\nimport { type GrafanaSpec, grafanaHandlers } from './resource.ts';\n\nexport type { DashboardModel };\n\nexport interface DashboardProps {\n uid: string;\n /** Omit for the General/root folder. */\n folderUid?: string;\n /** The dashboard JSON model — see the file header. `id`/`version`/`iteration`/`uid` inside it\n * are ignored; `uid` above is the single source of truth. */\n dashboard: DashboardModel;\n}\n\nexport interface DashboardAttributes {\n uid: string;\n title: string;\n version: number;\n folderUid: string;\n url: string;\n /** True when Grafana reports this dashboard as file-provisioned (read-only — see provisioned.ts). */\n provisioned: boolean;\n provisionedExternalId: string;\n /** The live model, normalized the same way `matches` compares it. */\n dashboard: DashboardModel;\n}\n\nexport interface GrafanaDashboard extends Resource<\n 'Grafana.Dashboard',\n DashboardProps,\n DashboardAttributes,\n never\n> {}\n\nexport const GrafanaDashboard = Resource<GrafanaDashboard>('Grafana.Dashboard');\n\nconst modelOf = (live: grafana.DashboardFullWithMeta): DashboardModel =>\n live.dashboard !== null && typeof live.dashboard === 'object'\n ? (live.dashboard as DashboardModel)\n : {};\n\nexport const spec: GrafanaSpec<\n DashboardProps,\n grafana.DashboardFullWithMeta,\n DashboardAttributes,\n | grafana.GetDashboardError\n | grafana.PostDashboardError\n | grafana.DeleteDashboardError\n | GrafanaProvisionedObjectError\n> = {\n attributes: (live, props) => {\n const model = normalizeModel(modelOf(live), props.uid);\n return {\n dashboard: model,\n folderUid: live.meta?.folderUid ?? '',\n provisioned: live.meta?.provisioned ?? false,\n provisionedExternalId: live.meta?.provisionedExternalId ?? '',\n title: modelTitle(model),\n uid: props.uid,\n url: live.meta?.url ?? '',\n version: live.meta?.version ?? 0,\n };\n },\n create: (props) =>\n grafana.postDashboard({\n dashboard: normalizeModel(props.dashboard, props.uid),\n ...(props.folderUid === undefined ? {} : { folderUid: props.folderUid }),\n }),\n destroy: (props, live) =>\n Effect.gen(function* () {\n if (live.meta?.provisioned) {\n return yield* refuseIfProvisioned(\n 'Grafana.Dashboard',\n props.uid,\n live.meta.provisionedExternalId,\n );\n }\n yield* grafana.deleteDashboard({ uid: props.uid });\n }),\n fetchLive: (props) =>\n grafana\n .getDashboard({ uid: props.uid })\n .pipe(Effect.catchTag('NotFound', () => Effect.succeed(undefined))),\n matches: (attributes, props) =>\n (props.folderUid === undefined || attributes.folderUid === props.folderUid) &&\n declaredContentMatches(normalizeModel(props.dashboard, props.uid), attributes.dashboard),\n update: (props, live) =>\n Effect.gen(function* () {\n if (live.meta?.provisioned) {\n return yield* refuseIfProvisioned(\n 'Grafana.Dashboard',\n props.uid,\n live.meta.provisionedExternalId,\n );\n }\n return yield* grafana.postDashboard({\n dashboard: {\n ...normalizeModel(props.dashboard, props.uid),\n ...(live.meta?.version === undefined ? {} : { version: live.meta.version }),\n },\n ...(props.folderUid === undefined ? {} : { folderUid: props.folderUid }),\n });\n }),\n};\n\nexport const handlers = grafanaHandlers(spec);\n\nexport const GrafanaDashboardProvider = () =>\n Provider.effect(GrafanaDashboard, Effect.succeed(GrafanaDashboard.Provider.of(handlers)));\n",
|
|
8
|
+
"/**\n * Pure functions over a dashboard JSON model — no SDK call, no Effect. Split out of dashboard.ts\n * so that file stays under the house's 250-line cap, and so `normalizeModel`'s no-change-is-noop\n * behavior is exercised directly in dashboard-model.test.ts without a fake Grafana in the way.\n */\nexport type DashboardModel = Record<string, unknown>;\n\n/** Grafana injects or increments these on every save; a declared model never sets them meaningfully. */\nconst VOLATILE_FIELDS = ['id', 'version', 'iteration'] as const;\n\n/**\n * ★ NORMALIZES BOTH SIDES THE SAME WAY, so a no-change plan is a true noop\n * (docs/grafana-folder-dashboard.md). Strips `id`/`version`/`iteration` (Grafana-managed, never\n * declared content) and forces `uid` to this resource's OWN canonical value, wherever — or\n * whether — the source object carried one. That second part is \"uid placement\": a dashboard\n * pasted from Grafana's own \"Export as JSON\" always embeds the OLD live uid (or none, for a\n * never-saved dashboard), while the live model this resource reads back always carries the\n * CURRENT one; forcing both to `props.uid` before comparing is what makes those two shapes equal\n * instead of permanently disagreeing on one field neither side is meant to declare independently\n * (`DashboardProps.uid` — see dashboard.ts — is the single source of truth).\n *\n * ⚠️ EVERY OTHER DECLARED FIELD COMPARES STRUCTURALLY, ARRAY ORDER INCLUDED — dashboard.ts's\n * `matches` runs this output through THIS FILE's own `declaredContentMatches` (below), which\n * never reorders an array. That is correct here: `panels` is position-significant (screen layout\n * via its own `gridPos`, but the array order also drives Grafana's internal panel indexing and\n * any `repeat` behavior) and each panel's `targets` is execution/legend order. Nothing in a\n * dashboard model is a genuinely unordered SET the way UniFi's `trustedDhcpServerIpAddresses` is\n * (unifi/network-form.ts's `sortedSet`) — this family has no field that needs one.\n * ⚠️ `schemaVersion` IS DELIBERATELY **NOT** NORMALIZED — NOT MEASURED against a live instance (no\n * live calls in this PR). Community reports say Grafana can rewrite an old dashboard's\n * `schemaVersion` upward on load; IF that also happens on every plain save of an\n * already-current-schema dashboard, every plan against it would show a false `update` forever —\n * but silently normalizing it away on a guess would just as easily hide a genuine schema\n * mismatch. Left comparing until measured: add it to `VOLATILE_FIELDS` with a dated, measured\n * comment the day it is seen live, the same standard every other row in this file meets.\n */\nexport const normalizeModel = (model: DashboardModel, uid: string): DashboardModel => {\n const normalized: DashboardModel = {};\n for (const [key, value] of Object.entries(model)) {\n if (key === 'uid') continue;\n if ((VOLATILE_FIELDS as readonly string[]).includes(key)) continue;\n normalized[key] = value;\n }\n normalized.uid = uid;\n return normalized;\n};\n\n/** The model's own declared title — reporting only. It is never compared on its own; the whole\n * normalized model is, in dashboard.ts's `matches`, so a title-only mismatch still shows `update`. */\nexport const modelTitle = (model: DashboardModel): string =>\n typeof model.title === 'string' ? model.title : '';\n\nconst isPlainObject = (value: unknown): value is Record<string, unknown> =>\n typeof value === 'object' && value !== null && !Array.isArray(value);\n\n/**\n * ★ `declared` NEED ONLY BE A SUBSET OF `live` — an adversarial review of this PR (2026-09-24)\n * found that plain structural equality made an ordinary, never-edited dashboard show `update`\n * FOREVER: Grafana's own dashboard-save schema migration rewrites any panel or target whose\n * `datasource` is absent into an explicit `{type, uid}` reference to the resolved default\n * datasource — documented Grafana schema-migration behavior (not re-measured against a live\n * instance in this PR — no live calls). A declared panel that never sets `datasource` (the\n * common case: hand-authored JSON, or anything relying on the dashboard-level default) would,\n * after the FIRST create, read back with that field populated; plain `deepEqual` (this file used\n * `alchemy/Diff`'s before this fix) then disagreed with the declaration on every subsequent plan,\n * `update` sent the undecorated model back, and Grafana redecorated it again on the next read —\n * non-convergent.\n *\n * This function instead asks only \"does every field the DECLARATION mentions, at any depth, still\n * match what is live\" — `live` may carry additional fields, at any depth, that `declared` never\n * mentioned (the datasource auto-decoration above, or any future Grafana-injected default,\n * `fieldConfig` defaults and `pluginVersion` included) without being compared at all. A field\n * `declared` DOES mention that is missing, or different, in `live` still fails the match — real\n * content drift is still caught exactly as before; only fields the declaration is silent about are\n * now tolerated rather than compared.\n *\n * ⚠️ THE TRADE-OFF THIS ACCEPTS, DELIBERATELY: removing a field from the declaration (rather than\n * changing its value) can no longer force Grafana to drop it. If a panel once declared an\n * explicit `datasource` and a later declaration deletes that key entirely (rather than setting a\n * different one), `live` keeps whatever the last write set — the declaration is silent about that\n * key now, so nothing compares it, so no `update` ever fires to change it. This is the same\n * \"undeclared means don't care, never means force-to-default\" convention already used everywhere\n * else in this kit for a plain optional prop (e.g. `datasource.ts`'s `matches` never treats an\n * undeclared `access` as \"must be unset\"); it now also applies inside the JSON model, not just at\n * the props level. Declare an explicit replacement value to change a field; omission never clears\n * one that was set by a previous declaration or by Grafana itself.\n *\n * ⚠️ ARRAYS COMPARE BY LENGTH, THEN ELEMENT-WISE SUBSET — never by a looser \"declared has fewer\n * items\" rule. `panels`/`targets` are position-significant (see `normalizeModel`'s header); an\n * array Grafana pads or reorders on its own would still need normalizing here explicitly, the\n * same as any other newly-discovered auto-decoration — this function does not paper over that.\n * ⚠️ `null` ON EITHER SIDE OF A LEAF NEVER MISMATCHES (mirrors `alchemy/Diff`'s `deepEqual` with\n * `stripNullish: true`, which this function replaces for the dashboard model specifically).\n */\nexport const declaredContentMatches = (declared: unknown, live: unknown): boolean => {\n if (declared == null) return true;\n if (Array.isArray(declared)) {\n return (\n Array.isArray(live) &&\n declared.length === live.length &&\n declared.every((item, index) => declaredContentMatches(item, live[index]))\n );\n }\n if (isPlainObject(declared)) {\n return (\n isPlainObject(live) &&\n Object.entries(declared).every(([key, value]) => declaredContentMatches(value, live[key]))\n );\n }\n return declared === live;\n};\n",
|
|
9
|
+
"/**\n * The shared refusal for a file-provisioned Grafana object — `Grafana.Folder` and\n * `Grafana.Dashboard` both carry a live signal Grafana itself treats as read-only through the\n * classic write API this family calls: `Folder.managedBy` (non-empty when something other than\n * this API manages the folder — provisioning is the case this house cares about, but the field\n * itself is Grafana's general \"who owns this\" marker) and `DashboardMeta.provisioned` (paired\n * with `provisionedExternalId`, the file path). A declaration must never attempt a write against\n * either — see docs/grafana-folder-dashboard.md's \"provisioned objects\" section.\n *\n * ⚠️ THE EXACT `managedBy` VALUES ARE INFERRED FROM THE SDK's SCHEMA, NOT MEASURED. No live\n * instance in this house currently has a file-provisioned FOLDER to read (docs/grafana.md:\n * `teslamate-grafana` has none at all; the mini's Grafana provisions dashboards, not folders) and\n * this PR makes no live calls. `Folder.managedBy` and `DashboardMeta.provisioned` are exactly\n * the fields the OpenAPI schema types for this purpose — treat any non-empty `managedBy` as\n * provisioned rather than special-casing specific string values that were never observed live.\n *\n * ⛔ ADOPTING A PROVISIONED OBJECT IS STILL FINE. `read`/`reconcile`'s no-write \"already converged\"\n * path (resource.ts) never calls `create`/`update`/`destroy` at all when the declaration already\n * matches what is live — only an actual attempted WRITE against a provisioned object is refused,\n * by the two callers of this function inside folder.ts/dashboard.ts's own `update`/`destroy`.\n */\nimport * as Data from 'effect/Data';\nimport * as Effect from 'effect/Effect';\n\nexport class GrafanaProvisionedObjectError extends Data.TaggedError(\n 'GrafanaProvisionedObjectError',\n)<{\n readonly message: string;\n}> {}\n\nexport const refuseIfProvisioned = (\n kind: 'Grafana.Folder' | 'Grafana.Dashboard',\n uid: string,\n owner: string | undefined,\n): Effect.Effect<never, GrafanaProvisionedObjectError> => {\n const ownerClause =\n owner === undefined || owner === ''\n ? `(this Grafana instance did not report an owning file)`\n : `by ${owner}`;\n return Effect.fail(\n new GrafanaProvisionedObjectError({\n message:\n `${kind} '${uid}' is file-provisioned ${ownerClause} and read-only through the classic ` +\n `write API this family calls. Edit the provisioning file instead — declaring this ` +\n `object read-only (a declaration matching what is live) is safe and will never reach ` +\n `this refusal.`,\n }),\n );\n};\n",
|
|
10
|
+
"/**\n * `Grafana.Folder` — one Grafana folder, keyed by `uid`. Mirrors `datasource.ts`'s shape and\n * doctrine (uid required, not generated — same reasoning, `getFolderByUID`/`updateFolder`/\n * `deleteFolder` are all UID-keyed and `getFolders` (list) has no per-uid filter).\n *\n * ⛔ `description` IS WRITE-ONLY — MEASURED against `@distilled.cloud/grafana`'s generated types\n * (`Folder` in services/grafana.ts has no `description` field at all, though\n * `CreateFolderRequest`/`UpdateFolderRequest` both accept one). Grafana's classic folder GET\n * never returns it, not even as an is-it-set flag the way `DataSource.secureJsonFields` reports\n * for a datasource's secure fields. So `description` is sent on every create/update that\n * declares it but is NEVER part of `matches` — this resource cannot detect drift in a value it\n * is never shown, the same treatment `datasource.ts` gives `secureJsonDataRefs`.\n *\n * ⚠️ NESTING (`parentUid`) IS CREATE-ONLY — MEASURED against the same generated types.\n * `CreateFolderRequest.parentUid` exists; `UpdateFolderRequest` has no `parentUid` field at all\n * (`folder_uid`, `description`, `overwrite`, `title`, `version` — reparenting is simply not a\n * thing `PUT /folders/{uid}` can do). A declaration whose `parentUid` no longer matches the live\n * folder therefore REFUSES with `GrafanaFolderReparentError` rather than either silently doing\n * nothing (which would lie about the declaration taking effect) or deleting and recreating the\n * folder under the new parent (which — see the ⛔ below — takes every dashboard and alert rule in\n * it with it). Move a folder by hand in Grafana, then adjust the declaration to match.\n *\n * ⛔ DELETE DEFAULTS TO `retain` — DELETING A FOLDER DELETES ITS DASHBOARDS AND ALERT RULES IN\n * GRAFANA, per `deleteFolder`'s own operation description (\"along with all dashboards (and their\n * alerts) stored in the folder... cannot be reverted... also deletes all the subfolders\"). That\n * is the same class of multi-object cascade `openbao/mount.ts` guards with\n * `defaultRemovalPolicy: 'retain'` (\"disabling a mount destroys every secret under it\") — VERIFIED\n * there against Alchemy's own `Apply.ts` (not re-verified here; same engine, same option). A\n * stack that explicitly wants the cascade opts in with `.pipe(RemovalPolicy.destroy())` on the\n * declared instance; `destroy` below still implements the real DELETE in full either way (S11).\n * `Grafana.Dashboard` does NOT default to retain — deleting one dashboard is the same\n * single-object blast radius `Grafana.Datasource` already accepts without a retain guard, not\n * this family's multi-object cascade.\n *\n * ⛔ A PROVISIONED FOLDER (`managedBy` set — see provisioned.ts) REFUSES EVERY UPDATE AND DESTROY.\n * `matches` still compares structurally either way, so a mismatched declaration against a\n * provisioned folder shows `update` in a plan (never a silent, permanently-hidden noop) and then\n * fails loudly, by design, when reconcile actually tries to write.\n */\nimport { Resource } from 'alchemy';\nimport * as Provider from 'alchemy/Provider';\nimport * as grafana from '@distilled.cloud/grafana';\nimport * as Data from 'effect/Data';\nimport * as Effect from 'effect/Effect';\nimport { type GrafanaProvisionedObjectError, refuseIfProvisioned } from './provisioned.ts';\nimport { type GrafanaSpec, grafanaHandlers } from './resource.ts';\n\nexport interface FolderProps {\n uid: string;\n title: string;\n /** Write-only — see the file header. Sent on every create/update that declares it; never diffed. */\n description?: string;\n /** Parent folder UID, for nested folders. Create-only — see the file header. */\n parentUid?: string;\n}\n\nexport interface FolderAttributes {\n uid: string;\n id: number;\n title: string;\n parentUid: string;\n /** Non-empty when Grafana reports this folder as externally managed (e.g. provisioning). */\n managedBy: string;\n url: string;\n version: number;\n}\n\nexport interface GrafanaFolder extends Resource<\n 'Grafana.Folder',\n FolderProps,\n FolderAttributes,\n never\n> {}\n\nexport const GrafanaFolder = Resource<GrafanaFolder>('Grafana.Folder', {\n defaultRemovalPolicy: 'retain',\n});\n\n/** A declared `parentUid` disagrees with the live folder's — `updateFolder` cannot reparent. */\nexport class GrafanaFolderReparentError extends Data.TaggedError('GrafanaFolderReparentError')<{\n readonly message: string;\n}> {}\n\nexport const spec: GrafanaSpec<\n FolderProps,\n grafana.Folder,\n FolderAttributes,\n | grafana.CreateFolderError\n | grafana.GetFolderByUIDError\n | grafana.UpdateFolderError\n | grafana.DeleteFolderError\n | GrafanaProvisionedObjectError\n | GrafanaFolderReparentError\n> = {\n attributes: (live) => ({\n id: live.id ?? 0,\n managedBy: live.managedBy ?? '',\n parentUid: live.parentUid ?? '',\n title: live.title ?? '',\n uid: live.uid ?? '',\n url: live.url ?? '',\n version: live.version ?? 0,\n }),\n create: (props) =>\n grafana.createFolder({\n uid: props.uid,\n title: props.title,\n ...(props.description === undefined ? {} : { description: props.description }),\n ...(props.parentUid === undefined ? {} : { parentUid: props.parentUid }),\n }),\n destroy: (props, live) =>\n Effect.gen(function* () {\n if (live.managedBy) {\n return yield* refuseIfProvisioned('Grafana.Folder', props.uid, live.managedBy);\n }\n yield* grafana.deleteFolder({ folder_uid: props.uid });\n }),\n fetchLive: (props) =>\n grafana\n .getFolderByUID({ folder_uid: props.uid })\n .pipe(Effect.catchTag('NotFound', () => Effect.succeed(undefined))),\n matches: (attributes, props) =>\n attributes.title === props.title &&\n (props.parentUid === undefined || attributes.parentUid === props.parentUid),\n update: (props, live) =>\n Effect.gen(function* () {\n if (live.managedBy) {\n return yield* refuseIfProvisioned('Grafana.Folder', props.uid, live.managedBy);\n }\n if (props.parentUid !== undefined && props.parentUid !== (live.parentUid ?? undefined)) {\n return yield* Effect.fail(\n new GrafanaFolderReparentError({\n message:\n `Grafana.Folder '${props.uid}' declares parentUid '${props.parentUid}' but is ` +\n `live under '${live.parentUid ?? '(root)'}'. PUT /folders/{uid} cannot reparent a ` +\n `folder — move it by hand in Grafana, then update the declaration to match.`,\n }),\n );\n }\n // ⚠️ UNLIKE DASHBOARD.TS's 412, THIS HAS NO VERIFIED CONFLICT SIGNAL. `version` is sent\n // optimistically, but the SDK's own doc comment on `UpdateFolderRequest.version` says\n // \"only used by the legacy folder implementation\" — on Grafana's newer unified-storage\n // folder backend it is plausibly ignored outright, meaning two concurrent title/description\n // writes could silently last-write-win with no error, unlike a dashboard save. NOT\n // MEASURED against a live instance either way (no live calls in this PR; flagged by an\n // adversarial review) — sending it is harmless (a no-op field on a backend that ignores it)\n // and correct on one that still honors it, so it stays, but do not read its PRESENCE here\n // as proof this resource detects folder update conflicts the way dashboard.ts does.\n return yield* grafana.updateFolder({\n folder_uid: props.uid,\n title: props.title,\n ...(props.description === undefined ? {} : { description: props.description }),\n ...(live.version === undefined ? {} : { version: live.version }),\n });\n }),\n};\n\nexport const handlers = grafanaHandlers(spec);\n\nexport const GrafanaFolderProvider = () =>\n Provider.effect(GrafanaFolder, Effect.succeed(GrafanaFolder.Provider.of(handlers)));\n",
|
|
7
11
|
"/**\n * Grafana credentials for this family — a URL plus a TOKEN REFERENCE (an env var NAME), never a\n * literal secret. Wraps `@distilled.cloud/grafana`'s own `Credentials` context directly (S23 —\n * reuse the SDK's own client rather than inventing a second one); this file only adds the house's\n * lazy, per-instance resolution on top of it.\n *\n * ★ ONE `GRAFANA_TOKEN`/`GRAFANA_URL` PAIR ISN'T ENOUGH FOR THIS HOUSE. Unlike Forgejo (one\n * instance), the estate runs at least two Grafanas — `grafana.homeflare.dev` and\n * `teslamate-grafana` on CT100, which the unit's own comment says has \"Own Access SaaS OIDC\n * client, never grafana.homeflare.dev's\". The SDK's `CredentialsFromEnv` reads one fixed pair\n * of names, so a stack declaring against more than one instance needs a target per instance —\n * `grafanaCredentials(target)` below, parameterized the way `litellm/credentials.ts` and\n * `proxmox/credentials.ts` already are in this kit.\n *\n * ⛔ THE TOKEN NEVER TOUCHES A PROP OR AN ATTRIBUTE (S25). `tokenEnv` is a NAME, resolved from\n * `process.env` fresh inside the credentials effect on every request (S24) — a layer built once\n * still re-reads the environment each time an operation runs, the same laziness\n * `litellm/credentials.ts` and the SDK's own `forgejo`/`netbox` `CredentialsFromEnv` have.\n *\n * ⚠️ THE SDK's `Credentials` SERVICE VALUE TYPE IS `Effect.Effect<Config, never, never>` — the\n * error channel is `never` (credentials.d.ts). A missing env var can only surface as a defect\n * here, via `Effect.orDie`, the same trade-off `forgejo/resource.ts` and `netbox/resource.ts`\n * already accept for their own `CredentialsFromEnv`; it is not a choice this file introduces.\n */\nimport { type Config, Credentials } from '@distilled.cloud/grafana/Credentials';\nimport { ConfigError } from '@distilled.cloud/grafana/Errors';\nimport * as Effect from 'effect/Effect';\nimport * as Layer from 'effect/Layer';\nimport * as Redacted from 'effect/Redacted';\n\nexport interface GrafanaTarget {\n /** Instance origin, e.g. `https://tesla.homeflare.dev/dash` — never `/api`; the SDK appends it. */\n readonly baseUrl: string;\n /** Env var NAME holding a Grafana service-account token. Never the value itself. */\n readonly tokenEnv: string;\n /** Selects the org a call applies to, via `X-Grafana-Org-Id`. Omit for the default org. */\n readonly orgId?: number | string;\n}\n\nconst resolve = (target: GrafanaTarget): Effect.Effect<Config, never, never> =>\n Effect.sync(() => process.env[target.tokenEnv]).pipe(\n Effect.flatMap((raw) =>\n raw === undefined || raw.trim() === ''\n ? Effect.fail(\n new ConfigError({\n message:\n `${target.tokenEnv} is not set. Export the Grafana service-account token there ` +\n `— never as an Alchemy prop — before running a stack that declares a resource ` +\n `against ${target.baseUrl}.`,\n }),\n )\n : Effect.succeed({\n apiBaseUrl: target.baseUrl.replace(/\\/+$/, ''),\n token: Redacted.make(raw.trim()),\n ...(target.orgId === undefined ? {} : { orgId: target.orgId }),\n } satisfies Config),\n ),\n Effect.orDie,\n );\n\n/** The lazy, per-instance layer `grafanaProviders(target)` composes by default. */\nexport const grafanaCredentials = (target: GrafanaTarget): Layer.Layer<Credentials> =>\n Layer.succeed(Credentials, resolve(target));\n\nexport type { Credentials };\n",
|
|
8
|
-
"/**\n * The Grafana providers for one instance, with its credentials, as one layer for a stack.\n *\n * Layer.mergeAll(grafanaProviders({ baseUrl: '...', tokenEnv: 'GRAFANA_TOKEN' }), …)\n *\n * ★ ONE CALL PER INSTANCE. A stack declaring resources against both `grafana.homeflare.dev` and\n * `teslamate-grafana` composes `grafanaProviders(targetA)` and `grafanaProviders(targetB)` —\n * each resource type's `Resource` constructor is a single global registration, so the SAME\n * `Grafana.Datasource` type serves both; only the credential layer differs per call.\n * ⚠️ `HttpClient` is NOT provided here. It is ambient in every Alchemy runtime, the same as\n * `litellmProviders`/`netboxProviders`-style families in this kit assume; a stack that already\n * deploys anything over HTTP already has one.\n */\nimport * as Layer from 'effect/Layer';\nimport { type GrafanaTarget, grafanaCredentials } from './credentials.ts';\nimport { GrafanaDatasourceProvider } from './datasource.ts';\n\nexport const grafanaProviders = (target: GrafanaTarget) =>\n GrafanaDatasourceProvider().pipe(Layer.provide(grafanaCredentials(target)));\n"
|
|
12
|
+
"/**\n * The Grafana providers for one instance, with its credentials, as one layer for a stack.\n *\n * Layer.mergeAll(grafanaProviders({ baseUrl: '...', tokenEnv: 'GRAFANA_TOKEN' }), …)\n *\n * ★ ONE CALL PER INSTANCE. A stack declaring resources against both `grafana.homeflare.dev` and\n * `teslamate-grafana` composes `grafanaProviders(targetA)` and `grafanaProviders(targetB)` —\n * each resource type's `Resource` constructor is a single global registration, so the SAME\n * `Grafana.Datasource` type serves both; only the credential layer differs per call.\n * ⚠️ `HttpClient` is NOT provided here. It is ambient in every Alchemy runtime, the same as\n * `litellmProviders`/`netboxProviders`-style families in this kit assume; a stack that already\n * deploys anything over HTTP already has one.\n */\nimport * as Layer from 'effect/Layer';\nimport { type GrafanaTarget, grafanaCredentials } from './credentials.ts';\nimport { GrafanaDashboardProvider } from './dashboard.ts';\nimport { GrafanaDatasourceProvider } from './datasource.ts';\nimport { GrafanaFolderProvider } from './folder.ts';\n\nexport const grafanaProviders = (target: GrafanaTarget) =>\n Layer.mergeAll(\n GrafanaDatasourceProvider(),\n GrafanaFolderProvider(),\n GrafanaDashboardProvider(),\n ).pipe(Layer.provide(grafanaCredentials(target)));\n"
|
|
9
13
|
],
|
|
10
|
-
"mappings": "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
|
|
11
|
-
"debugId": "
|
|
14
|
+
"mappings": "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",
|
|
15
|
+
"debugId": "D031E5F2BA23E8BC64756E2164756E21",
|
|
12
16
|
"names": []
|
|
13
17
|
}
|
|
@@ -13,5 +13,5 @@
|
|
|
13
13
|
*/
|
|
14
14
|
import * as Layer from 'effect/Layer';
|
|
15
15
|
import { type GrafanaTarget } from './credentials.ts';
|
|
16
|
-
export declare const grafanaProviders: (target: GrafanaTarget) => Layer.Layer<import("alchemy").Provider<import("./datasource.ts").GrafanaDatasource>, never, import("effect/unstable/http/HttpClient").HttpClient>;
|
|
16
|
+
export declare const grafanaProviders: (target: GrafanaTarget) => Layer.Layer<import("alchemy").Provider<import("./dashboard.ts").GrafanaDashboard> | import("alchemy").Provider<import("./datasource.ts").GrafanaDatasource> | import("alchemy").Provider<import("./folder.ts").GrafanaFolder>, never, import("effect/unstable/http/HttpClient").HttpClient>;
|
|
17
17
|
//# sourceMappingURL=providers.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"providers.d.ts","sourceRoot":"","sources":["../../src/grafana/providers.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,KAAK,KAAK,MAAM,cAAc,CAAC;AACtC,OAAO,EAAE,KAAK,aAAa,EAAsB,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"providers.d.ts","sourceRoot":"","sources":["../../src/grafana/providers.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,KAAK,KAAK,MAAM,cAAc,CAAC;AACtC,OAAO,EAAE,KAAK,aAAa,EAAsB,MAAM,kBAAkB,CAAC;AAK1E,eAAO,MAAM,gBAAgB,WAAY,aAAa,gSAKH,CAAC"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import * as Effect from 'effect/Effect';
|
|
2
|
+
declare const GrafanaProvisionedObjectError_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
|
|
3
|
+
readonly _tag: "GrafanaProvisionedObjectError";
|
|
4
|
+
} & Readonly<A>;
|
|
5
|
+
export declare class GrafanaProvisionedObjectError extends GrafanaProvisionedObjectError_base<{
|
|
6
|
+
readonly message: string;
|
|
7
|
+
}> {
|
|
8
|
+
}
|
|
9
|
+
export declare const refuseIfProvisioned: (kind: 'Grafana.Folder' | 'Grafana.Dashboard', uid: string, owner: string | undefined) => Effect.Effect<never, GrafanaProvisionedObjectError>;
|
|
10
|
+
export {};
|
|
11
|
+
//# sourceMappingURL=provisioned.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"provisioned.d.ts","sourceRoot":"","sources":["../../src/grafana/provisioned.ts"],"names":[],"mappings":"AAsBA,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;;;;AAExC,qBAAa,6BAA8B,SAAQ,mCAEjD;IACA,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B,CAAC;CAAG;AAEL,eAAO,MAAM,mBAAmB,SACxB,gBAAgB,GAAG,mBAAmB,OACvC,MAAM,SACJ,MAAM,GAAG,SAAS,KACxB,MAAM,CAAC,MAAM,CAAC,KAAK,EAAE,6BAA6B,CAcpD,CAAC"}
|
package/dist/proxmox/index.js
CHANGED
|
@@ -5239,13 +5239,40 @@ var ProxmoxStorageProvider = () => Provider34.effect(ProxmoxStorage, Effect65.su
|
|
|
5239
5239
|
})));
|
|
5240
5240
|
// src/proxmox/zfs-pool.ts
|
|
5241
5241
|
import { Resource as Resource35 } from "alchemy";
|
|
5242
|
+
import { isResolved as isResolved16 } from "alchemy/Diff";
|
|
5242
5243
|
import * as Provider35 from "alchemy/Provider";
|
|
5243
|
-
import * as
|
|
5244
|
+
import * as Effect68 from "effect/Effect";
|
|
5244
5245
|
|
|
5245
|
-
// src/proxmox/zfs-pool-
|
|
5246
|
+
// src/proxmox/zfs-pool-wire.ts
|
|
5247
|
+
import * as nodes from "@distilled.cloud/proxmox/nodes";
|
|
5246
5248
|
import * as Effect66 from "effect/Effect";
|
|
5247
|
-
var
|
|
5248
|
-
|
|
5249
|
+
var leafDevices = (children) => Array.isArray(children) ? children.flatMap((entry) => {
|
|
5250
|
+
const vdev = entry;
|
|
5251
|
+
const nested = leafDevices(vdev.children);
|
|
5252
|
+
return nested.length > 0 ? nested : [text(vdev.name)].filter((name) => name !== "");
|
|
5253
|
+
}) : [];
|
|
5254
|
+
var attributesOf8 = (live, props) => ({
|
|
5255
|
+
devices: leafDevices(live.children).join(","),
|
|
5256
|
+
errors: text(live.errors, "unknown"),
|
|
5257
|
+
name: props.name,
|
|
5258
|
+
node: props.node,
|
|
5259
|
+
state: text(live.state, "unknown")
|
|
5260
|
+
});
|
|
5261
|
+
var readPoolOrFail = (props) => readOrUnreadable(runPve(props.target, "read", false, nodes.getNodeDiskZfs({ name: props.name, node: props.node }))).pipe(Effect66.map((live) => {
|
|
5262
|
+
if (live === UNREADABLE)
|
|
5263
|
+
return UNREADABLE;
|
|
5264
|
+
return text(live.name) === "" ? undefined : attributesOf8(live, props);
|
|
5265
|
+
}));
|
|
5266
|
+
var readPool = (props) => readPoolOrFail(props).pipe(Effect66.orElseSucceed(() => {
|
|
5267
|
+
return;
|
|
5268
|
+
}));
|
|
5269
|
+
var dropUnreadable7 = (live) => live === UNREADABLE ? undefined : live;
|
|
5270
|
+
|
|
5271
|
+
// src/proxmox/zfs-pool-form.ts
|
|
5272
|
+
import * as Effect67 from "effect/Effect";
|
|
5273
|
+
import * as nodes2 from "@distilled.cloud/proxmox/nodes";
|
|
5274
|
+
var ZFS_POOL_CREATE = "pve:POST /nodes/{node}/disks/zfs";
|
|
5275
|
+
var zfsPoolCreateEndpoint = (props) => props.devices === undefined || props.raidlevel === undefined ? undefined : ZFS_POOL_CREATE;
|
|
5249
5276
|
var createForm11 = (props) => ({
|
|
5250
5277
|
...props.devices === undefined ? {} : { devices: props.devices.join(",") },
|
|
5251
5278
|
name: props.name,
|
|
@@ -5254,68 +5281,72 @@ var createForm11 = (props) => ({
|
|
|
5254
5281
|
...props.compression === undefined ? {} : { compression: props.compression },
|
|
5255
5282
|
...props["draid-config"] === undefined ? {} : { "draid-config": props["draid-config"] }
|
|
5256
5283
|
});
|
|
5284
|
+
var toDistilledCreate2 = (props) => {
|
|
5285
|
+
const form = createForm11(props);
|
|
5286
|
+
return {
|
|
5287
|
+
...form,
|
|
5288
|
+
draid_config: form["draid-config"],
|
|
5289
|
+
node: props.node
|
|
5290
|
+
};
|
|
5291
|
+
};
|
|
5257
5292
|
var SETTLE_ATTEMPTS = 30;
|
|
5258
|
-
var createPool = (props,
|
|
5293
|
+
var createPool = (props, read4) => Effect67.gen(function* () {
|
|
5259
5294
|
const existing = yield* read4(props);
|
|
5260
5295
|
if (existing !== undefined)
|
|
5261
5296
|
return existing;
|
|
5262
5297
|
if (props.devices === undefined || props.raidlevel === undefined) {
|
|
5263
|
-
return yield*
|
|
5298
|
+
return yield* Effect67.die(new Error(`${props.node}/${props.name}: this is an adopt-only declaration (no \`devices\` or ` + "`raidlevel`) and the pool is not there to adopt. An adopt-only Proxmox.ZfsPool never " + "creates -- declare both fields for a pool this resource should build, or point this " + "declaration at a pool that already exists on this node."));
|
|
5264
5299
|
}
|
|
5265
|
-
|
|
5266
|
-
const
|
|
5300
|
+
yield* guardForm(zfsPoolCreateEndpoint(props), asForm2(createForm11(props)), true);
|
|
5301
|
+
const upid = yield* runPve(props.target, "provision", true, nodes2.createNodeDiskZfs(toDistilledCreate2(props)));
|
|
5302
|
+
const settle4 = (attempts) => Effect67.gen(function* () {
|
|
5267
5303
|
const live = yield* read4(props);
|
|
5268
5304
|
if (live !== undefined || attempts <= 0)
|
|
5269
5305
|
return live;
|
|
5270
|
-
yield*
|
|
5306
|
+
yield* Effect67.sleep("2 seconds");
|
|
5271
5307
|
return yield* settle4(attempts - 1);
|
|
5272
5308
|
});
|
|
5273
5309
|
const after = yield* settle4(SETTLE_ATTEMPTS);
|
|
5274
5310
|
if (after === undefined) {
|
|
5275
|
-
return yield*
|
|
5311
|
+
return yield* Effect67.die(new Error(`nodes/${props.node}/disks/zfs/${props.name}: POST returned ${text(upid, "(no UPID)")} ` + "but the pool has not appeared. That POST only forks a `zfscreate` worker, so the task " + `holds the real error -- read it with \`pvesh get /nodes/${props.node}/tasks/<upid>/log\`. ` + "NOTHING WAS RETRIED AND NOTHING WAS CLEANED UP: if the worker reached `zpool create`, " + "the declared devices have already been written to. DO NOT RE-RUN THIS DEPLOY BEFORE " + 'READING THAT LOG: `read` cannot tell "the worker is still running" from "the pool is ' + 'genuinely gone", so a re-run this soon can POST a SECOND `zpool create` at the SAME ' + "devices while the first one is still mid-write -- confirm the task finished (one way " + "or the other) before trying again."));
|
|
5276
5312
|
}
|
|
5277
5313
|
return after;
|
|
5278
5314
|
});
|
|
5279
|
-
var destroyPool = (
|
|
5280
|
-
|
|
5281
|
-
// src/proxmox/zfs-pool-spec.ts
|
|
5282
|
-
var leafDevices = (children) => Array.isArray(children) ? children.flatMap((entry) => {
|
|
5283
|
-
const vdev = entry;
|
|
5284
|
-
const nested = leafDevices(vdev.children);
|
|
5285
|
-
return nested.length > 0 ? nested : [text(vdev.name)].filter((name) => name !== "");
|
|
5286
|
-
}) : [];
|
|
5287
|
-
var spec2 = {
|
|
5288
|
-
attributes: (live, props) => {
|
|
5289
|
-
if (text(live["name"]) === "")
|
|
5290
|
-
return;
|
|
5291
|
-
return {
|
|
5292
|
-
devices: leafDevices(live["children"]).join(","),
|
|
5293
|
-
errors: text(live["errors"], "unknown"),
|
|
5294
|
-
name: props.name,
|
|
5295
|
-
node: props.node,
|
|
5296
|
-
state: text(live["state"], "unknown")
|
|
5297
|
-
};
|
|
5298
|
-
},
|
|
5299
|
-
collection: (props) => `nodes/${props.node}/disks/zfs`,
|
|
5300
|
-
createForm: createForm11,
|
|
5301
|
-
endpoint: zfsPoolEndpoint,
|
|
5302
|
-
matches: () => true,
|
|
5303
|
-
path: (props) => `nodes/${props.node}/disks/zfs/${props.name}`
|
|
5304
|
-
};
|
|
5315
|
+
var destroyPool = (props) => runPve(props.target, "provision", true, nodes2.deleteNodeDiskZfs({ name: props.name, node: props.node }));
|
|
5305
5316
|
|
|
5306
5317
|
// src/proxmox/zfs-pool.ts
|
|
5307
5318
|
var ProxmoxZfsPool = Resource35("Proxmox.ZfsPool", {
|
|
5308
5319
|
defaultRemovalPolicy: "retain"
|
|
5309
5320
|
});
|
|
5310
|
-
var
|
|
5311
|
-
|
|
5312
|
-
|
|
5313
|
-
|
|
5314
|
-
yield* destroyPool(olds, spec2.path(olds));
|
|
5321
|
+
var ProxmoxZfsPoolProvider = () => Provider35.effect(ProxmoxZfsPool, Effect68.succeed(ProxmoxZfsPool.Provider.of({
|
|
5322
|
+
list: () => Effect68.succeed([]),
|
|
5323
|
+
read: Effect68.fn(function* ({ olds, output }) {
|
|
5324
|
+
return dropUnreadable7(yield* output === undefined ? readPool(olds) : readPoolOrFail(olds));
|
|
5315
5325
|
}),
|
|
5316
|
-
|
|
5317
|
-
|
|
5318
|
-
|
|
5326
|
+
diff: Effect68.fn(function* ({ news, output }) {
|
|
5327
|
+
if (!isResolved16(news))
|
|
5328
|
+
return;
|
|
5329
|
+
yield* guardForm(zfsPoolCreateEndpoint(news), asForm2(createForm11(news)), output === undefined);
|
|
5330
|
+
if (output === undefined)
|
|
5331
|
+
return;
|
|
5332
|
+
const live = yield* readPoolOrFail(news);
|
|
5333
|
+
if (live === UNREADABLE) {
|
|
5334
|
+
yield* unreadableWarning("Proxmox.ZfsPool", `${news.node}/${news.name}`);
|
|
5335
|
+
return { action: "noop" };
|
|
5336
|
+
}
|
|
5337
|
+
if (live === undefined) {
|
|
5338
|
+
yield* guardForm(zfsPoolCreateEndpoint(news), asForm2(createForm11(news)), true);
|
|
5339
|
+
return { action: "update" };
|
|
5340
|
+
}
|
|
5341
|
+
return { action: "noop" };
|
|
5342
|
+
}),
|
|
5343
|
+
reconcile: Effect68.fn(function* ({ news }) {
|
|
5344
|
+
return yield* createPool(news, (props) => readPool(props).pipe(Effect68.map(dropUnreadable7)));
|
|
5345
|
+
}),
|
|
5346
|
+
delete: Effect68.fn(function* ({ olds }) {
|
|
5347
|
+
yield* destroyPool(olds);
|
|
5348
|
+
})
|
|
5349
|
+
})));
|
|
5319
5350
|
export {
|
|
5320
5351
|
PROVISION_DEFAULTS,
|
|
5321
5352
|
PROVISION_PRIVILEGES,
|
|
@@ -5392,5 +5423,5 @@ export {
|
|
|
5392
5423
|
violations
|
|
5393
5424
|
};
|
|
5394
5425
|
|
|
5395
|
-
//# debugId=
|
|
5426
|
+
//# debugId=D100179D867AAA0364756E2164756E21
|
|
5396
5427
|
//# sourceMappingURL=index.js.map
|