@homeflare/alchemy 0.15.0 → 0.19.1
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 +29 -0
- package/dist/linux/directory-lifecycle.d.ts +50 -0
- package/dist/linux/directory-lifecycle.d.ts.map +1 -0
- package/dist/linux/directory.d.ts +19 -0
- package/dist/linux/directory.d.ts.map +1 -0
- package/dist/linux/fake-linux-host.d.ts +35 -0
- package/dist/linux/fake-linux-host.d.ts.map +1 -0
- package/dist/linux/fake-systemd.d.ts +34 -0
- package/dist/linux/fake-systemd.d.ts.map +1 -0
- package/dist/linux/index.d.ts +27 -0
- package/dist/linux/index.d.ts.map +1 -0
- package/dist/linux/index.js +1575 -0
- package/dist/linux/index.js.map +38 -0
- package/dist/linux/providers.d.ts +17 -0
- package/dist/linux/providers.d.ts.map +1 -0
- package/dist/linux/region.d.ts +43 -0
- package/dist/linux/region.d.ts.map +1 -0
- package/dist/linux/remote-file-form.d.ts +47 -0
- package/dist/linux/remote-file-form.d.ts.map +1 -0
- package/dist/linux/remote-file-lifecycle.d.ts +36 -0
- package/dist/linux/remote-file-lifecycle.d.ts.map +1 -0
- package/dist/linux/remote-file-plan.d.ts +33 -0
- package/dist/linux/remote-file-plan.d.ts.map +1 -0
- package/dist/linux/remote-file.d.ts +28 -0
- package/dist/linux/remote-file.d.ts.map +1 -0
- package/dist/linux/ssh-command.d.ts +68 -0
- package/dist/linux/ssh-command.d.ts.map +1 -0
- package/dist/linux/ssh-runner.d.ts +22 -0
- package/dist/linux/ssh-runner.d.ts.map +1 -0
- package/dist/linux/ssh-scripts.d.ts +27 -0
- package/dist/linux/ssh-scripts.d.ts.map +1 -0
- package/dist/linux/systemctl.d.ts +67 -0
- package/dist/linux/systemctl.d.ts.map +1 -0
- package/dist/linux/unit-form.d.ts +78 -0
- package/dist/linux/unit-form.d.ts.map +1 -0
- package/dist/linux/unit-handlers.d.ts +15 -0
- package/dist/linux/unit-handlers.d.ts.map +1 -0
- package/dist/linux/unit-lifecycle.d.ts +40 -0
- package/dist/linux/unit-lifecycle.d.ts.map +1 -0
- package/dist/linux/unit-preflight.d.ts +41 -0
- package/dist/linux/unit-preflight.d.ts.map +1 -0
- package/dist/linux/unit-settle.d.ts +31 -0
- package/dist/linux/unit-settle.d.ts.map +1 -0
- package/dist/linux/unit.d.ts +35 -0
- package/dist/linux/unit.d.ts.map +1 -0
- package/dist/netbox/client.d.ts +52 -0
- package/dist/netbox/client.d.ts.map +1 -0
- package/dist/netbox/constraint-guard.d.ts +26 -0
- package/dist/netbox/constraint-guard.d.ts.map +1 -0
- package/dist/netbox/constraints.d.ts +76 -0
- package/dist/netbox/constraints.d.ts.map +1 -0
- package/dist/netbox/generated/constraints/index.d.ts +19 -0
- package/dist/netbox/generated/constraints/index.d.ts.map +1 -0
- package/dist/netbox/generated/constraints/netbox-ipam.d.ts +21 -0
- package/dist/netbox/generated/constraints/netbox-ipam.d.ts.map +1 -0
- package/dist/netbox/index.d.ts +17 -0
- package/dist/netbox/index.d.ts.map +1 -0
- package/dist/netbox/index.js +331 -0
- package/dist/netbox/index.js.map +18 -0
- package/dist/netbox/prefix-form.d.ts +54 -0
- package/dist/netbox/prefix-form.d.ts.map +1 -0
- package/dist/netbox/prefix.d.ts +80 -0
- package/dist/netbox/prefix.d.ts.map +1 -0
- package/dist/netbox/resource.d.ts +94 -0
- package/dist/netbox/resource.d.ts.map +1 -0
- package/dist/netbox/values.d.ts +24 -0
- package/dist/netbox/values.d.ts.map +1 -0
- package/dist/proxmox/api-token-form.d.ts.map +1 -1
- package/dist/proxmox/apply-endpoints.d.ts +30 -0
- package/dist/proxmox/apply-endpoints.d.ts.map +1 -0
- package/dist/proxmox/ceph-daemon-form.d.ts +29 -1
- package/dist/proxmox/ceph-daemon-form.d.ts.map +1 -1
- package/dist/proxmox/ceph-daemon.d.ts.map +1 -1
- package/dist/proxmox/ceph-flag.d.ts.map +1 -1
- package/dist/proxmox/ceph-fs.d.ts.map +1 -1
- package/dist/proxmox/ceph-osd-write.d.ts +19 -0
- package/dist/proxmox/ceph-osd-write.d.ts.map +1 -1
- package/dist/proxmox/ceph-pool.d.ts.map +1 -1
- package/dist/proxmox/constraints.d.ts +28 -5
- package/dist/proxmox/constraints.d.ts.map +1 -1
- package/dist/proxmox/generated/constraints/index.d.ts +1 -1
- package/dist/proxmox/generated/constraints/index.d.ts.map +1 -1
- package/dist/proxmox/generated/constraints/pbs-config.d.ts +6 -1
- package/dist/proxmox/generated/constraints/pbs-config.d.ts.map +1 -1
- package/dist/proxmox/generated/constraints/pve-access.d.ts +6 -1
- package/dist/proxmox/generated/constraints/pve-access.d.ts.map +1 -1
- package/dist/proxmox/generated/constraints/pve-cluster-backup.d.ts +23 -0
- package/dist/proxmox/generated/constraints/pve-cluster-backup.d.ts.map +1 -0
- package/dist/proxmox/generated/constraints/pve-cluster-ceph.d.ts +23 -0
- package/dist/proxmox/generated/constraints/pve-cluster-ceph.d.ts.map +1 -0
- package/dist/proxmox/generated/constraints/pve-cluster-firewall.d.ts +23 -0
- package/dist/proxmox/generated/constraints/pve-cluster-firewall.d.ts.map +1 -0
- package/dist/proxmox/generated/constraints/pve-cluster-ha.d.ts +23 -0
- package/dist/proxmox/generated/constraints/pve-cluster-ha.d.ts.map +1 -0
- package/dist/proxmox/generated/constraints/pve-cluster-metrics.d.ts +23 -0
- package/dist/proxmox/generated/constraints/pve-cluster-metrics.d.ts.map +1 -0
- package/dist/proxmox/generated/constraints/pve-cluster-notifications.d.ts +23 -0
- package/dist/proxmox/generated/constraints/pve-cluster-notifications.d.ts.map +1 -0
- package/dist/proxmox/generated/constraints/pve-cluster-replication.d.ts +23 -0
- package/dist/proxmox/generated/constraints/pve-cluster-replication.d.ts.map +1 -0
- package/dist/proxmox/generated/constraints/pve-cluster-sdn.d.ts +23 -0
- package/dist/proxmox/generated/constraints/pve-cluster-sdn.d.ts.map +1 -0
- package/dist/proxmox/generated/constraints/pve-nodes-ceph.d.ts +23 -0
- package/dist/proxmox/generated/constraints/pve-nodes-ceph.d.ts.map +1 -0
- package/dist/proxmox/generated/constraints/pve-nodes-disks.d.ts +23 -0
- package/dist/proxmox/generated/constraints/pve-nodes-disks.d.ts.map +1 -0
- package/dist/proxmox/generated/constraints/pve-nodes-lxc.d.ts +23 -0
- package/dist/proxmox/generated/constraints/pve-nodes-lxc.d.ts.map +1 -0
- package/dist/proxmox/generated/constraints/pve-nodes-network.d.ts +23 -0
- package/dist/proxmox/generated/constraints/pve-nodes-network.d.ts.map +1 -0
- package/dist/proxmox/generated/constraints/pve-nodes-qemu.d.ts +23 -0
- package/dist/proxmox/generated/constraints/pve-nodes-qemu.d.ts.map +1 -0
- package/dist/proxmox/generated/constraints/pve-pools.d.ts +6 -1
- package/dist/proxmox/generated/constraints/pve-pools.d.ts.map +1 -1
- package/dist/proxmox/generated/constraints/pve-storage.d.ts +6 -1
- package/dist/proxmox/generated/constraints/pve-storage.d.ts.map +1 -1
- package/dist/proxmox/generated/pbs/access-users.d.ts +137 -0
- package/dist/proxmox/generated/pbs/access-users.d.ts.map +1 -0
- package/dist/proxmox/generated/pbs/access.d.ts +171 -0
- package/dist/proxmox/generated/pbs/access.d.ts.map +1 -0
- package/dist/proxmox/generated/pbs/admin-datastore-store.d.ts +252 -0
- package/dist/proxmox/generated/pbs/admin-datastore-store.d.ts.map +1 -0
- package/dist/proxmox/generated/pbs/admin-metrics.d.ts +147 -0
- package/dist/proxmox/generated/pbs/admin-metrics.d.ts.map +1 -0
- package/dist/proxmox/generated/pbs/admin.d.ts +217 -0
- package/dist/proxmox/generated/pbs/admin.d.ts.map +1 -0
- package/dist/proxmox/generated/pbs/backup.d.ts +114 -0
- package/dist/proxmox/generated/pbs/backup.d.ts.map +1 -0
- package/dist/proxmox/generated/pbs/config-access-pam.d.ts +218 -0
- package/dist/proxmox/generated/pbs/config-access-pam.d.ts.map +1 -0
- package/dist/proxmox/generated/pbs/config-access.d.ts +203 -0
- package/dist/proxmox/generated/pbs/config-access.d.ts.map +1 -0
- package/dist/proxmox/generated/pbs/config-datastore.d.ts +195 -0
- package/dist/proxmox/generated/pbs/config-datastore.d.ts.map +1 -0
- package/dist/proxmox/generated/pbs/config-media-pool.d.ts +207 -0
- package/dist/proxmox/generated/pbs/config-media-pool.d.ts.map +1 -0
- package/dist/proxmox/generated/pbs/config-notifications-matchers.d.ts +159 -0
- package/dist/proxmox/generated/pbs/config-notifications-matchers.d.ts.map +1 -0
- package/dist/proxmox/generated/pbs/config-notifications.d.ts +199 -0
- package/dist/proxmox/generated/pbs/config-notifications.d.ts.map +1 -0
- package/dist/proxmox/generated/pbs/config-remote.d.ts +175 -0
- package/dist/proxmox/generated/pbs/config-remote.d.ts.map +1 -0
- package/dist/proxmox/generated/pbs/config-sync.d.ts +145 -0
- package/dist/proxmox/generated/pbs/config-sync.d.ts.map +1 -0
- package/dist/proxmox/generated/pbs/config-tape.d.ts +200 -0
- package/dist/proxmox/generated/pbs/config-tape.d.ts.map +1 -0
- package/dist/proxmox/generated/pbs/config-verify.d.ts +77 -0
- package/dist/proxmox/generated/pbs/config-verify.d.ts.map +1 -0
- package/dist/proxmox/generated/pbs/config.d.ts +119 -0
- package/dist/proxmox/generated/pbs/config.d.ts.map +1 -0
- package/dist/proxmox/generated/pbs/nodes-node-dns.d.ts +178 -0
- package/dist/proxmox/generated/pbs/nodes-node-dns.d.ts.map +1 -0
- package/dist/proxmox/generated/pbs/nodes-node-services.d.ts +205 -0
- package/dist/proxmox/generated/pbs/nodes-node-services.d.ts.map +1 -0
- package/dist/proxmox/generated/pbs/nodes-node.d.ts +166 -0
- package/dist/proxmox/generated/pbs/nodes-node.d.ts.map +1 -0
- package/dist/proxmox/generated/pbs/nodes.d.ts +171 -0
- package/dist/proxmox/generated/pbs/nodes.d.ts.map +1 -0
- package/dist/proxmox/generated/pbs/ping.d.ts +18 -0
- package/dist/proxmox/generated/pbs/ping.d.ts.map +1 -0
- package/dist/proxmox/generated/pbs/pull.d.ts +37 -0
- package/dist/proxmox/generated/pbs/pull.d.ts.map +1 -0
- package/dist/proxmox/generated/pbs/push.d.ts +36 -0
- package/dist/proxmox/generated/pbs/push.d.ts.map +1 -0
- package/dist/proxmox/generated/pbs/reader.d.ts +41 -0
- package/dist/proxmox/generated/pbs/reader.d.ts.map +1 -0
- package/dist/proxmox/generated/pbs/root.d.ts +16 -0
- package/dist/proxmox/generated/pbs/root.d.ts.map +1 -0
- package/dist/proxmox/generated/pbs/status.d.ts +54 -0
- package/dist/proxmox/generated/pbs/status.d.ts.map +1 -0
- package/dist/proxmox/generated/pbs/tape-drive.d.ts +181 -0
- package/dist/proxmox/generated/pbs/tape-drive.d.ts.map +1 -0
- package/dist/proxmox/generated/pbs/tape-media.d.ts +126 -0
- package/dist/proxmox/generated/pbs/tape-media.d.ts.map +1 -0
- package/dist/proxmox/generated/pbs/tape.d.ts +90 -0
- package/dist/proxmox/generated/pbs/tape.d.ts.map +1 -0
- package/dist/proxmox/generated/pbs/version.d.ts +20 -0
- package/dist/proxmox/generated/pbs/version.d.ts.map +1 -0
- package/dist/proxmox/generated/pbs.d.ts +44 -662
- package/dist/proxmox/generated/pbs.d.ts.map +1 -1
- package/dist/proxmox/generated/pve/access-roles.d.ts +164 -0
- package/dist/proxmox/generated/pve/access-roles.d.ts.map +1 -0
- package/dist/proxmox/generated/pve/access-users.d.ts +152 -0
- package/dist/proxmox/generated/pve/access-users.d.ts.map +1 -0
- package/dist/proxmox/generated/pve/access.d.ts +221 -0
- package/dist/proxmox/generated/pve/access.d.ts.map +1 -0
- package/dist/proxmox/generated/pve/cluster-acme-plugins-id.d.ts +45 -0
- package/dist/proxmox/generated/pve/cluster-acme-plugins-id.d.ts.map +1 -0
- package/dist/proxmox/generated/pve/cluster-acme-plugins.d.ts +47 -0
- package/dist/proxmox/generated/pve/cluster-acme-plugins.d.ts.map +1 -0
- package/dist/proxmox/generated/pve/cluster-acme-tos.d.ts +240 -0
- package/dist/proxmox/generated/pve/cluster-acme-tos.d.ts.map +1 -0
- package/dist/proxmox/generated/pve/cluster-bulk-action.d.ts +200 -0
- package/dist/proxmox/generated/pve/cluster-bulk-action.d.ts.map +1 -0
- package/dist/proxmox/generated/pve/cluster-firewall-refs.d.ts +109 -0
- package/dist/proxmox/generated/pve/cluster-firewall-refs.d.ts.map +1 -0
- package/dist/proxmox/generated/pve/cluster-firewall.d.ts +225 -0
- package/dist/proxmox/generated/pve/cluster-firewall.d.ts.map +1 -0
- package/dist/proxmox/generated/pve/cluster-ha.d.ts +199 -0
- package/dist/proxmox/generated/pve/cluster-ha.d.ts.map +1 -0
- package/dist/proxmox/generated/pve/cluster-jobs.d.ts +183 -0
- package/dist/proxmox/generated/pve/cluster-jobs.d.ts.map +1 -0
- package/dist/proxmox/generated/pve/cluster-metrics.d.ts +110 -0
- package/dist/proxmox/generated/pve/cluster-metrics.d.ts.map +1 -0
- package/dist/proxmox/generated/pve/cluster-notifications-endpoints.d.ts +224 -0
- package/dist/proxmox/generated/pve/cluster-notifications-endpoints.d.ts.map +1 -0
- package/dist/proxmox/generated/pve/cluster-notifications.d.ts +91 -0
- package/dist/proxmox/generated/pve/cluster-notifications.d.ts.map +1 -0
- package/dist/proxmox/generated/pve/cluster-options.d.ts +85 -0
- package/dist/proxmox/generated/pve/cluster-options.d.ts.map +1 -0
- package/dist/proxmox/generated/pve/cluster-qemu-custom-cpu-models-cputype.d.ts +48 -0
- package/dist/proxmox/generated/pve/cluster-qemu-custom-cpu-models-cputype.d.ts.map +1 -0
- package/dist/proxmox/generated/pve/cluster-qemu-custom-cpu-models.d.ts +45 -0
- package/dist/proxmox/generated/pve/cluster-qemu-custom-cpu-models.d.ts.map +1 -0
- package/dist/proxmox/generated/pve/cluster-replication.d.ts +130 -0
- package/dist/proxmox/generated/pve/cluster-replication.d.ts.map +1 -0
- package/dist/proxmox/generated/pve/cluster-sdn-fabrics.d.ts +220 -0
- package/dist/proxmox/generated/pve/cluster-sdn-fabrics.d.ts.map +1 -0
- package/dist/proxmox/generated/pve/cluster-sdn-ipams.d.ts +229 -0
- package/dist/proxmox/generated/pve/cluster-sdn-ipams.d.ts.map +1 -0
- package/dist/proxmox/generated/pve/cluster-sdn-vnets-vnet.d.ts +229 -0
- package/dist/proxmox/generated/pve/cluster-sdn-vnets-vnet.d.ts.map +1 -0
- package/dist/proxmox/generated/pve/cluster-sdn-vnets.d.ts +51 -0
- package/dist/proxmox/generated/pve/cluster-sdn-vnets.d.ts.map +1 -0
- package/dist/proxmox/generated/pve/cluster-sdn-zones.d.ts +225 -0
- package/dist/proxmox/generated/pve/cluster-sdn-zones.d.ts.map +1 -0
- package/dist/proxmox/generated/pve/cluster-sdn.d.ts +202 -0
- package/dist/proxmox/generated/pve/cluster-sdn.d.ts.map +1 -0
- package/dist/proxmox/generated/pve/cluster.d.ts +69 -0
- package/dist/proxmox/generated/pve/cluster.d.ts.map +1 -0
- package/dist/proxmox/generated/pve/nodes-node-ceph.d.ts +167 -0
- package/dist/proxmox/generated/pve/nodes-node-ceph.d.ts.map +1 -0
- package/dist/proxmox/generated/pve/nodes-node-config.d.ts +43 -0
- package/dist/proxmox/generated/pve/nodes-node-config.d.ts.map +1 -0
- package/dist/proxmox/generated/pve/nodes-node-disks.d.ts +225 -0
- package/dist/proxmox/generated/pve/nodes-node-disks.d.ts.map +1 -0
- package/dist/proxmox/generated/pve/nodes-node-firewall.d.ts +145 -0
- package/dist/proxmox/generated/pve/nodes-node-firewall.d.ts.map +1 -0
- package/dist/proxmox/generated/pve/nodes-node-hardware.d.ts +195 -0
- package/dist/proxmox/generated/pve/nodes-node-hardware.d.ts.map +1 -0
- package/dist/proxmox/generated/pve/nodes-node-lxc-vmid-firewall.d.ts +191 -0
- package/dist/proxmox/generated/pve/nodes-node-lxc-vmid-firewall.d.ts.map +1 -0
- package/dist/proxmox/generated/pve/nodes-node-lxc-vmid-move-volume.d.ts +33 -0
- package/dist/proxmox/generated/pve/nodes-node-lxc-vmid-move-volume.d.ts.map +1 -0
- package/dist/proxmox/generated/pve/nodes-node-lxc-vmid-mtunnel.d.ts +56 -0
- package/dist/proxmox/generated/pve/nodes-node-lxc-vmid-mtunnel.d.ts.map +1 -0
- package/dist/proxmox/generated/pve/nodes-node-lxc-vmid-resize.d.ts +28 -0
- package/dist/proxmox/generated/pve/nodes-node-lxc-vmid-resize.d.ts.map +1 -0
- package/dist/proxmox/generated/pve/nodes-node-lxc-vmid-rrd.d.ts +186 -0
- package/dist/proxmox/generated/pve/nodes-node-lxc-vmid-rrd.d.ts.map +1 -0
- package/dist/proxmox/generated/pve/nodes-node-lxc-vmid.d.ts +191 -0
- package/dist/proxmox/generated/pve/nodes-node-lxc-vmid.d.ts.map +1 -0
- package/dist/proxmox/generated/pve/nodes-node-network.d.ts +150 -0
- package/dist/proxmox/generated/pve/nodes-node-network.d.ts.map +1 -0
- package/dist/proxmox/generated/pve/nodes-node-qemu-vmid-config.d.ts +301 -0
- package/dist/proxmox/generated/pve/nodes-node-qemu-vmid-config.d.ts.map +1 -0
- package/dist/proxmox/generated/pve/nodes-node-qemu-vmid-dbus-vmstate.d.ts +170 -0
- package/dist/proxmox/generated/pve/nodes-node-qemu-vmid-dbus-vmstate.d.ts.map +1 -0
- package/dist/proxmox/generated/pve/nodes-node-qemu-vmid-firewall-rules.d.ts +144 -0
- package/dist/proxmox/generated/pve/nodes-node-qemu-vmid-firewall-rules.d.ts.map +1 -0
- package/dist/proxmox/generated/pve/nodes-node-qemu-vmid-move-disk.d.ts +34 -0
- package/dist/proxmox/generated/pve/nodes-node-qemu-vmid-move-disk.d.ts.map +1 -0
- package/dist/proxmox/generated/pve/nodes-node-qemu-vmid-mtunnel.d.ts +137 -0
- package/dist/proxmox/generated/pve/nodes-node-qemu-vmid-mtunnel.d.ts.map +1 -0
- package/dist/proxmox/generated/pve/nodes-node-qemu-vmid-status.d.ts +179 -0
- package/dist/proxmox/generated/pve/nodes-node-qemu-vmid-status.d.ts.map +1 -0
- package/dist/proxmox/generated/pve/nodes-node-qemu-vmid.d.ts +143 -0
- package/dist/proxmox/generated/pve/nodes-node-qemu-vmid.d.ts.map +1 -0
- package/dist/proxmox/generated/pve/nodes-node-qemu.d.ts +158 -0
- package/dist/proxmox/generated/pve/nodes-node-qemu.d.ts.map +1 -0
- package/dist/proxmox/generated/pve/nodes-node-replication.d.ts +187 -0
- package/dist/proxmox/generated/pve/nodes-node-replication.d.ts.map +1 -0
- package/dist/proxmox/generated/pve/nodes-node-services.d.ts +115 -0
- package/dist/proxmox/generated/pve/nodes-node-services.d.ts.map +1 -0
- package/dist/proxmox/generated/pve/nodes-node-storage-storage.d.ts +202 -0
- package/dist/proxmox/generated/pve/nodes-node-storage-storage.d.ts.map +1 -0
- package/dist/proxmox/generated/pve/nodes-node-storage.d.ts +40 -0
- package/dist/proxmox/generated/pve/nodes-node-storage.d.ts.map +1 -0
- package/dist/proxmox/generated/pve/nodes-node-subscription.d.ts +173 -0
- package/dist/proxmox/generated/pve/nodes-node-subscription.d.ts.map +1 -0
- package/dist/proxmox/generated/pve/nodes-node-vzdump.d.ts +97 -0
- package/dist/proxmox/generated/pve/nodes-node-vzdump.d.ts.map +1 -0
- package/dist/proxmox/generated/pve/nodes-node.d.ts +207 -0
- package/dist/proxmox/generated/pve/nodes-node.d.ts.map +1 -0
- package/dist/proxmox/generated/pve/nodes.d.ts +236 -0
- package/dist/proxmox/generated/pve/nodes.d.ts.map +1 -0
- package/dist/proxmox/generated/pve/pools.d.ts +82 -0
- package/dist/proxmox/generated/pve/pools.d.ts.map +1 -0
- package/dist/proxmox/generated/pve/storage.d.ts +159 -0
- package/dist/proxmox/generated/pve/storage.d.ts.map +1 -0
- package/dist/proxmox/generated/pve/version.d.ts +21 -0
- package/dist/proxmox/generated/pve/version.d.ts.map +1 -0
- package/dist/proxmox/generated/pve.d.ts +69 -4766
- package/dist/proxmox/generated/pve.d.ts.map +1 -1
- package/dist/proxmox/index.js +996 -372
- package/dist/proxmox/index.js.map +48 -32
- package/dist/proxmox/lxc-lifecycle.d.ts.map +1 -1
- package/dist/proxmox/metric-server-form.d.ts +11 -0
- package/dist/proxmox/metric-server-form.d.ts.map +1 -1
- package/dist/proxmox/metric-server.d.ts.map +1 -1
- package/dist/proxmox/network-apply.d.ts.map +1 -1
- package/dist/proxmox/node-network-form.d.ts +15 -0
- package/dist/proxmox/node-network-form.d.ts.map +1 -1
- package/dist/proxmox/node-network.d.ts.map +1 -1
- package/dist/proxmox/notification-target-endpoint.d.ts +26 -0
- package/dist/proxmox/notification-target-endpoint.d.ts.map +1 -0
- package/dist/proxmox/notification-target-form.d.ts +7 -0
- package/dist/proxmox/notification-target-form.d.ts.map +1 -1
- package/dist/proxmox/notification-target.d.ts.map +1 -1
- package/dist/proxmox/pbs-notification-target-endpoint.d.ts +29 -0
- package/dist/proxmox/pbs-notification-target-endpoint.d.ts.map +1 -0
- package/dist/proxmox/pbs-notification-target-lifecycle.d.ts.map +1 -1
- package/dist/proxmox/qemu.d.ts +10 -0
- package/dist/proxmox/qemu.d.ts.map +1 -1
- package/dist/proxmox/resource-guard.d.ts +15 -0
- package/dist/proxmox/resource-guard.d.ts.map +1 -0
- package/dist/proxmox/resource-spec.d.ts +15 -4
- package/dist/proxmox/resource-spec.d.ts.map +1 -1
- package/dist/proxmox/resource.d.ts +8 -0
- package/dist/proxmox/resource.d.ts.map +1 -1
- package/dist/proxmox/sdn-apply.d.ts.map +1 -1
- package/dist/proxmox/sdn-subnet.d.ts.map +1 -1
- package/dist/proxmox/zfs-pool-write.d.ts +11 -0
- package/dist/proxmox/zfs-pool-write.d.ts.map +1 -1
- package/dist/proxmox/zfs-pool.d.ts.map +1 -1
- package/docs/linux-host-measured.md +56 -0
- package/docs/linux-host.md +192 -0
- package/docs/netbox.md +103 -0
- package/package.json +9 -1
- package/src/linux/directory-lifecycle.ts +220 -0
- package/src/linux/directory.test.ts +132 -0
- package/src/linux/directory.ts +71 -0
- package/src/linux/fake-linux-host.ts +197 -0
- package/src/linux/fake-systemd.ts +103 -0
- package/src/linux/index.ts +32 -0
- package/src/linux/providers.ts +26 -0
- package/src/linux/region.test.ts +89 -0
- package/src/linux/region.ts +118 -0
- package/src/linux/remote-file-form.ts +96 -0
- package/src/linux/remote-file-lifecycle.test.ts +191 -0
- package/src/linux/remote-file-lifecycle.ts +161 -0
- package/src/linux/remote-file-plan.ts +154 -0
- package/src/linux/remote-file.ts +88 -0
- package/src/linux/ssh-command.test.ts +140 -0
- package/src/linux/ssh-command.ts +99 -0
- package/src/linux/ssh-runner.ts +188 -0
- package/src/linux/ssh-scripts.ts +105 -0
- package/src/linux/systemctl.ts +145 -0
- package/src/linux/unit-form.ts +183 -0
- package/src/linux/unit-handlers.ts +73 -0
- package/src/linux/unit-lifecycle.test.ts +220 -0
- package/src/linux/unit-lifecycle.ts +175 -0
- package/src/linux/unit-preflight.test.ts +128 -0
- package/src/linux/unit-preflight.ts +100 -0
- package/src/linux/unit-settle.ts +110 -0
- package/src/linux/unit.ts +81 -0
- package/src/netbox/client.test.ts +67 -0
- package/src/netbox/client.ts +146 -0
- package/src/netbox/constraint-guard.ts +57 -0
- package/src/netbox/constraints.test.ts +114 -0
- package/src/netbox/constraints.ts +152 -0
- package/src/netbox/generated/constraints/index.ts +23 -0
- package/src/netbox/generated/constraints/netbox-ipam.ts +32 -0
- package/src/netbox/index.ts +34 -0
- package/src/netbox/prefix-form.test.ts +127 -0
- package/src/netbox/prefix-form.ts +84 -0
- package/src/netbox/prefix.ts +136 -0
- package/src/netbox/resource.ts +208 -0
- package/src/netbox/values.test.ts +66 -0
- package/src/netbox/values.ts +40 -0
- package/src/proxmox/api-token-form.ts +5 -0
- package/src/proxmox/apply-endpoints.ts +34 -0
- package/src/proxmox/ceph-daemon-form.ts +56 -2
- package/src/proxmox/ceph-daemon.ts +10 -21
- package/src/proxmox/ceph-flag.ts +1 -0
- package/src/proxmox/ceph-fs.ts +1 -0
- package/src/proxmox/ceph-osd-write.ts +30 -4
- package/src/proxmox/ceph-pool.ts +10 -0
- package/src/proxmox/constraints-dialect.test.ts +165 -0
- package/src/proxmox/constraints-forms.test.ts +228 -0
- package/src/proxmox/constraints-host-forms.test.ts +132 -0
- package/src/proxmox/constraints-live.test.ts +155 -0
- package/src/proxmox/constraints-node-forms.test.ts +197 -0
- package/src/proxmox/constraints-required.test.ts +172 -0
- package/src/proxmox/constraints.test.ts +8 -63
- package/src/proxmox/constraints.ts +31 -6
- package/src/proxmox/generated/constraints/index.ts +27 -3
- package/src/proxmox/generated/constraints/pbs-config.ts +63 -1
- package/src/proxmox/generated/constraints/pve-access.ts +15 -3
- package/src/proxmox/generated/constraints/pve-cluster-backup.ts +74 -0
- package/src/proxmox/generated/constraints/pve-cluster-ceph.ts +27 -0
- package/src/proxmox/generated/constraints/pve-cluster-firewall.ts +35 -0
- package/src/proxmox/generated/constraints/pve-cluster-ha.ts +55 -0
- package/src/proxmox/generated/constraints/pve-cluster-metrics.ts +59 -0
- package/src/proxmox/generated/constraints/pve-cluster-notifications.ts +80 -0
- package/src/proxmox/generated/constraints/pve-cluster-replication.ts +43 -0
- package/src/proxmox/generated/constraints/pve-cluster-sdn.ts +90 -0
- package/src/proxmox/generated/constraints/pve-nodes-ceph.ts +57 -0
- package/src/proxmox/generated/constraints/pve-nodes-disks.ts +31 -0
- package/src/proxmox/generated/constraints/pve-nodes-lxc.ts +82 -0
- package/src/proxmox/generated/constraints/pve-nodes-network.ts +77 -0
- package/src/proxmox/generated/constraints/pve-nodes-qemu.ts +125 -0
- package/src/proxmox/generated/constraints/pve-pools.ts +6 -1
- package/src/proxmox/generated/constraints/pve-storage.ts +8 -3
- package/src/proxmox/generated/pbs/access-users.ts +137 -0
- package/src/proxmox/generated/pbs/access.ts +232 -0
- package/src/proxmox/generated/pbs/admin-datastore-store.ts +238 -0
- package/src/proxmox/generated/pbs/admin-metrics.ts +147 -0
- package/src/proxmox/generated/pbs/admin.ts +225 -0
- package/src/proxmox/generated/pbs/backup.ts +121 -0
- package/src/proxmox/generated/pbs/config-access-pam.ts +228 -0
- package/src/proxmox/generated/pbs/config-access.ts +228 -0
- package/src/proxmox/generated/pbs/config-datastore.ts +217 -0
- package/src/proxmox/generated/pbs/config-media-pool.ts +232 -0
- package/src/proxmox/generated/pbs/config-notifications-matchers.ts +187 -0
- package/src/proxmox/generated/pbs/config-notifications.ts +221 -0
- package/src/proxmox/generated/pbs/config-remote.ts +192 -0
- package/src/proxmox/generated/pbs/config-sync.ts +167 -0
- package/src/proxmox/generated/pbs/config-tape.ts +226 -0
- package/src/proxmox/generated/pbs/config-verify.ts +87 -0
- package/src/proxmox/generated/pbs/config.ts +135 -0
- package/src/proxmox/generated/pbs/nodes-node-dns.ts +229 -0
- package/src/proxmox/generated/pbs/nodes-node-services.ts +181 -0
- package/src/proxmox/generated/pbs/nodes-node.ts +244 -0
- package/src/proxmox/generated/pbs/nodes.ts +160 -0
- package/src/proxmox/generated/pbs/ping.ts +16 -0
- package/src/proxmox/generated/pbs/pull.ts +37 -0
- package/src/proxmox/generated/pbs/push.ts +36 -0
- package/src/proxmox/generated/pbs/reader.ts +41 -0
- package/src/proxmox/generated/pbs/root.ts +16 -0
- package/src/proxmox/generated/pbs/status.ts +50 -0
- package/src/proxmox/generated/pbs/tape-drive.ts +220 -0
- package/src/proxmox/generated/pbs/tape-media.ts +133 -0
- package/src/proxmox/generated/pbs/tape.ts +92 -0
- package/src/proxmox/generated/pbs/version.ts +16 -0
- package/src/proxmox/generated/pbs.ts +44 -787
- package/src/proxmox/generated/pve/access-roles.ts +168 -0
- package/src/proxmox/generated/pve/access-users.ts +155 -0
- package/src/proxmox/generated/pve/access.ts +217 -0
- package/src/proxmox/generated/pve/cluster-acme-plugins-id.ts +367 -0
- package/src/proxmox/generated/pve/cluster-acme-plugins.ts +366 -0
- package/src/proxmox/generated/pve/cluster-acme-tos.ts +232 -0
- package/src/proxmox/generated/pve/cluster-bulk-action.ts +227 -0
- package/src/proxmox/generated/pve/cluster-firewall-refs.ts +110 -0
- package/src/proxmox/generated/pve/cluster-firewall.ts +236 -0
- package/src/proxmox/generated/pve/cluster-ha.ts +203 -0
- package/src/proxmox/generated/pve/cluster-jobs.ts +190 -0
- package/src/proxmox/generated/pve/cluster-metrics.ts +116 -0
- package/src/proxmox/generated/pve/cluster-notifications-endpoints.ts +244 -0
- package/src/proxmox/generated/pve/cluster-notifications.ts +99 -0
- package/src/proxmox/generated/pve/cluster-options.ts +189 -0
- package/src/proxmox/generated/pve/cluster-qemu-custom-cpu-models-cputype.ts +272 -0
- package/src/proxmox/generated/pve/cluster-qemu-custom-cpu-models.ts +268 -0
- package/src/proxmox/generated/pve/cluster-replication.ts +127 -0
- package/src/proxmox/generated/pve/cluster-sdn-fabrics.ts +218 -0
- package/src/proxmox/generated/pve/cluster-sdn-ipams.ts +229 -0
- package/src/proxmox/generated/pve/cluster-sdn-vnets-vnet.ts +245 -0
- package/src/proxmox/generated/pve/cluster-sdn-vnets.ts +49 -0
- package/src/proxmox/generated/pve/cluster-sdn-zones.ts +224 -0
- package/src/proxmox/generated/pve/cluster-sdn.ts +201 -0
- package/src/proxmox/generated/pve/cluster.ts +71 -0
- package/src/proxmox/generated/pve/nodes-node-ceph.ts +167 -0
- package/src/proxmox/generated/pve/nodes-node-config.ts +56 -0
- package/src/proxmox/generated/pve/nodes-node-disks.ts +237 -0
- package/src/proxmox/generated/pve/nodes-node-firewall.ts +239 -0
- package/src/proxmox/generated/pve/nodes-node-hardware.ts +215 -0
- package/src/proxmox/generated/pve/nodes-node-lxc-vmid-firewall.ts +232 -0
- package/src/proxmox/generated/pve/nodes-node-lxc-vmid-move-volume.ts +1059 -0
- package/src/proxmox/generated/pve/nodes-node-lxc-vmid-mtunnel.ts +49 -0
- package/src/proxmox/generated/pve/nodes-node-lxc-vmid-resize.ts +285 -0
- package/src/proxmox/generated/pve/nodes-node-lxc-vmid-rrd.ts +184 -0
- package/src/proxmox/generated/pve/nodes-node-lxc-vmid.ts +240 -0
- package/src/proxmox/generated/pve/nodes-node-network.ts +234 -0
- package/src/proxmox/generated/pve/nodes-node-qemu-vmid-config.ts +441 -0
- package/src/proxmox/generated/pve/nodes-node-qemu-vmid-dbus-vmstate.ts +215 -0
- package/src/proxmox/generated/pve/nodes-node-qemu-vmid-firewall-rules.ts +145 -0
- package/src/proxmox/generated/pve/nodes-node-qemu-vmid-move-disk.ts +664 -0
- package/src/proxmox/generated/pve/nodes-node-qemu-vmid-mtunnel.ts +188 -0
- package/src/proxmox/generated/pve/nodes-node-qemu-vmid-status.ts +233 -0
- package/src/proxmox/generated/pve/nodes-node-qemu-vmid.ts +187 -0
- package/src/proxmox/generated/pve/nodes-node-qemu.ts +204 -0
- package/src/proxmox/generated/pve/nodes-node-replication.ts +187 -0
- package/src/proxmox/generated/pve/nodes-node-services.ts +221 -0
- package/src/proxmox/generated/pve/nodes-node-storage-storage.ts +226 -0
- package/src/proxmox/generated/pve/nodes-node-storage.ts +40 -0
- package/src/proxmox/generated/pve/nodes-node-subscription.ts +168 -0
- package/src/proxmox/generated/pve/nodes-node-vzdump.ts +96 -0
- package/src/proxmox/generated/pve/nodes-node.ts +211 -0
- package/src/proxmox/generated/pve/nodes.ts +235 -0
- package/src/proxmox/generated/pve/pools.ts +78 -0
- package/src/proxmox/generated/pve/storage.ts +213 -0
- package/src/proxmox/generated/pve/version.ts +21 -0
- package/src/proxmox/generated/pve.ts +69 -7403
- package/src/proxmox/lxc-interior.test.ts +17 -1
- package/src/proxmox/lxc-lifecycle.ts +48 -9
- package/src/proxmox/metric-server-form.ts +10 -0
- package/src/proxmox/metric-server.ts +11 -2
- package/src/proxmox/network-apply.ts +4 -0
- package/src/proxmox/node-network-form.ts +19 -0
- package/src/proxmox/node-network.ts +9 -3
- package/src/proxmox/notification-target-endpoint.ts +46 -0
- package/src/proxmox/notification-target-form.ts +11 -0
- package/src/proxmox/notification-target.ts +5 -2
- package/src/proxmox/pbs-notification-target-endpoint.ts +45 -0
- package/src/proxmox/pbs-notification-target-lifecycle.ts +16 -14
- package/src/proxmox/qemu.ts +9 -1
- package/src/proxmox/resource-guard.ts +61 -0
- package/src/proxmox/resource-spec.ts +16 -4
- package/src/proxmox/resource.ts +27 -27
- package/src/proxmox/sdn-apply.ts +4 -0
- package/src/proxmox/sdn-subnet.ts +5 -0
- package/src/proxmox/zfs-pool-write.ts +12 -0
- package/src/proxmox/zfs-pool.ts +2 -1
- package/dist/proxmox/generated/constraints/pve-cluster.d.ts +0 -18
- package/dist/proxmox/generated/constraints/pve-cluster.d.ts.map +0 -1
- package/src/proxmox/generated/constraints/pve-cluster.ts +0 -174
|
@@ -14,8 +14,24 @@
|
|
|
14
14
|
* and have completely different reach. Never infer one family's endpoints from the other's.
|
|
15
15
|
*/
|
|
16
16
|
import { describe, expect, test } from 'bun:test';
|
|
17
|
+
import { readdirSync } from 'node:fs';
|
|
18
|
+
import { fileURLToPath } from 'node:url';
|
|
19
|
+
import { join } from 'node:path';
|
|
17
20
|
|
|
18
|
-
|
|
21
|
+
/**
|
|
22
|
+
* ⚠️ THE GENERATED TYPES ARE A DIRECTORY NOW, NOT ONE FILE. `generated/pve.ts` became a barrel of
|
|
23
|
+
* `export *` lines when codegen/types.ts split the 678 endpoints across files that fit the house
|
|
24
|
+
* cap, so reading that path alone would find no endpoint at all — and every absence assertion
|
|
25
|
+
* below would pass on an empty list. That is what the first test guards.
|
|
26
|
+
*/
|
|
27
|
+
const dir = fileURLToPath(new URL('./generated/pve/', import.meta.url));
|
|
28
|
+
const schema = (
|
|
29
|
+
await Promise.all(
|
|
30
|
+
readdirSync(dir)
|
|
31
|
+
.sort()
|
|
32
|
+
.map((name) => Bun.file(join(dir, name)).text()),
|
|
33
|
+
)
|
|
34
|
+
).join('\n');
|
|
19
35
|
|
|
20
36
|
/** Every endpoint the generated schema documents, as `METHOD /path`. */
|
|
21
37
|
const endpoints = [...schema.matchAll(/(GET|POST|PUT|DELETE) (\/[A-Za-z0-9/{}_.-]+)/g)].map(
|
|
@@ -25,6 +25,8 @@
|
|
|
25
25
|
*/
|
|
26
26
|
import * as Effect from 'effect/Effect';
|
|
27
27
|
import { PveError, pve, pveWith } from './client.ts';
|
|
28
|
+
import { guardForm } from './constraint-guard.ts';
|
|
29
|
+
import type { EndpointKey } from './constraints.ts';
|
|
28
30
|
import { type PveTarget, mint } from './credentials.ts';
|
|
29
31
|
import { createForm } from './lxc-create-form.ts';
|
|
30
32
|
import { type LxcChange, judge } from './lxc-judge.ts';
|
|
@@ -43,6 +45,26 @@ export type LxcWhere = { readonly target: PveTarget; readonly node: string; read
|
|
|
43
45
|
|
|
44
46
|
export const guestPath = (where: LxcWhere) => `nodes/${where.node}/lxc/${String(where.vmid)}`;
|
|
45
47
|
|
|
48
|
+
/**
|
|
49
|
+
* The three vendor endpoints a guest is written through, as the generated tables key them.
|
|
50
|
+
*
|
|
51
|
+
* ⛔ DECLARED HERE RATHER THAN ON A SPEC BECAUSE `Proxmox.Lxc` HAS NO SPEC — lxc.ts writes all
|
|
52
|
+
* five handlers by hand (a guest reads failures as failures, waits on tasks and grows disks
|
|
53
|
+
* through a second endpoint), so it cannot inherit the shared guard from resource.ts. Without
|
|
54
|
+
* these three it would be the largest write surface in the package with nothing checking the
|
|
55
|
+
* body: `POST /nodes/{node}/lxc` alone carries 26 constrained parameters.
|
|
56
|
+
* ⚠️ THREE, NOT TWO. `resize` is a separate PVE endpoint with its own schema — `disk` against an
|
|
57
|
+
* enum of `rootfs` and `mp0..mp255`, `size` against PVE's `+<n>` / `<n>` pattern — and it is
|
|
58
|
+
* the one lxc-volume.ts drives.
|
|
59
|
+
* ⛔ `{vmid}` AND `{node}` ARE PATH SEGMENTS, NOT FORM KEYS, on the two PUTs. On the POST `vmid`
|
|
60
|
+
* IS a form key and is required, which is why `createForm` sends it.
|
|
61
|
+
*/
|
|
62
|
+
const LXC_ENDPOINTS = {
|
|
63
|
+
create: 'pve:POST /nodes/{node}/lxc' as EndpointKey,
|
|
64
|
+
resize: 'pve:PUT /nodes/{node}/lxc/{vmid}/resize' as EndpointKey,
|
|
65
|
+
update: 'pve:PUT /nodes/{node}/lxc/{vmid}/config' as EndpointKey,
|
|
66
|
+
};
|
|
67
|
+
|
|
46
68
|
/** How long each task may run, in one-second polls. ⚠️ A cap, not an estimate. */
|
|
47
69
|
const CREATE_POLLS = 180;
|
|
48
70
|
const TASK_POLLS = 60;
|
|
@@ -132,14 +154,25 @@ const task = (
|
|
|
132
154
|
|
|
133
155
|
/** POST the create and wait for the template to unpack. ⛔ Refusals are the caller's to check. */
|
|
134
156
|
export const createGuest = (props: LxcProps) =>
|
|
135
|
-
|
|
136
|
-
props
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
157
|
+
Effect.gen(function* () {
|
|
158
|
+
const form = createForm(props);
|
|
159
|
+
/**
|
|
160
|
+
* ⛔ BEFORE THE POST, AND WITH PRESENCE, BECAUSE THIS IS UNCONDITIONALLY A CREATE. Reached
|
|
161
|
+
* only when the guest is absent (lxc.ts's reconcile), so the vendor's required parameters
|
|
162
|
+
* really are about to go on the wire — `ostemplate` and `vmid`, both of which
|
|
163
|
+
* `createRefusals` already insists on, plus every bound the schema carries and nothing here
|
|
164
|
+
* knew: `cores` 1..8192, `memory` ≥ 16, `hostname` as a dns-name, `tags`, `startup`.
|
|
165
|
+
*/
|
|
166
|
+
yield* guardForm(LXC_ENDPOINTS.create, form, true);
|
|
167
|
+
return yield* task(
|
|
168
|
+
props,
|
|
169
|
+
'POST',
|
|
170
|
+
`nodes/${props.node}/lxc`,
|
|
171
|
+
`create CT ${String(props.vmid)}`,
|
|
172
|
+
CREATE_POLLS,
|
|
173
|
+
form,
|
|
174
|
+
);
|
|
175
|
+
});
|
|
143
176
|
|
|
144
177
|
/**
|
|
145
178
|
* Apply a judged change: the config PUT first, then each resize.
|
|
@@ -158,16 +191,22 @@ export const updateGuest = (props: LxcProps, change: LxcChange, digest: string)
|
|
|
158
191
|
{ ...change.put, ...(digest === '' ? {} : { digest }) },
|
|
159
192
|
change.clear,
|
|
160
193
|
);
|
|
194
|
+
/** ⚠️ NO PRESENCE: this form is partial by construction — it is `judge`'s answer. */
|
|
195
|
+
yield* guardForm(LXC_ENDPOINTS.update, form, false);
|
|
161
196
|
yield* pve(props.target, 'provision', 'PUT', `${guestPath(props)}/config`, form);
|
|
162
197
|
}
|
|
163
198
|
for (const grow of change.resize) {
|
|
199
|
+
const grown = { disk: grow.disk, size: grow.size };
|
|
200
|
+
// ⛔ WITH PRESENCE: `disk` and `size` are both required and both always sent, so this is a
|
|
201
|
+
// value check over PVE's own `+<n>` size pattern and its `rootfs|mp0..mp255` enum.
|
|
202
|
+
yield* guardForm(LXC_ENDPOINTS.resize, grown, true);
|
|
164
203
|
yield* task(
|
|
165
204
|
props,
|
|
166
205
|
'PUT',
|
|
167
206
|
`${guestPath(props)}/resize`,
|
|
168
207
|
`grow ${grow.disk} of CT ${String(props.vmid)} to ${grow.size}`,
|
|
169
208
|
TASK_POLLS,
|
|
170
|
-
|
|
209
|
+
grown,
|
|
171
210
|
);
|
|
172
211
|
}
|
|
173
212
|
});
|
|
@@ -126,3 +126,13 @@ export const update = (props: MetricServerProps) => {
|
|
|
126
126
|
.map(([option]) => option);
|
|
127
127
|
return withClears(body(props), clear);
|
|
128
128
|
};
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* The create form: `body` plus the one field only a POST takes.
|
|
132
|
+
*
|
|
133
|
+
* ★ EXPORTED SO THE CONSTRAINT PROOF CAN RUN THE REAL FORM. `metric-server.ts` built this inline
|
|
134
|
+
* in its spec, which meant the only way to check it against PVE's table was to retype it in a
|
|
135
|
+
* test — and a retyped form proves the test, not the provider.
|
|
136
|
+
* ⚠️ `type` IS REQUIRED ON CREATE AND REFUSED ON UPDATE; `id` is the path, never the body.
|
|
137
|
+
*/
|
|
138
|
+
export const createBody = (props: MetricServerProps) => ({ ...body(props), type: props.type });
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
import { Resource } from 'alchemy';
|
|
32
32
|
import * as Provider from 'alchemy/Provider';
|
|
33
33
|
import * as Effect from 'effect/Effect';
|
|
34
|
-
import { UNSET,
|
|
34
|
+
import { UNSET, createBody, update } from './metric-server-form.ts';
|
|
35
35
|
import {
|
|
36
36
|
type MetricServerOtelAttributes,
|
|
37
37
|
type MetricServerOtelProps,
|
|
@@ -186,7 +186,16 @@ const handlers = pveHandlers<MetricServerProps, MetricServerAttributes>({
|
|
|
186
186
|
/** ⛔ THE SAME STRING AS `path`, DELIBERATELY — see the ⛔ in the header. */
|
|
187
187
|
collection: (props) => `cluster/metrics/server/${props.id}`,
|
|
188
188
|
/** ⚠️ `type` IS REQUIRED ON CREATE; `id` IS NOT SENT, because the id is the path. */
|
|
189
|
-
createForm:
|
|
189
|
+
createForm: createBody,
|
|
190
|
+
/**
|
|
191
|
+
* The vendor rules both forms are checked against at plan time — resource-spec.ts.
|
|
192
|
+
* ⚠️ POST AND PUT ARE THE SAME PATH HERE, and they are still two different tables: only the
|
|
193
|
+
* POST marks `type` required, and only the PUT accepts `delete` and `digest`.
|
|
194
|
+
*/
|
|
195
|
+
endpoint: {
|
|
196
|
+
create: 'pve:POST /cluster/metrics/server/{id}',
|
|
197
|
+
update: 'pve:PUT /cluster/metrics/server/{id}',
|
|
198
|
+
},
|
|
190
199
|
/**
|
|
191
200
|
* ⛔ `id` and `type` are absent: one is the key the object was read by, the other is refused
|
|
192
201
|
* rather than updated. Everything type-specific is `matchesType`'s, above.
|
|
@@ -50,6 +50,7 @@ import { isResolved } from 'alchemy/Diff';
|
|
|
50
50
|
import type { Input } from 'alchemy/Input';
|
|
51
51
|
import * as Provider from 'alchemy/Provider';
|
|
52
52
|
import * as Effect from 'effect/Effect';
|
|
53
|
+
import { guardNetworkApply } from './apply-endpoints.ts';
|
|
53
54
|
import { pveWith } from './client.ts';
|
|
54
55
|
import { mint } from './credentials.ts';
|
|
55
56
|
import { awaitTask, degradedReason, pendingCount } from './network-apply-read.ts';
|
|
@@ -172,6 +173,9 @@ const apply = (props: NetworkApplyProps) =>
|
|
|
172
173
|
* not have. The `provision` lease is 300s and non-renewable, which is far longer than a
|
|
173
174
|
* reload and is the reason no renew path is reached for.
|
|
174
175
|
*/
|
|
176
|
+
// ⛔ BEFORE THE MINT AND THE RELOAD. The apply takes no body, so this is the lookup rather
|
|
177
|
+
// than a value check: a vendor that moved this endpoint fails here, not mid-ifreload.
|
|
178
|
+
yield* guardNetworkApply;
|
|
175
179
|
const credential = yield* mint(props.target, 'provision');
|
|
176
180
|
const upid = yield* pveWith<string>(props.target, credential, 'PUT', `nodes/${node}/network`);
|
|
177
181
|
if (upid === undefined) {
|
|
@@ -132,6 +132,25 @@ export const body = (props: NodeNetworkProps): Record<string, string> => ({
|
|
|
132
132
|
type: props.type,
|
|
133
133
|
});
|
|
134
134
|
|
|
135
|
+
/**
|
|
136
|
+
* The create form: `body` plus the name of the interface being created.
|
|
137
|
+
*
|
|
138
|
+
* 🔴 `iface` WAS MISSING UNTIL 2026-09-22, AND EVERY CREATE THIS FAMILY HAS EVER PLANNED WOULD
|
|
139
|
+
* HAVE 400ed. `POST /nodes/{node}/network` declares `iface` REQUIRED (`pve-iface`, 2..20) and
|
|
140
|
+
* `{node}` is its only path parameter, so the name has to travel in the BODY — unlike the PUT,
|
|
141
|
+
* where `/network/{iface}` carries it and `updateBody` is right to leave it out. Nothing caught
|
|
142
|
+
* it because no NodeNetwork has been created from a declaration yet: C1's three nodes were all
|
|
143
|
+
* adopted, which takes the PUT path. Found by wiring this family to the vendor's own table,
|
|
144
|
+
* which is exactly the 128-character comment again with a different parameter.
|
|
145
|
+
*
|
|
146
|
+
* ⚠️ NOT ADDED TO `body`, BECAUSE THE PUT MUST NOT CARRY IT. `additionalProperties => 0` on the
|
|
147
|
+
* update schema makes a second copy of the name a 400 rather than an ignored hint.
|
|
148
|
+
*/
|
|
149
|
+
export const createBody = (props: NodeNetworkProps): Record<string, string> => ({
|
|
150
|
+
...body(props),
|
|
151
|
+
iface: props.iface,
|
|
152
|
+
});
|
|
153
|
+
|
|
135
154
|
/**
|
|
136
155
|
* The update form: `body` plus the two fields that can only be cleared explicitly.
|
|
137
156
|
*
|
|
@@ -69,7 +69,7 @@ import { Resource } from 'alchemy';
|
|
|
69
69
|
import * as Provider from 'alchemy/Provider';
|
|
70
70
|
import * as Effect from 'effect/Effect';
|
|
71
71
|
import {
|
|
72
|
-
|
|
72
|
+
createBody,
|
|
73
73
|
readAttributes,
|
|
74
74
|
same,
|
|
75
75
|
sameComment,
|
|
@@ -199,8 +199,14 @@ export const ProxmoxNodeNetwork = Resource<ProxmoxNodeNetwork>('Proxmox.NodeNetw
|
|
|
199
199
|
const handlers = pveHandlers<NodeNetworkProps, NodeNetworkAttributes>({
|
|
200
200
|
attributes: readAttributes,
|
|
201
201
|
collection: (props) => `nodes/${props.node}/network`,
|
|
202
|
-
/** ⚠️ No `delete` parameter on a POST — see the ⚠️ on `updateBody`.
|
|
203
|
-
|
|
202
|
+
/** ⚠️ No `delete` parameter on a POST — see the ⚠️ on `updateBody`. `iface` IS in the create
|
|
203
|
+
* body though, and was missing until 2026-09-22 — the 🔴 on `createBody`. */
|
|
204
|
+
createForm: createBody,
|
|
205
|
+
/** The vendor rules both forms are checked against at plan time — resource-spec.ts. */
|
|
206
|
+
endpoint: {
|
|
207
|
+
create: 'pve:POST /nodes/{node}/network',
|
|
208
|
+
update: 'pve:PUT /nodes/{node}/network/{iface}',
|
|
209
|
+
},
|
|
204
210
|
/**
|
|
205
211
|
* ⛔ `cidr` IS THE ONLY FIELD COMPARED WHEN UNDECLARED, AND THAT ASYMMETRY IS THE POINT. Every
|
|
206
212
|
* other field here follows storage.ts: undeclared means unmanaged, so it is neither sent nor
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `Proxmox.NotificationTarget`'s eight vendor endpoints — one create and one update per family.
|
|
3
|
+
*
|
|
4
|
+
* ⛔ PVE REGISTERS ONE PATH PER TYPE, NOT A `{type}` PARAMETER, AND THE FOUR SCHEMAS DIFFER IN
|
|
5
|
+
* WHAT THEY REQUIRE: gotify wants `server` and `token`, smtp `server` and `from-address`,
|
|
6
|
+
* webhook `url` and `method`, sendmail nothing but its name. One key for all four would enforce
|
|
7
|
+
* gotify's rules on a sendmail target and let three families through unchecked, which is why
|
|
8
|
+
* `PveSpec['endpoint']` admits a function of props.
|
|
9
|
+
*
|
|
10
|
+
* ⛔ THE STRINGS ARE LITERALS AND MUST STAY LITERALS. `codegen/constraints.ts` finds the endpoints
|
|
11
|
+
* to table by SCANNING THIS PACKAGE'S TEXT, so `` `pve:POST …/${props.type}` `` would be tabled
|
|
12
|
+
* by nothing and `constraintsFor` would throw on the first deploy that reached it.
|
|
13
|
+
*
|
|
14
|
+
* ⚠️ GOTIFY'S CREATE CAN NEVER SATISFY ITS OWN TABLE, AND THAT IS THE TRUTH RATHER THAN A BUG
|
|
15
|
+
* HERE. `token` is required by PVE and is a write-only secret, so it is not a prop — props are
|
|
16
|
+
* persisted to the state store unencrypted (the third ⛔ in notification-target.ts). A gotify
|
|
17
|
+
* target is created out of band and then DECLARED, which is an adopt followed by updates; the
|
|
18
|
+
* shared guard only demands the create form's required parameters when a create is really about
|
|
19
|
+
* to happen (`resource-guard.ts`), so declaring an existing gotify target still plans clean and
|
|
20
|
+
* asking to CREATE one is refused at plan time with `token: required` instead of by a 400.
|
|
21
|
+
*/
|
|
22
|
+
import type { NotificationTargetProps } from './notification-target.ts';
|
|
23
|
+
import type { EndpointPair } from './resource-spec.ts';
|
|
24
|
+
|
|
25
|
+
const ENDPOINTS: Readonly<Record<NotificationTargetProps['type'], EndpointPair>> = {
|
|
26
|
+
gotify: {
|
|
27
|
+
create: 'pve:POST /cluster/notifications/endpoints/gotify',
|
|
28
|
+
update: 'pve:PUT /cluster/notifications/endpoints/gotify/{name}',
|
|
29
|
+
},
|
|
30
|
+
sendmail: {
|
|
31
|
+
create: 'pve:POST /cluster/notifications/endpoints/sendmail',
|
|
32
|
+
update: 'pve:PUT /cluster/notifications/endpoints/sendmail/{name}',
|
|
33
|
+
},
|
|
34
|
+
smtp: {
|
|
35
|
+
create: 'pve:POST /cluster/notifications/endpoints/smtp',
|
|
36
|
+
update: 'pve:PUT /cluster/notifications/endpoints/smtp/{name}',
|
|
37
|
+
},
|
|
38
|
+
webhook: {
|
|
39
|
+
create: 'pve:POST /cluster/notifications/endpoints/webhook',
|
|
40
|
+
update: 'pve:PUT /cluster/notifications/endpoints/webhook/{name}',
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
/** The pair this declaration writes. ⚠️ `type` is identity here, so it never changes under a row. */
|
|
45
|
+
export const targetEndpoint = (props: NotificationTargetProps): EndpointPair =>
|
|
46
|
+
ENDPOINTS[props.type];
|
|
@@ -102,3 +102,14 @@ export const shape = (props: NotificationTargetProps): Record<string, string> =>
|
|
|
102
102
|
...set('url', props.url),
|
|
103
103
|
...set('username', props.username),
|
|
104
104
|
});
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* The create form: `shape` plus the name PVE takes in the body exactly once.
|
|
108
|
+
*
|
|
109
|
+
* ★ EXPORTED SO THE CONSTRAINT PROOF CAN RUN THE REAL FORM rather than a retyped copy of it.
|
|
110
|
+
* ⛔ `name` IS CREATE-ONLY: PVE takes it in the body on the POST and in the path forever after.
|
|
111
|
+
*/
|
|
112
|
+
export const createShape = (props: NotificationTargetProps): Record<string, string> => ({
|
|
113
|
+
...shape(props),
|
|
114
|
+
name: props.name,
|
|
115
|
+
});
|
|
@@ -53,7 +53,8 @@
|
|
|
53
53
|
import { Resource } from 'alchemy';
|
|
54
54
|
import * as Provider from 'alchemy/Provider';
|
|
55
55
|
import * as Effect from 'effect/Effect';
|
|
56
|
-
import {
|
|
56
|
+
import { targetEndpoint } from './notification-target-endpoint.ts';
|
|
57
|
+
import { addressList, createShape, headerList, portOf, shape } from './notification-target-form.ts';
|
|
57
58
|
import { type PveRequirements, type WithTarget, pveHandlers } from './resource.ts';
|
|
58
59
|
import { text } from './values.ts';
|
|
59
60
|
|
|
@@ -180,7 +181,9 @@ const handlers = pveHandlers<NotificationTargetProps, NotificationTargetAttribut
|
|
|
180
181
|
}),
|
|
181
182
|
collection: (props) => `cluster/notifications/endpoints/${props.type}`,
|
|
182
183
|
/** ⛔ `name` is create-only: PVE takes it in the body once, and in the path forever after. */
|
|
183
|
-
createForm:
|
|
184
|
+
createForm: createShape,
|
|
185
|
+
/** ⚠️ A function, because the type picks the endpoint — see `targetEndpoint`. */
|
|
186
|
+
endpoint: targetEndpoint,
|
|
184
187
|
/**
|
|
185
188
|
* Each line reads "not declared, or equal"; `disable` is the one field with no undeclared case.
|
|
186
189
|
*
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `Pbs.NotificationTarget`'s six vendor endpoints — one create and one update per family.
|
|
3
|
+
*
|
|
4
|
+
* 🔴 THIS FAMILY WAS THE ONE THE 2026-09-22 SWEEP NEARLY MISSED, and it carries the very
|
|
5
|
+
* parameter the incident was about: `comment` is `maxLength: 128` with a no-control-characters
|
|
6
|
+
* pattern on all three PBS creates, exactly as it is on `POST /config/verify`. A 129-character
|
|
7
|
+
* comment on a notification target would have failed the same way, half a deploy in, on the
|
|
8
|
+
* family whose whole purpose is to make failures visible.
|
|
9
|
+
*
|
|
10
|
+
* ⛔ PBS REGISTERS ONE PATH PER TYPE, NOT A `{type}` PARAMETER, and the three schemas differ:
|
|
11
|
+
* smtp requires `server` and `from-address`, webhook `url` and `method`, sendmail nothing but
|
|
12
|
+
* its name. One key would enforce one family's rules on all three.
|
|
13
|
+
* ⛔ THE STRINGS MUST STAY LITERALS. `codegen/constraints.ts` finds the endpoints to table by
|
|
14
|
+
* SCANNING THIS PACKAGE'S TEXT, so a key built from `props.type` would be tabled by nothing.
|
|
15
|
+
*
|
|
16
|
+
* ⚠️ NO GOTIFY KEY, BECAUSE THIS RESOURCE HAS NO GOTIFY TYPE. `PbsNotificationTargetType` is
|
|
17
|
+
* sendmail | smtp | webhook; PBS 4.2 does publish a gotify endpoint, and nothing here writes to
|
|
18
|
+
* it. ⛔ `scripts/proxmox-ownership-pbs.ts` says otherwise — it claims all four families
|
|
19
|
+
* through the shared `notificationEndpoints` helper — so `docs/api-coverage.md` credits this
|
|
20
|
+
* resource with twelve write endpoints where it really makes nine. That is an overclaim in the
|
|
21
|
+
* hand-written ownership ledger, recorded here rather than fixed in passing: correcting it
|
|
22
|
+
* regenerates a report built from a DIFFERENT (older, 9.2.4) schema manifest, which is its own
|
|
23
|
+
* change with its own diff.
|
|
24
|
+
*/
|
|
25
|
+
import type { PbsNotificationTargetProps } from './pbs-notification-target.ts';
|
|
26
|
+
import type { EndpointPair } from './resource-spec.ts';
|
|
27
|
+
|
|
28
|
+
const ENDPOINTS: Readonly<Record<PbsNotificationTargetProps['type'], EndpointPair>> = {
|
|
29
|
+
sendmail: {
|
|
30
|
+
create: 'pbs:POST /config/notifications/endpoints/sendmail',
|
|
31
|
+
update: 'pbs:PUT /config/notifications/endpoints/sendmail/{name}',
|
|
32
|
+
},
|
|
33
|
+
smtp: {
|
|
34
|
+
create: 'pbs:POST /config/notifications/endpoints/smtp',
|
|
35
|
+
update: 'pbs:PUT /config/notifications/endpoints/smtp/{name}',
|
|
36
|
+
},
|
|
37
|
+
webhook: {
|
|
38
|
+
create: 'pbs:POST /config/notifications/endpoints/webhook',
|
|
39
|
+
update: 'pbs:PUT /config/notifications/endpoints/webhook/{name}',
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
/** The pair this declaration writes. ⚠️ `type` is identity here — changing it is a replace. */
|
|
44
|
+
export const pbsTargetEndpoint = (props: PbsNotificationTargetProps): EndpointPair =>
|
|
45
|
+
ENDPOINTS[props.type];
|
|
@@ -15,6 +15,8 @@ import { isResolved } from 'alchemy/Diff';
|
|
|
15
15
|
import type { Input } from 'alchemy/Input';
|
|
16
16
|
import * as Effect from 'effect/Effect';
|
|
17
17
|
import { pve } from './client.ts';
|
|
18
|
+
import { guardForm } from './constraint-guard.ts';
|
|
19
|
+
import { pbsTargetEndpoint } from './pbs-notification-target-endpoint.ts';
|
|
18
20
|
import type {
|
|
19
21
|
PbsNotificationTargetAttributes,
|
|
20
22
|
PbsNotificationTargetProps,
|
|
@@ -142,25 +144,25 @@ export const handlers = {
|
|
|
142
144
|
const carry = { header: true, sealed: true } as const;
|
|
143
145
|
yield* requireValues(news, groups, carry);
|
|
144
146
|
const collection = `config/notifications/endpoints/${news.type}`;
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
147
|
+
const form = targetForm(news, groups, carry, 'create');
|
|
148
|
+
/**
|
|
149
|
+
* ⛔ WITH PRESENCE, BECAUSE THIS BRANCH IS UNCONDITIONALLY THE CREATE. PBS marks `name` on
|
|
150
|
+
* every family and `server`/`from-address` on smtp, `url`/`method` on webhook — and it
|
|
151
|
+
* is the same `comment: maxLength 128` that failed `POST /config/verify`.
|
|
152
|
+
* ⚠️ THE FORM CARRIES SECRETS ON THIS PATH. `violations` reads values and builds only the
|
|
153
|
+
* parameter NAME into its message (constraints.ts), so a refusal never quotes one.
|
|
154
|
+
*/
|
|
155
|
+
yield* guardForm(pbsTargetEndpoint(news).create, form, true);
|
|
156
|
+
yield* pve(news.target, 'provision', 'POST', collection, form);
|
|
152
157
|
sealed = seal(groups.sealed.values);
|
|
153
158
|
} else {
|
|
154
159
|
const carry = toCarry(live, news, groups, sealed);
|
|
155
160
|
if (carry !== undefined) {
|
|
156
161
|
yield* requireValues(news, groups, carry);
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
path(news),
|
|
162
|
-
targetForm(news, groups, carry, 'update'),
|
|
163
|
-
);
|
|
162
|
+
const form = targetForm(news, groups, carry, 'update');
|
|
163
|
+
/** ⚠️ NO PRESENCE: an update form is partial by design — it carries what `toCarry` said. */
|
|
164
|
+
yield* guardForm(pbsTargetEndpoint(news).update, form, false);
|
|
165
|
+
yield* pve(news.target, 'provision', 'PUT', path(news), form);
|
|
164
166
|
if (carry.sealed) sealed = seal(groups.sealed.values);
|
|
165
167
|
}
|
|
166
168
|
}
|
package/src/proxmox/qemu.ts
CHANGED
|
@@ -54,6 +54,9 @@ export interface ProxmoxVm extends Resource<
|
|
|
54
54
|
|
|
55
55
|
export const ProxmoxVm = Resource<ProxmoxVm>('Proxmox.Vm');
|
|
56
56
|
|
|
57
|
+
/** ★ Exported so the constraint proof can run the REAL create form, not a retyped copy of it. */
|
|
58
|
+
export const createForm = (props: VmProps) => ({ ...shape(props), vmid: String(props.vmid) });
|
|
59
|
+
|
|
57
60
|
const shape = (props: VmProps) => ({
|
|
58
61
|
cores: String(props.cores ?? 1),
|
|
59
62
|
memory: String(props.memory ?? 512),
|
|
@@ -74,7 +77,12 @@ const handlers = pveHandlers<VmProps, VmAttributes>({
|
|
|
74
77
|
vmid: props.vmid,
|
|
75
78
|
}),
|
|
76
79
|
collection: (props) => `nodes/${props.node}/qemu`,
|
|
77
|
-
createForm
|
|
80
|
+
createForm,
|
|
81
|
+
/** The vendor rules both forms are checked against at plan time — resource-spec.ts. */
|
|
82
|
+
endpoint: {
|
|
83
|
+
create: 'pve:POST /nodes/{node}/qemu',
|
|
84
|
+
update: 'pve:PUT /nodes/{node}/qemu/{vmid}/config',
|
|
85
|
+
},
|
|
78
86
|
matches: (attributes, props) =>
|
|
79
87
|
attributes.name === (props.name ?? `vm${String(props.vmid)}`) &&
|
|
80
88
|
attributes.memory === (props.memory ?? 512) &&
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A spec's two forms, checked against the vendor's own tables before either goes on the wire.
|
|
3
|
+
*
|
|
4
|
+
* ★ A FILE OF ITS OWN BECAUSE resource.ts REACHED THE 250-LINE CAP, and the seam is the same one
|
|
5
|
+
* resource-spec.ts cuts on: that file says what a PVE object DECLARES, resource.ts says how one
|
|
6
|
+
* is read and written, and this one says what makes a declaration legal. `constraint-guard.ts`
|
|
7
|
+
* is the layer below — it owns the lookup and the refusal; this owns which form to check when.
|
|
8
|
+
*/
|
|
9
|
+
import * as Effect from 'effect/Effect';
|
|
10
|
+
import { guardForm } from './constraint-guard.ts';
|
|
11
|
+
import type { PveSpec, WithApiTarget } from './resource-spec.ts';
|
|
12
|
+
|
|
13
|
+
export const specGuards = <Props extends WithApiTarget, Attributes>(
|
|
14
|
+
spec: PveSpec<Props, Attributes>,
|
|
15
|
+
) => {
|
|
16
|
+
/** ⚠️ A function for the two families whose endpoint is a prop — see `PveSpec['endpoint']`. */
|
|
17
|
+
const endpointsOf = (props: Props) =>
|
|
18
|
+
typeof spec.endpoint === 'function' ? spec.endpoint(props) : spec.endpoint;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* The create form against its own endpoint's table.
|
|
22
|
+
*
|
|
23
|
+
* ⛔ `presence` MEANS "A CREATE IS REALLY ABOUT TO HAPPEN", NOT "THIS IS THE CREATE FORM", AND
|
|
24
|
+
* THE DIFFERENCE IS A WHOLE FAMILY. `Proxmox.NotificationTarget` cannot send gotify's `token`
|
|
25
|
+
* or smtp's `password` — they are write-only secrets and props are persisted to the state
|
|
26
|
+
* store unencrypted (notification-target.ts's third ⛔) — so PVE's `token: required` is
|
|
27
|
+
* permanently unsatisfiable here. The documented way to run one is to create it out of band
|
|
28
|
+
* with its secret and then DECLARE it, which is an adopt and then updates forever after.
|
|
29
|
+
* Requiring the create form's parameters on that path would refuse a deploy that has worked
|
|
30
|
+
* all along, which is the one failure worse than the 400 this feature removes.
|
|
31
|
+
* ⚠️ VALUE RULES STILL RUN ON EVERY PLAN, presence or not: a 129-character comment is wrong
|
|
32
|
+
* whether it is being created or edited, and that is the case this whole feature exists for.
|
|
33
|
+
*
|
|
34
|
+
* ⛔ THE FORM IS BUILT ONLY WHEN THERE IS A TABLE TO CHECK IT AGAINST. Calling `spec.createForm`
|
|
35
|
+
* unconditionally would run every family's form builder on every diff — work nobody asked
|
|
36
|
+
* for, and a new way for a builder that throws on an update-only path to break a plan it
|
|
37
|
+
* never touched.
|
|
38
|
+
*/
|
|
39
|
+
const guardCreate = (props: Props, presence: boolean): Effect.Effect<void> =>
|
|
40
|
+
Effect.suspend(() => {
|
|
41
|
+
const create = endpointsOf(props)?.create;
|
|
42
|
+
return create === undefined
|
|
43
|
+
? Effect.void
|
|
44
|
+
: guardForm(create, spec.createForm(props), presence);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* ⛔ NEVER WITH PRESENCE. An update form is deliberately PARTIAL — `formToSend` sends the fields
|
|
49
|
+
* that changed and nothing else — so requiring the vendor's required parameters here would
|
|
50
|
+
* refuse every ordinary edit. Value rules (length, range, enum, pattern) apply to both forms.
|
|
51
|
+
*/
|
|
52
|
+
const guardUpdate = (props: Props): Effect.Effect<void> =>
|
|
53
|
+
Effect.suspend(() => {
|
|
54
|
+
const update = endpointsOf(props)?.update;
|
|
55
|
+
if (update === undefined) return Effect.void;
|
|
56
|
+
const form = spec.updateForm?.(props);
|
|
57
|
+
return form === undefined ? Effect.void : guardForm(update, form, false);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
return { guardCreate, guardUpdate };
|
|
61
|
+
};
|
|
@@ -109,9 +109,21 @@ export type PveSpec<Props extends WithApiTarget, Attributes> = {
|
|
|
109
109
|
* names an endpoint the vendor does not have stops the GENERATOR rather than a deploy.
|
|
110
110
|
* ⚠️ The spelling is the vendor's own path TEMPLATE, braces and all — not `spec.path(props)`,
|
|
111
111
|
* which has the ids substituted in.
|
|
112
|
+
*
|
|
113
|
+
* ⚠️ A FUNCTION FOR THE TWO FAMILIES WHOSE ENDPOINT IS CHOSEN BY A PROP, AND FOR NO OTHER
|
|
114
|
+
* REASON. `Proxmox.NotificationTarget` POSTs to one of four `endpoints/{type}` paths and
|
|
115
|
+
* `Proxmox.CephDaemon` to one of `ceph/mds|mgr|mon`, each with its own parameter schema, so a
|
|
116
|
+
* single key would table the wrong rules for three declarations out of four.
|
|
117
|
+
* ⛔ EVERY KEY THE FUNCTION CAN RETURN STILL HAS TO EXIST AS A LITERAL IN THIS PACKAGE'S
|
|
118
|
+
* SOURCE. `codegen/constraints.ts` finds keys by scanning text, so a key assembled from a
|
|
119
|
+
* template literal is tabled by nothing and `constraintsFor` throws on the deploy that first
|
|
120
|
+
* reaches it. Write the keys out in a record and index it — see `ceph-endpoints.ts`.
|
|
112
121
|
*/
|
|
113
|
-
readonly endpoint?:
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
122
|
+
readonly endpoint?: EndpointPair | ((props: Props) => EndpointPair);
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
/** The two endpoints a family writes. Either may be absent — see `PveSpec['endpoint']`. */
|
|
126
|
+
export type EndpointPair = {
|
|
127
|
+
readonly create?: EndpointKey;
|
|
128
|
+
readonly update?: EndpointKey;
|
|
117
129
|
};
|
package/src/proxmox/resource.ts
CHANGED
|
@@ -14,7 +14,7 @@ import { isResolved } from 'alchemy/Diff';
|
|
|
14
14
|
import type { Input } from 'alchemy/Input';
|
|
15
15
|
import * as Effect from 'effect/Effect';
|
|
16
16
|
import { pve } from './client.ts';
|
|
17
|
-
import {
|
|
17
|
+
import { specGuards } from './resource-guard.ts';
|
|
18
18
|
import type { PveSpec, WithApiTarget } from './resource-spec.ts';
|
|
19
19
|
import { formToSend } from './update-guard.ts';
|
|
20
20
|
|
|
@@ -29,28 +29,7 @@ export type {
|
|
|
29
29
|
export const pveOperations = <Props extends WithApiTarget, Attributes>(
|
|
30
30
|
spec: PveSpec<Props, Attributes>,
|
|
31
31
|
) => {
|
|
32
|
-
|
|
33
|
-
* Both forms against their own endpoint's table.
|
|
34
|
-
*
|
|
35
|
-
* ⚠️ PRESENCE IS CHECKED ON CREATE ONLY. An update form is deliberately partial — `formToSend`
|
|
36
|
-
* sends what changed — so requiring the vendor's required parameters there would refuse every
|
|
37
|
-
* ordinary edit. Value rules (length, range, enum, pattern) apply to both.
|
|
38
|
-
*/
|
|
39
|
-
const guardForms = (props: Props) =>
|
|
40
|
-
Effect.gen(function* () {
|
|
41
|
-
/**
|
|
42
|
-
* ⛔ THE FORM IS BUILT ONLY WHEN THERE IS A TABLE TO CHECK IT AGAINST. Calling
|
|
43
|
-
* `spec.createForm` unconditionally would run every family's form builder on every diff,
|
|
44
|
-
* including the fifteen that declare no `endpoint` — work nobody asked for, and a new way
|
|
45
|
-
* for a builder that throws on an update-only path to break a plan it never touched.
|
|
46
|
-
*/
|
|
47
|
-
const create = spec.endpoint?.create;
|
|
48
|
-
if (create !== undefined) yield* guardForm(create, spec.createForm(props), true);
|
|
49
|
-
const update = spec.endpoint?.update;
|
|
50
|
-
if (update === undefined) return;
|
|
51
|
-
const form = spec.updateForm?.(props);
|
|
52
|
-
if (form !== undefined) yield* guardForm(update, form, false);
|
|
53
|
-
});
|
|
32
|
+
const { guardCreate, guardUpdate } = specGuards(spec);
|
|
54
33
|
|
|
55
34
|
/** The live object, or undefined. ⚠️ A 404 and `{"data": null}` are ANSWERS, not failures. */
|
|
56
35
|
const read = (props: Props) =>
|
|
@@ -65,6 +44,15 @@ export const pveOperations = <Props extends WithApiTarget, Attributes>(
|
|
|
65
44
|
);
|
|
66
45
|
|
|
67
46
|
return {
|
|
47
|
+
/**
|
|
48
|
+
* ★ EXPORTED SO A FAMILY THAT WRITES ITS OWN `reconcile` REACHES THE SAME CHECK BY THE SAME
|
|
49
|
+
* NAME. CephPool, CephFs and ZfsPool all bypass the reconcile below — forked workers, a
|
|
50
|
+
* settle loop, a destructive create — and without these two they would be checked at plan
|
|
51
|
+
* time and not at apply time, which is exactly the gap an ADOPTED row falls through.
|
|
52
|
+
*/
|
|
53
|
+
guardCreate,
|
|
54
|
+
guardUpdate,
|
|
55
|
+
|
|
68
56
|
read,
|
|
69
57
|
|
|
70
58
|
// ⚠️ `Input<Props>`, NOT `Props`. At plan time a prop can still be an unresolved Output or
|
|
@@ -82,12 +70,21 @@ export const pveOperations = <Props extends WithApiTarget, Attributes>(
|
|
|
82
70
|
* create. Checking after it would leave exactly the case that failed on 2026-09-22
|
|
83
71
|
* unchecked, and the refusal would arrive from the server, half a deploy in.
|
|
84
72
|
*/
|
|
85
|
-
yield*
|
|
73
|
+
yield* guardCreate(news, output === undefined);
|
|
74
|
+
yield* guardUpdate(news);
|
|
86
75
|
if (output === undefined) return undefined;
|
|
87
76
|
const live = yield* read(news);
|
|
88
77
|
// ⚠️ `update`, NOT `create` — Alchemy's Diff admits only noop/update/replace. An object
|
|
89
78
|
// Alchemy has state for but the cluster does not is drift, and reconcile repairs it.
|
|
90
|
-
if (live === undefined)
|
|
79
|
+
if (live === undefined) {
|
|
80
|
+
/**
|
|
81
|
+
* ⛔ DRIFT REACHES `reconcile`'s POST, so the create form's required parameters ARE
|
|
82
|
+
* about to go on the wire after all. Saying so here rather than leaving it to
|
|
83
|
+
* reconcile keeps the refusal in `plan`, where an operator is reading.
|
|
84
|
+
*/
|
|
85
|
+
yield* guardCreate(news, true);
|
|
86
|
+
return { action: 'update' } as const;
|
|
87
|
+
}
|
|
91
88
|
if (spec.matches(live, news)) return { action: 'noop' } as const;
|
|
92
89
|
// An object with no update path cannot be edited in place; say replace and mean it.
|
|
93
90
|
return spec.updateForm === undefined
|
|
@@ -97,11 +94,14 @@ export const pveOperations = <Props extends WithApiTarget, Attributes>(
|
|
|
97
94
|
|
|
98
95
|
reconcile: (news: Props) =>
|
|
99
96
|
Effect.gen(function* () {
|
|
97
|
+
const live = yield* read(news);
|
|
100
98
|
// ⚠️ CHECKED AGAIN HERE, AND NOT BECAUSE `diff` IS UNTRUSTED. `reconcile` also runs for an
|
|
101
99
|
// ADOPTED row, whose diff answer Alchemy discards (verify/fake-engine.ts records it), and
|
|
102
100
|
// a family that writes its own reconcile bypasses the one above entirely.
|
|
103
|
-
|
|
104
|
-
|
|
101
|
+
// ⛔ AFTER THE READ, BECAUSE THE READ IS WHAT SAYS WHICH REQUEST IS ABOUT TO BE MADE. A GET
|
|
102
|
+
// changes nothing, so nothing has been written by the time a violation refuses the plan.
|
|
103
|
+
yield* guardCreate(news, live === undefined);
|
|
104
|
+
yield* guardUpdate(news);
|
|
105
105
|
if (live === undefined) {
|
|
106
106
|
yield* pve(
|
|
107
107
|
news.target,
|
package/src/proxmox/sdn-apply.ts
CHANGED
|
@@ -35,6 +35,7 @@ import { isResolved } from 'alchemy/Diff';
|
|
|
35
35
|
import type { Input } from 'alchemy/Input';
|
|
36
36
|
import * as Provider from 'alchemy/Provider';
|
|
37
37
|
import * as Effect from 'effect/Effect';
|
|
38
|
+
import { guardSdnApply } from './apply-endpoints.ts';
|
|
38
39
|
import { pve } from './client.ts';
|
|
39
40
|
import type { PveRequirements } from './resource.ts';
|
|
40
41
|
import type { WithTarget } from './resource.ts';
|
|
@@ -121,6 +122,9 @@ export const ProxmoxSdnApplyProvider = () =>
|
|
|
121
122
|
const staged = yield* read(news);
|
|
122
123
|
if (staged.pending === 0) return staged;
|
|
123
124
|
|
|
125
|
+
// ⛔ BEFORE THE PUBLISH. The apply takes no body, so this is the lookup rather than a
|
|
126
|
+
// value check: a vendor that moved this endpoint fails here, not on three nodes.
|
|
127
|
+
yield* guardSdnApply;
|
|
124
128
|
yield* pve(news.target, 'provision', 'PUT', 'cluster/sdn');
|
|
125
129
|
/**
|
|
126
130
|
* ⛔ READ BACK, FOR THE SAME REASON THE FACTORY DOES. PVE answers 200 on writes that did
|