@homeflare/alchemy 0.15.0 → 0.19.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 +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 +1554 -0
- package/dist/linux/index.js.map +37 -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 +44 -0
- package/dist/linux/unit-lifecycle.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 +186 -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 +193 -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 +220 -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 +188 -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
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `Netbox.Prefix` — one IP prefix and the decision recorded against it.
|
|
3
|
+
*
|
|
4
|
+
* ★ WHY THIS OBJECT CLASS IS THE FIRST ONE, AND NOT `Device`, `VLAN` OR `Site`. Three reasons,
|
|
5
|
+
* each measurable rather than aesthetic:
|
|
6
|
+
*
|
|
7
|
+
* 1. **It is the only interesting NetBox object with no foreign-key prerequisite.** MEASURED in
|
|
8
|
+
* the 4.7.0 schema: `WritablePrefixRequest.required` is exactly `["prefix"]`. A `VLAN` needs
|
|
9
|
+
* `vid` AND `name` and belongs to a group or site; a `Device` needs a role, a type, a site
|
|
10
|
+
* and usually a manufacturer — four more Resources before the first one can be declared.
|
|
11
|
+
* A scaffold that cannot declare anything until five families exist proves nothing.
|
|
12
|
+
* 2. **It is what the estate's NetBox is FOR.** The house's own IPAM agent describes its job as
|
|
13
|
+
* reconciling "what the network IS against what NetBox says it was DECIDED to be". A prefix
|
|
14
|
+
* is the atom of that decision. The estate has prefixes whose status is currently recorded
|
|
15
|
+
* only in an SSH config comment and a retirement doc — a management VLAN, a Thunderbolt
|
|
16
|
+
* point-to-point, and three ranges that are retired and still resolve in people's heads.
|
|
17
|
+
* 3. **`status: 'deprecated'` makes retirement declarable.** That is the move this unlocks:
|
|
18
|
+
* a retired range stops being folklore and becomes a line in a stack file with a reviewable
|
|
19
|
+
* diff. No other NetBox class turns an estate decision into one field this cleanly.
|
|
20
|
+
*
|
|
21
|
+
* ⚠️ ADOPT-FRIENDLY BY CONSTRUCTION. `locate` filters on the exact `prefix`; `identifies` then
|
|
22
|
+
* picks the declared VRF, because NetBox allows the same CIDR in two VRFs and in the global
|
|
23
|
+
* table. An instance that already holds `10.0.0.0/24` is BOUND, never duplicated — and ⛔ an
|
|
24
|
+
* adopt never clears prose it did not declare (prefix-form.ts).
|
|
25
|
+
*
|
|
26
|
+
* ⛔ THIS RESOURCE DOES NOT REMOVE PREFIXES IT DOES NOT DECLARE, and `defaultRemovalPolicy` is
|
|
27
|
+
* `retain` (resource.ts). Deleting a prefix in NetBox reparents its children and detaches its
|
|
28
|
+
* IP assignments; the record of why it existed survives only in the change log.
|
|
29
|
+
*
|
|
30
|
+
* ⛔ NO CREDENTIAL IS A PROP. `NETBOX_URL` and `NETBOX_TOKEN` are read from the environment at
|
|
31
|
+
* call time — the house convention, and the only shape that keeps a token out of Alchemy state.
|
|
32
|
+
*/
|
|
33
|
+
import { Resource } from 'alchemy';
|
|
34
|
+
import * as Provider from 'alchemy/Provider';
|
|
35
|
+
import * as Effect from 'effect/Effect';
|
|
36
|
+
import { prefixBody, prefixMatches } from './prefix-form.ts';
|
|
37
|
+
import { type NetboxRequirements, netboxHandlers } from './resource.ts';
|
|
38
|
+
import { choice, fk, text } from './values.ts';
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* ⚠️ THE FOUR VALUES ARE THE VENDOR'S, NOT OURS — copied from the generated table, which is
|
|
42
|
+
* generated from NetBox's own enum. The table is what ENFORCES them; this type is what makes
|
|
43
|
+
* the mistake visible in an editor.
|
|
44
|
+
*/
|
|
45
|
+
export type PrefixStatus = 'container' | 'active' | 'reserved' | 'deprecated';
|
|
46
|
+
|
|
47
|
+
export interface PrefixProps {
|
|
48
|
+
/** CIDR, exactly as NetBox stores it — `10.0.0.0/24`. The stable key for locate. */
|
|
49
|
+
prefix: string;
|
|
50
|
+
/**
|
|
51
|
+
* VRF id, or omitted for the global table.
|
|
52
|
+
*
|
|
53
|
+
* ⛔ PART OF THE IDENTITY, NOT A SETTING. NetBox's uniqueness on a prefix is per-VRF, so a
|
|
54
|
+
* locate that ignored it could match two rows — which `soleMatch` refuses rather than
|
|
55
|
+
* guessing between.
|
|
56
|
+
*/
|
|
57
|
+
vrf?: number;
|
|
58
|
+
status?: PrefixStatus;
|
|
59
|
+
/** ⚠️ `maxLength: 200` in the schema; the generated table enforces it at plan time. */
|
|
60
|
+
description?: string;
|
|
61
|
+
/** Free text. ⚠️ NetBox states no length limit, so nothing here checks one. */
|
|
62
|
+
comments?: string;
|
|
63
|
+
tenant?: number;
|
|
64
|
+
vlan?: number;
|
|
65
|
+
/** All addresses inside are usable. */
|
|
66
|
+
isPool?: boolean;
|
|
67
|
+
markUtilized?: boolean;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export interface PrefixAttributes {
|
|
71
|
+
prefixId: number;
|
|
72
|
+
prefix: string;
|
|
73
|
+
status: string;
|
|
74
|
+
description: string;
|
|
75
|
+
comments: string;
|
|
76
|
+
vrf: number | undefined;
|
|
77
|
+
tenant: number | undefined;
|
|
78
|
+
vlan: number | undefined;
|
|
79
|
+
isPool: boolean;
|
|
80
|
+
markUtilized: boolean;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export interface NetboxPrefix extends Resource<
|
|
84
|
+
'Netbox.Prefix',
|
|
85
|
+
PrefixProps,
|
|
86
|
+
PrefixAttributes,
|
|
87
|
+
never,
|
|
88
|
+
NetboxRequirements
|
|
89
|
+
> {}
|
|
90
|
+
|
|
91
|
+
export const NetboxPrefix = Resource<NetboxPrefix>('Netbox.Prefix', {
|
|
92
|
+
defaultRemovalPolicy: 'retain',
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
const handlers = netboxHandlers<PrefixProps, PrefixAttributes>({
|
|
96
|
+
attributes: (live, props) => {
|
|
97
|
+
const id = live['id'];
|
|
98
|
+
if (typeof id !== 'number') return undefined;
|
|
99
|
+
return {
|
|
100
|
+
comments: text(live['comments']),
|
|
101
|
+
description: text(live['description']),
|
|
102
|
+
isPool: live['is_pool'] === true,
|
|
103
|
+
markUtilized: live['mark_utilized'] === true,
|
|
104
|
+
prefix: text(live['prefix']) || props.prefix,
|
|
105
|
+
prefixId: id,
|
|
106
|
+
status: choice(live['status']),
|
|
107
|
+
tenant: fk(live['tenant']),
|
|
108
|
+
vlan: fk(live['vlan']),
|
|
109
|
+
vrf: fk(live['vrf']),
|
|
110
|
+
};
|
|
111
|
+
},
|
|
112
|
+
collection: 'ipam/prefixes',
|
|
113
|
+
createBody: prefixBody,
|
|
114
|
+
describe: (props) => `ipam/prefixes ${props.prefix}`,
|
|
115
|
+
endpoint: {
|
|
116
|
+
create: 'netbox:POST /api/ipam/prefixes/',
|
|
117
|
+
update: 'netbox:PATCH /api/ipam/prefixes/{id}/',
|
|
118
|
+
},
|
|
119
|
+
/**
|
|
120
|
+
* 🔴 THE VRF IS **NOT** IN THIS FILTER, AND THE REASON IS A GUESS THAT WAS CAUGHT. This read
|
|
121
|
+
* `vrf_id=null` for the global table — NetBox does define `FILTERS_NULL_CHOICE_VALUE = 'null'`
|
|
122
|
+
* and does honour it on filters that opt in. ⛔ MEASURED IN THE VENDOR'S OWN SOURCE at
|
|
123
|
+
* v4.7.0: `PrefixFilterSet.vrf_id` is a plain `django_filters.ModelMultipleChoiceFilter` with
|
|
124
|
+
* no `null_value`, so `'null'` is validated against the VRF queryset, fails, and NetBox
|
|
125
|
+
* answers 400 under strict filtering — on EVERY plan for EVERY global prefix.
|
|
126
|
+
* ★ So the CIDR narrows server-side with a filter the document plainly declares, and `identifies`
|
|
127
|
+
* picks the VRF in this process. One extra page of candidates, no vendor semantics guessed.
|
|
128
|
+
*/
|
|
129
|
+
identifies: (live, props) => fk(live['vrf']) === props.vrf,
|
|
130
|
+
locate: (props) => [['prefix', props.prefix]],
|
|
131
|
+
matches: prefixMatches,
|
|
132
|
+
updateBody: prefixBody,
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
export const NetboxPrefixProvider = () =>
|
|
136
|
+
Provider.effect(NetboxPrefix, Effect.succeed(NetboxPrefix.Provider.of(handlers)));
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One shape for every NetBox object — the same four operations, different path and JSON body.
|
|
3
|
+
*
|
|
4
|
+
* ⛔ THE ALTERNATIVE IS COPYING THE SAME HANDLERS PER RESOURCE. Prefixes, VLANs, devices, tenants
|
|
5
|
+
* and interfaces are all create / read / update / delete over `/api` with JSON bodies and a
|
|
6
|
+
* numeric primary key. Hand-rolling the fourth copy is where someone drops the read-back or the
|
|
7
|
+
* constraint guard and the plan lies.
|
|
8
|
+
*
|
|
9
|
+
* ★ ADOPT IS THE DEFAULT POSTURE, NOT A FLAG. `reconcile` LOCATES before it writes: an object the
|
|
10
|
+
* estate already has is bound, not duplicated. NetBox is a record that predates this code by
|
|
11
|
+
* years; a provider that created a second `10.20.10.0/24` on first deploy would be worse than
|
|
12
|
+
* no provider.
|
|
13
|
+
*
|
|
14
|
+
* ★ `defaultRemovalPolicy: 'retain'` ON EVERY FAMILY. NetBox holds the estate's record of what
|
|
15
|
+
* the network was DECIDED to be, and deleting a row deletes history — child prefixes reparent,
|
|
16
|
+
* IP assignments detach, and the change log is the only trace left. `retain` means the engine
|
|
17
|
+
* skips `delete` when a resource is orphaned unless the caller opts into
|
|
18
|
+
* `.pipe(RemovalPolicy.destroy())`. The `delete` handler is FULLY IMPLEMENTED anyway: a stub
|
|
19
|
+
* that silently does nothing lies to whoever reads the plan.
|
|
20
|
+
*/
|
|
21
|
+
import { isResolved } from 'alchemy/Diff';
|
|
22
|
+
import type { Input } from 'alchemy/Input';
|
|
23
|
+
import * as Effect from 'effect/Effect';
|
|
24
|
+
import type * as HttpClient from 'effect/unstable/http/HttpClient';
|
|
25
|
+
import { NetboxError, type NetboxList, type NetboxRow, netbox, soleMatch } from './client.ts';
|
|
26
|
+
import { guardBody } from './constraint-guard.ts';
|
|
27
|
+
import type { EndpointKey, NetboxBody } from './constraints.ts';
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* ★ WHAT EVERY OPERATION HERE NEEDS FROM THE RUNTIME, AND NOTHING MORE. The token comes from
|
|
31
|
+
* `NETBOX_TOKEN` in the environment, so there is no process to spawn and no `bao` to shell out
|
|
32
|
+
* to. Declaring a service you never use is not harmless — it makes a stack provide a layer for
|
|
33
|
+
* nothing, and it hides the one service you DO need.
|
|
34
|
+
*/
|
|
35
|
+
export type NetboxRequirements = HttpClient.HttpClient;
|
|
36
|
+
|
|
37
|
+
export interface NetboxSpec<Props extends object, Attributes> {
|
|
38
|
+
/** `ipam/prefixes` — where a new one is POSTed and where `locate` filters. */
|
|
39
|
+
readonly collection: string;
|
|
40
|
+
/**
|
|
41
|
+
* The query that narrows the candidates, as `URLSearchParams` pairs.
|
|
42
|
+
*
|
|
43
|
+
* ⛔ ONLY FILTERS THE VENDOR DOCUMENT DECLARES, AND ONLY IN THE FORM IT DECLARES THEM. A
|
|
44
|
+
* filter NetBox rejects answers 400, which `absentOn404` correctly does NOT fold to "absent" —
|
|
45
|
+
* so a guessed filter shape is a hard failure on every plan, not a soft one.
|
|
46
|
+
* ★ IT DOES NOT HAVE TO BE UNIQUE. `identifies` finishes the job in this process, where the
|
|
47
|
+
* rule is readable and testable offline.
|
|
48
|
+
*/
|
|
49
|
+
readonly locate: (props: Props) => readonly (readonly [string, string])[];
|
|
50
|
+
/**
|
|
51
|
+
* Which of the returned rows IS this object.
|
|
52
|
+
*
|
|
53
|
+
* 🔴 THIS EXISTS BECAUSE THE OBVIOUS ALTERNATIVE WAS A GUESS. `Netbox.Prefix` first narrowed
|
|
54
|
+
* server-side with `vrf_id=null`, the sentinel NetBox uses elsewhere for "no foreign key".
|
|
55
|
+
* ⛔ MEASURED IN THE VENDOR'S OWN SOURCE at v4.7.0: `PrefixFilterSet.vrf_id` is a plain
|
|
56
|
+
* `django_filters.ModelMultipleChoiceFilter` with NO `null_value`, so `'null'` is validated
|
|
57
|
+
* against the VRF queryset, fails, and NetBox answers 400 under strict filtering. The
|
|
58
|
+
* sentinel is real (`FILTERS_NULL_CHOICE_VALUE = 'null'`) but it is opt-in per filter, and
|
|
59
|
+
* this one did not opt in.
|
|
60
|
+
* ★ SO THE DISCRIMINATOR MOVED INTO THIS PROCESS. It costs one page of candidates and buys a
|
|
61
|
+
* rule that can be read, tested without a server, and cannot be wrong about a vendor's
|
|
62
|
+
* filter semantics. Omit it when `locate` really is unique on its own.
|
|
63
|
+
*/
|
|
64
|
+
readonly identifies?: (live: NetboxRow, props: Props) => boolean;
|
|
65
|
+
readonly attributes: (live: NetboxRow, props: Props) => Attributes | undefined;
|
|
66
|
+
readonly createBody: (props: Props) => NetboxBody;
|
|
67
|
+
/** ⚠️ PARTIAL BY DESIGN: NetBox updates are PATCH, so presence is not checked on this body. */
|
|
68
|
+
readonly updateBody?: (props: Props) => NetboxBody;
|
|
69
|
+
readonly matches: (attributes: Attributes, props: Props) => boolean;
|
|
70
|
+
/** The generated constraint table to check each body against, by endpoint key. */
|
|
71
|
+
readonly endpoint: { readonly create: EndpointKey; readonly update?: EndpointKey };
|
|
72
|
+
/** For the message `soleMatch` raises and for read-back failures. */
|
|
73
|
+
readonly describe: (props: Props) => string;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** ⚠️ Only 404 folds to "absent". A 403 or a 502 is a real failure — folding it would plan a create over an existing object. */
|
|
77
|
+
const absentOn404 = <A>(io: Effect.Effect<A, NetboxError, NetboxRequirements>) =>
|
|
78
|
+
io.pipe(
|
|
79
|
+
Effect.catchIf(
|
|
80
|
+
(cause): cause is NetboxError => cause instanceof NetboxError && cause.status === 404,
|
|
81
|
+
() => Effect.succeed(undefined as A),
|
|
82
|
+
),
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* ⚠️ NOT `limit=1`, AND NOT `limit=2` EITHER. One row is all a caller wants, but the page has to
|
|
87
|
+
* be wide enough for `identifies` to see every candidate `locate` matched — a prefix that
|
|
88
|
+
* exists in several VRFs comes back several times, and a page of two would hide the third.
|
|
89
|
+
* ★ TWENTY, AND THE NUMBER IS AN ASSERTION: a natural key matching more than twenty rows is not
|
|
90
|
+
* a natural key. `count` is checked against it, so the failure is "your filter is too broad"
|
|
91
|
+
* rather than a silent truncation.
|
|
92
|
+
*/
|
|
93
|
+
const LOCATE_PAGE = 20;
|
|
94
|
+
|
|
95
|
+
const query = (pairs: readonly (readonly [string, string])[]): string =>
|
|
96
|
+
new URLSearchParams([
|
|
97
|
+
...pairs.map(([k, v]) => [k, v] as [string, string]),
|
|
98
|
+
['limit', String(LOCATE_PAGE)],
|
|
99
|
+
]).toString();
|
|
100
|
+
|
|
101
|
+
export const netboxOperations = <Props extends object, Attributes>(
|
|
102
|
+
spec: NetboxSpec<Props, Attributes>,
|
|
103
|
+
) => {
|
|
104
|
+
const fetchLive = (props: Props) =>
|
|
105
|
+
absentOn404(
|
|
106
|
+
netbox<NetboxList<NetboxRow>>('GET', `${spec.collection}/?${query(spec.locate(props))}`),
|
|
107
|
+
).pipe(
|
|
108
|
+
Effect.map((list) => {
|
|
109
|
+
if (list !== undefined && list.count > LOCATE_PAGE) {
|
|
110
|
+
throw new Error(
|
|
111
|
+
`${spec.describe(props)}: the locate filter matched ${String(list.count)} rows, more ` +
|
|
112
|
+
`than the ${String(LOCATE_PAGE)}-row page this reads. That is not a natural key — ` +
|
|
113
|
+
'narrow the filter rather than paging through candidates.',
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
const rows = list?.results ?? [];
|
|
117
|
+
const narrow = spec.identifies;
|
|
118
|
+
return soleMatch(
|
|
119
|
+
narrow === undefined ? rows : rows.filter((row) => narrow(row, props)),
|
|
120
|
+
spec.describe(props),
|
|
121
|
+
);
|
|
122
|
+
}),
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
/** ⛔ The numeric `id` is the wire path. NetBox has no update-by-natural-key. */
|
|
126
|
+
const objectPath = (live: NetboxRow): string => `${spec.collection}/${String(live['id'])}`;
|
|
127
|
+
|
|
128
|
+
const read = (props: Props) =>
|
|
129
|
+
fetchLive(props).pipe(
|
|
130
|
+
Effect.map((live) => (live === undefined ? undefined : spec.attributes(live, props))),
|
|
131
|
+
);
|
|
132
|
+
|
|
133
|
+
return {
|
|
134
|
+
read,
|
|
135
|
+
|
|
136
|
+
diff: (news: Input<Props>, output: Attributes | undefined) =>
|
|
137
|
+
Effect.gen(function* () {
|
|
138
|
+
if (output === undefined || !isResolved(news)) return undefined;
|
|
139
|
+
const live = yield* read(news);
|
|
140
|
+
if (live === undefined) return { action: 'update' } as const;
|
|
141
|
+
if (spec.matches(live, news)) return { action: 'noop' } as const;
|
|
142
|
+
return spec.updateBody === undefined
|
|
143
|
+
? ({ action: 'replace' } as const)
|
|
144
|
+
: ({ action: 'update' } as const);
|
|
145
|
+
}),
|
|
146
|
+
|
|
147
|
+
reconcile: (news: Props) =>
|
|
148
|
+
Effect.gen(function* () {
|
|
149
|
+
const before = yield* fetchLive(news);
|
|
150
|
+
if (before === undefined) {
|
|
151
|
+
const body = spec.createBody(news);
|
|
152
|
+
// ⛔ GUARD BEFORE THE REQUEST, WITH PRESENCE ON. This is the only place a missing
|
|
153
|
+
// required property can still be caught locally.
|
|
154
|
+
yield* guardBody(spec.endpoint.create, body, true);
|
|
155
|
+
yield* netbox('POST', `${spec.collection}/`, body);
|
|
156
|
+
} else if (spec.updateBody !== undefined) {
|
|
157
|
+
const mapped = spec.attributes(before, news);
|
|
158
|
+
// ⚠️ ADOPTION IS THE `mapped === undefined` CASE TOO: a row this spec cannot map is not
|
|
159
|
+
// a row to PATCH blindly.
|
|
160
|
+
if (mapped !== undefined && !spec.matches(mapped, news)) {
|
|
161
|
+
const body = spec.updateBody(news);
|
|
162
|
+
yield* guardBody(spec.endpoint.update, body, false);
|
|
163
|
+
if (Object.keys(body).length > 0) {
|
|
164
|
+
yield* netbox('PATCH', `${objectPath(before)}/`, body);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
const after = yield* read(news);
|
|
169
|
+
if (after === undefined) {
|
|
170
|
+
return yield* Effect.die(
|
|
171
|
+
new Error(
|
|
172
|
+
`${spec.describe(news)}: the write returned no error but the object is still ` +
|
|
173
|
+
'absent. NetBox returns the object it wrote — read back rather than trusting the ' +
|
|
174
|
+
'status code.',
|
|
175
|
+
),
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
return after;
|
|
179
|
+
}),
|
|
180
|
+
|
|
181
|
+
destroy: (olds: Props) =>
|
|
182
|
+
Effect.gen(function* () {
|
|
183
|
+
const live = yield* fetchLive(olds);
|
|
184
|
+
if (live === undefined) return;
|
|
185
|
+
yield* netbox('DELETE', `${objectPath(live)}/`);
|
|
186
|
+
}),
|
|
187
|
+
};
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* The five provider handlers for a spec'd NetBox object, wired once.
|
|
192
|
+
*
|
|
193
|
+
* ⚠️ `list` answers empty. NetBox index endpoints return the whole estate — tens of thousands of
|
|
194
|
+
* rows on a real instance — so adoption stays explicit rather than sweeping.
|
|
195
|
+
*/
|
|
196
|
+
export const netboxHandlers = <Props extends object, Attributes extends object>(
|
|
197
|
+
spec: NetboxSpec<Props, Attributes>,
|
|
198
|
+
) => {
|
|
199
|
+
const ops = netboxOperations(spec);
|
|
200
|
+
return {
|
|
201
|
+
list: () => Effect.succeed([]),
|
|
202
|
+
read: ({ olds }: { olds: Props }) => ops.read(olds),
|
|
203
|
+
diff: ({ news, output }: { news: Input<Props>; output: Attributes | undefined }) =>
|
|
204
|
+
ops.diff(news, output),
|
|
205
|
+
reconcile: ({ news }: { news: Props }) => ops.reconcile(news),
|
|
206
|
+
delete: ({ olds }: { olds: Props }) => ops.destroy(olds),
|
|
207
|
+
};
|
|
208
|
+
};
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Read-versus-write asymmetry — the bug that makes a resource never converge.
|
|
3
|
+
*
|
|
4
|
+
* 🔴 THE SHAPE IS DIFFERENT IN EACH DIRECTION AND NOTHING IN THE TYPES SAYS SO. Measured in the
|
|
5
|
+
* NetBox 4.7.0 document: a prefix is WRITTEN with `status: "active"` and READ BACK as
|
|
6
|
+
* `status: {value: "active", label: "Active"}`; a foreign key is written as `4` and read back
|
|
7
|
+
* as `{id: 4, url: …, display: …}`. A `matches` that compared those directly would report drift
|
|
8
|
+
* on every plan, for every object, forever — and each "update" would PATCH the same value back.
|
|
9
|
+
*/
|
|
10
|
+
import { describe, expect, test } from 'bun:test';
|
|
11
|
+
import { bool, choice, fk, text } from './values.ts';
|
|
12
|
+
|
|
13
|
+
describe('a choice field reads back as an object', () => {
|
|
14
|
+
test('the object form reduces to the string a declaration writes', () => {
|
|
15
|
+
expect(choice({ label: 'Active', value: 'active' })).toBe('active');
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
/** ⚠️ Both directions must work: a fixture or a brief serialisation gives the bare string. */
|
|
19
|
+
test('the bare string passes through unchanged', () => {
|
|
20
|
+
expect(choice('deprecated')).toBe('deprecated');
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
test('anything else is empty rather than undefined, so a comparison stays total', () => {
|
|
24
|
+
expect(choice(undefined)).toBe('');
|
|
25
|
+
expect(choice(null)).toBe('');
|
|
26
|
+
expect(choice({ label: 'Active' })).toBe('');
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
describe('a foreign key reads back as a nested object', () => {
|
|
31
|
+
test('the nested object reduces to its id', () => {
|
|
32
|
+
expect(
|
|
33
|
+
fk({ display: 'HomeFlare', id: 4, url: 'https://example.invalid/api/tenancy/tenants/4/' }),
|
|
34
|
+
).toBe(4);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test('the bare integer passes through', () => {
|
|
38
|
+
expect(fk(7)).toBe(7);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* ⛔ `undefined`, NOT `0`. A cleared foreign key and "tenant 0" are different facts, and zero
|
|
43
|
+
* is falsy — a `matches` written against it would treat an unset tenant as a match for every
|
|
44
|
+
* declaration that omitted one.
|
|
45
|
+
*/
|
|
46
|
+
test('an absent or unrecognisable key is undefined', () => {
|
|
47
|
+
expect(fk(null)).toBeUndefined();
|
|
48
|
+
expect(fk(undefined)).toBeUndefined();
|
|
49
|
+
expect(fk({ display: 'no id here' })).toBeUndefined();
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
describe('text and booleans', () => {
|
|
54
|
+
/** ⚠️ NetBox stores an empty string for absent text, never null — so `''` is the settled value. */
|
|
55
|
+
test('absent text settles to the empty string', () => {
|
|
56
|
+
expect(text(undefined)).toBe('');
|
|
57
|
+
expect(text(null)).toBe('');
|
|
58
|
+
expect(text('a note')).toBe('a note');
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test('only a real true is true', () => {
|
|
62
|
+
expect(bool(true)).toBe(true);
|
|
63
|
+
expect(bool('true')).toBe(false);
|
|
64
|
+
expect(bool(1)).toBe(false);
|
|
65
|
+
});
|
|
66
|
+
});
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reading a NetBox row into settled values.
|
|
3
|
+
*
|
|
4
|
+
* ⛔ A NETBOX READ AND A NETBOX WRITE ARE NOT THE SAME SHAPE, AND THIS IS WHERE THAT IS HANDLED.
|
|
5
|
+
* MEASURED in the 4.7.0 document: `WritablePrefixRequest.status` is a bare string enum
|
|
6
|
+
* (`"active"`), while `Prefix.status` on the way back is an OBJECT — `{value, label}`. A `diff`
|
|
7
|
+
* that compared the declared string against the live object would see drift on every plan, for
|
|
8
|
+
* every prefix, forever, and each "update" would PATCH the same value back.
|
|
9
|
+
* ⚠️ The same asymmetry holds for every choice field in NetBox (`dcim` device `status`,
|
|
10
|
+
* `ipam` IP `role`, …). Anything read through `choice` is safe against it.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/** ⚠️ `''` for absent, not `undefined` — NetBox stores an empty string, never null, for text. */
|
|
14
|
+
export const text = (value: unknown): string => (typeof value === 'string' ? value : '');
|
|
15
|
+
|
|
16
|
+
export const bool = (value: unknown): boolean => value === true;
|
|
17
|
+
|
|
18
|
+
/** A `{value, label}` choice object, reduced to the string a declaration writes. */
|
|
19
|
+
export const choice = (value: unknown): string => {
|
|
20
|
+
if (typeof value === 'string') return value;
|
|
21
|
+
if (typeof value === 'object' && value !== null && 'value' in value) {
|
|
22
|
+
return text((value as { value: unknown }).value);
|
|
23
|
+
}
|
|
24
|
+
return '';
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* A foreign key, as the integer a declaration writes.
|
|
29
|
+
*
|
|
30
|
+
* ⚠️ IT ARRIVES AS A NESTED OBJECT ON READ AND AN INTEGER ON WRITE — `{id, url, display, …}`
|
|
31
|
+
* versus `4`. Comparing the two directly is the same never-converges bug as `choice`.
|
|
32
|
+
*/
|
|
33
|
+
export const fk = (value: unknown): number | undefined => {
|
|
34
|
+
if (typeof value === 'number') return value;
|
|
35
|
+
if (typeof value === 'object' && value !== null && 'id' in value) {
|
|
36
|
+
const id = (value as { id: unknown }).id;
|
|
37
|
+
return typeof id === 'number' ? id : undefined;
|
|
38
|
+
}
|
|
39
|
+
return undefined;
|
|
40
|
+
};
|
|
@@ -103,6 +103,11 @@ export const apiTokenSpec: PveSpec<ApiTokenProps, ApiTokenAttributes> = {
|
|
|
103
103
|
*/
|
|
104
104
|
collection: (props) => `access/users/${props.userid}/token/${props.tokenid}`,
|
|
105
105
|
createForm: shape,
|
|
106
|
+
/** The vendor rules both forms are checked against at plan time — resource-spec.ts. */
|
|
107
|
+
endpoint: {
|
|
108
|
+
create: 'pve:POST /access/users/{userid}/token/{tokenid}',
|
|
109
|
+
update: 'pve:PUT /access/users/{userid}/token/{tokenid}',
|
|
110
|
+
},
|
|
106
111
|
/**
|
|
107
112
|
* ⚠️ EXACTLY THE THREE FIELDS A PUT CAN PUT BACK, WHICH IS ALSO EXACTLY WHAT THE GET REPORTS.
|
|
108
113
|
* MEASURED, by replaying these two functions over the live GET bodies of all four token
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The two applies, and the vendor endpoints they publish through.
|
|
3
|
+
*
|
|
4
|
+
* ★ AN ACTION ENDPOINT IS STILL WIRED, EVEN THOUGH ITS TABLE IS EMPTY, AND THE REASON IS THE
|
|
5
|
+
* GENERATOR RATHER THAN THE VALIDATOR. `PUT /nodes/{node}/network` and `PUT /cluster/sdn` take
|
|
6
|
+
* no body at all — there is nothing for a length or a range to be wrong about — but
|
|
7
|
+
* `codegen/constraints.ts` RESOLVES every key named in this package against the vendor's own
|
|
8
|
+
* schema and STOPS when one is absent. So the key buys the thing these two families most need:
|
|
9
|
+
* a PVE that moves, renames or withdraws the apply fails `bun run check` instead of failing an
|
|
10
|
+
* `ifreload -a` on three nodes at once.
|
|
11
|
+
* ⚠️ AND THE LOOKUP IS NOT FREE EITHER. `constraintsFor` throws when a key has no table, so a
|
|
12
|
+
* checkout whose generated tables were never refreshed refuses the apply with the command to
|
|
13
|
+
* run rather than quietly doing nothing — the same defect `constraint-guard.ts` names.
|
|
14
|
+
*
|
|
15
|
+
* ⛔ NEITHER OF THESE IS A CREATE, AND NEITHER FAMILY HAS A `PveSpec`. Both write their own five
|
|
16
|
+
* handlers (an apply is not an object — see the ⛔ in each file), so they reach the shared check
|
|
17
|
+
* through `guardForm` by name, exactly as `pbs-datastore-endpoint.ts` does.
|
|
18
|
+
*/
|
|
19
|
+
import type * as Effect from 'effect/Effect';
|
|
20
|
+
import { guardForm } from './constraint-guard.ts';
|
|
21
|
+
import type { EndpointKey } from './constraints.ts';
|
|
22
|
+
|
|
23
|
+
/** ⚠️ `{node}` is a path segment, so this really does take no form keys at all. */
|
|
24
|
+
export const NETWORK_APPLY_ENDPOINT: EndpointKey = 'pve:PUT /nodes/{node}/network';
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* ⛔ CLUSTER-WIDE. One PUT publishes every staged zone, vnet and subnet on every node; there is no
|
|
28
|
+
* per-object apply and no pre-apply veto. `sdn-apply.ts` is the whole argument.
|
|
29
|
+
*/
|
|
30
|
+
export const SDN_APPLY_ENDPOINT: EndpointKey = 'pve:PUT /cluster/sdn';
|
|
31
|
+
|
|
32
|
+
export const guardNetworkApply: Effect.Effect<void> = guardForm(NETWORK_APPLY_ENDPOINT, {}, true);
|
|
33
|
+
|
|
34
|
+
export const guardSdnApply: Effect.Effect<void> = guardForm(SDN_APPLY_ENDPOINT, {}, true);
|
|
@@ -28,8 +28,30 @@
|
|
|
28
28
|
* the task in the PVE UI before concluding the create failed.
|
|
29
29
|
*
|
|
30
30
|
*/
|
|
31
|
-
import type { CephDaemonProps } from './ceph-daemon.ts';
|
|
32
|
-
import {
|
|
31
|
+
import type { CephDaemonAttributes, CephDaemonKind, CephDaemonProps } from './ceph-daemon.ts';
|
|
32
|
+
import type { EndpointPair } from './resource-spec.ts';
|
|
33
|
+
import { bool, flag, num, text } from './values.ts';
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* The vendor endpoint each kind is created at, as the generated tables key it.
|
|
37
|
+
*
|
|
38
|
+
* ⛔ ONE PER KIND, NOT A `{type}` PATH. PVE registers `mds`, `mgr` and `mon` as three separate
|
|
39
|
+
* nodes with three separate parameter schemas — `hotstandby` belongs to the first and
|
|
40
|
+
* `mon-address` to the third — so a single key would enforce one kind's rules on all three.
|
|
41
|
+
* That is why `PveSpec['endpoint']` admits a function of props at all.
|
|
42
|
+
* ⛔ THE STRINGS MUST STAY LITERALS. `codegen/constraints.ts` finds the endpoints to table by
|
|
43
|
+
* SCANNING THIS PACKAGE'S TEXT, so a key assembled from `props.kind` would be tabled by
|
|
44
|
+
* nothing and `constraintsFor` would throw on the first deploy that reached it.
|
|
45
|
+
* ⚠️ THE PARAMETER NAMES DIFFER AND ARE THE VENDOR'S OWN — `{name}`, `{id}`, `{monid}`. The
|
|
46
|
+
* generator resolves each against the schema, so a tidied-up spelling stops the build.
|
|
47
|
+
* ⚠️ NO `update` ON ANY OF THEM: none of the three has a PUT, which is why the spec declares no
|
|
48
|
+
* `updateForm` either.
|
|
49
|
+
*/
|
|
50
|
+
export const DAEMON_ENDPOINTS: Readonly<Record<CephDaemonKind, EndpointPair>> = {
|
|
51
|
+
mds: { create: 'pve:POST /nodes/{node}/ceph/mds/{name}' },
|
|
52
|
+
mgr: { create: 'pve:POST /nodes/{node}/ceph/mgr/{id}' },
|
|
53
|
+
mon: { create: 'pve:POST /nodes/{node}/ceph/mon/{monid}' },
|
|
54
|
+
};
|
|
33
55
|
|
|
34
56
|
/**
|
|
35
57
|
* PVE defaults the id to the nodename for all three kinds, and that default is resolved HERE
|
|
@@ -82,3 +104,35 @@ export const findRow = (live: unknown, props: CephDaemonProps) =>
|
|
|
82
104
|
(Array.isArray(live) ? live : [])
|
|
83
105
|
.filter((row): row is Record<string, unknown> => typeof row === 'object' && row !== null)
|
|
84
106
|
.find((row) => row['name'] === daemonId(props));
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* One live row as attributes, or `undefined` — "no daemon of this kind carries this name".
|
|
110
|
+
*
|
|
111
|
+
* ★ HERE RATHER THAN IN ceph-daemon.ts FOR THE REASON THAT FILE'S SIBLING EXISTS AT ALL: the
|
|
112
|
+
* 250-line cap, and a seam that is real. It reads the row `findRow` picked out of an answer its
|
|
113
|
+
* caller already fetched, three lines above, so the lookup and the mapping now live together
|
|
114
|
+
* instead of a function call apart.
|
|
115
|
+
*
|
|
116
|
+
* ⚠️ `undefined` WHEN NO ROW CARRIES THIS NAME: that is how the factory learns to create.
|
|
117
|
+
*/
|
|
118
|
+
export const daemonAttributes = (
|
|
119
|
+
live: Record<string, unknown>,
|
|
120
|
+
props: CephDaemonProps,
|
|
121
|
+
): CephDaemonAttributes | undefined => {
|
|
122
|
+
const row = findRow(live, props);
|
|
123
|
+
if (row === undefined) return undefined;
|
|
124
|
+
return {
|
|
125
|
+
addr: text(row['addr']),
|
|
126
|
+
fsName: text(row['fs_name']),
|
|
127
|
+
host: text(row['host']),
|
|
128
|
+
kind: props.kind,
|
|
129
|
+
name: daemonId(props),
|
|
130
|
+
node: props.node,
|
|
131
|
+
quorum: bool(row['quorum']),
|
|
132
|
+
rank: num(row['rank'], -1),
|
|
133
|
+
service: bool(row['service']),
|
|
134
|
+
standbyReplay: bool(row['standby_replay']),
|
|
135
|
+
state: text(row['state']),
|
|
136
|
+
version: text(row['ceph_version_short']),
|
|
137
|
+
};
|
|
138
|
+
};
|
|
@@ -49,10 +49,15 @@
|
|
|
49
49
|
import { Resource } from 'alchemy';
|
|
50
50
|
import * as Provider from 'alchemy/Provider';
|
|
51
51
|
import * as Effect from 'effect/Effect';
|
|
52
|
-
import {
|
|
52
|
+
import {
|
|
53
|
+
DAEMON_ENDPOINTS,
|
|
54
|
+
collectionPath,
|
|
55
|
+
createForm,
|
|
56
|
+
daemonAttributes,
|
|
57
|
+
daemonPath,
|
|
58
|
+
} from './ceph-daemon-form.ts';
|
|
53
59
|
import { pve } from './client.ts';
|
|
54
60
|
import { type PveRequirements, type WithTarget, pveHandlers } from './resource.ts';
|
|
55
|
-
import { bool, num, text } from './values.ts';
|
|
56
61
|
|
|
57
62
|
/**
|
|
58
63
|
* Which Ceph daemon this is. The discriminant picks the collection, the create parameters and
|
|
@@ -176,28 +181,12 @@ export const ProxmoxCephDaemon = Resource<ProxmoxCephDaemon>('Proxmox.CephDaemon
|
|
|
176
181
|
});
|
|
177
182
|
|
|
178
183
|
const handlers = pveHandlers<CephDaemonProps, CephDaemonAttributes>({
|
|
179
|
-
|
|
180
|
-
attributes: (live, props) => {
|
|
181
|
-
const row = findRow(live, props);
|
|
182
|
-
if (row === undefined) return undefined;
|
|
183
|
-
return {
|
|
184
|
-
addr: text(row['addr']),
|
|
185
|
-
fsName: text(row['fs_name']),
|
|
186
|
-
host: text(row['host']),
|
|
187
|
-
kind: props.kind,
|
|
188
|
-
name: daemonId(props),
|
|
189
|
-
node: props.node,
|
|
190
|
-
quorum: bool(row['quorum']),
|
|
191
|
-
rank: num(row['rank'], -1),
|
|
192
|
-
service: bool(row['service']),
|
|
193
|
-
standbyReplay: bool(row['standby_replay']),
|
|
194
|
-
state: text(row['state']),
|
|
195
|
-
version: text(row['ceph_version_short']),
|
|
196
|
-
};
|
|
197
|
-
},
|
|
184
|
+
attributes: daemonAttributes,
|
|
198
185
|
/** ⛔ The ID path, not the collection — POST is registered on `{id}`, exactly like a metric server. */
|
|
199
186
|
collection: daemonPath,
|
|
200
187
|
createForm,
|
|
188
|
+
/** ⚠️ A function, because the kind picks the endpoint — see `DAEMON_ENDPOINTS`. */
|
|
189
|
+
endpoint: (props: CephDaemonProps) => DAEMON_ENDPOINTS[props.kind],
|
|
201
190
|
/**
|
|
202
191
|
* ⛔ TOTAL, AND THIS IS THE REASON THE FILE EXISTS RATHER THAN A SHORTCUT PAST WRITING IT. Every
|
|
203
192
|
* field a declaration can carry is CREATE-ONLY and unreadable — `mon-address` comes back as a
|
package/src/proxmox/ceph-flag.ts
CHANGED
|
@@ -181,6 +181,7 @@ const handlers = pveHandlers<CephFlagProps, CephFlagAttributes>({
|
|
|
181
181
|
* only body `cluster/ceph/flags` has ever documented. Nothing sends it today.
|
|
182
182
|
*/
|
|
183
183
|
createForm: (props) => ({ [props.flag]: flag(props.value) ?? '0' }),
|
|
184
|
+
endpoint: { update: 'pve:PUT /cluster/ceph/flags/{flag}' }, // ⛔ No create: see above.
|
|
184
185
|
/**
|
|
185
186
|
* ⚠️ `value` IS THE ONLY THING COMPARED, AND THE LIST OF THINGS DELIBERATELY NOT COMPARED IS THE
|
|
186
187
|
* point of this resource. `flag` is props-derived (see `CephFlagAttributes`). `description`
|
package/src/proxmox/ceph-fs.ts
CHANGED
|
@@ -148,6 +148,7 @@ const ops = pveOperations<CephFsProps, CephFsAttributes>({
|
|
|
148
148
|
/** ⚠️ The `{name}` path — longer than `path()`, deliberately. See the second ⛔ in the header. */
|
|
149
149
|
collection: objectPath,
|
|
150
150
|
createForm,
|
|
151
|
+
endpoint: { create: 'pve:POST /nodes/{node}/ceph/fs/{name}' }, // ⚠️ POSTed on the object.
|
|
151
152
|
/**
|
|
152
153
|
* ⛔ NOTHING IS COMPARED, AND THAT IS THE MOST DELIBERATE LINE IN THIS FILE. A CephFS has no
|
|
153
154
|
* readable, writable field: the index returns only the name and the pools behind it, and every
|