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,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pvectl
4
+ module Commands
5
+ module Top
6
+ module Handlers
7
+ # Handler for Top command VM metrics display.
8
+ #
9
+ # Wraps Get::Handlers::Vms to fetch VM data and pairs it
10
+ # with TopVm presenter for metrics-focused output.
11
+ #
12
+ # @example Using via ResourceRegistry
13
+ # handler = Top::ResourceRegistry.for("vms")
14
+ # vms = handler.list(sort: "cpu")
15
+ # presenter = handler.presenter
16
+ #
17
+ # @see Pvectl::Commands::Get::Handlers::Vms Get handler
18
+ # @see Pvectl::Presenters::TopVm Top presenter
19
+ #
20
+ class Vms
21
+ include Top::ResourceHandler
22
+
23
+ # Creates handler with optional Get handler for dependency injection.
24
+ #
25
+ # @param get_handler [Get::Handlers::Vms, nil] handler (default: create new)
26
+ def initialize(get_handler: nil)
27
+ @get_handler = get_handler
28
+ end
29
+
30
+ # Lists VMs with optional sorting.
31
+ #
32
+ # @param sort [String, nil] sort field (cpu, memory, disk)
33
+ # @return [Array<Models::Vm>] collection of VM models
34
+ def list(sort: nil, **_)
35
+ get_handler.list(sort: sort)
36
+ end
37
+
38
+ # Returns Top-specific presenter for VMs.
39
+ #
40
+ # @return [Presenters::TopVm] TopVm presenter instance
41
+ def presenter
42
+ Presenters::TopVm.new
43
+ end
44
+
45
+ private
46
+
47
+ # Returns Get handler, creating it if necessary.
48
+ #
49
+ # @return [Get::Handlers::Vms] Vms get handler
50
+ def get_handler
51
+ @get_handler ||= Get::Handlers::Vms.new
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
58
+
59
+ Pvectl::Commands::Top::ResourceRegistry.register(
60
+ "vms", Pvectl::Commands::Top::Handlers::Vms, aliases: ["vm"]
61
+ )
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pvectl
4
+ module Commands
5
+ module Top
6
+ # Interface module for Top command handlers.
7
+ #
8
+ # Each handler wraps a Get handler and returns a Top-specific presenter
9
+ # for metrics-focused display (CPU%, MEM%, etc.).
10
+ #
11
+ # @abstract Include in handler class and implement required methods.
12
+ #
13
+ # @example Implementing a handler
14
+ # class NodesHandler
15
+ # include Top::ResourceHandler
16
+ #
17
+ # def list(sort: nil, **_)
18
+ # get_handler.list(sort: sort)
19
+ # end
20
+ #
21
+ # def presenter
22
+ # Presenters::TopNode.new
23
+ # end
24
+ # end
25
+ #
26
+ module ResourceHandler
27
+ # Lists resources with optional sorting.
28
+ #
29
+ # @param options [Hash] keyword arguments (e.g., sort:)
30
+ # @return [Array<Object>] collection of model objects
31
+ # @raise [NotImplementedError] if not implemented by including class
32
+ def list(**options)
33
+ raise NotImplementedError, "#{self.class}#list must be implemented"
34
+ end
35
+
36
+ # Returns the Top-specific presenter for this resource type.
37
+ #
38
+ # @return [Presenters::Base] presenter instance
39
+ # @raise [NotImplementedError] if not implemented by including class
40
+ def presenter
41
+ raise NotImplementedError, "#{self.class}#presenter must be implemented"
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pvectl
4
+ module Commands
5
+ module Top
6
+ # Registry for Top command resource handlers.
7
+ #
8
+ # Inherits registration and lookup from Commands::ResourceRegistry,
9
+ # maintaining its own isolated set of handlers.
10
+ #
11
+ # @example Registering a handler
12
+ # Top::ResourceRegistry.register("nodes", Handlers::Nodes, aliases: ["node"])
13
+ #
14
+ # @example Looking up a handler
15
+ # handler = Top::ResourceRegistry.for("nodes")
16
+ #
17
+ # @see Pvectl::Commands::ResourceRegistry Base registry
18
+ #
19
+ class ResourceRegistry < Commands::ResourceRegistry; end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,232 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pvectl
4
+ module Commands
5
+ # Handler for the `pvectl unlink disk vm` command.
6
+ #
7
+ # Removes one or more disks from a VM configuration. By default, the
8
+ # disk entry is converted to `unused[n]` (the underlying volume is
9
+ # preserved). With `--force`, the underlying volume is physically
10
+ # deleted.
11
+ #
12
+ # @example Soft unlink (keeps volume as unused0)
13
+ # pvectl unlink disk vm 100 scsi1
14
+ #
15
+ # @example Unlink multiple disks
16
+ # pvectl unlink disk vm 100 scsi1,virtio0
17
+ #
18
+ # @example Hard delete the underlying volume
19
+ # pvectl unlink disk vm 100 scsi1 --force --yes
20
+ #
21
+ class UnlinkDiskVm
22
+ # Registers the unlink command with the CLI.
23
+ #
24
+ # @param cli [GLI::App] the CLI application object
25
+ # @return [void]
26
+ def self.register(cli)
27
+ cli.desc "Unlink a disk from a resource"
28
+ cli.long_desc <<~HELP
29
+ DESCRIPTION
30
+ Remove one or more disks from a VM configuration.
31
+
32
+ By default, the disk entry is moved to `unused[n]` in the VM
33
+ config — the underlying volume is preserved so it can be
34
+ re-attached or inspected later. With --force, the underlying
35
+ volume is physically deleted and cannot be recovered.
36
+
37
+ Multiple disks may be removed in a single call by passing a
38
+ comma-separated list (e.g. "scsi1,virtio0").
39
+
40
+ EXAMPLES
41
+ Soft unlink (keeps the volume as unused0):
42
+ $ pvectl unlink disk vm 100 scsi1
43
+
44
+ Unlink multiple disks at once:
45
+ $ pvectl unlink disk vm 100 scsi1,virtio0
46
+
47
+ Permanently delete the underlying volume:
48
+ $ pvectl unlink disk vm 100 scsi1 --force --yes
49
+
50
+ Skip confirmation prompt:
51
+ $ pvectl unlink disk vm 100 scsi1 -y
52
+
53
+ NOTES
54
+ --force is destructive: the underlying volume is removed and
55
+ cannot be recovered. Without --force, the volume can be
56
+ re-attached later (e.g. via `pvectl set vm`).
57
+
58
+ The command operates only on QEMU virtual machines; LXC
59
+ containers are not supported by the Proxmox /unlink endpoint.
60
+
61
+ SEE ALSO
62
+ pvectl help describe vm Show VM configuration including disks
63
+ pvectl help edit volume Edit volume properties interactively
64
+ pvectl help set vm Modify VM configuration (re-attach unused)
65
+ HELP
66
+ cli.arg_name "RESOURCE_TYPE ID DISK_LIST"
67
+ cli.command :unlink do |c|
68
+ c.desc "Skip confirmation prompt"
69
+ c.switch [:yes, :y], negatable: false
70
+
71
+ c.desc "Physically delete the underlying volume(s)"
72
+ c.switch [:force], negatable: false
73
+
74
+ c.action do |global_options, options, args|
75
+ resource_type = args.shift
76
+ scope = args.shift
77
+
78
+ exit_code = case [resource_type, scope]
79
+ when %w[disk vm]
80
+ Commands::UnlinkDiskVm.execute(args, options, global_options)
81
+ else
82
+ $stderr.puts "Error: Unknown unlink target: #{resource_type} #{scope}".strip
83
+ $stderr.puts "Valid form: unlink disk vm <id> <disk_list>"
84
+ ExitCodes::USAGE_ERROR
85
+ end
86
+
87
+ exit exit_code if exit_code != 0
88
+ end
89
+ end
90
+ end
91
+
92
+ # Executes the unlink disk vm command.
93
+ #
94
+ # @param args [Array<String>] command arguments (VMID, DISK_LIST)
95
+ # @param options [Hash] command options
96
+ # @param global_options [Hash] global CLI options
97
+ # @return [Integer] exit code
98
+ def self.execute(args, options, global_options)
99
+ new(args, options, global_options).execute
100
+ end
101
+
102
+ # @param args [Array<String>] command arguments
103
+ # @param options [Hash] command options
104
+ # @param global_options [Hash] global CLI options
105
+ def initialize(args, options, global_options)
106
+ @args = args
107
+ @options = options
108
+ @global_options = global_options
109
+ end
110
+
111
+ # Executes the unlink disk vm command.
112
+ #
113
+ # @return [Integer] exit code
114
+ def execute
115
+ vmid_arg = @args[0]
116
+ disk_list = @args[1]
117
+
118
+ return usage_error("VMID required") if vmid_arg.nil? || vmid_arg.to_s.empty?
119
+ return usage_error("Disk list required (e.g., scsi1 or scsi1,virtio0)") if disk_list.nil? || disk_list.to_s.empty?
120
+ return usage_error("Invalid VMID: #{vmid_arg}") unless vmid_arg.to_s.match?(/\A\d+\z/)
121
+
122
+ vmid = vmid_arg.to_i
123
+ load_config
124
+ node = resolve_node(vmid)
125
+ return ExitCodes::NOT_FOUND if node.nil?
126
+
127
+ return ExitCodes::SUCCESS unless confirm_operation(vmid, disk_list)
128
+
129
+ repo = build_repository
130
+ service = Pvectl::Services::UnlinkDisk.new(repository: repo)
131
+ result = service.execute(
132
+ vmid: vmid,
133
+ node: node,
134
+ disk_ids: disk_list,
135
+ force: @options[:force] == true
136
+ )
137
+
138
+ output_result(result)
139
+ result.failed? ? ExitCodes::GENERAL_ERROR : ExitCodes::SUCCESS
140
+ rescue Pvectl::Config::ConfigNotFoundError,
141
+ Pvectl::Config::InvalidConfigError,
142
+ Pvectl::Config::ContextNotFoundError,
143
+ Pvectl::Config::ClusterNotFoundError,
144
+ Pvectl::Config::UserNotFoundError
145
+ raise
146
+ rescue StandardError => e
147
+ $stderr.puts "Error: #{e.message}"
148
+ ExitCodes::GENERAL_ERROR
149
+ end
150
+
151
+ private
152
+
153
+ # Loads configuration.
154
+ #
155
+ # @return [void]
156
+ def load_config
157
+ service = Pvectl::Config::Service.new
158
+ service.load(config: @global_options[:config])
159
+ @config = service.current_config
160
+ end
161
+
162
+ # Resolves the node for a VMID.
163
+ #
164
+ # @param vmid [Integer] VM identifier
165
+ # @return [String, nil] node name or nil if not found
166
+ def resolve_node(vmid)
167
+ connection = Pvectl::Connection.new(@config)
168
+ resolver = Pvectl::Utils::ResourceResolver.new(connection)
169
+ resolved = resolver.resolve(vmid)
170
+
171
+ unless resolved && resolved[:type] == :qemu
172
+ $stderr.puts "Error: VM #{vmid} not found"
173
+ return nil
174
+ end
175
+
176
+ resolved[:node]
177
+ end
178
+
179
+ # Builds the VM repository.
180
+ #
181
+ # @return [Repositories::Vm] VM repository
182
+ def build_repository
183
+ connection = Pvectl::Connection.new(@config)
184
+ Pvectl::Repositories::Vm.new(connection)
185
+ end
186
+
187
+ # Confirms the unlink operation with the user, if not auto-approved.
188
+ #
189
+ # @param vmid [Integer] VM identifier
190
+ # @param disk_list [String] comma-separated disk list
191
+ # @return [Boolean] true to proceed, false to cancel
192
+ def confirm_operation(vmid, disk_list)
193
+ return true if @options[:yes]
194
+
195
+ $stdout.puts "About to unlink disk(s) #{disk_list} from VM #{vmid}."
196
+ if @options[:force]
197
+ $stdout.puts ""
198
+ $stdout.puts "WARNING: --force will delete the underlying volume(s) permanently."
199
+ $stdout.puts ""
200
+ end
201
+ $stdout.print "Proceed? [y/N]: "
202
+ $stdout.flush
203
+
204
+ answer = $stdin.gets&.strip&.downcase
205
+ answer == "y"
206
+ end
207
+
208
+ # Outputs the operation result via the configured formatter.
209
+ #
210
+ # @param result [Models::VmOperationResult] operation result
211
+ # @return [void]
212
+ def output_result(result)
213
+ presenter = Pvectl::Presenters::VmOperationResult.new
214
+ format = @global_options[:output] || "table"
215
+ color_flag = @global_options[:color]
216
+
217
+ formatter = Pvectl::Formatters::Registry.for(format)
218
+ output = formatter.format([result], presenter, color: color_flag)
219
+ $stdout.puts output
220
+ end
221
+
222
+ # Outputs a usage error message and returns the corresponding exit code.
223
+ #
224
+ # @param message [String] error message
225
+ # @return [Integer] usage error exit code
226
+ def usage_error(message)
227
+ $stderr.puts "Error: #{message}"
228
+ ExitCodes::USAGE_ERROR
229
+ end
230
+ end
231
+ end
232
+ end
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pvectl
4
+ module Commands
5
+ # VM-specific lifecycle command hooks.
6
+ #
7
+ # Provides VM repository, selector, service, and presenter
8
+ # to ResourceLifecycleCommand's template methods.
9
+ #
10
+ # @example Usage
11
+ # class Start
12
+ # include VmLifecycleCommand
13
+ # OPERATION = :start
14
+ # end
15
+ #
16
+ module VmLifecycleCommand
17
+ include ResourceLifecycleCommand
18
+
19
+ # Extends the including class with ClassMethods from ResourceLifecycleCommand.
20
+ #
21
+ # @param base [Class] the class including this module
22
+ def self.included(base)
23
+ base.extend(ResourceLifecycleCommand::ClassMethods)
24
+ end
25
+
26
+ private
27
+
28
+ # @return [Array<String>] supported resource types
29
+ def supported_resources
30
+ %w[vm]
31
+ end
32
+
33
+ # @return [String] human label for VM resources
34
+ def resource_label
35
+ "VM"
36
+ end
37
+
38
+ # @return [String] human label for VM IDs
39
+ def resource_id_label
40
+ "VMID"
41
+ end
42
+
43
+ # Builds VM repository.
44
+ #
45
+ # @param connection [Connection] API connection
46
+ # @return [Repositories::Vm] VM repository
47
+ def build_repository(connection)
48
+ Pvectl::Repositories::Vm.new(connection)
49
+ end
50
+
51
+ # Builds VM lifecycle service.
52
+ #
53
+ # @param repo [Repositories::Vm] VM repository
54
+ # @param task_repo [Repositories::Task] task repository
55
+ # @param options [Hash] service options
56
+ # @return [Services::VmLifecycle] VM lifecycle service
57
+ def build_service(repo, task_repo, options)
58
+ Pvectl::Services::VmLifecycle.new(repo, task_repo, options)
59
+ end
60
+
61
+ # Builds VM operation result presenter.
62
+ #
63
+ # @return [Presenters::VmOperationResult] presenter
64
+ def build_presenter
65
+ Pvectl::Presenters::VmOperationResult.new
66
+ end
67
+
68
+ # Builds VM selector from strings.
69
+ #
70
+ # @param strings [Array<String>] selector strings
71
+ # @return [Selectors::Vm] parsed selector
72
+ def build_selector(strings)
73
+ Pvectl::Selectors::Vm.parse_all(strings)
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,153 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pvectl
4
+ module Commands
5
+ # Handler for the `pvectl wakeonlan node` command.
6
+ #
7
+ # Sends a Wake-on-LAN magic packet to a cluster node. The target node
8
+ # must have its MAC address registered in the cluster configuration
9
+ # (via `pvecm` or the web UI) — without it, Proxmox cannot assemble
10
+ # the packet and the command returns an error.
11
+ #
12
+ # The Proxmox API returns the MAC address used for the packet on
13
+ # success, which is surfaced in the output for confirmation.
14
+ #
15
+ # @example Wake up a node
16
+ # pvectl wakeonlan node pve3
17
+ #
18
+ # @example JSON output for scripting
19
+ # pvectl wakeonlan node pve3 -o json
20
+ #
21
+ class WakeonlanNode
22
+ # Registers the `wakeonlan node` command with the CLI.
23
+ #
24
+ # @param cli [GLI::App] the CLI application object
25
+ # @return [void]
26
+ def self.register(cli)
27
+ cli.desc "Send Wake-on-LAN packet to a cluster node"
28
+ cli.long_desc <<~HELP
29
+ Trigger a Wake-on-LAN magic packet to a cluster node via the
30
+ Proxmox API (POST /nodes/{node}/wakeonlan).
31
+
32
+ EXAMPLES
33
+ Wake a node:
34
+ $ pvectl wakeonlan node pve3
35
+
36
+ Output as JSON:
37
+ $ pvectl wakeonlan node pve3 -o json
38
+
39
+ NOTES
40
+ The target node must have its MAC address registered in the
41
+ cluster configuration beforehand. Use `pvecm` or the Proxmox
42
+ web UI to set the MAC for each node before relying on WoL.
43
+
44
+ The packet is sent by another online node in the cluster, so
45
+ at least one other node must be reachable.
46
+
47
+ SEE ALSO
48
+ pvectl help get nodes List nodes and current status
49
+ pvectl help describe node Show node details
50
+ HELP
51
+ cli.arg_name "RESOURCE_TYPE NAME"
52
+ cli.command :wakeonlan do |c|
53
+ c.action do |global_options, _options, args|
54
+ resource_type = args.shift
55
+ resource_name = args.shift
56
+
57
+ unless resource_type == "node"
58
+ $stderr.puts "Error: Only `pvectl wakeonlan node <NAME>` is supported"
59
+ exit Pvectl::ExitCodes::USAGE_ERROR
60
+ end
61
+
62
+ cmd = WakeonlanNode.new(resource_name, {}, global_options)
63
+ exit_code = cmd.execute
64
+ exit exit_code if exit_code != 0
65
+ end
66
+ end
67
+ end
68
+
69
+ # Creates a new command instance.
70
+ #
71
+ # @param node_name [String, nil] target node name
72
+ # @param options [Hash] command options
73
+ # @param global_options [Hash] global CLI options
74
+ # @param service [Services::Wakeonlan, nil] injected service (testing)
75
+ def initialize(node_name, options, global_options, service: nil)
76
+ @node_name = node_name
77
+ @options = options
78
+ @global_options = global_options
79
+ @service = service
80
+ end
81
+
82
+ # Executes the wakeonlan command.
83
+ #
84
+ # @return [Integer] exit code
85
+ def execute
86
+ return usage_error("NODE name is required") if @node_name.nil? || @node_name.to_s.empty?
87
+
88
+ result = service.execute(node_name: @node_name)
89
+ render(result)
90
+
91
+ result.successful? ? ExitCodes::SUCCESS : ExitCodes::GENERAL_ERROR
92
+ rescue Pvectl::Config::ConfigNotFoundError,
93
+ Pvectl::Config::InvalidConfigError,
94
+ Pvectl::Config::ContextNotFoundError,
95
+ Pvectl::Config::ClusterNotFoundError,
96
+ Pvectl::Config::UserNotFoundError
97
+ raise
98
+ rescue Timeout::Error, Errno::ECONNREFUSED, SocketError => e
99
+ $stderr.puts "Error: #{e.message}"
100
+ ExitCodes::CONNECTION_ERROR
101
+ rescue StandardError => e
102
+ $stderr.puts "Error: #{e.message}"
103
+ ExitCodes::GENERAL_ERROR
104
+ end
105
+
106
+ private
107
+
108
+ # Lazily builds the Wakeonlan service from current config.
109
+ #
110
+ # @return [Services::Wakeonlan]
111
+ def service
112
+ @service ||= begin
113
+ config_service = Pvectl::Config::Service.new
114
+ config_service.load(config: @global_options[:config])
115
+ connection = Pvectl::Connection.new(config_service.current_config)
116
+ node_repo = Pvectl::Repositories::Node.new(connection)
117
+ Pvectl::Services::Wakeonlan.new(node_repository: node_repo)
118
+ end
119
+ end
120
+
121
+ # Renders the operation result using the requested output format.
122
+ #
123
+ # @param result [Models::NodeOperationResult]
124
+ # @return [void]
125
+ def render(result)
126
+ format = @global_options[:output] || "table"
127
+ presenter = Pvectl::Presenters::NodeOperationResult.new
128
+ formatter = Pvectl::Formatters::Registry.for(format)
129
+ puts formatter.format([result], presenter, color_enabled: color_enabled)
130
+ end
131
+
132
+ # Determines color output based on global flag and TTY status.
133
+ #
134
+ # @return [Boolean]
135
+ def color_enabled
136
+ explicit = @global_options[:color]
137
+ return explicit unless explicit.nil?
138
+
139
+ $stdout.tty?
140
+ end
141
+
142
+ # Outputs a usage error and returns the proper exit code.
143
+ #
144
+ # @param msg [String]
145
+ # @return [Integer]
146
+ def usage_error(msg)
147
+ $stderr.puts "Error: #{msg}"
148
+ $stderr.puts "Usage: pvectl wakeonlan node <NODE>"
149
+ ExitCodes::USAGE_ERROR
150
+ end
151
+ end
152
+ end
153
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pvectl
4
+ module Config
5
+ # Base error class for all configuration-related exceptions.
6
+ #
7
+ # All configuration errors inherit from this class, allowing
8
+ # consumers to catch all config errors with a single rescue clause.
9
+ #
10
+ # @example Catching all configuration errors
11
+ # begin
12
+ # service.load(config: path)
13
+ # rescue Pvectl::Config::ConfigError => e
14
+ # $stderr.puts "Configuration error: #{e.message}"
15
+ # exit Pvectl::ExitCodes::CONFIG_ERROR
16
+ # end
17
+ #
18
+ class ConfigError < StandardError; end
19
+
20
+ # Raised when the configuration file cannot be found at the specified path.
21
+ #
22
+ # @example
23
+ # raise ConfigNotFoundError, "Configuration file not found: ~/.pvectl/config"
24
+ #
25
+ class ConfigNotFoundError < ConfigError; end
26
+
27
+ # Raised when the configuration file contains invalid YAML or structure.
28
+ #
29
+ # @example
30
+ # raise InvalidConfigError, "Invalid YAML syntax at line 5"
31
+ #
32
+ class InvalidConfigError < ConfigError; end
33
+
34
+ # Raised when a referenced context does not exist in the configuration.
35
+ #
36
+ # @example
37
+ # raise ContextNotFoundError, "Context 'production' not found"
38
+ #
39
+ class ContextNotFoundError < ConfigError; end
40
+
41
+ # Raised when a context references a cluster that does not exist.
42
+ #
43
+ # @example
44
+ # raise ClusterNotFoundError, "Cluster 'main' not found in configuration"
45
+ #
46
+ class ClusterNotFoundError < ConfigError; end
47
+
48
+ # Raised when a context references a user that does not exist.
49
+ #
50
+ # @example
51
+ # raise UserNotFoundError, "User 'admin' not found in configuration"
52
+ #
53
+ class UserNotFoundError < ConfigError; end
54
+
55
+ # Raised when no valid credentials are available for authentication.
56
+ #
57
+ # @example
58
+ # raise MissingCredentialsError, "No valid credentials found. Provide token or username/password"
59
+ #
60
+ class MissingCredentialsError < ConfigError; end
61
+ end
62
+ end