@homeflare/alchemy 0.13.0 → 0.15.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/README.md +7 -0
- package/dist/github/index.d.ts +18 -0
- package/dist/github/index.d.ts.map +1 -0
- package/dist/github/index.js +161 -0
- package/dist/github/index.js.map +12 -0
- package/dist/github/repo-policy-form.d.ts +86 -0
- package/dist/github/repo-policy-form.d.ts.map +1 -0
- package/dist/github/repo-policy-guards.d.ts +72 -0
- package/dist/github/repo-policy-guards.d.ts.map +1 -0
- package/dist/github/repo-policy.d.ts +21 -0
- package/dist/github/repo-policy.d.ts.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/openbao/index.d.ts +10 -3
- package/dist/openbao/index.d.ts.map +1 -1
- package/dist/proxmox/acl.d.ts.map +1 -1
- package/dist/proxmox/backup-job.d.ts.map +1 -1
- package/dist/proxmox/constraint-guard.d.ts +28 -0
- package/dist/proxmox/constraint-guard.d.ts.map +1 -0
- package/dist/proxmox/constraints.d.ts +71 -0
- package/dist/proxmox/constraints.d.ts.map +1 -0
- package/dist/proxmox/firewall-alias.d.ts.map +1 -1
- package/dist/proxmox/generated/constraints/index.d.ts +19 -0
- package/dist/proxmox/generated/constraints/index.d.ts.map +1 -0
- package/dist/proxmox/generated/constraints/pbs-config.d.ts +18 -0
- package/dist/proxmox/generated/constraints/pbs-config.d.ts.map +1 -0
- package/dist/proxmox/generated/constraints/pve-access.d.ts +18 -0
- package/dist/proxmox/generated/constraints/pve-access.d.ts.map +1 -0
- package/dist/proxmox/generated/constraints/pve-cluster.d.ts +18 -0
- package/dist/proxmox/generated/constraints/pve-cluster.d.ts.map +1 -0
- package/dist/proxmox/generated/constraints/pve-pools.d.ts +18 -0
- package/dist/proxmox/generated/constraints/pve-pools.d.ts.map +1 -0
- package/dist/proxmox/generated/constraints/pve-storage.d.ts +18 -0
- package/dist/proxmox/generated/constraints/pve-storage.d.ts.map +1 -0
- package/dist/proxmox/group.d.ts.map +1 -1
- package/dist/proxmox/ha-resource.d.ts.map +1 -1
- package/dist/proxmox/ha-rule.d.ts.map +1 -1
- package/dist/proxmox/index.d.ts +6 -1
- package/dist/proxmox/index.d.ts.map +1 -1
- package/dist/proxmox/index.js +816 -257
- package/dist/proxmox/index.js.map +33 -24
- package/dist/proxmox/notification-matcher.d.ts.map +1 -1
- package/dist/proxmox/pbs-datastore-endpoint.d.ts +22 -0
- package/dist/proxmox/pbs-datastore-endpoint.d.ts.map +1 -0
- package/dist/proxmox/pbs-datastore.d.ts.map +1 -1
- package/dist/proxmox/pbs-notification-matcher.d.ts.map +1 -1
- package/dist/proxmox/pbs-prune-job.d.ts.map +1 -1
- package/dist/proxmox/pbs-sync-job-form.d.ts.map +1 -1
- package/dist/proxmox/pbs-verify-job.d.ts.map +1 -1
- package/dist/proxmox/pool.d.ts.map +1 -1
- package/dist/proxmox/provision-baseline.d.ts +30 -5
- package/dist/proxmox/provision-baseline.d.ts.map +1 -1
- package/dist/proxmox/replication-job.d.ts.map +1 -1
- package/dist/proxmox/resource-spec.d.ts +120 -0
- package/dist/proxmox/resource-spec.d.ts.map +1 -0
- package/dist/proxmox/resource.d.ts +10 -91
- package/dist/proxmox/resource.d.ts.map +1 -1
- package/dist/proxmox/role.d.ts.map +1 -1
- package/dist/proxmox/sdn-vnet.d.ts.map +1 -1
- package/dist/proxmox/sdn-zone.d.ts.map +1 -1
- package/dist/proxmox/storage.d.ts.map +1 -1
- package/dist/proxmox/user.d.ts.map +1 -1
- package/docs/provision-baseline.md +34 -7
- package/docs/proxmox-lxc.md +40 -0
- package/docs/repo-policy-ruleset-hazards.md +65 -0
- package/docs/repo-policy.md +155 -0
- package/package.json +5 -1
- package/src/github/index.ts +23 -0
- package/src/github/repo-policy-form.test.ts +239 -0
- package/src/github/repo-policy-form.ts +173 -0
- package/src/github/repo-policy-guards.ts +179 -0
- package/src/github/repo-policy.test.ts +58 -0
- package/src/github/repo-policy.ts +65 -0
- package/src/index.ts +1 -0
- package/src/openbao/index-types.test.ts +87 -0
- package/src/openbao/index.ts +15 -3
- package/src/proxmox/acl.ts +2 -0
- package/src/proxmox/backup-job.ts +2 -0
- package/src/proxmox/constraint-guard.ts +53 -0
- package/src/proxmox/constraints.test.ts +260 -0
- package/src/proxmox/constraints.ts +135 -0
- package/src/proxmox/firewall-alias.ts +5 -0
- package/src/proxmox/generated/constraints/index.ts +31 -0
- package/src/proxmox/generated/constraints/pbs-config.ts +141 -0
- package/src/proxmox/generated/constraints/pve-access.ts +57 -0
- package/src/proxmox/generated/constraints/pve-cluster.ts +174 -0
- package/src/proxmox/generated/constraints/pve-pools.ts +26 -0
- package/src/proxmox/generated/constraints/pve-storage.ts +75 -0
- package/src/proxmox/group.ts +2 -0
- package/src/proxmox/ha-resource.ts +5 -0
- package/src/proxmox/ha-rule.ts +2 -0
- package/src/proxmox/index.ts +12 -0
- package/src/proxmox/lxc-interior.test.ts +61 -0
- package/src/proxmox/notification-matcher.ts +5 -0
- package/src/proxmox/pbs-datastore-endpoint.ts +29 -0
- package/src/proxmox/pbs-datastore.ts +8 -1
- package/src/proxmox/pbs-notification-matcher.ts +5 -0
- package/src/proxmox/pbs-prune-job.ts +2 -0
- package/src/proxmox/pbs-sync-job-form.ts +2 -0
- package/src/proxmox/pbs-verify-job.ts +2 -0
- package/src/proxmox/pool.ts +2 -0
- package/src/proxmox/provision-baseline.test.ts +38 -0
- package/src/proxmox/provision-baseline.ts +73 -14
- package/src/proxmox/provision-bootstrap.test.ts +51 -0
- package/src/proxmox/replication-job.ts +5 -0
- package/src/proxmox/resource-spec.ts +117 -0
- package/src/proxmox/resource.ts +50 -91
- package/src/proxmox/role.ts +2 -0
- package/src/proxmox/sdn-vnet.ts +2 -0
- package/src/proxmox/sdn-zone.ts +2 -0
- package/src/proxmox/storage.ts +2 -0
- package/src/proxmox/user.ts +2 -0
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generated pve-manager parameter constraints for `/cluster` — DO NOT EDIT BY HAND.
|
|
3
|
+
*
|
|
4
|
+
* Run: bun codegen/constraints.ts
|
|
5
|
+
* Manifest entry: `pve-apidoc` — pve-manager 9.2.11/f6997e698c7933ea
|
|
6
|
+
* sha256 9def8f13611184ee, read on a PVE cluster node from
|
|
7
|
+
* /usr/share/pve-docs/api-viewer/apidoc.js
|
|
8
|
+
*
|
|
9
|
+
* 27 of this product's 258 POST/PUT endpoints are tabled across all areas: the ones
|
|
10
|
+
* this package writes to, named in its own source. Every other vendor write endpoint is UNTABLED
|
|
11
|
+
* and therefore unchecked at plan time.
|
|
12
|
+
*
|
|
13
|
+
* ⚠️ A `patternSource` with no `pattern` beside it is a rule that could NOT be carried into a
|
|
14
|
+
* JavaScript RegExp faithfully (codegen/pattern.ts). It is recorded and NOT enforced.
|
|
15
|
+
*/
|
|
16
|
+
import type { EndpointConstraints } from '../../constraints.ts';
|
|
17
|
+
|
|
18
|
+
export const PVE_CLUSTER_CONSTRAINTS: Readonly<Record<string, EndpointConstraints>> = {
|
|
19
|
+
"pve:POST /cluster/backup": {
|
|
20
|
+
"bwlimit": {"default":"0","minimum":0,"type":"integer"},
|
|
21
|
+
"comment": {"maxLength":512,"type":"string"},
|
|
22
|
+
"compress": {"default":"0","enum":["0","1","gzip","lzo","zstd"],"type":"string"},
|
|
23
|
+
"dow": {"default":"mon,tue,wed,thu,fri,sat,sun","format":"pve-day-of-week-list","type":"string"},
|
|
24
|
+
"exclude": {"format":"pve-vmid-list","type":"string"},
|
|
25
|
+
"fleecing": {"format":"backup-fleecing","type":"string"},
|
|
26
|
+
"id": {"format":"pve-configid","type":"string"},
|
|
27
|
+
"ionice": {"default":"7","maximum":8,"minimum":0,"type":"integer"},
|
|
28
|
+
"lockwait": {"default":"180","minimum":0,"type":"integer"},
|
|
29
|
+
"mailnotification": {"default":"always","enum":["always","failure"],"type":"string"},
|
|
30
|
+
"mailto": {"format":"email-or-username-list","type":"string"},
|
|
31
|
+
"mode": {"default":"snapshot","enum":["snapshot","suspend","stop"],"type":"string"},
|
|
32
|
+
"node": {"format":"pve-node","type":"string"},
|
|
33
|
+
"notes-template": {"maxLength":1024,"type":"string"},
|
|
34
|
+
"notification-mode": {"default":"auto","enum":["auto","legacy-sendmail","notification-system"],"type":"string"},
|
|
35
|
+
"pbs-change-detection-mode": {"enum":["legacy","data","metadata"],"type":"string"},
|
|
36
|
+
"performance": {"format":"backup-performance","type":"string"},
|
|
37
|
+
"prune-backups": {"default":"keep-all=1","format":"prune-backups","type":"string"},
|
|
38
|
+
"schedule": {"format":"pve-calendar-event","maxLength":128,"type":"string"},
|
|
39
|
+
"starttime": {"pattern":"\\d{1,2}:\\d{1,2}","patternSource":"\\d{1,2}:\\d{1,2}","type":"string"},
|
|
40
|
+
"stopwait": {"default":"10","minimum":0,"type":"integer"},
|
|
41
|
+
"storage": {"format":"pve-storage-id","type":"string"},
|
|
42
|
+
"vmid": {"format":"pve-vmid-list","type":"string"},
|
|
43
|
+
},
|
|
44
|
+
"pve:POST /cluster/firewall/aliases": {
|
|
45
|
+
"cidr": {"format":"IPorCIDR","required":true,"type":"string"},
|
|
46
|
+
"comment": {"format":"pve-fw-comment-spec","type":"string"},
|
|
47
|
+
"name": {"maxLength":64,"minLength":2,"pattern":"[A-Za-z][A-Za-z0-9\\-\\_]+","patternSource":"[A-Za-z][A-Za-z0-9\\-\\_]+","required":true,"type":"string"},
|
|
48
|
+
},
|
|
49
|
+
"pve:POST /cluster/ha/resources": {
|
|
50
|
+
"comment": {"maxLength":4096,"type":"string"},
|
|
51
|
+
"group": {"format":"pve-configid","type":"string"},
|
|
52
|
+
"max_relocate": {"default":"1","minimum":0,"type":"integer"},
|
|
53
|
+
"max_restart": {"default":"1","minimum":0,"type":"integer"},
|
|
54
|
+
"sid": {"format":"pve-ha-resource-or-vm-id","required":true,"type":"string"},
|
|
55
|
+
"state": {"default":"started","enum":["started","stopped","enabled","disabled","ignored"],"type":"string"},
|
|
56
|
+
"type": {"enum":["ct","vm"],"type":"string"},
|
|
57
|
+
},
|
|
58
|
+
"pve:POST /cluster/ha/rules": {},
|
|
59
|
+
"pve:POST /cluster/notifications/matchers": {
|
|
60
|
+
"mode": {"default":"all","enum":["all","any"],"type":"string"},
|
|
61
|
+
"name": {"format":"pve-configid","required":true,"type":"string"},
|
|
62
|
+
},
|
|
63
|
+
"pve:POST /cluster/replication": {
|
|
64
|
+
"comment": {"maxLength":4096,"type":"string"},
|
|
65
|
+
"id": {"format":"pve-replication-job-id","pattern":"[1-9][0-9]{2,8}-\\d{1,9}","patternSource":"[1-9][0-9]{2,8}-\\d{1,9}","required":true,"type":"string"},
|
|
66
|
+
"rate": {"minimum":1,"type":"number"},
|
|
67
|
+
"remove_job": {"enum":["local","full"],"type":"string"},
|
|
68
|
+
"schedule": {"default":"*/15","format":"pve-calendar-event","maxLength":128,"type":"string"},
|
|
69
|
+
"source": {"format":"pve-node","type":"string"},
|
|
70
|
+
"target": {"format":"pve-node","required":true,"type":"string"},
|
|
71
|
+
"type": {"enum":["local"],"required":true,"type":"string"},
|
|
72
|
+
},
|
|
73
|
+
"pve:POST /cluster/sdn/vnets": {
|
|
74
|
+
"alias": {"maxLength":256,"patternSource":"(?^i:[\\(\\)-_.\\w\\d\\s]{0,256})","type":"string"},
|
|
75
|
+
"tag": {"maximum":16777215,"minimum":1,"type":"integer"},
|
|
76
|
+
"type": {"enum":["vnet"],"type":"string"},
|
|
77
|
+
"vnet": {"maxLength":8,"minLength":2,"pattern":"[a-zA-Z][a-zA-Z0-9]*[a-zA-Z0-9]","patternSource":"[a-zA-Z][a-zA-Z0-9]*[a-zA-Z0-9]","required":true,"type":"string"},
|
|
78
|
+
"zone": {"required":true,"type":"string"},
|
|
79
|
+
},
|
|
80
|
+
"pve:POST /cluster/sdn/zones": {
|
|
81
|
+
"dhcp": {"enum":["dnsmasq"],"type":"string"},
|
|
82
|
+
"dnszone": {"format":"dns-name","type":"string"},
|
|
83
|
+
"exitnodes": {"format":"pve-node-list","type":"string"},
|
|
84
|
+
"exitnodes-primary": {"format":"pve-node","type":"string"},
|
|
85
|
+
"fabric": {"format":"pve-sdn-fabric-id","type":"string"},
|
|
86
|
+
"mac": {"format":"mac-addr","type":"string"},
|
|
87
|
+
"nodes": {"format":"pve-node-list","type":"string"},
|
|
88
|
+
"peers": {"format":"ip-list","type":"string"},
|
|
89
|
+
"rt-import": {"format":"pve-sdn-bgp-rt-list","type":"string"},
|
|
90
|
+
"tag": {"minimum":0,"type":"integer"},
|
|
91
|
+
"type": {"enum":["evpn","faucet","qinq","simple","vlan","vxlan"],"format":"pve-configid","required":true,"type":"string"},
|
|
92
|
+
"vlan-protocol": {"default":"802.1q","enum":["802.1q","802.1ad"],"type":"string"},
|
|
93
|
+
"vrf-vxlan": {"maximum":16777215,"minimum":1,"type":"integer"},
|
|
94
|
+
"vxlan-port": {"default":"4789","maximum":65536,"minimum":1,"type":"integer"},
|
|
95
|
+
"zone": {"maxLength":8,"minLength":2,"pattern":"[a-zA-Z][a-zA-Z0-9]*[a-zA-Z0-9]","patternSource":"[a-zA-Z][a-zA-Z0-9]*[a-zA-Z0-9]","required":true,"type":"string"},
|
|
96
|
+
},
|
|
97
|
+
"pve:PUT /cluster/backup/{id}": {
|
|
98
|
+
"bwlimit": {"default":"0","minimum":0,"type":"integer"},
|
|
99
|
+
"comment": {"maxLength":512,"type":"string"},
|
|
100
|
+
"compress": {"default":"0","enum":["0","1","gzip","lzo","zstd"],"type":"string"},
|
|
101
|
+
"delete": {"format":"pve-configid-list","type":"string"},
|
|
102
|
+
"dow": {"format":"pve-day-of-week-list","type":"string"},
|
|
103
|
+
"exclude": {"format":"pve-vmid-list","type":"string"},
|
|
104
|
+
"fleecing": {"format":"backup-fleecing","type":"string"},
|
|
105
|
+
"ionice": {"default":"7","maximum":8,"minimum":0,"type":"integer"},
|
|
106
|
+
"lockwait": {"default":"180","minimum":0,"type":"integer"},
|
|
107
|
+
"mailnotification": {"default":"always","enum":["always","failure"],"type":"string"},
|
|
108
|
+
"mailto": {"format":"email-or-username-list","type":"string"},
|
|
109
|
+
"mode": {"default":"snapshot","enum":["snapshot","suspend","stop"],"type":"string"},
|
|
110
|
+
"node": {"format":"pve-node","type":"string"},
|
|
111
|
+
"notes-template": {"maxLength":1024,"type":"string"},
|
|
112
|
+
"notification-mode": {"default":"auto","enum":["auto","legacy-sendmail","notification-system"],"type":"string"},
|
|
113
|
+
"pbs-change-detection-mode": {"enum":["legacy","data","metadata"],"type":"string"},
|
|
114
|
+
"performance": {"format":"backup-performance","type":"string"},
|
|
115
|
+
"prune-backups": {"default":"keep-all=1","format":"prune-backups","type":"string"},
|
|
116
|
+
"schedule": {"format":"pve-calendar-event","maxLength":128,"type":"string"},
|
|
117
|
+
"starttime": {"pattern":"\\d{1,2}:\\d{1,2}","patternSource":"\\d{1,2}:\\d{1,2}","type":"string"},
|
|
118
|
+
"stopwait": {"default":"10","minimum":0,"type":"integer"},
|
|
119
|
+
"storage": {"format":"pve-storage-id","type":"string"},
|
|
120
|
+
"vmid": {"format":"pve-vmid-list","type":"string"},
|
|
121
|
+
},
|
|
122
|
+
"pve:PUT /cluster/firewall/aliases/{name}": {
|
|
123
|
+
"cidr": {"format":"IPorCIDR","required":true,"type":"string"},
|
|
124
|
+
"comment": {"format":"pve-fw-comment-spec","type":"string"},
|
|
125
|
+
"digest": {"maxLength":64,"type":"string"},
|
|
126
|
+
"rename": {"maxLength":64,"minLength":2,"pattern":"[A-Za-z][A-Za-z0-9\\-\\_]+","patternSource":"[A-Za-z][A-Za-z0-9\\-\\_]+","type":"string"},
|
|
127
|
+
},
|
|
128
|
+
"pve:PUT /cluster/ha/resources/{sid}": {
|
|
129
|
+
"comment": {"maxLength":4096,"type":"string"},
|
|
130
|
+
"delete": {"format":"pve-configid-list","maxLength":4096,"type":"string"},
|
|
131
|
+
"digest": {"maxLength":64,"type":"string"},
|
|
132
|
+
"group": {"format":"pve-configid","type":"string"},
|
|
133
|
+
"max_relocate": {"default":"1","minimum":0,"type":"integer"},
|
|
134
|
+
"max_restart": {"default":"1","minimum":0,"type":"integer"},
|
|
135
|
+
"state": {"default":"started","enum":["started","stopped","enabled","disabled","ignored"],"type":"string"},
|
|
136
|
+
},
|
|
137
|
+
"pve:PUT /cluster/ha/rules/{rule}": {},
|
|
138
|
+
"pve:PUT /cluster/notifications/matchers/{name}": {
|
|
139
|
+
"digest": {"maxLength":64,"type":"string"},
|
|
140
|
+
"mode": {"default":"all","enum":["all","any"],"type":"string"},
|
|
141
|
+
},
|
|
142
|
+
"pve:PUT /cluster/replication/{id}": {
|
|
143
|
+
"comment": {"maxLength":4096,"type":"string"},
|
|
144
|
+
"delete": {"format":"pve-configid-list","maxLength":4096,"type":"string"},
|
|
145
|
+
"digest": {"maxLength":64,"type":"string"},
|
|
146
|
+
"rate": {"minimum":1,"type":"number"},
|
|
147
|
+
"remove_job": {"enum":["local","full"],"type":"string"},
|
|
148
|
+
"schedule": {"default":"*/15","format":"pve-calendar-event","maxLength":128,"type":"string"},
|
|
149
|
+
"source": {"format":"pve-node","type":"string"},
|
|
150
|
+
},
|
|
151
|
+
"pve:PUT /cluster/sdn/vnets/{vnet}": {
|
|
152
|
+
"alias": {"maxLength":256,"patternSource":"(?^i:[\\(\\)-_.\\w\\d\\s]{0,256})","type":"string"},
|
|
153
|
+
"delete": {"format":"pve-configid-list","maxLength":4096,"type":"string"},
|
|
154
|
+
"digest": {"maxLength":64,"type":"string"},
|
|
155
|
+
"tag": {"maximum":16777215,"minimum":1,"type":"integer"},
|
|
156
|
+
},
|
|
157
|
+
"pve:PUT /cluster/sdn/zones/{zone}": {
|
|
158
|
+
"delete": {"format":"pve-configid-list","maxLength":4096,"type":"string"},
|
|
159
|
+
"dhcp": {"enum":["dnsmasq"],"type":"string"},
|
|
160
|
+
"digest": {"maxLength":64,"type":"string"},
|
|
161
|
+
"dnszone": {"format":"dns-name","type":"string"},
|
|
162
|
+
"exitnodes": {"format":"pve-node-list","type":"string"},
|
|
163
|
+
"exitnodes-primary": {"format":"pve-node","type":"string"},
|
|
164
|
+
"fabric": {"format":"pve-sdn-fabric-id","type":"string"},
|
|
165
|
+
"mac": {"format":"mac-addr","type":"string"},
|
|
166
|
+
"nodes": {"format":"pve-node-list","type":"string"},
|
|
167
|
+
"peers": {"format":"ip-list","type":"string"},
|
|
168
|
+
"rt-import": {"format":"pve-sdn-bgp-rt-list","type":"string"},
|
|
169
|
+
"tag": {"minimum":0,"type":"integer"},
|
|
170
|
+
"vlan-protocol": {"default":"802.1q","enum":["802.1q","802.1ad"],"type":"string"},
|
|
171
|
+
"vrf-vxlan": {"maximum":16777215,"minimum":1,"type":"integer"},
|
|
172
|
+
"vxlan-port": {"default":"4789","maximum":65536,"minimum":1,"type":"integer"},
|
|
173
|
+
},
|
|
174
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generated pve-manager parameter constraints for `/pools` — DO NOT EDIT BY HAND.
|
|
3
|
+
*
|
|
4
|
+
* Run: bun codegen/constraints.ts
|
|
5
|
+
* Manifest entry: `pve-apidoc` — pve-manager 9.2.11/f6997e698c7933ea
|
|
6
|
+
* sha256 9def8f13611184ee, read on a PVE cluster node from
|
|
7
|
+
* /usr/share/pve-docs/api-viewer/apidoc.js
|
|
8
|
+
*
|
|
9
|
+
* 27 of this product's 258 POST/PUT endpoints are tabled across all areas: the ones
|
|
10
|
+
* this package writes to, named in its own source. Every other vendor write endpoint is UNTABLED
|
|
11
|
+
* and therefore unchecked at plan time.
|
|
12
|
+
*
|
|
13
|
+
* ⚠️ A `patternSource` with no `pattern` beside it is a rule that could NOT be carried into a
|
|
14
|
+
* JavaScript RegExp faithfully (codegen/pattern.ts). It is recorded and NOT enforced.
|
|
15
|
+
*/
|
|
16
|
+
import type { EndpointConstraints } from '../../constraints.ts';
|
|
17
|
+
|
|
18
|
+
export const PVE_POOLS_CONSTRAINTS: Readonly<Record<string, EndpointConstraints>> = {
|
|
19
|
+
"pve:POST /pools": {
|
|
20
|
+
"poolid": {"format":"pve-poolid","required":true,"type":"string"},
|
|
21
|
+
},
|
|
22
|
+
"pve:PUT /pools/{poolid}": {
|
|
23
|
+
"storage": {"format":"pve-storage-id-list","type":"string"},
|
|
24
|
+
"vms": {"format":"pve-vmid-list","type":"string"},
|
|
25
|
+
},
|
|
26
|
+
};
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generated pve-manager parameter constraints for `/storage` — DO NOT EDIT BY HAND.
|
|
3
|
+
*
|
|
4
|
+
* Run: bun codegen/constraints.ts
|
|
5
|
+
* Manifest entry: `pve-apidoc` — pve-manager 9.2.11/f6997e698c7933ea
|
|
6
|
+
* sha256 9def8f13611184ee, read on a PVE cluster node from
|
|
7
|
+
* /usr/share/pve-docs/api-viewer/apidoc.js
|
|
8
|
+
*
|
|
9
|
+
* 27 of this product's 258 POST/PUT endpoints are tabled across all areas: the ones
|
|
10
|
+
* this package writes to, named in its own source. Every other vendor write endpoint is UNTABLED
|
|
11
|
+
* and therefore unchecked at plan time.
|
|
12
|
+
*
|
|
13
|
+
* ⚠️ A `patternSource` with no `pattern` beside it is a rule that could NOT be carried into a
|
|
14
|
+
* JavaScript RegExp faithfully (codegen/pattern.ts). It is recorded and NOT enforced.
|
|
15
|
+
*/
|
|
16
|
+
import type { EndpointConstraints } from '../../constraints.ts';
|
|
17
|
+
|
|
18
|
+
export const PVE_STORAGE_CONSTRAINTS: Readonly<Record<string, EndpointConstraints>> = {
|
|
19
|
+
"pve:POST /storage": {
|
|
20
|
+
"base": {"format":"pve-volume-id","type":"string"},
|
|
21
|
+
"blocksize": {"format":"pve-storage-zfs-blocksize","type":"string"},
|
|
22
|
+
"content": {"format":"pve-storage-content-list","type":"string"},
|
|
23
|
+
"content-dirs": {"format":"pve-dir-override-list","type":"string"},
|
|
24
|
+
"domain": {"maxLength":256,"type":"string"},
|
|
25
|
+
"export": {"format":"pve-storage-path","type":"string"},
|
|
26
|
+
"fingerprint": {"pattern":"([A-Fa-f0-9]{2}:){31}[A-Fa-f0-9]{2}","patternSource":"([A-Fa-f0-9]{2}:){31}[A-Fa-f0-9]{2}","type":"string"},
|
|
27
|
+
"format": {"enum":["raw","qcow2","subvol","vmdk"],"type":"string"},
|
|
28
|
+
"fs-name": {"format":"pve-configid","type":"string"},
|
|
29
|
+
"max-protected-backups": {"default":"Unlimited for users with Datastore.Allocate privilege, 5 for other users","minimum":-1,"type":"integer"},
|
|
30
|
+
"monhost": {"format":"pve-storage-portal-dns-list","type":"string"},
|
|
31
|
+
"mountpoint": {"format":"pve-storage-path","type":"string"},
|
|
32
|
+
"nodes": {"format":"pve-node-list","type":"string"},
|
|
33
|
+
"options": {"format":"pve-storage-options","type":"string"},
|
|
34
|
+
"password": {"maxLength":256,"type":"string"},
|
|
35
|
+
"path": {"format":"pve-storage-path","type":"string"},
|
|
36
|
+
"port": {"maximum":65535,"minimum":1,"type":"integer"},
|
|
37
|
+
"portal": {"format":"pve-storage-portal-dns","type":"string"},
|
|
38
|
+
"preallocation": {"default":"metadata","enum":["off","metadata","falloc","full"],"type":"string"},
|
|
39
|
+
"prune-backups": {"format":"prune-backups","type":"string"},
|
|
40
|
+
"saferemove-stepsize": {"default":"32","enum":["1","2","4","8","16","32"],"type":"integer"},
|
|
41
|
+
"server": {"format":"pve-storage-server","type":"string"},
|
|
42
|
+
"smbversion": {"default":"default","enum":["default","2.0","2.1","3","3.0","3.11"],"type":"string"},
|
|
43
|
+
"storage": {"format":"pve-storage-id","required":true,"type":"string"},
|
|
44
|
+
"subdir": {"format":"pve-storage-path","type":"string"},
|
|
45
|
+
"thinpool": {"format":"pve-storage-vgname","type":"string"},
|
|
46
|
+
"type": {"enum":["btrfs","cephfs","cifs","dir","esxi","iscsi","iscsidirect","lvm","lvmthin","nfs","pbs","rbd","zfs","zfspool"],"required":true,"type":"string"},
|
|
47
|
+
"vgname": {"format":"pve-storage-vgname","type":"string"},
|
|
48
|
+
"zfs-base-path": {"format":"pve-storage-path","type":"string"},
|
|
49
|
+
},
|
|
50
|
+
"pve:PUT /storage/{storage}": {
|
|
51
|
+
"blocksize": {"format":"pve-storage-zfs-blocksize","type":"string"},
|
|
52
|
+
"content": {"format":"pve-storage-content-list","type":"string"},
|
|
53
|
+
"content-dirs": {"format":"pve-dir-override-list","type":"string"},
|
|
54
|
+
"delete": {"format":"pve-configid-list","maxLength":4096,"type":"string"},
|
|
55
|
+
"digest": {"maxLength":64,"type":"string"},
|
|
56
|
+
"domain": {"maxLength":256,"type":"string"},
|
|
57
|
+
"fingerprint": {"pattern":"([A-Fa-f0-9]{2}:){31}[A-Fa-f0-9]{2}","patternSource":"([A-Fa-f0-9]{2}:){31}[A-Fa-f0-9]{2}","type":"string"},
|
|
58
|
+
"format": {"enum":["raw","qcow2","subvol","vmdk"],"type":"string"},
|
|
59
|
+
"fs-name": {"format":"pve-configid","type":"string"},
|
|
60
|
+
"max-protected-backups": {"default":"Unlimited for users with Datastore.Allocate privilege, 5 for other users","minimum":-1,"type":"integer"},
|
|
61
|
+
"monhost": {"format":"pve-storage-portal-dns-list","type":"string"},
|
|
62
|
+
"mountpoint": {"format":"pve-storage-path","type":"string"},
|
|
63
|
+
"nodes": {"format":"pve-node-list","type":"string"},
|
|
64
|
+
"options": {"format":"pve-storage-options","type":"string"},
|
|
65
|
+
"password": {"maxLength":256,"type":"string"},
|
|
66
|
+
"port": {"maximum":65535,"minimum":1,"type":"integer"},
|
|
67
|
+
"preallocation": {"default":"metadata","enum":["off","metadata","falloc","full"],"type":"string"},
|
|
68
|
+
"prune-backups": {"format":"prune-backups","type":"string"},
|
|
69
|
+
"saferemove-stepsize": {"default":"32","enum":["1","2","4","8","16","32"],"type":"integer"},
|
|
70
|
+
"server": {"format":"pve-storage-server","type":"string"},
|
|
71
|
+
"smbversion": {"default":"default","enum":["default","2.0","2.1","3","3.0","3.11"],"type":"string"},
|
|
72
|
+
"subdir": {"format":"pve-storage-path","type":"string"},
|
|
73
|
+
"zfs-base-path": {"format":"pve-storage-path","type":"string"},
|
|
74
|
+
},
|
|
75
|
+
};
|
package/src/proxmox/group.ts
CHANGED
|
@@ -176,6 +176,8 @@ const handlers = pveHandlers<GroupProps, GroupAttributes>({
|
|
|
176
176
|
* `resource.ts` states once in the ⛔ on adoption: a field deliberately out of `matches` is a
|
|
177
177
|
* field this resource does not manage.
|
|
178
178
|
*/
|
|
179
|
+
/** The vendor rules these forms are checked against at plan time — resource-spec.ts. */
|
|
180
|
+
endpoint: { create: 'pve:POST /access/groups', update: 'pve:PUT /access/groups/{groupid}' },
|
|
179
181
|
matches: (attributes, props) => attributes.comment === storedComment(props.comment),
|
|
180
182
|
path: (props) => `access/groups/${props.groupid}`,
|
|
181
183
|
/**
|
|
@@ -137,6 +137,11 @@ const handlers = pveHandlers<HaResourceProps, HaResourceAttributes>({
|
|
|
137
137
|
* ⚠️ `sid` AND `type` ARE NOT COMPARED EITHER: sid is the path, so a change there is a different
|
|
138
138
|
* object, and type is create-only — it is read back from the prefix, never edited.
|
|
139
139
|
*/
|
|
140
|
+
/** The vendor rules these forms are checked against at plan time — resource-spec.ts. */
|
|
141
|
+
endpoint: {
|
|
142
|
+
create: 'pve:POST /cluster/ha/resources',
|
|
143
|
+
update: 'pve:PUT /cluster/ha/resources/{sid}',
|
|
144
|
+
},
|
|
140
145
|
matches: (attributes, props) =>
|
|
141
146
|
attributes.state === (props.state ?? 'started') &&
|
|
142
147
|
attributes.comment === (props.comment ?? '') &&
|
package/src/proxmox/ha-rule.ts
CHANGED
|
@@ -206,6 +206,8 @@ const handlers = pveHandlers<HaRuleProps, HaRuleAttributes>({
|
|
|
206
206
|
* ⚠️ THE COMMENT IS COMPARED AS PVE WILL STORE IT, not as it was declared — `commentText` and
|
|
207
207
|
* its ⛔ explain why a `%XX` in a comment does not survive the round trip.
|
|
208
208
|
*/
|
|
209
|
+
/** The vendor rules these forms are checked against at plan time — resource-spec.ts. */
|
|
210
|
+
endpoint: { create: 'pve:POST /cluster/ha/rules', update: 'pve:PUT /cluster/ha/rules/{rule}' },
|
|
209
211
|
matches: (attributes, props) =>
|
|
210
212
|
attributes.resources === csv(props.resources) &&
|
|
211
213
|
attributes.affinity === props.affinity &&
|
package/src/proxmox/index.ts
CHANGED
|
@@ -18,6 +18,9 @@
|
|
|
18
18
|
* ★ THE NOTIFICATION FAMILIES JOINED 2026-09-22: PBS targets and matchers, the PVE matcher, the
|
|
19
19
|
* `FromEnv` shape their write-only values take, and the Alertmanager body template
|
|
20
20
|
* (docs/pbs-notifications.md; the template alone: docs/pbs-alertmanager-body.md).
|
|
21
|
+
* ★ THE VENDOR CONSTRAINT READER JOINED 2026-09-22 (codegen/README.md). `constraintsFor` and
|
|
22
|
+
* `violations` are exported so a CONSUMING stack can assert its own declarations offline — the
|
|
23
|
+
* `deploy:pbs` that failed on a 128-character comment had no way to ask that question.
|
|
21
24
|
* ★ Anything else unlisted is still reachable by path if you genuinely need
|
|
22
25
|
* it — that is a deliberate, visible act rather than an accident of barrelling.
|
|
23
26
|
*/
|
|
@@ -30,6 +33,13 @@ export { ProxmoxCephFlagProvider } from './ceph-flag.ts';
|
|
|
30
33
|
export { ProxmoxCephFsProvider } from './ceph-fs.ts';
|
|
31
34
|
export { ProxmoxCephOsdProvider } from './ceph-osd.ts';
|
|
32
35
|
export { ProxmoxCephPool, ProxmoxCephPoolProvider } from './ceph-pool.ts';
|
|
36
|
+
export { constraintsFor, formViolations } from './constraint-guard.ts';
|
|
37
|
+
export {
|
|
38
|
+
type EndpointConstraints,
|
|
39
|
+
type EndpointKey,
|
|
40
|
+
type ParamConstraint,
|
|
41
|
+
violations,
|
|
42
|
+
} from './constraints.ts';
|
|
33
43
|
export { ProxmoxFirewallAliasProvider } from './firewall-alias.ts';
|
|
34
44
|
export { ProxmoxGroup, ProxmoxGroupProvider } from './group.ts';
|
|
35
45
|
export { ProxmoxHaResource, ProxmoxHaResourceProvider } from './ha-resource.ts';
|
|
@@ -63,7 +73,9 @@ export { ProxmoxPool, ProxmoxPoolProvider } from './pool.ts';
|
|
|
63
73
|
export {
|
|
64
74
|
PROVISION_DEFAULTS,
|
|
65
75
|
PROVISION_PRIVILEGES,
|
|
76
|
+
type CoreProvisionNames,
|
|
66
77
|
type ProvisionBaseline,
|
|
78
|
+
type ProvisionComments,
|
|
67
79
|
type ProvisionLane,
|
|
68
80
|
type ProvisionNames,
|
|
69
81
|
type ResolvedProvisionNames,
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PVE exposes no way to reach INSIDE a container, and this pins that as a measured fact rather
|
|
3
|
+
* than a thing people keep re-deriving.
|
|
4
|
+
*
|
|
5
|
+
* ★ WHY A TEST AND NOT ONLY A DOC. `Proxmox.Lxc` declares a guest's PVE-level config and nothing
|
|
6
|
+
* within its filesystem, and every consumer that meets that limit goes looking for the resource
|
|
7
|
+
* that must surely exist — an exec, a file write, a cloud-init. It does not, for containers. A
|
|
8
|
+
* grep proves that once; this proves it on every run, and fails the day PVE adds one, which is
|
|
9
|
+
* exactly when the kit would want to wrap it.
|
|
10
|
+
* ⛔ THE POSITIVE CONTROL IS LOAD BEARING. A test that only asserts absence passes for free if the
|
|
11
|
+
* generated file's comment shape ever changes. QEMU's `agent/exec` and `agent/file-write` are
|
|
12
|
+
* asserted present in the same pass, so a format change fails here instead of going quiet.
|
|
13
|
+
* ⚠️ SIBLING FAMILIES ARE NOT AT PARITY. QEMU and LXC sit under the same `/nodes/{node}/…` tree
|
|
14
|
+
* and have completely different reach. Never infer one family's endpoints from the other's.
|
|
15
|
+
*/
|
|
16
|
+
import { describe, expect, test } from 'bun:test';
|
|
17
|
+
|
|
18
|
+
const schema = await Bun.file(new URL('./generated/pve.ts', import.meta.url)).text();
|
|
19
|
+
|
|
20
|
+
/** Every endpoint the generated schema documents, as `METHOD /path`. */
|
|
21
|
+
const endpoints = [...schema.matchAll(/(GET|POST|PUT|DELETE) (\/[A-Za-z0-9/{}_.-]+)/g)].map(
|
|
22
|
+
(match) => `${match[1]} ${match[2]}`,
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
const under = (prefix: string) =>
|
|
26
|
+
endpoints.filter((line) => line.slice(line.indexOf(' ') + 1).startsWith(prefix));
|
|
27
|
+
|
|
28
|
+
const LXC = '/nodes/{node}/lxc/{vmid}';
|
|
29
|
+
const QEMU = '/nodes/{node}/qemu/{vmid}';
|
|
30
|
+
|
|
31
|
+
describe('a container has no interior API', () => {
|
|
32
|
+
test('the schema was parsed at all', () => {
|
|
33
|
+
// ⛔ Without this, every absence assertion below would pass on an empty list.
|
|
34
|
+
expect(endpoints.length).toBeGreaterThan(100);
|
|
35
|
+
expect(under(LXC).length).toBeGreaterThan(20);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test('QEMU can exec and write files inside a guest — the positive control', () => {
|
|
39
|
+
expect(under(QEMU)).toContain(`POST ${QEMU}/agent/exec`);
|
|
40
|
+
expect(under(QEMU)).toContain(`POST ${QEMU}/agent/file-write`);
|
|
41
|
+
expect(under(QEMU)).toContain(`GET ${QEMU}/agent/file-read`);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test('no LXC endpoint execs, reads or writes a file, or takes cloud-init', () => {
|
|
45
|
+
// ⛔ So a generic, vendor-API-based Resource for a container's interior cannot be written:
|
|
46
|
+
// there is nothing to wrap. What is left is an image baked before create, a first-boot
|
|
47
|
+
// artifact, or a recorded human step — see docs/proxmox-lxc.md.
|
|
48
|
+
const reaching = under(LXC).filter((line) =>
|
|
49
|
+
/\/(agent|exec|file-read|file-write|cloudinit|cloud-init)(\/|$)/.test(line),
|
|
50
|
+
);
|
|
51
|
+
expect(reaching).toEqual([]);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
test('the only LXC reach inside is an interactive console, which is not declarative', () => {
|
|
55
|
+
// ⚠️ `termproxy` and `vncwebsocket` open a terminal for a person. They are not an API a
|
|
56
|
+
// resource can diff, and the kit wraps neither.
|
|
57
|
+
const consoles = under(LXC).filter((line) => /(termproxy|vncwebsocket|spiceproxy)/.test(line));
|
|
58
|
+
expect(consoles.length).toBeGreaterThan(0);
|
|
59
|
+
expect(schema.includes('LxcExec')).toBe(false);
|
|
60
|
+
});
|
|
61
|
+
});
|
|
@@ -64,6 +64,11 @@ const handlers = pveHandlers<NotificationMatcherProps, NotificationMatcherAttrib
|
|
|
64
64
|
attributes: (live, props) => matcherAttributes(live, props.name),
|
|
65
65
|
collection: () => 'cluster/notifications/matchers',
|
|
66
66
|
createForm: matcherCreateForm,
|
|
67
|
+
/** The vendor rules these forms are checked against at plan time — resource-spec.ts. */
|
|
68
|
+
endpoint: {
|
|
69
|
+
create: 'pve:POST /cluster/notifications/matchers',
|
|
70
|
+
update: 'pve:PUT /cluster/notifications/matchers/{name}',
|
|
71
|
+
},
|
|
67
72
|
matches: matcherMatches,
|
|
68
73
|
path: (props) => `cluster/notifications/matchers/${props.name}`,
|
|
69
74
|
updateForm: matcherUpdateForm,
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `Pbs.Datastore`'s two vendor endpoints, and the plan-time check over its two forms.
|
|
3
|
+
*
|
|
4
|
+
* ★ A FILE OF ITS OWN BECAUSE THIS FAMILY DOES NOT USE `pveHandlers`. It writes its own `diff` and
|
|
5
|
+
* `reconcile` (a create answers with a task id, so it settles rather than reading back once), so
|
|
6
|
+
* it cannot inherit the guard from resource.ts — and a copy of the two calls inside that already
|
|
7
|
+
* long file is exactly the per-resource copy-paste the shared handler exists to prevent.
|
|
8
|
+
*
|
|
9
|
+
* ⛔ THE FOUR FIELDS THAT MADE THIS WORTH DOING, from the PBS 4.2.6 schema: `comment` is
|
|
10
|
+
* maxLength 128 with a no-control-characters pattern, `name` is 3..32 with a safe-id pattern,
|
|
11
|
+
* and every `keep-*` has minimum 1. All four were `string`/`number` with the limits in prose.
|
|
12
|
+
*/
|
|
13
|
+
import * as Effect from 'effect/Effect';
|
|
14
|
+
import { guardForm } from './constraint-guard.ts';
|
|
15
|
+
import type { EndpointKey } from './constraints.ts';
|
|
16
|
+
import { createForm, updateForm } from './pbs-datastore-form.ts';
|
|
17
|
+
import type { PbsDatastoreProps } from './pbs-datastore.ts';
|
|
18
|
+
|
|
19
|
+
export const DATASTORE_ENDPOINT: { readonly create: EndpointKey; readonly update: EndpointKey } = {
|
|
20
|
+
create: 'pbs:POST /config/datastore',
|
|
21
|
+
update: 'pbs:PUT /config/datastore/{name}',
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
/** ⚠️ Presence on create only — an update form is partial by design (update-guard.ts). */
|
|
25
|
+
export const guardDatastoreForms = (props: PbsDatastoreProps): Effect.Effect<void> =>
|
|
26
|
+
Effect.gen(function* () {
|
|
27
|
+
yield* guardForm(DATASTORE_ENDPOINT.create, createForm(props), true);
|
|
28
|
+
yield* guardForm(DATASTORE_ENDPOINT.update, updateForm(props), false);
|
|
29
|
+
});
|
|
@@ -64,6 +64,7 @@ import {
|
|
|
64
64
|
updateForm,
|
|
65
65
|
} from './pbs-datastore-form.ts';
|
|
66
66
|
import { guardBackend, guardPath, settle } from './pbs-datastore-guard.ts';
|
|
67
|
+
import { guardDatastoreForms } from './pbs-datastore-endpoint.ts';
|
|
67
68
|
import type { PveRequirements } from './resource.ts';
|
|
68
69
|
import { formToSend } from './update-guard.ts';
|
|
69
70
|
|
|
@@ -181,7 +182,11 @@ export const PbsDatastoreProvider = () =>
|
|
|
181
182
|
*/
|
|
182
183
|
diff: ({ news, output }) =>
|
|
183
184
|
Effect.gen(function* () {
|
|
184
|
-
if (
|
|
185
|
+
if (!isResolved(news)) return undefined;
|
|
186
|
+
// ⛔ BEFORE the `output === undefined` return: that branch IS the create, which is the
|
|
187
|
+
// one this family has never checked. resource.ts carries the argument in full.
|
|
188
|
+
yield* guardDatastoreForms(news);
|
|
189
|
+
if (output === undefined) return undefined;
|
|
185
190
|
const live = yield* readOne(news);
|
|
186
191
|
// ⚠️ `update`, not `create` — Alchemy's Diff admits only noop/update/replace, and an
|
|
187
192
|
// object Alchemy has state for but PBS does not is drift for reconcile to repair.
|
|
@@ -198,6 +203,8 @@ export const PbsDatastoreProvider = () =>
|
|
|
198
203
|
* ⛔ THE `matches` GUARD BEFORE THE PUT IS resource.ts's, AND IT IS WHY ADOPTION IS FREE.
|
|
199
204
|
*/
|
|
200
205
|
reconcile: Effect.fn(function* ({ news }) {
|
|
206
|
+
// ⚠️ Again here: an ADOPTED row's diff answer is discarded by Alchemy and reconcile runs.
|
|
207
|
+
yield* guardDatastoreForms(news);
|
|
201
208
|
const live = yield* readOne(news);
|
|
202
209
|
let upid: string | undefined;
|
|
203
210
|
if (live === undefined) {
|
|
@@ -69,6 +69,11 @@ const handlers = pveHandlers<PbsNotificationMatcherProps, NotificationMatcherAtt
|
|
|
69
69
|
attributes: (live, props) => matcherAttributes(live, props.name),
|
|
70
70
|
collection: () => 'config/notifications/matchers',
|
|
71
71
|
createForm: matcherCreateForm,
|
|
72
|
+
/** The vendor rules these forms are checked against at plan time — resource-spec.ts. */
|
|
73
|
+
endpoint: {
|
|
74
|
+
create: 'pbs:POST /config/notifications/matchers',
|
|
75
|
+
update: 'pbs:PUT /config/notifications/matchers/{name}',
|
|
76
|
+
},
|
|
72
77
|
matches: matcherMatches,
|
|
73
78
|
path: (props) => `config/notifications/matchers/${props.name}`,
|
|
74
79
|
updateForm: matcherUpdateForm,
|
|
@@ -194,6 +194,8 @@ const handlers = pveHandlers<PruneJobProps, PruneJobAttributes>({
|
|
|
194
194
|
* compared unconditionally. A changed `store` is an update, not a replace: PBS accepts `store`
|
|
195
195
|
* on PUT, so the job is re-pointed in place.
|
|
196
196
|
*/
|
|
197
|
+
/** The vendor rules these forms are checked against at plan time — resource-spec.ts. */
|
|
198
|
+
endpoint: { create: 'pbs:POST /config/prune', update: 'pbs:PUT /config/prune/{id}' },
|
|
197
199
|
matches: (attributes, props) =>
|
|
198
200
|
attributes.schedule === props.schedule &&
|
|
199
201
|
attributes.store === props.store &&
|
|
@@ -209,6 +209,8 @@ export const handlers = pveHandlers<SyncJobProps, SyncJobAttributes>({
|
|
|
209
209
|
* ⚠️ `id`, `group-filter` AND `sync-direction` ARE OUT. The first is the path, a change to which
|
|
210
210
|
* is a different object; the other two are argued on their props in pbs-sync-job.ts.
|
|
211
211
|
*/
|
|
212
|
+
/** The vendor rules these forms are checked against at plan time — resource-spec.ts. */
|
|
213
|
+
endpoint: { create: 'pbs:POST /config/sync', update: 'pbs:PUT /config/sync/{id}' },
|
|
212
214
|
matches: (attributes, props) =>
|
|
213
215
|
attributes['remove-vanished'] === (props['remove-vanished'] === true) &&
|
|
214
216
|
attributes.store === props.store &&
|
|
@@ -193,6 +193,8 @@ const handlers = pveHandlers<PbsVerifyJobProps, PbsVerifyJobAttributes>({
|
|
|
193
193
|
* ⚠️ `rechecks` IS ABSENT HERE ON PURPOSE. It is a rendering of the two fields on the lines
|
|
194
194
|
* above; comparing it too would report the same drift twice.
|
|
195
195
|
*/
|
|
196
|
+
/** The vendor rules these forms are checked against at plan time — resource-spec.ts. */
|
|
197
|
+
endpoint: { create: 'pbs:POST /config/verify', update: 'pbs:PUT /config/verify/{id}' },
|
|
196
198
|
matches: (attributes, props) =>
|
|
197
199
|
attributes.store === props.store &&
|
|
198
200
|
// ⚠️ `null` IS COMPARED AGAINST `''`, NOT SKIPPED. A parked job is a declaration like any
|
package/src/proxmox/pool.ts
CHANGED
|
@@ -71,6 +71,8 @@ const handlers = pveHandlers<PoolProps, PoolAttributes>({
|
|
|
71
71
|
},
|
|
72
72
|
collection: () => 'pools',
|
|
73
73
|
createForm: poolCreateForm,
|
|
74
|
+
/** The vendor rules these forms are checked against at plan time — resource-spec.ts. */
|
|
75
|
+
endpoint: { create: 'pve:POST /pools', update: 'pve:PUT /pools/{poolid}' },
|
|
74
76
|
matches: (attributes, props) => attributes.comment === (props.comment ?? ''),
|
|
75
77
|
path: (props) => `pools/${props.poolid}`,
|
|
76
78
|
updateForm: poolUpdateForm,
|
|
@@ -84,4 +84,42 @@ describe('provisionBaseline', () => {
|
|
|
84
84
|
test('refuses one user for both lanes', () => {
|
|
85
85
|
expect(() => provisionBaseline({ readUser: 'hf-provision@pve' })).toThrow(/one user/);
|
|
86
86
|
});
|
|
87
|
+
|
|
88
|
+
test('a comment per object, each defaulting to `comment` on its own', () => {
|
|
89
|
+
// \u2605 THE EXISTING-CLUSTER CASE: the group and the read user already carry their own live
|
|
90
|
+
// comments, and only the provision user is new. One shared comment could not say this.
|
|
91
|
+
const baseline = provisionBaseline({
|
|
92
|
+
comment: 'shared',
|
|
93
|
+
groupComment: '',
|
|
94
|
+
provisionComment: 'mint target: provision (ops)',
|
|
95
|
+
readComment: 'mint target: read (ops)',
|
|
96
|
+
});
|
|
97
|
+
expect(baseline.group.comment).toBe('');
|
|
98
|
+
expect(baseline.users.map((u) => u.comment)).toEqual([
|
|
99
|
+
'mint target: provision (ops)',
|
|
100
|
+
'mint target: read (ops)',
|
|
101
|
+
]);
|
|
102
|
+
// Overriding one leaves the others on `comment`; the generic case is still one string.
|
|
103
|
+
const one = provisionBaseline({ comment: 'shared', provisionComment: 'only this' });
|
|
104
|
+
expect([one.group.comment, ...one.users.map((u) => u.comment)]).toEqual([
|
|
105
|
+
'shared',
|
|
106
|
+
'only this',
|
|
107
|
+
'shared',
|
|
108
|
+
]);
|
|
109
|
+
expect(provisionBaseline({ comment: 'shared' }).group.comment).toBe('shared');
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
test('every comment is refused by its own field name', () => {
|
|
113
|
+
const attempt = () =>
|
|
114
|
+
provisionBaseline({ groupComment: "it's", provisionComment: 'q{x}', readComment: '$(id)' });
|
|
115
|
+
expect(attempt).toThrow(/groupComment.*provisionComment.*readComment/);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
test('readComment goes with its lane: unchecked, and unused, when readUser is null', () => {
|
|
119
|
+
// ⚠️ Nothing would ever paste it, so refusing it would be a refusal over a field the caller
|
|
120
|
+
// is not using. The lane it belongs to is gone.
|
|
121
|
+
const none = provisionBaseline({ readComment: "would need quoting'", readUser: null });
|
|
122
|
+
expect(none.users.map((u) => u.lane)).toEqual(['provision']);
|
|
123
|
+
expect(() => provisionBaseline({ readComment: "would need quoting'" })).toThrow(/readComment/);
|
|
124
|
+
});
|
|
87
125
|
});
|