pvectl 0.2.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.
Files changed (558) hide show
  1. checksums.yaml +7 -0
  2. data/.claude/rules/branch-before-changes.md +52 -0
  3. data/.claude/rules/documentation-updates.md +104 -0
  4. data/.claude/rules/git-workflow.md +84 -0
  5. data/.claude/rules/proxmox-api-docs.md +58 -0
  6. data/.claude/rules/rbs-signatures.md +80 -0
  7. data/.claude/rules/refactoring-as-design-option.md +35 -0
  8. data/.claude/scheduled_tasks.lock +1 -0
  9. data/.claude/settings.json +51 -0
  10. data/.mcp.json +8 -0
  11. data/.ruby-gemset +1 -0
  12. data/.ruby-version +1 -0
  13. data/CHANGELOG.md +138 -0
  14. data/CLAUDE.md +211 -0
  15. data/CODE_OF_CONDUCT.md +132 -0
  16. data/LICENSE.txt +21 -0
  17. data/README.md +143 -0
  18. data/Rakefile +8 -0
  19. data/docs/proxmox-api-update.sh +96 -0
  20. data/exe/pvectl +5 -0
  21. data/lib/pvectl/argv_preprocessor.rb +334 -0
  22. data/lib/pvectl/cli.rb +102 -0
  23. data/lib/pvectl/commands/apt.rb +389 -0
  24. data/lib/pvectl/commands/clone_container.rb +230 -0
  25. data/lib/pvectl/commands/clone_vm.rb +331 -0
  26. data/lib/pvectl/commands/cloudinit/command.rb +122 -0
  27. data/lib/pvectl/commands/cloudinit/dump.rb +94 -0
  28. data/lib/pvectl/commands/cloudinit/pending.rb +137 -0
  29. data/lib/pvectl/commands/cloudinit/regenerate.rb +79 -0
  30. data/lib/pvectl/commands/config/command.rb +65 -0
  31. data/lib/pvectl/commands/config/get_contexts.rb +68 -0
  32. data/lib/pvectl/commands/config/set_cluster.rb +103 -0
  33. data/lib/pvectl/commands/config/set_context.rb +136 -0
  34. data/lib/pvectl/commands/config/set_credentials.rb +181 -0
  35. data/lib/pvectl/commands/config/use_context.rb +69 -0
  36. data/lib/pvectl/commands/config/view.rb +67 -0
  37. data/lib/pvectl/commands/console.rb +93 -0
  38. data/lib/pvectl/commands/console_ct.rb +187 -0
  39. data/lib/pvectl/commands/console_vm.rb +187 -0
  40. data/lib/pvectl/commands/container_lifecycle_command.rb +77 -0
  41. data/lib/pvectl/commands/create_backup.rb +173 -0
  42. data/lib/pvectl/commands/create_container.rb +141 -0
  43. data/lib/pvectl/commands/create_resource_command.rb +244 -0
  44. data/lib/pvectl/commands/create_snapshot.rb +242 -0
  45. data/lib/pvectl/commands/create_vm.rb +267 -0
  46. data/lib/pvectl/commands/delete_backup.rb +139 -0
  47. data/lib/pvectl/commands/delete_command.rb +119 -0
  48. data/lib/pvectl/commands/delete_container.rb +30 -0
  49. data/lib/pvectl/commands/delete_snapshot.rb +248 -0
  50. data/lib/pvectl/commands/delete_vm.rb +127 -0
  51. data/lib/pvectl/commands/describe/command.rb +251 -0
  52. data/lib/pvectl/commands/edit_container.rb +56 -0
  53. data/lib/pvectl/commands/edit_dns.rb +149 -0
  54. data/lib/pvectl/commands/edit_hosts.rb +135 -0
  55. data/lib/pvectl/commands/edit_node.rb +54 -0
  56. data/lib/pvectl/commands/edit_resource_command.rb +180 -0
  57. data/lib/pvectl/commands/edit_vm.rb +154 -0
  58. data/lib/pvectl/commands/edit_volume.rb +189 -0
  59. data/lib/pvectl/commands/feature_command.rb +230 -0
  60. data/lib/pvectl/commands/feature_container.rb +21 -0
  61. data/lib/pvectl/commands/feature_vm.rb +94 -0
  62. data/lib/pvectl/commands/get/command.rb +360 -0
  63. data/lib/pvectl/commands/get/handlers/backups.rb +76 -0
  64. data/lib/pvectl/commands/get/handlers/capabilities.rb +107 -0
  65. data/lib/pvectl/commands/get/handlers/containers.rb +148 -0
  66. data/lib/pvectl/commands/get/handlers/disks.rb +107 -0
  67. data/lib/pvectl/commands/get/handlers/dns.rb +94 -0
  68. data/lib/pvectl/commands/get/handlers/hosts.rb +94 -0
  69. data/lib/pvectl/commands/get/handlers/nodes.rb +162 -0
  70. data/lib/pvectl/commands/get/handlers/services.rb +81 -0
  71. data/lib/pvectl/commands/get/handlers/snapshots.rb +97 -0
  72. data/lib/pvectl/commands/get/handlers/storage.rb +118 -0
  73. data/lib/pvectl/commands/get/handlers/subscription.rb +69 -0
  74. data/lib/pvectl/commands/get/handlers/tasks.rb +89 -0
  75. data/lib/pvectl/commands/get/handlers/templates.rb +175 -0
  76. data/lib/pvectl/commands/get/handlers/time.rb +118 -0
  77. data/lib/pvectl/commands/get/handlers/vms.rb +145 -0
  78. data/lib/pvectl/commands/get/handlers/volume.rb +134 -0
  79. data/lib/pvectl/commands/get/resource_handler.rb +63 -0
  80. data/lib/pvectl/commands/get/resource_registry.rb +18 -0
  81. data/lib/pvectl/commands/get/watch_loop.rb +129 -0
  82. data/lib/pvectl/commands/irreversible_command.rb +265 -0
  83. data/lib/pvectl/commands/logs/command.rb +275 -0
  84. data/lib/pvectl/commands/logs/handlers/journal.rb +46 -0
  85. data/lib/pvectl/commands/logs/handlers/syslog.rb +53 -0
  86. data/lib/pvectl/commands/logs/handlers/task_detail.rb +52 -0
  87. data/lib/pvectl/commands/logs/handlers/task_logs.rb +115 -0
  88. data/lib/pvectl/commands/logs/resource_handler.rb +46 -0
  89. data/lib/pvectl/commands/logs/resource_registry.rb +22 -0
  90. data/lib/pvectl/commands/migrate_command.rb +282 -0
  91. data/lib/pvectl/commands/migrate_container.rb +23 -0
  92. data/lib/pvectl/commands/migrate_vm.rb +122 -0
  93. data/lib/pvectl/commands/move_disk_command.rb +239 -0
  94. data/lib/pvectl/commands/move_disk_container.rb +21 -0
  95. data/lib/pvectl/commands/move_disk_vm.rb +127 -0
  96. data/lib/pvectl/commands/ping.rb +249 -0
  97. data/lib/pvectl/commands/pull.rb +342 -0
  98. data/lib/pvectl/commands/push.rb +352 -0
  99. data/lib/pvectl/commands/reset.rb +64 -0
  100. data/lib/pvectl/commands/resource_lifecycle_command.rb +277 -0
  101. data/lib/pvectl/commands/resource_registry.rb +73 -0
  102. data/lib/pvectl/commands/restart.rb +70 -0
  103. data/lib/pvectl/commands/restart_container.rb +18 -0
  104. data/lib/pvectl/commands/restore_backup.rb +236 -0
  105. data/lib/pvectl/commands/resume.rb +57 -0
  106. data/lib/pvectl/commands/rollback_snapshot.rb +228 -0
  107. data/lib/pvectl/commands/sendkey_vm.rb +205 -0
  108. data/lib/pvectl/commands/service.rb +293 -0
  109. data/lib/pvectl/commands/set_container.rb +50 -0
  110. data/lib/pvectl/commands/set_node.rb +52 -0
  111. data/lib/pvectl/commands/set_resource_command.rb +185 -0
  112. data/lib/pvectl/commands/set_vm.rb +136 -0
  113. data/lib/pvectl/commands/set_volume.rb +212 -0
  114. data/lib/pvectl/commands/shared_config_parsers.rb +126 -0
  115. data/lib/pvectl/commands/shared_flags.rb +155 -0
  116. data/lib/pvectl/commands/shutdown.rb +73 -0
  117. data/lib/pvectl/commands/shutdown_container.rb +18 -0
  118. data/lib/pvectl/commands/start.rb +79 -0
  119. data/lib/pvectl/commands/start_container.rb +18 -0
  120. data/lib/pvectl/commands/stop.rb +75 -0
  121. data/lib/pvectl/commands/stop_container.rb +18 -0
  122. data/lib/pvectl/commands/suspend.rb +64 -0
  123. data/lib/pvectl/commands/template_command.rb +205 -0
  124. data/lib/pvectl/commands/template_container.rb +27 -0
  125. data/lib/pvectl/commands/template_vm.rb +106 -0
  126. data/lib/pvectl/commands/top/command.rb +206 -0
  127. data/lib/pvectl/commands/top/handlers/containers.rb +61 -0
  128. data/lib/pvectl/commands/top/handlers/nodes.rb +61 -0
  129. data/lib/pvectl/commands/top/handlers/vms.rb +61 -0
  130. data/lib/pvectl/commands/top/resource_handler.rb +46 -0
  131. data/lib/pvectl/commands/top/resource_registry.rb +22 -0
  132. data/lib/pvectl/commands/unlink_disk_vm.rb +232 -0
  133. data/lib/pvectl/commands/vm_lifecycle_command.rb +77 -0
  134. data/lib/pvectl/commands/wakeonlan_node.rb +153 -0
  135. data/lib/pvectl/config/errors.rb +62 -0
  136. data/lib/pvectl/config/models/cluster.rb +180 -0
  137. data/lib/pvectl/config/models/context.rb +100 -0
  138. data/lib/pvectl/config/models/resolved_config.rb +171 -0
  139. data/lib/pvectl/config/models/user.rb +133 -0
  140. data/lib/pvectl/config/provider.rb +297 -0
  141. data/lib/pvectl/config/service.rb +300 -0
  142. data/lib/pvectl/config/store.rb +161 -0
  143. data/lib/pvectl/config/wizard.rb +309 -0
  144. data/lib/pvectl/config_serializer.rb +1034 -0
  145. data/lib/pvectl/connection/retry_handler.rb +161 -0
  146. data/lib/pvectl/connection.rb +157 -0
  147. data/lib/pvectl/console/terminal_session.rb +449 -0
  148. data/lib/pvectl/editor_session.rb +157 -0
  149. data/lib/pvectl/exit_codes.rb +43 -0
  150. data/lib/pvectl/formatters/base.rb +55 -0
  151. data/lib/pvectl/formatters/color_support.rb +90 -0
  152. data/lib/pvectl/formatters/json.rb +45 -0
  153. data/lib/pvectl/formatters/output_helper.rb +77 -0
  154. data/lib/pvectl/formatters/registry.rb +72 -0
  155. data/lib/pvectl/formatters/table.rb +235 -0
  156. data/lib/pvectl/formatters/wide.rb +93 -0
  157. data/lib/pvectl/formatters/yaml.rb +49 -0
  158. data/lib/pvectl/manifest_serializer.rb +142 -0
  159. data/lib/pvectl/models/apt_package.rb +107 -0
  160. data/lib/pvectl/models/backup.rb +173 -0
  161. data/lib/pvectl/models/base.rb +49 -0
  162. data/lib/pvectl/models/capability.rb +62 -0
  163. data/lib/pvectl/models/container.rb +205 -0
  164. data/lib/pvectl/models/container_operation_result.rb +27 -0
  165. data/lib/pvectl/models/dns_config.rb +54 -0
  166. data/lib/pvectl/models/hosts_file.rb +47 -0
  167. data/lib/pvectl/models/journal_entry.rb +16 -0
  168. data/lib/pvectl/models/network_interface.rb +85 -0
  169. data/lib/pvectl/models/node.rb +195 -0
  170. data/lib/pvectl/models/node_operation_result.rb +45 -0
  171. data/lib/pvectl/models/operation_result.rb +110 -0
  172. data/lib/pvectl/models/physical_disk.rb +193 -0
  173. data/lib/pvectl/models/service.rb +80 -0
  174. data/lib/pvectl/models/snapshot.rb +101 -0
  175. data/lib/pvectl/models/snapshot_description.rb +39 -0
  176. data/lib/pvectl/models/storage.rb +180 -0
  177. data/lib/pvectl/models/subscription.rb +87 -0
  178. data/lib/pvectl/models/syslog_entry.rb +17 -0
  179. data/lib/pvectl/models/task.rb +95 -0
  180. data/lib/pvectl/models/task_entry.rb +52 -0
  181. data/lib/pvectl/models/task_log_line.rb +17 -0
  182. data/lib/pvectl/models/time_config.rb +47 -0
  183. data/lib/pvectl/models/vm.rb +137 -0
  184. data/lib/pvectl/models/vm_operation_result.rb +27 -0
  185. data/lib/pvectl/models/volume.rb +133 -0
  186. data/lib/pvectl/models/volume_operation_result.rb +26 -0
  187. data/lib/pvectl/parsers/cloud_init_config.rb +92 -0
  188. data/lib/pvectl/parsers/disk_config.rb +97 -0
  189. data/lib/pvectl/parsers/lxc_mount_config.rb +98 -0
  190. data/lib/pvectl/parsers/lxc_net_config.rb +97 -0
  191. data/lib/pvectl/parsers/net_config.rb +95 -0
  192. data/lib/pvectl/parsers/smart_text.rb +42 -0
  193. data/lib/pvectl/plugin_loader.rb +157 -0
  194. data/lib/pvectl/presenters/apt_package.rb +99 -0
  195. data/lib/pvectl/presenters/backup.rb +128 -0
  196. data/lib/pvectl/presenters/base.rb +283 -0
  197. data/lib/pvectl/presenters/capability.rb +104 -0
  198. data/lib/pvectl/presenters/config/context.rb +80 -0
  199. data/lib/pvectl/presenters/container.rb +574 -0
  200. data/lib/pvectl/presenters/container_operation_result.rb +109 -0
  201. data/lib/pvectl/presenters/disk.rb +184 -0
  202. data/lib/pvectl/presenters/dns_config.rb +68 -0
  203. data/lib/pvectl/presenters/hosts_file.rb +61 -0
  204. data/lib/pvectl/presenters/journal_entry.rb +20 -0
  205. data/lib/pvectl/presenters/node.rb +762 -0
  206. data/lib/pvectl/presenters/node_operation_result.rb +50 -0
  207. data/lib/pvectl/presenters/operation_result.rb +61 -0
  208. data/lib/pvectl/presenters/service.rb +76 -0
  209. data/lib/pvectl/presenters/snapshot.rb +239 -0
  210. data/lib/pvectl/presenters/snapshot_operation_result.rb +125 -0
  211. data/lib/pvectl/presenters/storage.rb +329 -0
  212. data/lib/pvectl/presenters/subscription.rb +189 -0
  213. data/lib/pvectl/presenters/syslog_entry.rb +20 -0
  214. data/lib/pvectl/presenters/task_entry.rb +69 -0
  215. data/lib/pvectl/presenters/task_log_line.rb +20 -0
  216. data/lib/pvectl/presenters/template.rb +76 -0
  217. data/lib/pvectl/presenters/time_config.rb +86 -0
  218. data/lib/pvectl/presenters/top_container.rb +112 -0
  219. data/lib/pvectl/presenters/top_node.rb +115 -0
  220. data/lib/pvectl/presenters/top_presenter.rb +59 -0
  221. data/lib/pvectl/presenters/top_vm.rb +105 -0
  222. data/lib/pvectl/presenters/vm.rb +853 -0
  223. data/lib/pvectl/presenters/vm_operation_result.rb +109 -0
  224. data/lib/pvectl/presenters/volume.rb +136 -0
  225. data/lib/pvectl/presenters/volume_operation_result.rb +58 -0
  226. data/lib/pvectl/repositories/apt.rb +93 -0
  227. data/lib/pvectl/repositories/backup.rb +186 -0
  228. data/lib/pvectl/repositories/base.rb +110 -0
  229. data/lib/pvectl/repositories/capabilities.rb +96 -0
  230. data/lib/pvectl/repositories/container.rb +503 -0
  231. data/lib/pvectl/repositories/disk.rb +87 -0
  232. data/lib/pvectl/repositories/dns.rb +54 -0
  233. data/lib/pvectl/repositories/hosts.rb +63 -0
  234. data/lib/pvectl/repositories/journal.rb +23 -0
  235. data/lib/pvectl/repositories/node.rb +537 -0
  236. data/lib/pvectl/repositories/service.rb +139 -0
  237. data/lib/pvectl/repositories/snapshot.rb +133 -0
  238. data/lib/pvectl/repositories/storage.rb +302 -0
  239. data/lib/pvectl/repositories/subscription.rb +77 -0
  240. data/lib/pvectl/repositories/syslog.rb +25 -0
  241. data/lib/pvectl/repositories/task.rb +82 -0
  242. data/lib/pvectl/repositories/task_list.rb +30 -0
  243. data/lib/pvectl/repositories/task_log.rb +31 -0
  244. data/lib/pvectl/repositories/time_config.rb +53 -0
  245. data/lib/pvectl/repositories/vm.rb +616 -0
  246. data/lib/pvectl/repositories/volume.rb +306 -0
  247. data/lib/pvectl/selectors/base.rb +201 -0
  248. data/lib/pvectl/selectors/container.rb +116 -0
  249. data/lib/pvectl/selectors/disk.rb +59 -0
  250. data/lib/pvectl/selectors/vm.rb +116 -0
  251. data/lib/pvectl/selectors/volume.rb +59 -0
  252. data/lib/pvectl/services/backup.rb +209 -0
  253. data/lib/pvectl/services/clone_container.rb +260 -0
  254. data/lib/pvectl/services/clone_vm.rb +265 -0
  255. data/lib/pvectl/services/cloudinit.rb +96 -0
  256. data/lib/pvectl/services/console.rb +152 -0
  257. data/lib/pvectl/services/container_lifecycle.rb +124 -0
  258. data/lib/pvectl/services/create_container.rb +179 -0
  259. data/lib/pvectl/services/create_vm.rb +191 -0
  260. data/lib/pvectl/services/edit_container.rb +125 -0
  261. data/lib/pvectl/services/edit_dns.rb +159 -0
  262. data/lib/pvectl/services/edit_hosts.rb +78 -0
  263. data/lib/pvectl/services/edit_node.rb +147 -0
  264. data/lib/pvectl/services/edit_vm.rb +125 -0
  265. data/lib/pvectl/services/edit_volume.rb +224 -0
  266. data/lib/pvectl/services/get/resource_service.rb +98 -0
  267. data/lib/pvectl/services/move_disk.rb +132 -0
  268. data/lib/pvectl/services/pull_config.rb +94 -0
  269. data/lib/pvectl/services/push_config.rb +524 -0
  270. data/lib/pvectl/services/resize_volume.rb +253 -0
  271. data/lib/pvectl/services/resource_delete.rb +169 -0
  272. data/lib/pvectl/services/resource_migration.rb +170 -0
  273. data/lib/pvectl/services/sendkey.rb +108 -0
  274. data/lib/pvectl/services/service_lifecycle.rb +89 -0
  275. data/lib/pvectl/services/set_container.rb +128 -0
  276. data/lib/pvectl/services/set_node.rb +236 -0
  277. data/lib/pvectl/services/set_vm.rb +128 -0
  278. data/lib/pvectl/services/set_volume.rb +126 -0
  279. data/lib/pvectl/services/snapshot.rb +261 -0
  280. data/lib/pvectl/services/task_listing.rb +75 -0
  281. data/lib/pvectl/services/unlink_disk.rb +86 -0
  282. data/lib/pvectl/services/vm_lifecycle.rb +124 -0
  283. data/lib/pvectl/services/wakeonlan.rb +79 -0
  284. data/lib/pvectl/utils/resource_resolver.rb +80 -0
  285. data/lib/pvectl/version.rb +13 -0
  286. data/lib/pvectl/wizards/create_container.rb +105 -0
  287. data/lib/pvectl/wizards/create_vm.rb +98 -0
  288. data/lib/pvectl.rb +439 -0
  289. data/sig/external/gli.rbs +16 -0
  290. data/sig/external/proxmox_api.rbs +10 -0
  291. data/sig/pvectl/argv_preprocessor.rbs +53 -0
  292. data/sig/pvectl/cli.rbs +26 -0
  293. data/sig/pvectl/commands/apt.rbs +47 -0
  294. data/sig/pvectl/commands/clone_container.rbs +31 -0
  295. data/sig/pvectl/commands/clone_vm.rbs +33 -0
  296. data/sig/pvectl/commands/cloudinit/command.rbs +13 -0
  297. data/sig/pvectl/commands/cloudinit/dump.rbs +13 -0
  298. data/sig/pvectl/commands/cloudinit/pending.rbs +17 -0
  299. data/sig/pvectl/commands/cloudinit/regenerate.rbs +11 -0
  300. data/sig/pvectl/commands/config/command.rbs +9 -0
  301. data/sig/pvectl/commands/config/get_contexts.rbs +11 -0
  302. data/sig/pvectl/commands/config/set_cluster.rbs +11 -0
  303. data/sig/pvectl/commands/config/set_context.rbs +15 -0
  304. data/sig/pvectl/commands/config/set_credentials.rbs +15 -0
  305. data/sig/pvectl/commands/config/use_context.rbs +11 -0
  306. data/sig/pvectl/commands/config/view.rbs +11 -0
  307. data/sig/pvectl/commands/console.rbs +9 -0
  308. data/sig/pvectl/commands/console_ct.rbs +27 -0
  309. data/sig/pvectl/commands/console_vm.rbs +27 -0
  310. data/sig/pvectl/commands/container_lifecycle_command.rbs +25 -0
  311. data/sig/pvectl/commands/create_backup.rbs +29 -0
  312. data/sig/pvectl/commands/create_container.rbs +30 -0
  313. data/sig/pvectl/commands/create_resource_command.rbs +53 -0
  314. data/sig/pvectl/commands/create_snapshot.rbs +35 -0
  315. data/sig/pvectl/commands/create_vm.rbs +30 -0
  316. data/sig/pvectl/commands/delete_backup.rbs +25 -0
  317. data/sig/pvectl/commands/delete_command.rbs +45 -0
  318. data/sig/pvectl/commands/delete_container.rbs +11 -0
  319. data/sig/pvectl/commands/delete_snapshot.rbs +35 -0
  320. data/sig/pvectl/commands/delete_vm.rbs +13 -0
  321. data/sig/pvectl/commands/describe/command.rbs +27 -0
  322. data/sig/pvectl/commands/edit_container.rbs +17 -0
  323. data/sig/pvectl/commands/edit_dns.rbs +25 -0
  324. data/sig/pvectl/commands/edit_hosts.rbs +23 -0
  325. data/sig/pvectl/commands/edit_node.rbs +17 -0
  326. data/sig/pvectl/commands/edit_resource_command.rbs +35 -0
  327. data/sig/pvectl/commands/edit_vm.rbs +19 -0
  328. data/sig/pvectl/commands/edit_volume.rbs +24 -0
  329. data/sig/pvectl/commands/feature_command.rbs +43 -0
  330. data/sig/pvectl/commands/feature_container.rbs +10 -0
  331. data/sig/pvectl/commands/feature_vm.rbs +12 -0
  332. data/sig/pvectl/commands/get/command.rbs +42 -0
  333. data/sig/pvectl/commands/get/handlers/backups.rbs +23 -0
  334. data/sig/pvectl/commands/get/handlers/capabilities.rbs +29 -0
  335. data/sig/pvectl/commands/get/handlers/containers.rbs +35 -0
  336. data/sig/pvectl/commands/get/handlers/disks.rbs +27 -0
  337. data/sig/pvectl/commands/get/handlers/dns.rbs +25 -0
  338. data/sig/pvectl/commands/get/handlers/hosts.rbs +25 -0
  339. data/sig/pvectl/commands/get/handlers/nodes.rbs +33 -0
  340. data/sig/pvectl/commands/get/handlers/services.rbs +23 -0
  341. data/sig/pvectl/commands/get/handlers/snapshots.rbs +27 -0
  342. data/sig/pvectl/commands/get/handlers/storage.rbs +25 -0
  343. data/sig/pvectl/commands/get/handlers/subscription.rbs +25 -0
  344. data/sig/pvectl/commands/get/handlers/tasks.rbs +28 -0
  345. data/sig/pvectl/commands/get/handlers/templates.rbs +35 -0
  346. data/sig/pvectl/commands/get/handlers/time.rbs +29 -0
  347. data/sig/pvectl/commands/get/handlers/vms.rbs +35 -0
  348. data/sig/pvectl/commands/get/handlers/volume.rbs +27 -0
  349. data/sig/pvectl/commands/get/resource_handler.rbs +13 -0
  350. data/sig/pvectl/commands/get/resource_registry.rbs +8 -0
  351. data/sig/pvectl/commands/get/watch_loop.rbs +33 -0
  352. data/sig/pvectl/commands/irreversible_command.rbs +32 -0
  353. data/sig/pvectl/commands/logs/command.rbs +35 -0
  354. data/sig/pvectl/commands/logs/handlers/journal.rbs +21 -0
  355. data/sig/pvectl/commands/logs/handlers/syslog.rbs +21 -0
  356. data/sig/pvectl/commands/logs/handlers/task_detail.rbs +21 -0
  357. data/sig/pvectl/commands/logs/handlers/task_logs.rbs +35 -0
  358. data/sig/pvectl/commands/logs/resource_handler.rbs +11 -0
  359. data/sig/pvectl/commands/logs/resource_registry.rbs +8 -0
  360. data/sig/pvectl/commands/migrate_command.rbs +45 -0
  361. data/sig/pvectl/commands/migrate_container.rbs +11 -0
  362. data/sig/pvectl/commands/migrate_vm.rbs +13 -0
  363. data/sig/pvectl/commands/move_disk_command.rbs +43 -0
  364. data/sig/pvectl/commands/move_disk_container.rbs +11 -0
  365. data/sig/pvectl/commands/move_disk_vm.rbs +13 -0
  366. data/sig/pvectl/commands/ping.rbs +39 -0
  367. data/sig/pvectl/commands/pull.rbs +33 -0
  368. data/sig/pvectl/commands/push.rbs +32 -0
  369. data/sig/pvectl/commands/reset.rbs +11 -0
  370. data/sig/pvectl/commands/resource_lifecycle_command.rbs +55 -0
  371. data/sig/pvectl/commands/resource_registry.rbs +19 -0
  372. data/sig/pvectl/commands/restart.rbs +11 -0
  373. data/sig/pvectl/commands/restart_container.rbs +9 -0
  374. data/sig/pvectl/commands/restore_backup.rbs +27 -0
  375. data/sig/pvectl/commands/resume.rbs +11 -0
  376. data/sig/pvectl/commands/rollback_snapshot.rbs +31 -0
  377. data/sig/pvectl/commands/sendkey_vm.rbs +25 -0
  378. data/sig/pvectl/commands/service.rbs +38 -0
  379. data/sig/pvectl/commands/set_container.rbs +13 -0
  380. data/sig/pvectl/commands/set_node.rbs +13 -0
  381. data/sig/pvectl/commands/set_resource_command.rbs +25 -0
  382. data/sig/pvectl/commands/set_vm.rbs +15 -0
  383. data/sig/pvectl/commands/set_volume.rbs +24 -0
  384. data/sig/pvectl/commands/shared_config_parsers.rbs +19 -0
  385. data/sig/pvectl/commands/shared_flags.rbs +10 -0
  386. data/sig/pvectl/commands/shutdown.rbs +11 -0
  387. data/sig/pvectl/commands/shutdown_container.rbs +9 -0
  388. data/sig/pvectl/commands/start.rbs +11 -0
  389. data/sig/pvectl/commands/start_container.rbs +9 -0
  390. data/sig/pvectl/commands/stop.rbs +11 -0
  391. data/sig/pvectl/commands/stop_container.rbs +9 -0
  392. data/sig/pvectl/commands/suspend.rbs +11 -0
  393. data/sig/pvectl/commands/template_command.rbs +21 -0
  394. data/sig/pvectl/commands/template_container.rbs +10 -0
  395. data/sig/pvectl/commands/template_vm.rbs +12 -0
  396. data/sig/pvectl/commands/top/command.rbs +31 -0
  397. data/sig/pvectl/commands/top/handlers/containers.rbs +21 -0
  398. data/sig/pvectl/commands/top/handlers/nodes.rbs +21 -0
  399. data/sig/pvectl/commands/top/handlers/vms.rbs +21 -0
  400. data/sig/pvectl/commands/top/resource_handler.rbs +11 -0
  401. data/sig/pvectl/commands/top/resource_registry.rbs +8 -0
  402. data/sig/pvectl/commands/unlink_disk_vm.rbs +27 -0
  403. data/sig/pvectl/commands/vm_lifecycle_command.rbs +25 -0
  404. data/sig/pvectl/commands/wakeonlan_node.rbs +21 -0
  405. data/sig/pvectl/config/errors.rbs +24 -0
  406. data/sig/pvectl/config/models/cluster.rbs +39 -0
  407. data/sig/pvectl/config/models/context.rbs +23 -0
  408. data/sig/pvectl/config/models/resolved_config.rbs +51 -0
  409. data/sig/pvectl/config/models/user.rbs +31 -0
  410. data/sig/pvectl/config/provider.rbs +40 -0
  411. data/sig/pvectl/config/service.rbs +65 -0
  412. data/sig/pvectl/config/store.rbs +14 -0
  413. data/sig/pvectl/config/wizard.rbs +48 -0
  414. data/sig/pvectl/config_serializer.rbs +121 -0
  415. data/sig/pvectl/connection/retry_handler.rbs +31 -0
  416. data/sig/pvectl/connection.rbs +35 -0
  417. data/sig/pvectl/console/terminal_session.rbs +63 -0
  418. data/sig/pvectl/editor_session.rbs +33 -0
  419. data/sig/pvectl/exit_codes.rbs +19 -0
  420. data/sig/pvectl/formatters/base.rbs +13 -0
  421. data/sig/pvectl/formatters/color_support.rbs +13 -0
  422. data/sig/pvectl/formatters/json.rbs +7 -0
  423. data/sig/pvectl/formatters/output_helper.rbs +9 -0
  424. data/sig/pvectl/formatters/registry.rbs +13 -0
  425. data/sig/pvectl/formatters/table.rbs +25 -0
  426. data/sig/pvectl/formatters/wide.rbs +15 -0
  427. data/sig/pvectl/formatters/yaml.rbs +7 -0
  428. data/sig/pvectl/manifest_serializer.rbs +18 -0
  429. data/sig/pvectl/models/apt_package.rbs +26 -0
  430. data/sig/pvectl/models/backup.rbs +31 -0
  431. data/sig/pvectl/models/base.rbs +11 -0
  432. data/sig/pvectl/models/capability.rbs +16 -0
  433. data/sig/pvectl/models/container.rbs +44 -0
  434. data/sig/pvectl/models/container_operation_result.rbs +9 -0
  435. data/sig/pvectl/models/dns_config.rbs +15 -0
  436. data/sig/pvectl/models/hosts_file.rbs +13 -0
  437. data/sig/pvectl/models/journal_entry.rbs +10 -0
  438. data/sig/pvectl/models/network_interface.rbs +20 -0
  439. data/sig/pvectl/models/node.rbs +47 -0
  440. data/sig/pvectl/models/node_operation_result.rbs +12 -0
  441. data/sig/pvectl/models/operation_result.rbs +21 -0
  442. data/sig/pvectl/models/physical_disk.rbs +35 -0
  443. data/sig/pvectl/models/service.rbs +18 -0
  444. data/sig/pvectl/models/snapshot.rbs +21 -0
  445. data/sig/pvectl/models/snapshot_description.rbs +18 -0
  446. data/sig/pvectl/models/storage.rbs +39 -0
  447. data/sig/pvectl/models/subscription.rbs +24 -0
  448. data/sig/pvectl/models/syslog_entry.rbs +10 -0
  449. data/sig/pvectl/models/task.rbs +22 -0
  450. data/sig/pvectl/models/task_entry.rbs +24 -0
  451. data/sig/pvectl/models/task_log_line.rbs +10 -0
  452. data/sig/pvectl/models/time_config.rbs +12 -0
  453. data/sig/pvectl/models/vm.rbs +32 -0
  454. data/sig/pvectl/models/vm_operation_result.rbs +9 -0
  455. data/sig/pvectl/models/volume.rbs +29 -0
  456. data/sig/pvectl/models/volume_operation_result.rbs +9 -0
  457. data/sig/pvectl/parsers/cloud_init_config.rbs +15 -0
  458. data/sig/pvectl/parsers/disk_config.rbs +19 -0
  459. data/sig/pvectl/parsers/lxc_mount_config.rbs +19 -0
  460. data/sig/pvectl/parsers/lxc_net_config.rbs +19 -0
  461. data/sig/pvectl/parsers/net_config.rbs +19 -0
  462. data/sig/pvectl/parsers/smart_text.rbs +7 -0
  463. data/sig/pvectl/plugin_loader.rbs +25 -0
  464. data/sig/pvectl/presenters/apt_package.rbs +19 -0
  465. data/sig/pvectl/presenters/backup.rbs +25 -0
  466. data/sig/pvectl/presenters/base.rbs +41 -0
  467. data/sig/pvectl/presenters/capability.rbs +19 -0
  468. data/sig/pvectl/presenters/config/context.rbs +17 -0
  469. data/sig/pvectl/presenters/container.rbs +78 -0
  470. data/sig/pvectl/presenters/container_operation_result.rbs +19 -0
  471. data/sig/pvectl/presenters/disk.rbs +31 -0
  472. data/sig/pvectl/presenters/dns_config.rbs +13 -0
  473. data/sig/pvectl/presenters/hosts_file.rbs +13 -0
  474. data/sig/pvectl/presenters/journal_entry.rbs +11 -0
  475. data/sig/pvectl/presenters/node.rbs +118 -0
  476. data/sig/pvectl/presenters/node_operation_result.rbs +11 -0
  477. data/sig/pvectl/presenters/operation_result.rbs +17 -0
  478. data/sig/pvectl/presenters/service.rbs +15 -0
  479. data/sig/pvectl/presenters/snapshot.rbs +35 -0
  480. data/sig/pvectl/presenters/snapshot_operation_result.rbs +27 -0
  481. data/sig/pvectl/presenters/storage.rbs +59 -0
  482. data/sig/pvectl/presenters/subscription.rbs +36 -0
  483. data/sig/pvectl/presenters/syslog_entry.rbs +11 -0
  484. data/sig/pvectl/presenters/task_entry.rbs +21 -0
  485. data/sig/pvectl/presenters/task_log_line.rbs +11 -0
  486. data/sig/pvectl/presenters/template.rbs +15 -0
  487. data/sig/pvectl/presenters/time_config.rbs +19 -0
  488. data/sig/pvectl/presenters/top_container.rbs +17 -0
  489. data/sig/pvectl/presenters/top_node.rbs +17 -0
  490. data/sig/pvectl/presenters/top_presenter.rbs +13 -0
  491. data/sig/pvectl/presenters/top_vm.rbs +17 -0
  492. data/sig/pvectl/presenters/vm.rbs +91 -0
  493. data/sig/pvectl/presenters/vm_operation_result.rbs +19 -0
  494. data/sig/pvectl/presenters/volume.rbs +23 -0
  495. data/sig/pvectl/presenters/volume_operation_result.rbs +11 -0
  496. data/sig/pvectl/repositories/apt.rbs +17 -0
  497. data/sig/pvectl/repositories/backup.rbs +27 -0
  498. data/sig/pvectl/repositories/base.rbs +23 -0
  499. data/sig/pvectl/repositories/capabilities.rbs +20 -0
  500. data/sig/pvectl/repositories/container.rbs +63 -0
  501. data/sig/pvectl/repositories/disk.rbs +17 -0
  502. data/sig/pvectl/repositories/dns.rbs +13 -0
  503. data/sig/pvectl/repositories/hosts.rbs +13 -0
  504. data/sig/pvectl/repositories/journal.rbs +7 -0
  505. data/sig/pvectl/repositories/node.rbs +68 -0
  506. data/sig/pvectl/repositories/service.rbs +27 -0
  507. data/sig/pvectl/repositories/snapshot.rbs +19 -0
  508. data/sig/pvectl/repositories/storage.rbs +37 -0
  509. data/sig/pvectl/repositories/subscription.rbs +17 -0
  510. data/sig/pvectl/repositories/syslog.rbs +7 -0
  511. data/sig/pvectl/repositories/task.rbs +19 -0
  512. data/sig/pvectl/repositories/task_list.rbs +7 -0
  513. data/sig/pvectl/repositories/task_log.rbs +11 -0
  514. data/sig/pvectl/repositories/time_config.rbs +13 -0
  515. data/sig/pvectl/repositories/vm.rbs +85 -0
  516. data/sig/pvectl/repositories/volume.rbs +43 -0
  517. data/sig/pvectl/selectors/base.rbs +37 -0
  518. data/sig/pvectl/selectors/container.rbs +19 -0
  519. data/sig/pvectl/selectors/disk.rbs +13 -0
  520. data/sig/pvectl/selectors/vm.rbs +19 -0
  521. data/sig/pvectl/selectors/volume.rbs +13 -0
  522. data/sig/pvectl/services/backup.rbs +27 -0
  523. data/sig/pvectl/services/clone_container.rbs +35 -0
  524. data/sig/pvectl/services/clone_vm.rbs +35 -0
  525. data/sig/pvectl/services/cloudinit.rbs +19 -0
  526. data/sig/pvectl/services/console.rbs +23 -0
  527. data/sig/pvectl/services/container_lifecycle.rbs +26 -0
  528. data/sig/pvectl/services/create_container.rbs +64 -0
  529. data/sig/pvectl/services/create_vm.rbs +72 -0
  530. data/sig/pvectl/services/edit_container.rbs +17 -0
  531. data/sig/pvectl/services/edit_dns.rbs +23 -0
  532. data/sig/pvectl/services/edit_hosts.rbs +13 -0
  533. data/sig/pvectl/services/edit_node.rbs +21 -0
  534. data/sig/pvectl/services/edit_vm.rbs +17 -0
  535. data/sig/pvectl/services/edit_volume.rbs +18 -0
  536. data/sig/pvectl/services/get/resource_service.rbs +23 -0
  537. data/sig/pvectl/services/move_disk.rbs +21 -0
  538. data/sig/pvectl/services/pull_config.rbs +18 -0
  539. data/sig/pvectl/services/push_config.rbs +37 -0
  540. data/sig/pvectl/services/resize_volume.rbs +47 -0
  541. data/sig/pvectl/services/resource_delete.rbs +27 -0
  542. data/sig/pvectl/services/resource_migration.rbs +29 -0
  543. data/sig/pvectl/services/sendkey.rbs +19 -0
  544. data/sig/pvectl/services/service_lifecycle.rbs +17 -0
  545. data/sig/pvectl/services/set_container.rbs +14 -0
  546. data/sig/pvectl/services/set_node.rbs +23 -0
  547. data/sig/pvectl/services/set_vm.rbs +14 -0
  548. data/sig/pvectl/services/set_volume.rbs +12 -0
  549. data/sig/pvectl/services/snapshot.rbs +35 -0
  550. data/sig/pvectl/services/task_listing.rbs +13 -0
  551. data/sig/pvectl/services/unlink_disk.rbs +17 -0
  552. data/sig/pvectl/services/vm_lifecycle.rbs +26 -0
  553. data/sig/pvectl/services/wakeonlan.rbs +17 -0
  554. data/sig/pvectl/utils/resource_resolver.rbs +17 -0
  555. data/sig/pvectl/wizards/create_container.rbs +21 -0
  556. data/sig/pvectl/wizards/create_vm.rbs +21 -0
  557. data/sig/pvectl.rbs +9 -0
  558. metadata +675 -0
