@homeflare/alchemy 0.19.0 → 0.20.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/linux/index.js +140 -93
- package/dist/linux/index.js.map +11 -10
- package/dist/linux/region.d.ts +11 -0
- package/dist/linux/region.d.ts.map +1 -1
- package/dist/linux/remote-file-lifecycle.d.ts.map +1 -1
- package/dist/linux/remote-file-plan.d.ts +22 -0
- package/dist/linux/remote-file-plan.d.ts.map +1 -1
- package/dist/linux/remote-file.d.ts +4 -1
- package/dist/linux/remote-file.d.ts.map +1 -1
- package/dist/linux/unit-handlers.d.ts.map +1 -1
- package/dist/linux/unit-lifecycle.d.ts +1 -5
- package/dist/linux/unit-lifecycle.d.ts.map +1 -1
- package/dist/linux/unit-preflight.d.ts +50 -0
- package/dist/linux/unit-preflight.d.ts.map +1 -0
- package/dist/proxmox/index.d.ts +15 -3
- package/dist/proxmox/index.d.ts.map +1 -1
- package/dist/proxmox/index.js +4 -1
- package/dist/proxmox/index.js.map +1 -1
- package/docs/adopted-deploys.md +13 -0
- package/docs/linux-host.md +11 -1
- package/docs/provider-standard.md +138 -0
- package/docs/upstream-conformance.md +164 -0
- package/package.json +1 -1
- package/src/linux/region.ts +15 -0
- package/src/linux/remote-file-lifecycle.test.ts +0 -88
- package/src/linux/remote-file-lifecycle.ts +25 -7
- package/src/linux/remote-file-plan.ts +37 -1
- package/src/linux/remote-file-region.test.ts +181 -0
- package/src/linux/remote-file.ts +4 -1
- package/src/linux/unit-handlers.ts +12 -5
- package/src/linux/unit-lifecycle.ts +12 -30
- package/src/linux/unit-preflight.test.ts +128 -0
- package/src/linux/unit-preflight.ts +116 -0
- package/src/linux/unit-rename.test.ts +76 -0
- package/src/proxmox/ceph-adopt.test.ts +210 -0
- package/src/proxmox/constraints-forms.test.ts +14 -6
- package/src/proxmox/constraints-node-forms.test.ts +12 -3
- package/src/proxmox/index.test.ts +7 -4
- package/src/proxmox/index.ts +15 -3
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The three Ceph Resources the barrel exports since 2026-09-22 — `CephDaemon`, `CephFs`,
|
|
3
|
+
* `CephOsd` — adopted and then undeclared through Alchemy's own Plan and Apply, over a fake cluster.
|
|
4
|
+
*
|
|
5
|
+
* ★ WHY THESE THREE COULD JOIN THE BARREL AT ALL: each is adopt-only BY SHAPE, and this file is
|
|
6
|
+
* the proof rather than the claim. CephDaemon and CephFs compare nothing (`matches: () => true`),
|
|
7
|
+
* so a present object is always `noop` and never a replace; CephOsd reads the CRUSH tree and
|
|
8
|
+
* POSTs only when `dev` is declared, which nothing here does. All three default to `retain`.
|
|
9
|
+
* ⛔ WHAT A MISTAKE HERE WOULD COST ON A REAL CLUSTER: a mon DELETE loses a quorum member, a CephFS
|
|
10
|
+
* DELETE breaks every mount, an OSD DELETE starts a rebalance. So every test ends on the same
|
|
11
|
+
* assertion — `fake.writes()` is empty — and one row proves the harness can still see a refusal.
|
|
12
|
+
* ★ THE FIXTURE IS C1, THE KIT'S REFERENCE CLUSTER (docs/proxmox.md): three nodes, a mon, mgr and
|
|
13
|
+
* mds on each, one CephFS, two ssd OSDs per node — shapes and counts measured, names and
|
|
14
|
+
* addresses placeholders (RFC 5737). The rows carry only the keys the families read.
|
|
15
|
+
*/
|
|
16
|
+
import { describe, expect, test } from 'bun:test';
|
|
17
|
+
import * as Effect from 'effect/Effect';
|
|
18
|
+
import * as Layer from 'effect/Layer';
|
|
19
|
+
import { engineOver } from '../verify/fake-engine.ts';
|
|
20
|
+
import {
|
|
21
|
+
type CephDaemonKind,
|
|
22
|
+
ProxmoxCephDaemon,
|
|
23
|
+
ProxmoxCephDaemonProvider,
|
|
24
|
+
} from './ceph-daemon.ts';
|
|
25
|
+
import { ProxmoxCephFs, ProxmoxCephFsProvider } from './ceph-fs.ts';
|
|
26
|
+
import { ProxmoxCephOsd, ProxmoxCephOsdProvider } from './ceph-osd.ts';
|
|
27
|
+
import { FAKE_TARGET, type PveCall, fakePve, withoutBao } from './fake-pve.ts';
|
|
28
|
+
|
|
29
|
+
const NODES = ['node-b', 'node-c', 'node-d'] as const;
|
|
30
|
+
const KINDS: readonly CephDaemonKind[] = ['mon', 'mgr', 'mds'];
|
|
31
|
+
const ip = (node: string) => `198.51.100.${String(12 + NODES.indexOf(node as never))}`;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* One collection answer per kind. ⚠️ Every node answers the SAME cluster-wide list — measured on
|
|
35
|
+
* C1, and the reason ceph-daemon-form.ts matches rows by `name` and never by position.
|
|
36
|
+
*/
|
|
37
|
+
const daemons: Record<CephDaemonKind, Record<string, unknown>[]> = {
|
|
38
|
+
mds: NODES.map((node) => ({
|
|
39
|
+
addr: `${ip(node)}:6801/1`,
|
|
40
|
+
ceph_version_short: '20.2.2',
|
|
41
|
+
host: node,
|
|
42
|
+
name: node,
|
|
43
|
+
rank: node === 'node-c' ? 0 : -1,
|
|
44
|
+
service: true,
|
|
45
|
+
standby_replay: false,
|
|
46
|
+
state: node === 'node-c' ? 'up:active' : 'up:standby',
|
|
47
|
+
...(node === 'node-c' ? { fs_name: 'cephfs-c1' } : {}),
|
|
48
|
+
})),
|
|
49
|
+
mgr: NODES.map((node) => ({
|
|
50
|
+
addr: ip(node),
|
|
51
|
+
host: node,
|
|
52
|
+
name: node,
|
|
53
|
+
service: true,
|
|
54
|
+
state: node === 'node-b' ? 'active' : 'standby',
|
|
55
|
+
})),
|
|
56
|
+
mon: NODES.map((node, rank) => ({
|
|
57
|
+
addr: `${ip(node)}:6789/0`,
|
|
58
|
+
host: node,
|
|
59
|
+
name: node,
|
|
60
|
+
quorum: 1,
|
|
61
|
+
rank,
|
|
62
|
+
service: true,
|
|
63
|
+
state: 'running',
|
|
64
|
+
})),
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
const fs = [
|
|
68
|
+
{
|
|
69
|
+
data_pool: 'cephfs-c1_data',
|
|
70
|
+
data_pools: ['cephfs-c1_data'],
|
|
71
|
+
metadata_pool: 'cephfs-c1_metadata',
|
|
72
|
+
metadata_pool_id: 7,
|
|
73
|
+
name: 'cephfs-c1',
|
|
74
|
+
},
|
|
75
|
+
];
|
|
76
|
+
|
|
77
|
+
/** osd.{2,3} on node-b, osd.{1,4} on node-c, osd.{0,5} on node-d — C1's placement. */
|
|
78
|
+
const OSDS: readonly (readonly [number, string])[] = [
|
|
79
|
+
[2, 'node-b'],
|
|
80
|
+
[3, 'node-b'],
|
|
81
|
+
[1, 'node-c'],
|
|
82
|
+
[4, 'node-c'],
|
|
83
|
+
[0, 'node-d'],
|
|
84
|
+
[5, 'node-d'],
|
|
85
|
+
];
|
|
86
|
+
|
|
87
|
+
/** ⚠️ The tree spells ids as STRINGS (`"2"`), exactly as measured; ceph-osd-tree.ts narrows them. */
|
|
88
|
+
const tree = {
|
|
89
|
+
flags: 'sortbitwise,recovery_deletes,purged_snapdirs,pglog_hardlimit',
|
|
90
|
+
root: {
|
|
91
|
+
children: [
|
|
92
|
+
{
|
|
93
|
+
children: NODES.map((host) => ({
|
|
94
|
+
children: OSDS.filter(([, on]) => on === host).map(([id]) => ({
|
|
95
|
+
device_class: 'ssd',
|
|
96
|
+
host,
|
|
97
|
+
id: String(id),
|
|
98
|
+
in: 1,
|
|
99
|
+
leaf: 1,
|
|
100
|
+
name: `osd.${String(id)}`,
|
|
101
|
+
osdtype: 'bluestore',
|
|
102
|
+
status: 'up',
|
|
103
|
+
type: 'osd',
|
|
104
|
+
})),
|
|
105
|
+
name: host,
|
|
106
|
+
type: 'host',
|
|
107
|
+
})),
|
|
108
|
+
name: 'default',
|
|
109
|
+
type: 'root',
|
|
110
|
+
},
|
|
111
|
+
],
|
|
112
|
+
leaf: 0,
|
|
113
|
+
},
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
/** GETs answer from the fixture; anything else is recorded and answered like a task that ran. */
|
|
117
|
+
const cluster = () =>
|
|
118
|
+
fakePve((call: PveCall) => {
|
|
119
|
+
if (call.method !== 'GET') return 'UPID:node-b:fake';
|
|
120
|
+
const hit = /^nodes\/[^/]+\/ceph\/(mon|mgr|mds|fs|osd)$/.exec(call.path)?.[1];
|
|
121
|
+
if (hit === 'fs') return fs;
|
|
122
|
+
if (hit === 'osd') return tree;
|
|
123
|
+
return hit === undefined ? undefined : daemons[hit as CephDaemonKind];
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
const providers = Layer.mergeAll(
|
|
127
|
+
ProxmoxCephDaemonProvider(),
|
|
128
|
+
ProxmoxCephFsProvider(),
|
|
129
|
+
ProxmoxCephOsdProvider(),
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Every live Ceph object of the three families, declared the way a consuming stack would: each
|
|
134
|
+
* daemon on the node it runs on, the filesystem asked of any node, each OSD's failure domain and
|
|
135
|
+
* device class asserted.
|
|
136
|
+
*/
|
|
137
|
+
const everything = (osdHost: (host: string) => string = (host) => host) =>
|
|
138
|
+
Effect.all([
|
|
139
|
+
...KINDS.flatMap((kind) =>
|
|
140
|
+
NODES.map((node) =>
|
|
141
|
+
ProxmoxCephDaemon(`${kind}-${node}`, { kind, node, target: FAKE_TARGET }),
|
|
142
|
+
),
|
|
143
|
+
),
|
|
144
|
+
ProxmoxCephFs('cephfs-c1', { name: 'cephfs-c1', node: 'node-b', target: FAKE_TARGET }),
|
|
145
|
+
...OSDS.map(([osdid, host]) =>
|
|
146
|
+
ProxmoxCephOsd(`osd-${String(osdid)}`, {
|
|
147
|
+
device_class: 'ssd',
|
|
148
|
+
host: osdHost(host),
|
|
149
|
+
node: 'node-b',
|
|
150
|
+
osdid,
|
|
151
|
+
target: FAKE_TARGET,
|
|
152
|
+
}),
|
|
153
|
+
),
|
|
154
|
+
]);
|
|
155
|
+
|
|
156
|
+
const engineFor = (fake: ReturnType<typeof cluster>) =>
|
|
157
|
+
engineOver(providers.pipe(Layer.provideMerge(fake.layer)));
|
|
158
|
+
|
|
159
|
+
describe('adopting the live Ceph daemons, filesystem and OSDs', () => {
|
|
160
|
+
test('every row is noop to the verifier, adopted by the deploy, and only read', async () => {
|
|
161
|
+
const fake = cluster();
|
|
162
|
+
await withoutBao(async () => {
|
|
163
|
+
const engine = engineFor(fake);
|
|
164
|
+
const report = await engine.verify(everything());
|
|
165
|
+
expect(report.rows).toHaveLength(16);
|
|
166
|
+
for (const row of report.rows) {
|
|
167
|
+
expect(row).toMatchObject({ changed: [], diff: 'noop', ok: true, planned: 'adopted' });
|
|
168
|
+
}
|
|
169
|
+
const planned = await engine.deploy(everything());
|
|
170
|
+
expect(new Set(Object.values(planned))).toEqual(new Set(['adopted']));
|
|
171
|
+
expect(Object.keys(planned)).toHaveLength(16);
|
|
172
|
+
});
|
|
173
|
+
expect(fake.writes()).toEqual([]);
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* ⛔ THE ONE THAT MATTERS MOST. `retain` is the whole reason a deleted line is not a lost mon, a
|
|
178
|
+
* destroyed filesystem or a pulled replica; Alchemy plans the removal `orphaned` and must drop
|
|
179
|
+
* only its own state row.
|
|
180
|
+
*/
|
|
181
|
+
test('undeclaring all sixteen drops state rows and sends no DELETE', async () => {
|
|
182
|
+
const fake = cluster();
|
|
183
|
+
await withoutBao(async () => {
|
|
184
|
+
const engine = engineFor(fake);
|
|
185
|
+
await engine.deploy(everything());
|
|
186
|
+
expect(engine.stored()).toContain('mon-node-b');
|
|
187
|
+
// ⚠️ `deploy` reports planned resources only; a removal lives in the plan's `deletions`, so
|
|
188
|
+
// the proof is the state store emptying and the cluster seeing no write.
|
|
189
|
+
expect(await engine.deploy(Effect.void)).toEqual({});
|
|
190
|
+
for (const fqn of ['mon-node-b', 'mds-node-c', 'cephfs-c1', 'osd-0']) {
|
|
191
|
+
expect(engine.stored()).not.toContain(`"${fqn}"`);
|
|
192
|
+
}
|
|
193
|
+
});
|
|
194
|
+
expect(fake.writes()).toEqual([]);
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
test('an OSD asserted on the wrong host fails the verifier and the deploy, and writes nothing', async () => {
|
|
198
|
+
const fake = cluster();
|
|
199
|
+
const moved = (host: string) => (host === 'node-b' ? 'node-c' : host);
|
|
200
|
+
await withoutBao(async () => {
|
|
201
|
+
const engine = engineFor(fake);
|
|
202
|
+
const report = await engine.verify(everything(moved));
|
|
203
|
+
const wrong = report.rows.filter((row) => !row.ok).map((row) => row.fqn);
|
|
204
|
+
expect(wrong.toSorted()).toEqual(['osd-2', 'osd-3']);
|
|
205
|
+
expect(report.rows.find((row) => row.fqn === 'osd-2')?.changed).toEqual(['host']);
|
|
206
|
+
await expect(engine.deploy(everything(moved))).rejects.toBeDefined();
|
|
207
|
+
});
|
|
208
|
+
expect(fake.writes()).toEqual([]);
|
|
209
|
+
});
|
|
210
|
+
});
|
|
@@ -58,17 +58,25 @@ describe('Proxmox.ApiToken plans clean against POST /access/users/{userid}/token
|
|
|
58
58
|
});
|
|
59
59
|
|
|
60
60
|
describe('Proxmox.MetricServer plans clean against POST /cluster/metrics/server/{id}', () => {
|
|
61
|
-
/**
|
|
61
|
+
/**
|
|
62
|
+
* The estate's own row, SHAPED: InfluxDB v2 over https with an api-path-prefix, an
|
|
63
|
+
* organization and a bucket, every value a placeholder.
|
|
64
|
+
*
|
|
65
|
+
* ⛔ SHAPE ONLY, FOR THE REASON lxc-harness.ts GIVES. `src` ships in the npm tarball and this
|
|
66
|
+
* repository is public, so a real metrics host, cluster name or organization here would be
|
|
67
|
+
* estate topology published forever in the git history. The proof is about which KEYS the
|
|
68
|
+
* create form sends and which bounds they face; the strings carry none of it.
|
|
69
|
+
*/
|
|
62
70
|
const declared = {
|
|
63
|
-
'api-path-prefix': '/
|
|
71
|
+
'api-path-prefix': '/cluster-a/',
|
|
64
72
|
bucket: 'pve',
|
|
65
73
|
disable: false,
|
|
66
|
-
id: '
|
|
74
|
+
id: 'metrics-example',
|
|
67
75
|
influxdbproto: 'https',
|
|
68
76
|
'max-body-size': 10_000_000,
|
|
69
|
-
organization: '
|
|
77
|
+
organization: 'example',
|
|
70
78
|
port: 443,
|
|
71
|
-
server: 'metrics.
|
|
79
|
+
server: 'metrics.example.invalid',
|
|
72
80
|
target: TARGET,
|
|
73
81
|
type: 'influxdb',
|
|
74
82
|
'verify-certificate': true,
|
|
@@ -102,7 +110,7 @@ describe('Proxmox.MetricServer plans clean against POST /cluster/metrics/server/
|
|
|
102
110
|
});
|
|
103
111
|
|
|
104
112
|
describe('Proxmox.NotificationTarget plans clean per family', () => {
|
|
105
|
-
/** The
|
|
113
|
+
/** The shipped `mail-to-root`, edited — the shape every estate has. sendmail needs only a name. */
|
|
106
114
|
test('the live sendmail declaration has no violations', () => {
|
|
107
115
|
expect(
|
|
108
116
|
onCreate(
|
|
@@ -50,7 +50,12 @@ describe('the Ceph families plan clean against their own creates', () => {
|
|
|
50
50
|
expect(
|
|
51
51
|
onCreate(
|
|
52
52
|
'pve:POST /nodes/{node}/ceph/fs/{name}',
|
|
53
|
-
cephFsCreateForm({
|
|
53
|
+
cephFsCreateForm({
|
|
54
|
+
'add-storage': true,
|
|
55
|
+
name: 'cephfs-example',
|
|
56
|
+
node: 'n2',
|
|
57
|
+
target: TARGET,
|
|
58
|
+
}),
|
|
54
59
|
),
|
|
55
60
|
).toEqual([]);
|
|
56
61
|
expect(constraintsFor('pve:POST /nodes/{node}/ceph/fs/{name}')['pg_num']).toMatchObject({
|
|
@@ -69,9 +74,13 @@ describe('the Ceph families plan clean against their own creates', () => {
|
|
|
69
74
|
expect(constraintsFor('pve:POST /nodes/{node}/ceph/osd')['dev']?.required).toBe(true);
|
|
70
75
|
});
|
|
71
76
|
|
|
72
|
-
/**
|
|
77
|
+
/**
|
|
78
|
+
* The estate's own pool set, SHAPED: an RBD pool and a CephFS data/metadata pair, size 3 and
|
|
79
|
+
* min_size 2, names replaced. ⛔ `src` ships in the npm tarball and this repository is public —
|
|
80
|
+
* lxc-harness.ts has the rule. The proof is the keys and the bounds, never the strings.
|
|
81
|
+
*/
|
|
73
82
|
test('the live CephPool declarations have no violations', () => {
|
|
74
|
-
for (const name of ['
|
|
83
|
+
for (const name of ['rbd-example', 'cephfs-example_data', 'cephfs-example_metadata']) {
|
|
75
84
|
expect(
|
|
76
85
|
onCreate(
|
|
77
86
|
'pve:POST /nodes/{node}/ceph/pool',
|
|
@@ -4,6 +4,9 @@
|
|
|
4
4
|
* ⛔ Vm stays Provider-only. NodeNetwork/NetworkApply stay off the barrel — they stage
|
|
5
5
|
* interfaces.new. ★ Lxc is a Resource since 2026-09-21: adopt-first, refuse-not-replace,
|
|
6
6
|
* retain on destroy (lxc.ts).
|
|
7
|
+
* ★ CephDaemon, CephFs and CephOsd are Resources since 2026-09-22: adopt-only by shape and retain
|
|
8
|
+
* on destroy (ceph-adopt.test.ts). ⛔ CephFlag stays Provider-only: a declared flag reasserts a
|
|
9
|
+
* maintenance toggle on every deploy (ceph-flag.ts).
|
|
7
10
|
*/
|
|
8
11
|
import { expect, test } from 'bun:test';
|
|
9
12
|
import { PbsDatastore, ProxmoxStorage } from './index.ts';
|
|
@@ -19,6 +22,9 @@ const resourceExports = [
|
|
|
19
22
|
'PbsVerifyJob',
|
|
20
23
|
'ProxmoxAcl',
|
|
21
24
|
'ProxmoxBackupJob',
|
|
25
|
+
'ProxmoxCephDaemon',
|
|
26
|
+
'ProxmoxCephFs',
|
|
27
|
+
'ProxmoxCephOsd',
|
|
22
28
|
'ProxmoxGroup',
|
|
23
29
|
'ProxmoxHaResource',
|
|
24
30
|
'ProxmoxHaRule',
|
|
@@ -50,14 +56,11 @@ test('the provisioning baseline is on the barrel: the list, the declaration, the
|
|
|
50
56
|
expect(barrel.provisionBootstrap()).toStartWith('#!/bin/sh\n');
|
|
51
57
|
});
|
|
52
58
|
|
|
53
|
-
test('QEMU
|
|
59
|
+
test('QEMU, NIC apply and the Ceph flag stay Provider-only', () => {
|
|
54
60
|
expect(src).not.toMatch(/export \{ ProxmoxVm[, }]/);
|
|
55
61
|
expect(src).not.toMatch(/export \{ ProxmoxNodeNetwork[, }]/);
|
|
56
62
|
expect(src).not.toMatch(/export \{ ProxmoxNetworkApply[, }]/);
|
|
57
63
|
expect(src).not.toMatch(/export \{ ProxmoxApiToken[, }]/);
|
|
58
64
|
expect(src).not.toMatch(/export \{ ProxmoxZfsPool[, }]/);
|
|
59
|
-
expect(src).not.toMatch(/export \{ ProxmoxCephOsd[, }]/);
|
|
60
|
-
expect(src).not.toMatch(/export \{ ProxmoxCephFs[, }]/);
|
|
61
|
-
expect(src).not.toMatch(/export \{ ProxmoxCephDaemon[, }]/);
|
|
62
65
|
expect(src).not.toMatch(/export \{ ProxmoxCephFlag[, }]/);
|
|
63
66
|
});
|
package/src/proxmox/index.ts
CHANGED
|
@@ -21,6 +21,18 @@
|
|
|
21
21
|
* ★ THE VENDOR CONSTRAINT READER JOINED 2026-09-22 (codegen/README.md). `constraintsFor` and
|
|
22
22
|
* `violations` are exported so a CONSUMING stack can assert its own declarations offline — the
|
|
23
23
|
* `deploy:pbs` that failed on a 128-character comment had no way to ask that question.
|
|
24
|
+
* ★ THE CEPH DAEMON, FILESYSTEM AND OSD RESOURCES JOINED 2026-09-22, BECAUSE EACH IS ALREADY
|
|
25
|
+
* ADOPT-ONLY BY SHAPE and a live cluster measured that way was waiting on nothing but this line.
|
|
26
|
+
* `ProxmoxCephDaemon` (mon/mgr/mds) and `ProxmoxCephFs` compare nothing, so no edit can plan a
|
|
27
|
+
* replace; `ProxmoxCephOsd` never POSTs unless `dev` is declared. All three default to `retain`,
|
|
28
|
+
* so deleting a declaration drops a state row and leaves the quorum member, the filesystem or
|
|
29
|
+
* the replica alone. ceph-adopt.test.ts pins adopting each as reads only, and removing each as
|
|
30
|
+
* no DELETE, through Alchemy's own Plan and Apply.
|
|
31
|
+
* ⛔ `ProxmoxCephFlag` STAYS PROVIDER-ONLY, AND ON PURPOSE. A flag is a hand on the brake, not
|
|
32
|
+
* configuration: a declared `noout: false` clears an operator's `noout` on EVERY deploy,
|
|
33
|
+
* including one run for an unrelated resource in the middle of a maintenance window
|
|
34
|
+
* (ceph-flag.ts has the whole argument). A stack that wants a flag set must reach for the
|
|
35
|
+
* Provider deliberately; the barrel does not offer the sentence by default.
|
|
24
36
|
* ★ Anything else unlisted is still reachable by path if you genuinely need
|
|
25
37
|
* it — that is a deliberate, visible act rather than an accident of barrelling.
|
|
26
38
|
*/
|
|
@@ -28,10 +40,10 @@ export { ProxmoxAcl, ProxmoxAclProvider } from './acl.ts';
|
|
|
28
40
|
export { type AlertmanagerBodyOptions, alertmanagerAlertBody } from './alertmanager-body.ts';
|
|
29
41
|
export { ProxmoxApiTokenProvider } from './api-token.ts';
|
|
30
42
|
export { ProxmoxBackupJob, ProxmoxBackupJobProvider } from './backup-job.ts';
|
|
31
|
-
export { ProxmoxCephDaemonProvider } from './ceph-daemon.ts';
|
|
43
|
+
export { ProxmoxCephDaemon, ProxmoxCephDaemonProvider } from './ceph-daemon.ts';
|
|
32
44
|
export { ProxmoxCephFlagProvider } from './ceph-flag.ts';
|
|
33
|
-
export { ProxmoxCephFsProvider } from './ceph-fs.ts';
|
|
34
|
-
export { ProxmoxCephOsdProvider } from './ceph-osd.ts';
|
|
45
|
+
export { ProxmoxCephFs, ProxmoxCephFsProvider } from './ceph-fs.ts';
|
|
46
|
+
export { ProxmoxCephOsd, ProxmoxCephOsdProvider } from './ceph-osd.ts';
|
|
35
47
|
export { ProxmoxCephPool, ProxmoxCephPoolProvider } from './ceph-pool.ts';
|
|
36
48
|
export { constraintsFor, formViolations } from './constraint-guard.ts';
|
|
37
49
|
export {
|