@sonde/packs 0.1.0 → 0.1.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/.turbo/turbo-build.log +6 -0
- package/.turbo/turbo-test.log +814 -0
- package/.turbo/turbo-typecheck.log +4 -0
- package/CHANGELOG.md +10 -0
- package/dist/index.d.ts +16 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +40 -2
- package/dist/index.js.map +1 -1
- package/dist/integrations/citrix.d.ts +13 -0
- package/dist/integrations/citrix.d.ts.map +1 -0
- package/dist/integrations/citrix.js +420 -0
- package/dist/integrations/citrix.js.map +1 -0
- package/dist/integrations/citrix.test.d.ts +2 -0
- package/dist/integrations/citrix.test.d.ts.map +1 -0
- package/dist/integrations/citrix.test.js +464 -0
- package/dist/integrations/citrix.test.js.map +1 -0
- package/dist/integrations/graph.d.ts +9 -0
- package/dist/integrations/graph.d.ts.map +1 -0
- package/dist/integrations/graph.js +290 -0
- package/dist/integrations/graph.js.map +1 -0
- package/dist/integrations/graph.test.d.ts +2 -0
- package/dist/integrations/graph.test.d.ts.map +1 -0
- package/dist/integrations/graph.test.js +356 -0
- package/dist/integrations/graph.test.js.map +1 -0
- package/dist/integrations/httpbin.d.ts +3 -0
- package/dist/integrations/httpbin.d.ts.map +1 -0
- package/dist/integrations/httpbin.js +70 -0
- package/dist/integrations/httpbin.js.map +1 -0
- package/dist/integrations/nutanix.d.ts +18 -0
- package/dist/integrations/nutanix.d.ts.map +1 -0
- package/dist/integrations/nutanix.js +1121 -0
- package/dist/integrations/nutanix.js.map +1 -0
- package/dist/integrations/nutanix.test.d.ts +2 -0
- package/dist/integrations/nutanix.test.d.ts.map +1 -0
- package/dist/integrations/nutanix.test.js +978 -0
- package/dist/integrations/nutanix.test.js.map +1 -0
- package/dist/integrations/proxmox.d.ts +12 -0
- package/dist/integrations/proxmox.d.ts.map +1 -0
- package/dist/integrations/proxmox.js +733 -0
- package/dist/integrations/proxmox.js.map +1 -0
- package/dist/integrations/proxmox.test.d.ts +2 -0
- package/dist/integrations/proxmox.test.d.ts.map +1 -0
- package/dist/integrations/proxmox.test.js +697 -0
- package/dist/integrations/proxmox.test.js.map +1 -0
- package/dist/integrations/servicenow.d.ts +3 -0
- package/dist/integrations/servicenow.d.ts.map +1 -0
- package/dist/integrations/servicenow.js +257 -0
- package/dist/integrations/servicenow.js.map +1 -0
- package/dist/integrations/servicenow.test.d.ts +2 -0
- package/dist/integrations/servicenow.test.d.ts.map +1 -0
- package/dist/integrations/servicenow.test.js +217 -0
- package/dist/integrations/servicenow.test.js.map +1 -0
- package/dist/integrations/splunk.d.ts +9 -0
- package/dist/integrations/splunk.d.ts.map +1 -0
- package/dist/integrations/splunk.js +242 -0
- package/dist/integrations/splunk.js.map +1 -0
- package/dist/integrations/splunk.test.d.ts +2 -0
- package/dist/integrations/splunk.test.d.ts.map +1 -0
- package/dist/integrations/splunk.test.js +323 -0
- package/dist/integrations/splunk.test.js.map +1 -0
- package/dist/mysql/index.d.ts +3 -0
- package/dist/mysql/index.d.ts.map +1 -0
- package/dist/mysql/index.js +13 -0
- package/dist/mysql/index.js.map +1 -0
- package/dist/mysql/manifest.d.ts +3 -0
- package/dist/mysql/manifest.d.ts.map +1 -0
- package/dist/mysql/manifest.js +69 -0
- package/dist/mysql/manifest.js.map +1 -0
- package/dist/mysql/probes/databases-list.d.ts +13 -0
- package/dist/mysql/probes/databases-list.d.ts.map +1 -0
- package/dist/mysql/probes/databases-list.js +31 -0
- package/dist/mysql/probes/databases-list.js.map +1 -0
- package/dist/mysql/probes/databases-list.test.d.ts +2 -0
- package/dist/mysql/probes/databases-list.test.d.ts.map +1 -0
- package/dist/mysql/probes/databases-list.test.js +54 -0
- package/dist/mysql/probes/databases-list.test.js.map +1 -0
- package/dist/mysql/probes/processlist.d.ts +18 -0
- package/dist/mysql/probes/processlist.d.ts.map +1 -0
- package/dist/mysql/probes/processlist.js +36 -0
- package/dist/mysql/probes/processlist.js.map +1 -0
- package/dist/mysql/probes/processlist.test.d.ts +2 -0
- package/dist/mysql/probes/processlist.test.d.ts.map +1 -0
- package/dist/mysql/probes/processlist.test.js +41 -0
- package/dist/mysql/probes/processlist.test.js.map +1 -0
- package/dist/mysql/probes/status.d.ts +14 -0
- package/dist/mysql/probes/status.d.ts.map +1 -0
- package/dist/mysql/probes/status.js +40 -0
- package/dist/mysql/probes/status.js.map +1 -0
- package/dist/mysql/probes/status.test.d.ts +2 -0
- package/dist/mysql/probes/status.test.d.ts.map +1 -0
- package/dist/mysql/probes/status.test.js +43 -0
- package/dist/mysql/probes/status.test.js.map +1 -0
- package/dist/nginx/index.d.ts +3 -0
- package/dist/nginx/index.d.ts.map +1 -0
- package/dist/nginx/index.js +13 -0
- package/dist/nginx/index.js.map +1 -0
- package/dist/nginx/manifest.d.ts +3 -0
- package/dist/nginx/manifest.d.ts.map +1 -0
- package/dist/nginx/manifest.js +68 -0
- package/dist/nginx/manifest.js.map +1 -0
- package/dist/nginx/probes/access-log-tail.d.ts +9 -0
- package/dist/nginx/probes/access-log-tail.d.ts.map +1 -0
- package/dist/nginx/probes/access-log-tail.js +14 -0
- package/dist/nginx/probes/access-log-tail.js.map +1 -0
- package/dist/nginx/probes/access-log-tail.test.d.ts +2 -0
- package/dist/nginx/probes/access-log-tail.test.d.ts.map +1 -0
- package/dist/nginx/probes/access-log-tail.test.js +40 -0
- package/dist/nginx/probes/access-log-tail.test.js.map +1 -0
- package/dist/nginx/probes/config-test.d.ts +8 -0
- package/dist/nginx/probes/config-test.d.ts.map +1 -0
- package/dist/nginx/probes/config-test.js +18 -0
- package/dist/nginx/probes/config-test.js.map +1 -0
- package/dist/nginx/probes/config-test.test.d.ts +2 -0
- package/dist/nginx/probes/config-test.test.d.ts.map +1 -0
- package/dist/nginx/probes/config-test.test.js +35 -0
- package/dist/nginx/probes/config-test.test.js.map +1 -0
- package/dist/nginx/probes/error-log-tail.d.ts +9 -0
- package/dist/nginx/probes/error-log-tail.d.ts.map +1 -0
- package/dist/nginx/probes/error-log-tail.js +14 -0
- package/dist/nginx/probes/error-log-tail.js.map +1 -0
- package/dist/nginx/probes/error-log-tail.test.d.ts +2 -0
- package/dist/nginx/probes/error-log-tail.test.d.ts.map +1 -0
- package/dist/nginx/probes/error-log-tail.test.js +34 -0
- package/dist/nginx/probes/error-log-tail.test.js.map +1 -0
- package/dist/postgres/index.d.ts +3 -0
- package/dist/postgres/index.d.ts.map +1 -0
- package/dist/postgres/index.js +13 -0
- package/dist/postgres/index.js.map +1 -0
- package/dist/postgres/manifest.d.ts +3 -0
- package/dist/postgres/manifest.d.ts.map +1 -0
- package/dist/postgres/manifest.js +90 -0
- package/dist/postgres/manifest.js.map +1 -0
- package/dist/postgres/probes/connections-active.d.ts +17 -0
- package/dist/postgres/probes/connections-active.d.ts.map +1 -0
- package/dist/postgres/probes/connections-active.js +37 -0
- package/dist/postgres/probes/connections-active.js.map +1 -0
- package/dist/postgres/probes/connections-active.test.d.ts +2 -0
- package/dist/postgres/probes/connections-active.test.d.ts.map +1 -0
- package/dist/postgres/probes/connections-active.test.js +36 -0
- package/dist/postgres/probes/connections-active.test.js.map +1 -0
- package/dist/postgres/probes/databases-list.d.ts +14 -0
- package/dist/postgres/probes/databases-list.d.ts.map +1 -0
- package/dist/postgres/probes/databases-list.js +34 -0
- package/dist/postgres/probes/databases-list.js.map +1 -0
- package/dist/postgres/probes/databases-list.test.d.ts +2 -0
- package/dist/postgres/probes/databases-list.test.d.ts.map +1 -0
- package/dist/postgres/probes/databases-list.test.js +49 -0
- package/dist/postgres/probes/databases-list.test.js.map +1 -0
- package/dist/postgres/probes/query-slow.d.ts +17 -0
- package/dist/postgres/probes/query-slow.d.ts.map +1 -0
- package/dist/postgres/probes/query-slow.js +37 -0
- package/dist/postgres/probes/query-slow.js.map +1 -0
- package/dist/postgres/probes/query-slow.test.d.ts +2 -0
- package/dist/postgres/probes/query-slow.test.d.ts.map +1 -0
- package/dist/postgres/probes/query-slow.test.js +30 -0
- package/dist/postgres/probes/query-slow.test.js.map +1 -0
- package/dist/proxmox/index.d.ts +3 -0
- package/dist/proxmox/index.d.ts.map +1 -0
- package/dist/proxmox/index.js +23 -0
- package/dist/proxmox/index.js.map +1 -0
- package/dist/proxmox/manifest.d.ts +3 -0
- package/dist/proxmox/manifest.d.ts.map +1 -0
- package/dist/proxmox/manifest.js +75 -0
- package/dist/proxmox/manifest.js.map +1 -0
- package/dist/proxmox/probes/ceph-status.d.ts +36 -0
- package/dist/proxmox/probes/ceph-status.d.ts.map +1 -0
- package/dist/proxmox/probes/ceph-status.js +71 -0
- package/dist/proxmox/probes/ceph-status.js.map +1 -0
- package/dist/proxmox/probes/ceph-status.test.d.ts +2 -0
- package/dist/proxmox/probes/ceph-status.test.d.ts.map +1 -0
- package/dist/proxmox/probes/ceph-status.test.js +115 -0
- package/dist/proxmox/probes/ceph-status.test.js.map +1 -0
- package/dist/proxmox/probes/cluster-config.d.ts +31 -0
- package/dist/proxmox/probes/cluster-config.d.ts.map +1 -0
- package/dist/proxmox/probes/cluster-config.js +72 -0
- package/dist/proxmox/probes/cluster-config.js.map +1 -0
- package/dist/proxmox/probes/cluster-config.test.d.ts +2 -0
- package/dist/proxmox/probes/cluster-config.test.d.ts.map +1 -0
- package/dist/proxmox/probes/cluster-config.test.js +107 -0
- package/dist/proxmox/probes/cluster-config.test.js.map +1 -0
- package/dist/proxmox/probes/ha-status.d.ts +18 -0
- package/dist/proxmox/probes/ha-status.d.ts.map +1 -0
- package/dist/proxmox/probes/ha-status.js +38 -0
- package/dist/proxmox/probes/ha-status.js.map +1 -0
- package/dist/proxmox/probes/ha-status.test.d.ts +2 -0
- package/dist/proxmox/probes/ha-status.test.d.ts.map +1 -0
- package/dist/proxmox/probes/ha-status.test.js +66 -0
- package/dist/proxmox/probes/ha-status.test.js.map +1 -0
- package/dist/proxmox/probes/lvm.d.ts +35 -0
- package/dist/proxmox/probes/lvm.d.ts.map +1 -0
- package/dist/proxmox/probes/lvm.js +75 -0
- package/dist/proxmox/probes/lvm.js.map +1 -0
- package/dist/proxmox/probes/lvm.test.d.ts +2 -0
- package/dist/proxmox/probes/lvm.test.d.ts.map +1 -0
- package/dist/proxmox/probes/lvm.test.js +128 -0
- package/dist/proxmox/probes/lvm.test.js.map +1 -0
- package/dist/proxmox/probes/lxc-config.d.ts +29 -0
- package/dist/proxmox/probes/lxc-config.d.ts.map +1 -0
- package/dist/proxmox/probes/lxc-config.js +67 -0
- package/dist/proxmox/probes/lxc-config.js.map +1 -0
- package/dist/proxmox/probes/lxc-config.test.d.ts +2 -0
- package/dist/proxmox/probes/lxc-config.test.d.ts.map +1 -0
- package/dist/proxmox/probes/lxc-config.test.js +77 -0
- package/dist/proxmox/probes/lxc-config.test.js.map +1 -0
- package/dist/proxmox/probes/lxc-list.d.ts +20 -0
- package/dist/proxmox/probes/lxc-list.d.ts.map +1 -0
- package/dist/proxmox/probes/lxc-list.js +49 -0
- package/dist/proxmox/probes/lxc-list.js.map +1 -0
- package/dist/proxmox/probes/lxc-list.test.d.ts +2 -0
- package/dist/proxmox/probes/lxc-list.test.d.ts.map +1 -0
- package/dist/proxmox/probes/lxc-list.test.js +51 -0
- package/dist/proxmox/probes/lxc-list.test.js.map +1 -0
- package/dist/proxmox/probes/vm-config.d.ts +21 -0
- package/dist/proxmox/probes/vm-config.d.ts.map +1 -0
- package/dist/proxmox/probes/vm-config.js +58 -0
- package/dist/proxmox/probes/vm-config.js.map +1 -0
- package/dist/proxmox/probes/vm-config.test.d.ts +2 -0
- package/dist/proxmox/probes/vm-config.test.d.ts.map +1 -0
- package/dist/proxmox/probes/vm-config.test.js +80 -0
- package/dist/proxmox/probes/vm-config.test.js.map +1 -0
- package/dist/proxmox/probes/vm-locks.d.ts +16 -0
- package/dist/proxmox/probes/vm-locks.d.ts.map +1 -0
- package/dist/proxmox/probes/vm-locks.js +35 -0
- package/dist/proxmox/probes/vm-locks.js.map +1 -0
- package/dist/proxmox/probes/vm-locks.test.d.ts +2 -0
- package/dist/proxmox/probes/vm-locks.test.d.ts.map +1 -0
- package/dist/proxmox/probes/vm-locks.test.js +54 -0
- package/dist/proxmox/probes/vm-locks.test.js.map +1 -0
- package/dist/redis/index.d.ts +3 -0
- package/dist/redis/index.d.ts.map +1 -0
- package/dist/redis/index.js +13 -0
- package/dist/redis/index.js.map +1 -0
- package/dist/redis/manifest.d.ts +3 -0
- package/dist/redis/manifest.d.ts.map +1 -0
- package/dist/redis/manifest.js +51 -0
- package/dist/redis/manifest.js.map +1 -0
- package/dist/redis/probes/info.d.ts +15 -0
- package/dist/redis/probes/info.d.ts.map +1 -0
- package/dist/redis/probes/info.js +32 -0
- package/dist/redis/probes/info.js.map +1 -0
- package/dist/redis/probes/info.test.d.ts +2 -0
- package/dist/redis/probes/info.test.d.ts.map +1 -0
- package/dist/redis/probes/info.test.js +64 -0
- package/dist/redis/probes/info.test.js.map +1 -0
- package/dist/redis/probes/keys-count.d.ts +13 -0
- package/dist/redis/probes/keys-count.d.ts.map +1 -0
- package/dist/redis/probes/keys-count.js +24 -0
- package/dist/redis/probes/keys-count.js.map +1 -0
- package/dist/redis/probes/keys-count.test.d.ts +2 -0
- package/dist/redis/probes/keys-count.test.d.ts.map +1 -0
- package/dist/redis/probes/keys-count.test.js +37 -0
- package/dist/redis/probes/keys-count.test.js.map +1 -0
- package/dist/redis/probes/memory-usage.d.ts +16 -0
- package/dist/redis/probes/memory-usage.d.ts.map +1 -0
- package/dist/redis/probes/memory-usage.js +31 -0
- package/dist/redis/probes/memory-usage.js.map +1 -0
- package/dist/redis/probes/memory-usage.test.d.ts +2 -0
- package/dist/redis/probes/memory-usage.test.d.ts.map +1 -0
- package/dist/redis/probes/memory-usage.test.js +48 -0
- package/dist/redis/probes/memory-usage.test.js.map +1 -0
- package/dist/runbooks/nutanix.d.ts +3 -0
- package/dist/runbooks/nutanix.d.ts.map +1 -0
- package/dist/runbooks/nutanix.js +619 -0
- package/dist/runbooks/nutanix.js.map +1 -0
- package/dist/runbooks/nutanix.test.d.ts +2 -0
- package/dist/runbooks/nutanix.test.d.ts.map +1 -0
- package/dist/runbooks/nutanix.test.js +971 -0
- package/dist/runbooks/nutanix.test.js.map +1 -0
- package/dist/runbooks/proxmox.d.ts +3 -0
- package/dist/runbooks/proxmox.d.ts.map +1 -0
- package/dist/runbooks/proxmox.js +451 -0
- package/dist/runbooks/proxmox.js.map +1 -0
- package/dist/runbooks/proxmox.test.d.ts +2 -0
- package/dist/runbooks/proxmox.test.d.ts.map +1 -0
- package/dist/runbooks/proxmox.test.js +700 -0
- package/dist/runbooks/proxmox.test.js.map +1 -0
- package/dist/signatures.d.ts +2 -0
- package/dist/signatures.d.ts.map +1 -0
- package/dist/signatures.js +2 -0
- package/dist/signatures.js.map +1 -0
- package/dist/types.d.ts +53 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/validation.d.ts +6 -1
- package/dist/validation.d.ts.map +1 -1
- package/dist/validation.js +10 -1
- package/dist/validation.js.map +1 -1
- package/package.json +1 -1
- package/src/index.ts +60 -6
- package/src/integrations/citrix.test.ts +592 -0
- package/src/integrations/citrix.ts +557 -0
- package/src/integrations/graph.test.ts +478 -0
- package/src/integrations/graph.ts +413 -0
- package/src/integrations/httpbin.ts +72 -0
- package/src/integrations/nutanix.test.ts +1508 -0
- package/src/integrations/nutanix.ts +1460 -0
- package/src/integrations/proxmox.test.ts +1020 -0
- package/src/integrations/proxmox.ts +989 -0
- package/src/integrations/servicenow.test.ts +314 -0
- package/src/integrations/servicenow.ts +285 -0
- package/src/integrations/splunk.test.ts +440 -0
- package/src/integrations/splunk.ts +356 -0
- package/src/mysql/index.ts +14 -0
- package/src/mysql/manifest.ts +70 -0
- package/src/mysql/probes/databases-list.test.ts +62 -0
- package/src/mysql/probes/databases-list.ts +45 -0
- package/src/mysql/probes/processlist.test.ts +47 -0
- package/src/mysql/probes/processlist.ts +55 -0
- package/src/mysql/probes/status.test.ts +50 -0
- package/src/mysql/probes/status.ts +56 -0
- package/src/nginx/index.ts +14 -0
- package/src/nginx/manifest.ts +69 -0
- package/src/nginx/probes/access-log-tail.test.ts +51 -0
- package/src/nginx/probes/access-log-tail.ts +23 -0
- package/src/nginx/probes/config-test.test.ts +47 -0
- package/src/nginx/probes/config-test.ts +24 -0
- package/src/nginx/probes/error-log-tail.test.ts +44 -0
- package/src/nginx/probes/error-log-tail.ts +23 -0
- package/src/postgres/index.ts +14 -0
- package/src/postgres/manifest.ts +91 -0
- package/src/postgres/probes/connections-active.test.ts +42 -0
- package/src/postgres/probes/connections-active.ts +55 -0
- package/src/postgres/probes/databases-list.test.ts +57 -0
- package/src/postgres/probes/databases-list.ts +49 -0
- package/src/postgres/probes/query-slow.test.ts +37 -0
- package/src/postgres/probes/query-slow.ts +55 -0
- package/src/proxmox/index.ts +24 -0
- package/src/proxmox/manifest.ts +76 -0
- package/src/proxmox/probes/ceph-status.test.ts +126 -0
- package/src/proxmox/probes/ceph-status.ts +116 -0
- package/src/proxmox/probes/cluster-config.test.ts +118 -0
- package/src/proxmox/probes/cluster-config.ts +97 -0
- package/src/proxmox/probes/ha-status.test.ts +76 -0
- package/src/proxmox/probes/ha-status.ts +56 -0
- package/src/proxmox/probes/lvm.test.ts +140 -0
- package/src/proxmox/probes/lvm.ts +121 -0
- package/src/proxmox/probes/lxc-config.test.ts +89 -0
- package/src/proxmox/probes/lxc-config.ts +90 -0
- package/src/proxmox/probes/lxc-list.test.ts +60 -0
- package/src/proxmox/probes/lxc-list.ts +67 -0
- package/src/proxmox/probes/vm-config.test.ts +93 -0
- package/src/proxmox/probes/vm-config.ts +77 -0
- package/src/proxmox/probes/vm-locks.test.ts +63 -0
- package/src/proxmox/probes/vm-locks.ts +49 -0
- package/src/redis/index.ts +14 -0
- package/src/redis/manifest.ts +52 -0
- package/src/redis/probes/info.test.ts +73 -0
- package/src/redis/probes/info.ts +46 -0
- package/src/redis/probes/keys-count.test.ts +44 -0
- package/src/redis/probes/keys-count.ts +38 -0
- package/src/redis/probes/memory-usage.test.ts +54 -0
- package/src/redis/probes/memory-usage.ts +46 -0
- package/src/runbooks/nutanix.test.ts +1138 -0
- package/src/runbooks/nutanix.ts +941 -0
- package/src/runbooks/proxmox.test.ts +838 -0
- package/src/runbooks/proxmox.ts +626 -0
- package/src/signatures.ts +1 -0
- package/src/types.ts +62 -0
- package/src/validation.ts +21 -1
- package/tsconfig.tsbuildinfo +1 -0
|
@@ -0,0 +1,626 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
DiagnosticFinding,
|
|
3
|
+
DiagnosticRunbookDefinition,
|
|
4
|
+
DiagnosticRunbookResult,
|
|
5
|
+
RunProbe,
|
|
6
|
+
RunbookContext,
|
|
7
|
+
RunbookProbeResult,
|
|
8
|
+
} from '../types.js';
|
|
9
|
+
|
|
10
|
+
// --- Helper ---
|
|
11
|
+
|
|
12
|
+
function buildResult(
|
|
13
|
+
category: string,
|
|
14
|
+
findings: DiagnosticFinding[],
|
|
15
|
+
probeResults: Record<string, RunbookProbeResult>,
|
|
16
|
+
startTime: number,
|
|
17
|
+
summaryText: string,
|
|
18
|
+
): DiagnosticRunbookResult {
|
|
19
|
+
const counts = { info: 0, warning: 0, critical: 0 };
|
|
20
|
+
for (const f of findings) counts[f.severity]++;
|
|
21
|
+
|
|
22
|
+
const results = Object.values(probeResults);
|
|
23
|
+
return {
|
|
24
|
+
category,
|
|
25
|
+
findings,
|
|
26
|
+
probeResults,
|
|
27
|
+
summary: {
|
|
28
|
+
probesRun: results.length,
|
|
29
|
+
probesSucceeded: results.filter((r) => r.status === 'success').length,
|
|
30
|
+
probesFailed: results.filter((r) => r.status !== 'success').length,
|
|
31
|
+
findingsCount: counts,
|
|
32
|
+
durationMs: Date.now() - startTime,
|
|
33
|
+
summaryText,
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function storeResult(
|
|
39
|
+
map: Record<string, RunbookProbeResult>,
|
|
40
|
+
result: RunbookProbeResult,
|
|
41
|
+
): RunbookProbeResult {
|
|
42
|
+
map[result.probe] = result;
|
|
43
|
+
return result;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function probeData<T>(result: RunbookProbeResult): T | undefined {
|
|
47
|
+
if (result.status !== 'success') return undefined;
|
|
48
|
+
return result.data as T;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// --- VM Health Runbook ---
|
|
52
|
+
|
|
53
|
+
async function vmHealthHandler(
|
|
54
|
+
params: Record<string, unknown>,
|
|
55
|
+
runProbe: RunProbe,
|
|
56
|
+
context: RunbookContext,
|
|
57
|
+
): Promise<DiagnosticRunbookResult> {
|
|
58
|
+
const startTime = Date.now();
|
|
59
|
+
const vmid = params.vmid as number;
|
|
60
|
+
const findings: DiagnosticFinding[] = [];
|
|
61
|
+
const probeResults: Record<string, RunbookProbeResult> = {};
|
|
62
|
+
|
|
63
|
+
// Group 1: parallel integration probes
|
|
64
|
+
const [clusterStatusR, haStatusR, vmStatusR, tasksR] = await Promise.all([
|
|
65
|
+
runProbe('proxmox.cluster.status').then((r) => storeResult(probeResults, r)),
|
|
66
|
+
runProbe('proxmox.cluster.ha.status').then((r) => storeResult(probeResults, r)),
|
|
67
|
+
runProbe('proxmox.vm.status', { vmid }).then((r) => storeResult(probeResults, r)),
|
|
68
|
+
runProbe('proxmox.cluster.tasks', { vmid }).then((r) => storeResult(probeResults, r)),
|
|
69
|
+
]);
|
|
70
|
+
|
|
71
|
+
// Extract node and type from vm.status
|
|
72
|
+
const vmData = probeData<{
|
|
73
|
+
node?: string;
|
|
74
|
+
type?: string;
|
|
75
|
+
status?: string;
|
|
76
|
+
lock?: string;
|
|
77
|
+
hastate?: string;
|
|
78
|
+
name?: string;
|
|
79
|
+
}>(vmStatusR);
|
|
80
|
+
|
|
81
|
+
if (!vmData?.node) {
|
|
82
|
+
findings.push({
|
|
83
|
+
severity: 'critical',
|
|
84
|
+
title: `VM ${vmid} not found or unreachable`,
|
|
85
|
+
detail: vmStatusR.error ?? 'Could not retrieve VM status',
|
|
86
|
+
relatedProbes: ['proxmox.vm.status'],
|
|
87
|
+
});
|
|
88
|
+
return buildResult('proxmox-vm', findings, probeResults, startTime, `VM ${vmid} not found`);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const node = vmData.node;
|
|
92
|
+
const vmType = vmData.type as 'qemu' | 'lxc';
|
|
93
|
+
|
|
94
|
+
// Group 2: conditional config + storage
|
|
95
|
+
const configProbe =
|
|
96
|
+
vmType === 'qemu'
|
|
97
|
+
? runProbe('proxmox.vm.config', { vmid, node }).then((r) => storeResult(probeResults, r))
|
|
98
|
+
: runProbe('proxmox.lxc.config', { vmid, node }).then((r) => storeResult(probeResults, r));
|
|
99
|
+
const storageProbe = runProbe('proxmox.node.storage', { node }).then((r) =>
|
|
100
|
+
storeResult(probeResults, r),
|
|
101
|
+
);
|
|
102
|
+
const [configR, storageR] = await Promise.all([configProbe, storageProbe] as const);
|
|
103
|
+
|
|
104
|
+
// Group 3: optional agent probe
|
|
105
|
+
if (context.connectedAgents.includes(node)) {
|
|
106
|
+
const lvmR = await runProbe('proxmox-node.local.lvm', undefined, node);
|
|
107
|
+
storeResult(probeResults, lvmR);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// --- Analysis ---
|
|
111
|
+
|
|
112
|
+
// Cluster health
|
|
113
|
+
const clusterData = probeData<{
|
|
114
|
+
quorate?: boolean;
|
|
115
|
+
nodes?: Array<{ name?: string; online?: boolean }>;
|
|
116
|
+
}>(clusterStatusR);
|
|
117
|
+
if (clusterData) {
|
|
118
|
+
if (!clusterData.quorate) {
|
|
119
|
+
findings.push({
|
|
120
|
+
severity: 'critical',
|
|
121
|
+
title: 'Cluster unhealthy — quorum lost',
|
|
122
|
+
detail: 'The cluster has lost quorum. Nodes may be unreachable.',
|
|
123
|
+
relatedProbes: ['proxmox.cluster.status'],
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
const offlineNodes = (clusterData.nodes ?? []).filter((n) => !n.online);
|
|
127
|
+
if (offlineNodes.length > 0) {
|
|
128
|
+
findings.push({
|
|
129
|
+
severity: 'critical',
|
|
130
|
+
title: 'Cluster unhealthy — nodes offline',
|
|
131
|
+
detail: `Offline nodes: ${offlineNodes.map((n) => n.name).join(', ')}`,
|
|
132
|
+
relatedProbes: ['proxmox.cluster.status'],
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// HA status for this VM
|
|
138
|
+
const haData = probeData<{
|
|
139
|
+
resources?: Array<{ sid?: string; state?: string }>;
|
|
140
|
+
}>(haStatusR);
|
|
141
|
+
const haResources = haData?.resources ?? [];
|
|
142
|
+
const vmSid = `vm:${vmid}`;
|
|
143
|
+
const vmHaResource = haResources.find((r) => r.sid === vmSid);
|
|
144
|
+
const isHaManaged = !!vmHaResource;
|
|
145
|
+
|
|
146
|
+
if (vmHaResource && (vmHaResource.state === 'error' || vmHaResource.state === 'fence')) {
|
|
147
|
+
const unlockCmd = vmType === 'qemu' ? `qm unlock ${vmid}` : `pct unlock ${vmid}`;
|
|
148
|
+
findings.push({
|
|
149
|
+
severity: 'critical',
|
|
150
|
+
title: `HA error for vm:${vmid}`,
|
|
151
|
+
detail: `HA resource vm:${vmid} is in "${vmHaResource.state}" state`,
|
|
152
|
+
remediation: `ha-manager set vm:${vmid} --state disabled && ${unlockCmd}`,
|
|
153
|
+
relatedProbes: ['proxmox.cluster.ha.status'],
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// VM lock
|
|
158
|
+
if (vmData.lock) {
|
|
159
|
+
const unlockCmd = vmType === 'qemu' ? `qm unlock ${vmid}` : `pct unlock ${vmid}`;
|
|
160
|
+
findings.push({
|
|
161
|
+
severity: 'warning',
|
|
162
|
+
title: `VM ${vmid} has stale lock: ${vmData.lock}`,
|
|
163
|
+
detail: `VM is locked with reason "${vmData.lock}". This may prevent operations.`,
|
|
164
|
+
remediation: unlockCmd,
|
|
165
|
+
relatedProbes: ['proxmox.vm.status'],
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// VM stopped
|
|
170
|
+
if (vmData.status === 'stopped') {
|
|
171
|
+
findings.push({
|
|
172
|
+
severity: 'warning',
|
|
173
|
+
title: `VM ${vmid} is stopped`,
|
|
174
|
+
detail: `VM ${vmid} (${vmData.name ?? 'unknown'}) is not running`,
|
|
175
|
+
relatedProbes: ['proxmox.vm.status'],
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Storage accessibility
|
|
180
|
+
const storageData = probeData<{
|
|
181
|
+
storages?: Array<{ storage?: string; active?: boolean; enabled?: boolean; shared?: boolean }>;
|
|
182
|
+
}>(storageR);
|
|
183
|
+
const storages = storageData?.storages ?? [];
|
|
184
|
+
|
|
185
|
+
// Check config disks against storage
|
|
186
|
+
if (configR) {
|
|
187
|
+
const configData = probeData<{
|
|
188
|
+
disks?: Array<{ key: string; storage: string }>;
|
|
189
|
+
rootfs?: { storage: string };
|
|
190
|
+
mountpoints?: Array<{ key: string; storage: string }>;
|
|
191
|
+
}>(configR);
|
|
192
|
+
|
|
193
|
+
// Collect disk storage names from config
|
|
194
|
+
const diskStorages: Array<{ key: string; storage: string }> = [];
|
|
195
|
+
if (vmType === 'qemu' && configData?.disks) {
|
|
196
|
+
for (const d of configData.disks) diskStorages.push(d);
|
|
197
|
+
} else if (vmType === 'lxc') {
|
|
198
|
+
if (configData?.rootfs)
|
|
199
|
+
diskStorages.push({ key: 'rootfs', storage: configData.rootfs.storage });
|
|
200
|
+
if (configData?.mountpoints) {
|
|
201
|
+
for (const mp of configData.mountpoints) diskStorages.push(mp);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
for (const disk of diskStorages) {
|
|
206
|
+
const stor = storages.find((s) => s.storage === disk.storage);
|
|
207
|
+
|
|
208
|
+
// Storage inactive
|
|
209
|
+
if (stor && !stor.active) {
|
|
210
|
+
findings.push({
|
|
211
|
+
severity: 'critical',
|
|
212
|
+
title: `Storage ${disk.storage} not accessible on ${node}`,
|
|
213
|
+
detail: `Storage "${disk.storage}" used by disk ${disk.key} is inactive on node ${node}`,
|
|
214
|
+
remediation: 'Check LVM/NFS mounts on the node',
|
|
215
|
+
relatedProbes: [
|
|
216
|
+
'proxmox.node.storage',
|
|
217
|
+
vmType === 'qemu' ? 'proxmox.vm.config' : 'proxmox.lxc.config',
|
|
218
|
+
],
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Local storage + HA managed
|
|
223
|
+
if (isHaManaged && stor && !stor.shared) {
|
|
224
|
+
const sharedStor = storages.find((s) => s.shared && s.active);
|
|
225
|
+
const moveCmd =
|
|
226
|
+
vmType === 'qemu'
|
|
227
|
+
? `qm move-disk ${vmid} ${disk.key} ${sharedStor?.storage ?? '<shared-storage>'} --delete`
|
|
228
|
+
: `pct move-volume ${vmid} ${disk.key} ${sharedStor?.storage ?? '<shared-storage>'}`;
|
|
229
|
+
findings.push({
|
|
230
|
+
severity: 'critical',
|
|
231
|
+
title: `VM ${vmid} uses local storage but is HA-managed`,
|
|
232
|
+
detail: `Disk ${disk.key} is on local storage "${disk.storage}" but VM is HA-managed. HA failover will fail.`,
|
|
233
|
+
remediation: moveCmd,
|
|
234
|
+
relatedProbes: [
|
|
235
|
+
'proxmox.cluster.ha.status',
|
|
236
|
+
vmType === 'qemu' ? 'proxmox.vm.config' : 'proxmox.lxc.config',
|
|
237
|
+
'proxmox.node.storage',
|
|
238
|
+
],
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Failed recent tasks
|
|
245
|
+
const tasksData = probeData<{
|
|
246
|
+
tasks?: Array<{ type?: string; status?: string; node?: string; starttime?: number }>;
|
|
247
|
+
}>(tasksR);
|
|
248
|
+
const failedTasks = (tasksData?.tasks ?? []).filter(
|
|
249
|
+
(t) => t.status && t.status !== 'OK' && t.status !== '',
|
|
250
|
+
);
|
|
251
|
+
if (failedTasks.length > 0) {
|
|
252
|
+
findings.push({
|
|
253
|
+
severity: 'warning',
|
|
254
|
+
title: `Recent task failures for VM ${vmid}`,
|
|
255
|
+
detail: failedTasks.map((t) => `${t.type} on ${t.node}: ${t.status}`).join('; '),
|
|
256
|
+
relatedProbes: ['proxmox.cluster.tasks'],
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// All clear
|
|
261
|
+
if (findings.length === 0) {
|
|
262
|
+
findings.push({
|
|
263
|
+
severity: 'info',
|
|
264
|
+
title: `VM ${vmid} is healthy`,
|
|
265
|
+
detail: `VM ${vmid} (${vmData.name ?? 'unknown'}) is running on ${node}, all storage accessible.`,
|
|
266
|
+
relatedProbes: ['proxmox.vm.status', 'proxmox.node.storage'],
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
return buildResult(
|
|
271
|
+
'proxmox-vm',
|
|
272
|
+
findings,
|
|
273
|
+
probeResults,
|
|
274
|
+
startTime,
|
|
275
|
+
`VM ${vmid} health check: ${findings.length} finding(s)`,
|
|
276
|
+
);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// --- Cluster Health Runbook ---
|
|
280
|
+
|
|
281
|
+
async function clusterHealthHandler(
|
|
282
|
+
params: Record<string, unknown>,
|
|
283
|
+
runProbe: RunProbe,
|
|
284
|
+
_context: RunbookContext,
|
|
285
|
+
): Promise<DiagnosticRunbookResult> {
|
|
286
|
+
const startTime = Date.now();
|
|
287
|
+
const limit = (params.limit as number) ?? 20;
|
|
288
|
+
const findings: DiagnosticFinding[] = [];
|
|
289
|
+
const probeResults: Record<string, RunbookProbeResult> = {};
|
|
290
|
+
|
|
291
|
+
// Group 1: parallel
|
|
292
|
+
const [clusterStatusR, nodesListR, haStatusR, tasksR] = await Promise.all([
|
|
293
|
+
runProbe('proxmox.cluster.status').then((r) => storeResult(probeResults, r)),
|
|
294
|
+
runProbe('proxmox.nodes.list').then((r) => storeResult(probeResults, r)),
|
|
295
|
+
runProbe('proxmox.cluster.ha.status').then((r) => storeResult(probeResults, r)),
|
|
296
|
+
runProbe('proxmox.cluster.tasks', { limit }).then((r) => storeResult(probeResults, r)),
|
|
297
|
+
]);
|
|
298
|
+
|
|
299
|
+
// Extract nodes
|
|
300
|
+
const nodesData = probeData<{
|
|
301
|
+
nodes?: Array<{
|
|
302
|
+
node?: string;
|
|
303
|
+
status?: string;
|
|
304
|
+
cpu?: number;
|
|
305
|
+
mem?: number;
|
|
306
|
+
maxmem?: number;
|
|
307
|
+
}>;
|
|
308
|
+
}>(nodesListR);
|
|
309
|
+
const nodes = nodesData?.nodes ?? [];
|
|
310
|
+
|
|
311
|
+
// Group 2: storage per node (parallel)
|
|
312
|
+
const storageResults = await Promise.all(
|
|
313
|
+
nodes.map(async (n) => {
|
|
314
|
+
if (!n.node) return null;
|
|
315
|
+
const r = await runProbe('proxmox.node.storage', { node: n.node });
|
|
316
|
+
storeResult(probeResults, { ...r, probe: `proxmox.node.storage:${n.node}` });
|
|
317
|
+
return { node: n.node, result: r };
|
|
318
|
+
}),
|
|
319
|
+
);
|
|
320
|
+
|
|
321
|
+
// --- Analysis ---
|
|
322
|
+
|
|
323
|
+
// Node offline
|
|
324
|
+
for (const n of nodes) {
|
|
325
|
+
if (n.status === 'offline') {
|
|
326
|
+
findings.push({
|
|
327
|
+
severity: 'critical',
|
|
328
|
+
title: `Node ${n.node} is offline`,
|
|
329
|
+
detail: `Node ${n.node} is not responding`,
|
|
330
|
+
relatedProbes: ['proxmox.nodes.list'],
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// High CPU
|
|
336
|
+
for (const n of nodes) {
|
|
337
|
+
if (n.cpu != null && n.cpu > 0.9) {
|
|
338
|
+
findings.push({
|
|
339
|
+
severity: 'warning',
|
|
340
|
+
title: `Node ${n.node} CPU at ${Math.round(n.cpu * 100)}%`,
|
|
341
|
+
detail: `Node ${n.node} CPU utilization is critically high`,
|
|
342
|
+
relatedProbes: ['proxmox.nodes.list'],
|
|
343
|
+
});
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// High memory
|
|
348
|
+
for (const n of nodes) {
|
|
349
|
+
if (n.mem != null && n.maxmem != null && n.maxmem > 0 && n.mem / n.maxmem > 0.9) {
|
|
350
|
+
findings.push({
|
|
351
|
+
severity: 'warning',
|
|
352
|
+
title: `Node ${n.node} memory at ${Math.round((n.mem / n.maxmem) * 100)}%`,
|
|
353
|
+
detail: `Node ${n.node} memory utilization is critically high`,
|
|
354
|
+
relatedProbes: ['proxmox.nodes.list'],
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// HA resources in error/fence
|
|
360
|
+
const haData = probeData<{
|
|
361
|
+
resources?: Array<{ sid?: string; state?: string; node?: string }>;
|
|
362
|
+
}>(haStatusR);
|
|
363
|
+
for (const r of haData?.resources ?? []) {
|
|
364
|
+
if (r.state === 'error' || r.state === 'fence') {
|
|
365
|
+
findings.push({
|
|
366
|
+
severity: 'critical',
|
|
367
|
+
title: `HA resource ${r.sid} in ${r.state} state`,
|
|
368
|
+
detail: `HA resource ${r.sid} on node ${r.node ?? 'unknown'} is in "${r.state}" state`,
|
|
369
|
+
remediation: `ha-manager set ${r.sid} --state disabled`,
|
|
370
|
+
relatedProbes: ['proxmox.cluster.ha.status'],
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// Storage > 85% or inactive
|
|
376
|
+
for (const sr of storageResults) {
|
|
377
|
+
if (!sr) continue;
|
|
378
|
+
const storData = probeData<{
|
|
379
|
+
storages?: Array<{
|
|
380
|
+
storage?: string;
|
|
381
|
+
total?: number;
|
|
382
|
+
used?: number;
|
|
383
|
+
active?: boolean;
|
|
384
|
+
enabled?: boolean;
|
|
385
|
+
}>;
|
|
386
|
+
}>(sr.result);
|
|
387
|
+
for (const s of storData?.storages ?? []) {
|
|
388
|
+
if (s.total && s.used && s.total > 0 && s.used / s.total > 0.85) {
|
|
389
|
+
findings.push({
|
|
390
|
+
severity: 'warning',
|
|
391
|
+
title: `Storage ${s.storage} on ${sr.node} at ${Math.round((s.used / s.total) * 100)}%`,
|
|
392
|
+
detail: `Storage pool "${s.storage}" on node ${sr.node} is running low on space`,
|
|
393
|
+
relatedProbes: [`proxmox.node.storage:${sr.node}`],
|
|
394
|
+
});
|
|
395
|
+
}
|
|
396
|
+
if (!s.active && s.enabled !== false) {
|
|
397
|
+
findings.push({
|
|
398
|
+
severity: 'warning',
|
|
399
|
+
title: `Storage ${s.storage} inactive on ${sr.node}`,
|
|
400
|
+
detail: `Storage "${s.storage}" on node ${sr.node} is enabled but not active`,
|
|
401
|
+
relatedProbes: [`proxmox.node.storage:${sr.node}`],
|
|
402
|
+
});
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
// Failed tasks in last 24h
|
|
408
|
+
const tasksData = probeData<{
|
|
409
|
+
tasks?: Array<{
|
|
410
|
+
type?: string;
|
|
411
|
+
status?: string;
|
|
412
|
+
node?: string;
|
|
413
|
+
starttime?: number;
|
|
414
|
+
endtime?: number;
|
|
415
|
+
}>;
|
|
416
|
+
}>(tasksR);
|
|
417
|
+
const now = Date.now() / 1000;
|
|
418
|
+
const oneDayAgo = now - 24 * 60 * 60;
|
|
419
|
+
const recentFailedTasks = (tasksData?.tasks ?? []).filter(
|
|
420
|
+
(t) =>
|
|
421
|
+
t.status &&
|
|
422
|
+
t.status !== 'OK' &&
|
|
423
|
+
t.status !== '' &&
|
|
424
|
+
t.endtime != null &&
|
|
425
|
+
t.starttime != null &&
|
|
426
|
+
t.starttime > oneDayAgo,
|
|
427
|
+
);
|
|
428
|
+
for (const t of recentFailedTasks) {
|
|
429
|
+
findings.push({
|
|
430
|
+
severity: 'warning',
|
|
431
|
+
title: `Failed task: ${t.type} on ${t.node}`,
|
|
432
|
+
detail: `Task ${t.type} on ${t.node} failed: ${t.status}`,
|
|
433
|
+
relatedProbes: ['proxmox.cluster.tasks'],
|
|
434
|
+
});
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
const onlineNodes = nodes.filter((n) => n.status !== 'offline').length;
|
|
438
|
+
const issueCount = findings.length;
|
|
439
|
+
const summaryText = `${onlineNodes} nodes online, ${issueCount} issues found`;
|
|
440
|
+
|
|
441
|
+
return buildResult('proxmox-cluster', findings, probeResults, startTime, summaryText);
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
// --- Storage Audit Runbook ---
|
|
445
|
+
|
|
446
|
+
async function storageAuditHandler(
|
|
447
|
+
_params: Record<string, unknown>,
|
|
448
|
+
runProbe: RunProbe,
|
|
449
|
+
_context: RunbookContext,
|
|
450
|
+
): Promise<DiagnosticRunbookResult> {
|
|
451
|
+
const startTime = Date.now();
|
|
452
|
+
const findings: DiagnosticFinding[] = [];
|
|
453
|
+
const probeResults: Record<string, RunbookProbeResult> = {};
|
|
454
|
+
|
|
455
|
+
// Group 1: get all resources + HA status
|
|
456
|
+
const [resourcesR, haStatusR] = await Promise.all([
|
|
457
|
+
runProbe('proxmox.cluster.resources').then((r) => storeResult(probeResults, r)),
|
|
458
|
+
runProbe('proxmox.cluster.ha.status').then((r) => storeResult(probeResults, r)),
|
|
459
|
+
]);
|
|
460
|
+
|
|
461
|
+
const resourcesData = probeData<{
|
|
462
|
+
resources?: Array<{
|
|
463
|
+
vmid?: number;
|
|
464
|
+
name?: string;
|
|
465
|
+
node?: string;
|
|
466
|
+
type?: string;
|
|
467
|
+
status?: string;
|
|
468
|
+
}>;
|
|
469
|
+
}>(resourcesR);
|
|
470
|
+
const haData = probeData<{
|
|
471
|
+
resources?: Array<{ sid?: string; state?: string }>;
|
|
472
|
+
}>(haStatusR);
|
|
473
|
+
|
|
474
|
+
const allVMs = resourcesData?.resources ?? [];
|
|
475
|
+
const haResources = haData?.resources ?? [];
|
|
476
|
+
|
|
477
|
+
// Extract HA-managed VMIDs from SIDs like "vm:100" or "ct:200"
|
|
478
|
+
const haManagedVmids = new Set<number>();
|
|
479
|
+
for (const r of haResources) {
|
|
480
|
+
if (!r.sid) continue;
|
|
481
|
+
const match = r.sid.match(/^(?:vm|ct):(\d+)$/);
|
|
482
|
+
if (match) haManagedVmids.add(Number(match[1]));
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
if (haManagedVmids.size === 0) {
|
|
486
|
+
findings.push({
|
|
487
|
+
severity: 'info',
|
|
488
|
+
title: 'No HA-managed VMs to audit',
|
|
489
|
+
detail: 'No VMs or containers are configured with HA management',
|
|
490
|
+
relatedProbes: ['proxmox.cluster.ha.status'],
|
|
491
|
+
});
|
|
492
|
+
return buildResult(
|
|
493
|
+
'proxmox-storage',
|
|
494
|
+
findings,
|
|
495
|
+
probeResults,
|
|
496
|
+
startTime,
|
|
497
|
+
'0 HA-managed VMs audited, 0 using local storage',
|
|
498
|
+
);
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
// Get config for each HA-managed VM
|
|
502
|
+
const haVMs = allVMs.filter((vm) => vm.vmid != null && haManagedVmids.has(vm.vmid));
|
|
503
|
+
|
|
504
|
+
// Group 2: get config for each HA VM (parallel)
|
|
505
|
+
const configResults = await Promise.all(
|
|
506
|
+
haVMs.map(async (vm) => {
|
|
507
|
+
const probe = vm.type === 'qemu' ? 'proxmox.vm.config' : 'proxmox.lxc.config';
|
|
508
|
+
const r = await runProbe(probe, { vmid: vm.vmid, node: vm.node });
|
|
509
|
+
storeResult(probeResults, { ...r, probe: `${probe}:${vm.vmid}` });
|
|
510
|
+
return { vm, result: r };
|
|
511
|
+
}),
|
|
512
|
+
);
|
|
513
|
+
|
|
514
|
+
// Group 3: get storage for each unique node (parallel)
|
|
515
|
+
const uniqueNodes = [...new Set(haVMs.map((vm) => vm.node).filter(Boolean))] as string[];
|
|
516
|
+
const nodeStorageMap = new Map<
|
|
517
|
+
string,
|
|
518
|
+
Array<{ storage?: string; shared?: boolean; active?: boolean }>
|
|
519
|
+
>();
|
|
520
|
+
|
|
521
|
+
await Promise.all(
|
|
522
|
+
uniqueNodes.map(async (nodeName) => {
|
|
523
|
+
const r = await runProbe('proxmox.node.storage', { node: nodeName });
|
|
524
|
+
storeResult(probeResults, { ...r, probe: `proxmox.node.storage:${nodeName}` });
|
|
525
|
+
const storData = probeData<{
|
|
526
|
+
storages?: Array<{ storage?: string; shared?: boolean; active?: boolean }>;
|
|
527
|
+
}>(r);
|
|
528
|
+
nodeStorageMap.set(nodeName, storData?.storages ?? []);
|
|
529
|
+
}),
|
|
530
|
+
);
|
|
531
|
+
|
|
532
|
+
// Cross-reference: check each HA VM's disk storage against shared flag
|
|
533
|
+
let localCount = 0;
|
|
534
|
+
for (const { vm, result } of configResults) {
|
|
535
|
+
const configData = probeData<{
|
|
536
|
+
disks?: Array<{ key: string; storage: string }>;
|
|
537
|
+
rootfs?: { storage: string };
|
|
538
|
+
mountpoints?: Array<{ key: string; storage: string }>;
|
|
539
|
+
}>(result);
|
|
540
|
+
if (!configData) continue;
|
|
541
|
+
|
|
542
|
+
const nodeStorages = nodeStorageMap.get(vm.node ?? '') ?? [];
|
|
543
|
+
|
|
544
|
+
// Collect disk entries
|
|
545
|
+
const diskEntries: Array<{ key: string; storage: string }> = [];
|
|
546
|
+
if (vm.type === 'qemu' && configData.disks) {
|
|
547
|
+
for (const d of configData.disks) diskEntries.push(d);
|
|
548
|
+
} else if (vm.type === 'lxc') {
|
|
549
|
+
if (configData.rootfs)
|
|
550
|
+
diskEntries.push({ key: 'rootfs', storage: configData.rootfs.storage });
|
|
551
|
+
if (configData.mountpoints) {
|
|
552
|
+
for (const mp of configData.mountpoints) diskEntries.push(mp);
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
for (const disk of diskEntries) {
|
|
557
|
+
const stor = nodeStorages.find((s) => s.storage === disk.storage);
|
|
558
|
+
if (stor && !stor.shared) {
|
|
559
|
+
localCount++;
|
|
560
|
+
const sharedStor = nodeStorages.find((s) => s.shared && s.active);
|
|
561
|
+
const moveCmd =
|
|
562
|
+
vm.type === 'qemu'
|
|
563
|
+
? `qm move-disk ${vm.vmid} ${disk.key} ${sharedStor?.storage ?? '<shared-storage>'} --delete`
|
|
564
|
+
: `pct move-volume ${vm.vmid} ${disk.key} ${sharedStor?.storage ?? '<shared-storage>'}`;
|
|
565
|
+
findings.push({
|
|
566
|
+
severity: 'critical',
|
|
567
|
+
title: `VM ${vm.vmid} (${vm.name ?? 'unknown'}) uses local storage ${disk.storage} for ${disk.key}`,
|
|
568
|
+
detail: `HA-managed VM ${vm.vmid} has disk "${disk.key}" on local storage "${disk.storage}". HA failover will fail for this disk.`,
|
|
569
|
+
remediation: moveCmd,
|
|
570
|
+
relatedProbes: [
|
|
571
|
+
'proxmox.cluster.ha.status',
|
|
572
|
+
vm.type === 'qemu' ? `proxmox.vm.config:${vm.vmid}` : `proxmox.lxc.config:${vm.vmid}`,
|
|
573
|
+
`proxmox.node.storage:${vm.node}`,
|
|
574
|
+
],
|
|
575
|
+
});
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
if (findings.length === 0) {
|
|
581
|
+
findings.push({
|
|
582
|
+
severity: 'info',
|
|
583
|
+
title: `All ${haManagedVmids.size} HA-managed VMs use shared storage`,
|
|
584
|
+
detail: 'No local storage risks detected for HA-managed VMs',
|
|
585
|
+
relatedProbes: ['proxmox.cluster.ha.status', 'proxmox.cluster.resources'],
|
|
586
|
+
});
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
return buildResult(
|
|
590
|
+
'proxmox-storage',
|
|
591
|
+
findings,
|
|
592
|
+
probeResults,
|
|
593
|
+
startTime,
|
|
594
|
+
`${haManagedVmids.size} HA-managed VMs audited, ${localCount} using local storage`,
|
|
595
|
+
);
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
// --- Export ---
|
|
599
|
+
|
|
600
|
+
export const proxmoxDiagnosticRunbooks: DiagnosticRunbookDefinition[] = [
|
|
601
|
+
{
|
|
602
|
+
category: 'proxmox-vm',
|
|
603
|
+
description: 'Check the health of a specific VM or container',
|
|
604
|
+
params: {
|
|
605
|
+
vmid: { type: 'number', description: 'VM or container ID', required: true },
|
|
606
|
+
},
|
|
607
|
+
handler: vmHealthHandler,
|
|
608
|
+
},
|
|
609
|
+
{
|
|
610
|
+
category: 'proxmox-cluster',
|
|
611
|
+
description: 'Fleet-wide Proxmox cluster health overview',
|
|
612
|
+
params: {
|
|
613
|
+
limit: {
|
|
614
|
+
type: 'number',
|
|
615
|
+
description: 'Max recent tasks to check (default: 20)',
|
|
616
|
+
required: false,
|
|
617
|
+
},
|
|
618
|
+
},
|
|
619
|
+
handler: clusterHealthHandler,
|
|
620
|
+
},
|
|
621
|
+
{
|
|
622
|
+
category: 'proxmox-storage',
|
|
623
|
+
description: 'Audit which VMs have local-only disks that would break HA',
|
|
624
|
+
handler: storageAuditHandler,
|
|
625
|
+
},
|
|
626
|
+
];
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const PACK_SIGNATURES: Record<string, string> = {};
|
package/src/types.ts
CHANGED
|
@@ -14,3 +14,65 @@ export interface Pack {
|
|
|
14
14
|
manifest: PackManifest;
|
|
15
15
|
handlers: Record<string, ProbeHandler>;
|
|
16
16
|
}
|
|
17
|
+
|
|
18
|
+
// --- Diagnostic runbook types ---
|
|
19
|
+
|
|
20
|
+
/** Structured finding from diagnostic runbook analysis */
|
|
21
|
+
export interface DiagnosticFinding {
|
|
22
|
+
severity: 'info' | 'warning' | 'critical';
|
|
23
|
+
title: string;
|
|
24
|
+
detail: string;
|
|
25
|
+
remediation?: string;
|
|
26
|
+
relatedProbes: string[];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Result from a single probe within a diagnostic runbook */
|
|
30
|
+
export interface RunbookProbeResult {
|
|
31
|
+
probe: string;
|
|
32
|
+
status: 'success' | 'error' | 'timeout';
|
|
33
|
+
data?: unknown;
|
|
34
|
+
durationMs: number;
|
|
35
|
+
error?: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** Function to execute a probe — injected, mockable in tests */
|
|
39
|
+
export type RunProbe = (
|
|
40
|
+
probe: string,
|
|
41
|
+
params?: Record<string, unknown>,
|
|
42
|
+
agent?: string,
|
|
43
|
+
) => Promise<RunbookProbeResult>;
|
|
44
|
+
|
|
45
|
+
/** Context available to diagnostic runbook handlers */
|
|
46
|
+
export interface RunbookContext {
|
|
47
|
+
connectedAgents: string[];
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Result of a diagnostic runbook execution */
|
|
51
|
+
export interface DiagnosticRunbookResult {
|
|
52
|
+
category: string;
|
|
53
|
+
findings: DiagnosticFinding[];
|
|
54
|
+
probeResults: Record<string, RunbookProbeResult>;
|
|
55
|
+
summary: {
|
|
56
|
+
probesRun: number;
|
|
57
|
+
probesSucceeded: number;
|
|
58
|
+
probesFailed: number;
|
|
59
|
+
findingsCount: { info: number; warning: number; critical: number };
|
|
60
|
+
durationMs: number;
|
|
61
|
+
summaryText: string;
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Handler function for a diagnostic runbook */
|
|
66
|
+
export type DiagnosticRunbookHandler = (
|
|
67
|
+
params: Record<string, unknown>,
|
|
68
|
+
runProbe: RunProbe,
|
|
69
|
+
context: RunbookContext,
|
|
70
|
+
) => Promise<DiagnosticRunbookResult>;
|
|
71
|
+
|
|
72
|
+
/** Registered diagnostic runbook definition */
|
|
73
|
+
export interface DiagnosticRunbookDefinition {
|
|
74
|
+
category: string;
|
|
75
|
+
description: string;
|
|
76
|
+
params?: Record<string, { type: string; description: string; required?: boolean }>;
|
|
77
|
+
handler: DiagnosticRunbookHandler;
|
|
78
|
+
}
|