@@ -0,0 +1,616 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "set"
4
+ require_relative "task_list"
5
+
6
+ module Pvectl
7
+ module Repositories
8
+ # Repository for QEMU virtual machines.
9
+ #
10
+ # Uses the `/cluster/resources` API endpoint to list VMs across the cluster.
11
+ # Filters to only include QEMU VMs (excludes LXC containers).
12
+ #
13
+ # @example Listing all VMs
14
+ # repo = Vm.new(connection)
15
+ # vms = repo.list
16
+ # vms.each { |vm| puts "#{vm.vmid}: #{vm.name}" }
17
+ #
18
+ # @example Listing VMs on a specific node
19
+ # vms = repo.list(node: "pve-node1")
20
+ #
21
+ # @example Getting a single VM
22
+ # vm = repo.get(100)
23
+ # puts vm.name if vm
24
+ #
25
+ # @see Pvectl::Models::Vm VM model
26
+ # @see Pvectl::Connection API connection
27
+ #
28
+ class Vm < Base
29
+ # Lists all VMs in the cluster.
30
+ #
31
+ # Uses `/cluster/resources?type=vm` endpoint for efficient cluster-wide
32
+ # listing. Filters to only include QEMU VMs (type == "qemu").
33
+ #
34
+ # @param node [String, nil] filter by node name
35
+ # @return [Array<Models::Vm>] collection of VM models
36
+ def list(node: nil)
37
+ response = connection.client["cluster/resources"].get(params: { type: "vm" })
38
+ vms = response.select { |r| r[:type] == "qemu" }
39
+ vms = vms.select { |r| r[:node] == node } if node
40
+ vms.map { |data| build_model(data) }
41
+ end
42
+
43
+ # Gets a single VM by VMID.
44
+ #
45
+ # @param vmid [Integer, String] VM identifier
46
+ # @return [Models::Vm, nil] VM model or nil if not found
47
+ def get(vmid)
48
+ list.find { |vm| vm.vmid == vmid.to_i }
49
+ end
50
+
51
+ # Describes a VM with comprehensive details from multiple API endpoints.
52
+ #
53
+ # @param vmid [Integer, String] VM identifier
54
+ # @return [Models::Vm, nil] VM model with full details, or nil if not found
55
+ def describe(vmid)
56
+ vmid = vmid.to_i
57
+
58
+ # 1. Find VM in cluster to get node
59
+ basic_data = find_vm_basic_data(vmid)
60
+ return nil if basic_data.nil?
61
+
62
+ node = basic_data[:node]
63
+
64
+ # 2. Fetch detailed data from node-specific endpoints
65
+ describe_data = {
66
+ config: fetch_config(node, vmid),
67
+ status: fetch_status(node, vmid),
68
+ snapshots: fetch_snapshots(node, vmid),
69
+ agent_ips: fetch_agent_ips(node, vmid),
70
+ pending: fetch_pending(node, vmid),
71
+ tasks: fetch_tasks(node, vmid),
72
+ firewall: fetch_firewall(node, vmid)
73
+ }
74
+
75
+ build_describe_model(basic_data, describe_data)
76
+ end
77
+
78
+ # ---------------------------
79
+ # Lifecycle Operations
80
+ # ---------------------------
81
+
82
+ # Starts a VM.
83
+ #
84
+ # @param vmid [Integer, String] VM identifier
85
+ # @param node [String] Node name
86
+ # @return [String] Task UPID
87
+ def start(vmid, node)
88
+ post_status(vmid, node, "start")
89
+ end
90
+
91
+ # Stops a VM immediately (hard stop).
92
+ #
93
+ # @param vmid [Integer, String] VM identifier
94
+ # @param node [String] Node name
95
+ # @return [String] Task UPID
96
+ def stop(vmid, node)
97
+ post_status(vmid, node, "stop")
98
+ end
99
+
100
+ # Shuts down a VM gracefully (ACPI).
101
+ #
102
+ # @param vmid [Integer, String] VM identifier
103
+ # @param node [String] Node name
104
+ # @return [String] Task UPID
105
+ def shutdown(vmid, node)
106
+ post_status(vmid, node, "shutdown")
107
+ end
108
+
109
+ # Restarts a VM (reboot).
110
+ #
111
+ # @param vmid [Integer, String] VM identifier
112
+ # @param node [String] Node name
113
+ # @return [String] Task UPID
114
+ def restart(vmid, node)
115
+ post_status(vmid, node, "reboot")
116
+ end
117
+
118
+ # Resets a VM (hard reset).
119
+ #
120
+ # @param vmid [Integer, String] VM identifier
121
+ # @param node [String] Node name
122
+ # @return [String] Task UPID
123
+ def reset(vmid, node)
124
+ post_status(vmid, node, "reset")
125
+ end
126
+
127
+ # Suspends a VM (hibernate).
128
+ #
129
+ # @param vmid [Integer, String] VM identifier
130
+ # @param node [String] Node name
131
+ # @return [String] Task UPID
132
+ def suspend(vmid, node)
133
+ post_status(vmid, node, "suspend")
134
+ end
135
+
136
+ # Resumes a suspended VM.
137
+ #
138
+ # @param vmid [Integer, String] VM identifier
139
+ # @param node [String] Node name
140
+ # @return [String] Task UPID
141
+ def resume(vmid, node)
142
+ post_status(vmid, node, "resume")
143
+ end
144
+
145
+ # Opens a terminal proxy session for a VM.
146
+ #
147
+ # @param vmid [Integer, String] VM identifier
148
+ # @param node [String] Node name
149
+ # @return [Hash] termproxy data with :port, :ticket, :user keys
150
+ def termproxy(vmid, node)
151
+ response = connection.client["nodes/#{node}/qemu/#{vmid}/termproxy"].post({})
152
+ normalize_hash_response(response)
153
+ end
154
+
155
+ # Deletes a VM from the cluster.
156
+ #
157
+ # @param vmid [Integer, String] VM identifier
158
+ # @param node [String] Node name
159
+ # @param destroy_disks [Boolean] destroy unreferenced disks (default: true)
160
+ # @param purge [Boolean] remove from HA, replication, backups (default: false)
161
+ # @param force [Boolean] skip lock (default: false)
162
+ # @return [String] Task UPID
163
+ def delete(vmid, node, destroy_disks: true, purge: false, force: false)
164
+ params = {}
165
+ params["destroy-unreferenced-disks"] = 1 if destroy_disks
166
+ params[:purge] = 1 if purge
167
+ params[:skiplock] = 1 if force
168
+
169
+ connection.client["nodes/#{node}/qemu/#{vmid}"].delete(params)
170
+ end
171
+
172
+ # Clones a VM to create a new VM.
173
+ #
174
+ # Posts to `/nodes/{node}/qemu/{vmid}/clone` with the specified parameters.
175
+ #
176
+ # @param vmid [Integer, String] source VM identifier
177
+ # @param node [String] source node name
178
+ # @param new_vmid [Integer] VMID for the new cloned VM
179
+ # @param options [Hash] optional clone parameters
180
+ # @option options [String] :name name for the new VM
181
+ # @option options [String] :target target node for the clone
182
+ # @option options [String] :storage target storage for the clone
183
+ # @option options [Boolean] :full full clone (true) or linked clone (false)
184
+ # @option options [String] :description description for the new VM
185
+ # @option options [String] :pool resource pool for the new VM
186
+ # @return [String] Task UPID
187
+ def clone(vmid, node, new_vmid, options = {})
188
+ params = { newid: new_vmid }
189
+ params[:name] = options[:name] if options[:name]
190
+ params[:target] = options[:target] if options[:target]
191
+ params[:storage] = options[:storage] if options[:storage]
192
+ params[:full] = options[:full] ? 1 : 0 if options.key?(:full)
193
+ params[:description] = options[:description] if options[:description]
194
+ params[:pool] = options[:pool] if options[:pool]
195
+
196
+ connection.client["nodes/#{node}/qemu/#{vmid}/clone"].post(params)
197
+ end
198
+
199
+ # Converts a VM to a template.
200
+ #
201
+ # This is an irreversible operation. The VM will become read-only
202
+ # and can only be used as a source for cloning.
203
+ #
204
+ # @param vmid [Integer, String] VM identifier
205
+ # @param node [String] Node name
206
+ # @param disk [String, nil] specific disk to convert (e.g., "scsi0")
207
+ # @return [void]
208
+ def convert_to_template(vmid, node, disk: nil)
209
+ params = {}
210
+ params[:disk] = disk if disk
211
+ connection.client["nodes/#{node}/qemu/#{vmid}/template"].post(params)
212
+ end
213
+
214
+ # Creates a new VM on the specified node.
215
+ #
216
+ # Posts to `/nodes/{node}/qemu` with the VM configuration parameters.
217
+ # The vmid is merged into params automatically.
218
+ #
219
+ # @param node [String] target node name
220
+ # @param vmid [Integer] VM identifier
221
+ # @param params [Hash] VM configuration parameters (name, cores, memory, etc.)
222
+ # @return [String] Task UPID
223
+ #
224
+ # @example Create a basic VM
225
+ # repo.create("pve1", 100, { name: "web-server", cores: 4, memory: 4096 })
226
+ # #=> "UPID:pve1:..."
227
+ def create(node, vmid, params = {})
228
+ api_params = params.merge(vmid: vmid)
229
+ connection.client["nodes/#{node}/qemu"].post(api_params)
230
+ end
231
+
232
+ # Updates an existing VM configuration.
233
+ #
234
+ # PUTs to +/nodes/{node}/qemu/{vmid}/config+ with configuration parameters.
235
+ # This is a synchronous operation — changes are applied immediately.
236
+ #
237
+ # @param vmid [Integer, String] VM identifier
238
+ # @param node [String] node name
239
+ # @param params [Hash] VM configuration parameters to update
240
+ # @return [nil]
241
+ def update(vmid, node, params = {})
242
+ connection.client["nodes/#{node}/qemu/#{vmid}/config"].put(params)
243
+ end
244
+
245
+ # Resizes a VM disk.
246
+ #
247
+ # PUTs to +/nodes/{node}/qemu/{vmid}/resize+ with disk and size parameters.
248
+ # Size can be absolute (e.g., "50G") or relative (e.g., "+10G").
249
+ #
250
+ # @param vmid [Integer, String] VM identifier
251
+ # @param node [String] node name
252
+ # @param disk [String] disk name (e.g., "scsi0", "virtio0")
253
+ # @param size [String] new size or size increment (e.g., "50G", "+10G")
254
+ # @return [nil]
255
+ def resize(vmid, node, disk:, size:)
256
+ connection.client["nodes/#{node}/qemu/#{vmid}/resize"].put({ disk: disk, size: size })
257
+ end
258
+
259
+ # Sends a QEMU monitor key event to a running VM.
260
+ #
261
+ # PUTs to +/nodes/{node}/qemu/{vmid}/sendkey+ with the +key+ parameter.
262
+ # The +key+ uses QEMU qcode format (e.g., "ctrl-alt-delete", "ret", "f1").
263
+ # This is a synchronous operation — Proxmox returns null on success.
264
+ #
265
+ # @param vmid [Integer, String] VM identifier
266
+ # @param node [String] node name
267
+ # @param key [String] QEMU qcode key sequence (e.g., "ctrl-alt-delete")
268
+ # @return [nil]
269
+ def sendkey(vmid, node, key)
270
+ connection.client["nodes/#{node}/qemu/#{vmid}/sendkey"].put({ key: key })
271
+ end
272
+
273
+ # Unlinks (removes) one or more disks from a VM configuration.
274
+ #
275
+ # PUTs to +/nodes/{node}/qemu/{vmid}/unlink+ with the comma-separated
276
+ # list of disk IDs. By default, Proxmox keeps removed volumes as
277
+ # +unused[n]+ entries in the config; with +force: true+ the underlying
278
+ # volume is physically removed.
279
+ #
280
+ # @param node [String] node name
281
+ # @param vmid [Integer, String] VM identifier
282
+ # @param disk_ids [Array<String>, String] disk identifiers (e.g., "scsi0"
283
+ # or %w[scsi0 scsi1] or "scsi0,scsi1")
284
+ # @param force [Boolean] physically delete the underlying volume(s)
285
+ # (default: false — keep as unused[n])
286
+ # @return [nil] this is a synchronous operation and returns no UPID
287
+ def unlink_disks(node, vmid, disk_ids, force: false)
288
+ idlist = Array(disk_ids).flat_map { |id| id.to_s.split(",") }.map(&:strip).reject(&:empty?).join(",")
289
+ connection.client["nodes/#{node}/qemu/#{vmid}/unlink"].put(
290
+ { idlist: idlist, force: force ? 1 : 0 }
291
+ )
292
+ end
293
+
294
+ # Fetches VM configuration.
295
+ #
296
+ # @param node [String] node name
297
+ # @param vmid [Integer] VM identifier
298
+ # @return [Hash] config data
299
+ def fetch_config(node, vmid)
300
+ resp = connection.client["nodes/#{node}/qemu/#{vmid}/config"].get
301
+ normalize_hash_response(resp)
302
+ rescue StandardError
303
+ {}
304
+ end
305
+
306
+ # Migrates a VM to another node.
307
+ #
308
+ # @param vmid [Integer, String] VM identifier
309
+ # @param node [String] current node name
310
+ # @param params [Hash] migration parameters (:target, :online, :"with-local-disks", :targetstorage)
311
+ # @return [String] Task UPID
312
+ # @raise [ArgumentError] if node name or vmid format is invalid
313
+ def migrate(vmid, node, params = {})
314
+ unless node.match?(/\A[a-z][a-z0-9-]*\z/)
315
+ raise ArgumentError, "Invalid node name: #{node}"
316
+ end
317
+ unless vmid.is_a?(Integer) && vmid.positive?
318
+ raise ArgumentError, "Invalid VMID: #{vmid}"
319
+ end
320
+
321
+ connection.client["nodes/#{node}/qemu/#{vmid}/migrate"].post(params)
322
+ end
323
+
324
+ # Moves a VM disk to a different storage on the same node.
325
+ #
326
+ # POSTs to +/nodes/{node}/qemu/{vmid}/move_disk+ with the disk identifier
327
+ # and target storage. The operation is asynchronous — the returned UPID
328
+ # can be polled via Repositories::Task to track completion.
329
+ #
330
+ # @param vmid [Integer, String] VM identifier
331
+ # @param node [String] node name where the VM currently resides
332
+ # @param disk [String] disk identifier (e.g., "scsi0", "virtio0")
333
+ # @param target_storage [String] destination storage ID
334
+ # @param format [String, nil] target disk format ("raw", "qcow2", "vmdk")
335
+ # @param delete [Boolean] delete the source disk after copy (default: false)
336
+ # @param bwlimit [Integer, nil] I/O bandwidth limit in KiB/s
337
+ # @return [String] Task UPID
338
+ #
339
+ # @example Move scsi0 to local-lvm storage
340
+ # repo.move_disk(100, "pve1", "scsi0", "local-lvm")
341
+ # #=> "UPID:pve1:..."
342
+ #
343
+ # @example Move and convert format, delete source
344
+ # repo.move_disk(100, "pve1", "scsi0", "local-lvm",
345
+ # format: "qcow2", delete: true)
346
+ def move_disk(vmid, node, disk, target_storage, format: nil, delete: false, bwlimit: nil)
347
+ params = { disk: disk, storage: target_storage }
348
+ params[:format] = format if format
349
+ params[:delete] = 1 if delete
350
+ params[:bwlimit] = bwlimit if bwlimit
351
+
352
+ connection.client["nodes/#{node}/qemu/#{vmid}/move_disk"].post(params)
353
+ end
354
+
355
+ # Regenerates the cloud-init configuration ISO for a VM.
356
+ #
357
+ # PUTs to +/nodes/{node}/qemu/{vmid}/cloudinit+. The Proxmox API
358
+ # endpoint returns null on success.
359
+ #
360
+ # @param node [String] node name
361
+ # @param vmid [Integer, String] VM identifier
362
+ # @return [nil]
363
+ def cloudinit_regenerate(node, vmid)
364
+ connection.client["nodes/#{node}/qemu/#{vmid}/cloudinit"].put
365
+ end
366
+
367
+ # Fetches pending cloud-init configuration changes for a VM.
368
+ #
369
+ # GETs from +/nodes/{node}/qemu/{vmid}/cloudinit+. Returns an array
370
+ # of pending entries, each with +:key+, +:value+, +:pending+, and
371
+ # optional +:delete+ keys.
372
+ #
373
+ # @param node [String] node name
374
+ # @param vmid [Integer, String] VM identifier
375
+ # @return [Array<Hash{Symbol => untyped}>] pending entries
376
+ def cloudinit_pending(node, vmid)
377
+ response = connection.client["nodes/#{node}/qemu/#{vmid}/cloudinit"].get
378
+ normalize_response(response)
379
+ end
380
+
381
+ # Dumps the generated cloud-init configuration for a VM.
382
+ #
383
+ # GETs from +/nodes/{node}/qemu/{vmid}/cloudinit/dump+ with the
384
+ # specified +type+ query parameter. Returns the raw YAML/text body.
385
+ #
386
+ # @param node [String] node name
387
+ # @param vmid [Integer, String] VM identifier
388
+ # @param type [String] config type — one of +"user"+, +"network"+, +"meta"+
389
+ # @return [String] cloud-init configuration as raw text
390
+ def cloudinit_dump(node, vmid, type)
391
+ connection.client["nodes/#{node}/qemu/#{vmid}/cloudinit/dump"].get(params: { type: type })
392
+ end
393
+
394
+ # Checks whether a feature (clone, snapshot, copy) is available for a VM.
395
+ #
396
+ # Calls +GET /nodes/{node}/qemu/{vmid}/feature+ with the feature and
397
+ # optional snapshot name. The Proxmox API returns +hasFeature+ (0/1) and
398
+ # a +nodes+ array listing cluster nodes that satisfy the feature.
399
+ #
400
+ # @param vmid [Integer, String] VM identifier
401
+ # @param node [String] node currently hosting the VM
402
+ # @param feature [String] feature name (one of: clone, snapshot, copy)
403
+ # @param snapname [String, nil] snapshot name (required for some checks)
404
+ # @return [Hash] result with :available (Boolean) and :nodes (Array<String>)
405
+ #
406
+ # @example Check whether VM 100 can be cloned
407
+ # repo.feature_available?(100, "pve1", "clone")
408
+ # #=> { available: true, nodes: ["pve1", "pve2"] }
409
+ def feature_available?(vmid, node, feature, snapname: nil)
410
+ params = { feature: feature }
411
+ params[:snapname] = snapname unless snapname.nil?
412
+
413
+ response = connection.client["nodes/#{node}/qemu/#{vmid}/feature"].get(params: params)
414
+ data = normalize_hash_response(response)
415
+
416
+ {
417
+ available: data[:hasFeature].to_i == 1,
418
+ nodes: Array(data[:nodes])
419
+ }
420
+ end
421
+
422
+ # Returns the next available VMID from the Proxmox cluster.
423
+ #
424
+ # Uses the +/cluster/nextid+ API endpoint which performs server-side allocation.
425
+ # This is more reliable than client-side scanning because it detects stale
426
+ # config files that don't appear in +/cluster/resources+.
427
+ #
428
+ # @return [Integer] next available VMID
429
+ def next_available_vmid
430
+ connection.client["cluster/nextid"].get.to_i
431
+ end
432
+
433
+ protected
434
+
435
+ # Builds Vm model from API response data.
436
+ #
437
+ # @param data [Hash] API response hash with string keys
438
+ # @return [Models::Vm] VM model instance
439
+ def build_model(data)
440
+ Models::Vm.new(
441
+ vmid: data[:vmid],
442
+ name: data[:name],
443
+ status: data[:status],
444
+ node: data[:node],
445
+ cpu: data[:cpu],
446
+ maxcpu: data[:maxcpu],
447
+ mem: data[:mem],
448
+ maxmem: data[:maxmem],
449
+ disk: data[:disk],
450
+ maxdisk: data[:maxdisk],
451
+ uptime: data[:uptime],
452
+ template: data[:template],
453
+ tags: data[:tags],
454
+ hastate: data[:hastate],
455
+ netin: data[:netin],
456
+ netout: data[:netout],
457
+ type: data[:type]
458
+ )
459
+ end
460
+
461
+ private
462
+
463
+ # Posts to VM status endpoint.
464
+ #
465
+ # @param vmid [Integer, String] VM identifier
466
+ # @param node [String] Node name
467
+ # @param action [String] Action (start, stop, etc.)
468
+ # @return [String] Task UPID
469
+ def post_status(vmid, node, action)
470
+ connection.client["nodes/#{node}/qemu/#{vmid}/status/#{action}"].post
471
+ end
472
+
473
+ # Finds VM basic data from cluster resources.
474
+ #
475
+ # @param vmid [Integer] VM identifier
476
+ # @return [Hash, nil] VM data or nil if not found
477
+ def find_vm_basic_data(vmid)
478
+ response = connection.client["cluster/resources"].get(params: { type: "vm" })
479
+ response.find { |r| r[:type] == "qemu" && r[:vmid] == vmid }
480
+ end
481
+
482
+ # Fetches VM runtime status.
483
+ #
484
+ # @param node [String] node name
485
+ # @param vmid [Integer] VM identifier
486
+ # @return [Hash] status data
487
+ def fetch_status(node, vmid)
488
+ resp = connection.client["nodes/#{node}/qemu/#{vmid}/status/current"].get
489
+ normalize_hash_response(resp)
490
+ rescue StandardError
491
+ {}
492
+ end
493
+
494
+ # Fetches VM snapshots.
495
+ #
496
+ # @param node [String] node name
497
+ # @param vmid [Integer] VM identifier
498
+ # @return [Array<Hash>] snapshots list
499
+ def fetch_snapshots(node, vmid)
500
+ resp = connection.client["nodes/#{node}/qemu/#{vmid}/snapshot"].get
501
+ normalize_response(resp).reject { |s| s[:name] == "current" }
502
+ rescue StandardError
503
+ []
504
+ end
505
+
506
+ # Fetches IP addresses from QEMU guest agent.
507
+ # Graceful failure - returns nil on any error.
508
+ #
509
+ # @param node [String] node name
510
+ # @param vmid [Integer] VM identifier
511
+ # @return [Array<Hash>, nil] interfaces or nil on error
512
+ def fetch_agent_ips(node, vmid)
513
+ resp = connection.client["nodes/#{node}/qemu/#{vmid}/agent/network-get-interfaces"].get
514
+ data = normalize_hash_response(resp)
515
+ data[:result]
516
+ rescue StandardError
517
+ nil
518
+ end
519
+
520
+ # Fetches pending configuration changes.
521
+ #
522
+ # @param node [String] node name
523
+ # @param vmid [Integer] VM identifier
524
+ # @return [Array<Hash>] pending changes
525
+ def fetch_pending(node, vmid)
526
+ resp = connection.client["nodes/#{node}/qemu/#{vmid}/pending"].get
527
+ normalize_response(resp)
528
+ rescue StandardError
529
+ []
530
+ end
531
+
532
+ # Fetches firewall configuration (options, rules, aliases, IP sets).
533
+ #
534
+ # @param node [String] node name
535
+ # @param vmid [Integer] VM identifier
536
+ # @return [Hash] firewall data with :options, :rules, :aliases, :ipset keys
537
+ def fetch_firewall(node, vmid)
538
+ base = "nodes/#{node}/qemu/#{vmid}/firewall"
539
+ options = (normalize_hash_response(connection.client["#{base}/options"].get) rescue {})
540
+ rules = (normalize_response(connection.client["#{base}/rules"].get) rescue [])
541
+ aliases_data = (normalize_response(connection.client["#{base}/aliases"].get) rescue [])
542
+ ipset = (normalize_response(connection.client["#{base}/ipset"].get) rescue [])
543
+ { options: options, rules: rules, aliases: aliases_data, ipset: ipset }
544
+ rescue StandardError
545
+ {}
546
+ end
547
+
548
+ # Fetches recent task history for the VM.
549
+ #
550
+ # @param node [String] node name
551
+ # @param vmid [Integer] VM identifier
552
+ # @param limit [Integer] max entries (default 10)
553
+ # @return [Array<Models::TaskEntry>] recent tasks
554
+ def fetch_tasks(node, vmid, limit: 10)
555
+ task_list_repo = TaskList.new(connection)
556
+ task_list_repo.list(node: node, vmid: vmid, limit: limit)
557
+ rescue StandardError
558
+ []
559
+ end
560
+
561
+ # Normalizes hash response that may be wrapped in :data key.
562
+ #
563
+ # @param response [Hash] API response
564
+ # @return [Hash] normalized hash
565
+ def normalize_hash_response(response)
566
+ if response.is_a?(Hash) && response[:data]
567
+ response[:data]
568
+ else
569
+ response || {}
570
+ end
571
+ end
572
+
573
+ # Normalizes array response.
574
+ #
575
+ # @param response [Array, Hash] API response
576
+ # @return [Array<Hash>] normalized array
577
+ def normalize_response(response)
578
+ if response.is_a?(Array)
579
+ response
580
+ elsif response.is_a?(Hash) && response[:data]
581
+ response[:data]
582
+ else
583
+ response.to_a
584
+ end
585
+ end
586
+
587
+ # Builds VM model with describe-specific attributes.
588
+ #
589
+ # @param basic_data [Hash] basic VM data from cluster/resources
590
+ # @param describe_data [Hash] aggregated describe data
591
+ # @return [Models::Vm] VM model
592
+ def build_describe_model(basic_data, describe_data)
593
+ Models::Vm.new(
594
+ vmid: basic_data[:vmid],
595
+ name: basic_data[:name],
596
+ status: basic_data[:status],
597
+ node: basic_data[:node],
598
+ cpu: basic_data[:cpu],
599
+ maxcpu: basic_data[:maxcpu],
600
+ mem: basic_data[:mem],
601
+ maxmem: basic_data[:maxmem],
602
+ disk: basic_data[:disk],
603
+ maxdisk: basic_data[:maxdisk],
604
+ uptime: basic_data[:uptime],
605
+ template: basic_data[:template],
606
+ tags: basic_data[:tags],
607
+ hastate: basic_data[:hastate],
608
+ netin: basic_data[:netin],
609
+ netout: basic_data[:netout],
610
+ type: basic_data[:type],
611
+ describe_data: describe_data
612
+ )
613
+ end
614
+ end
615
+ end
616
+ end