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,129 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pvectl
4
+ module Commands
5
+ module Get
6
+ # Handles continuous watch mode for the get command.
7
+ #
8
+ # Repeatedly executes a block at specified intervals, clearing the
9
+ # screen between iterations when running in a TTY. Handles SIGINT
10
+ # for graceful termination.
11
+ #
12
+ # @example Basic usage
13
+ # loop = WatchLoop.new(interval: 5)
14
+ # loop.run { puts Time.now }
15
+ #
16
+ # @example With custom interval (clamped to minimum)
17
+ # loop = WatchLoop.new(interval: 0.5) # Will be clamped to 1 second
18
+ #
19
+ class WatchLoop
20
+ # Default refresh interval in seconds.
21
+ DEFAULT_INTERVAL = 2
22
+
23
+ # Minimum allowed refresh interval in seconds.
24
+ MIN_INTERVAL = 1
25
+
26
+ # @return [Integer] the effective refresh interval
27
+ attr_reader :interval
28
+
29
+ # Creates a new WatchLoop.
30
+ #
31
+ # @param interval [Integer, Float] refresh interval in seconds
32
+ # Values below MIN_INTERVAL are automatically clamped.
33
+ # @param output [IO] output stream for TTY detection (default: $stdout)
34
+ def initialize(interval: DEFAULT_INTERVAL, output: $stdout)
35
+ @interval = [interval.to_i, MIN_INTERVAL].max
36
+ @output = output
37
+ @running = false
38
+ end
39
+
40
+ # Runs the watch loop, executing the block repeatedly.
41
+ #
42
+ # @yield Block to execute on each iteration
43
+ # @return [void]
44
+ #
45
+ # @example
46
+ # loop.run do
47
+ # models = handler.list
48
+ # puts format(models)
49
+ # end
50
+ def run
51
+ @running = true
52
+ setup_signal_handler
53
+
54
+ while @running
55
+ clear_screen if tty?
56
+ print_header if tty?
57
+ yield
58
+ sleep_interruptible(@interval)
59
+ end
60
+ end
61
+
62
+ # Stops the watch loop.
63
+ #
64
+ # @return [void]
65
+ def stop
66
+ @running = false
67
+ end
68
+
69
+ # Checks if the loop is currently running.
70
+ #
71
+ # @return [Boolean] true if running
72
+ def running?
73
+ @running
74
+ end
75
+
76
+ private
77
+
78
+ # Clears the terminal screen using ANSI escape codes.
79
+ #
80
+ # @return [void]
81
+ def clear_screen
82
+ @output.print "\e[H\e[2J"
83
+ end
84
+
85
+ # Prints the watch mode header with timestamp.
86
+ #
87
+ # @return [void]
88
+ def print_header
89
+ timestamp = Time.now.strftime("%Y-%m-%d %H:%M:%S")
90
+ @output.puts "Every #{@interval}s: #{timestamp}"
91
+ @output.puts
92
+ end
93
+
94
+ # Checks if output is a TTY.
95
+ #
96
+ # @return [Boolean] true if output is a TTY
97
+ def tty?
98
+ @output.tty?
99
+ end
100
+
101
+ # Sets up SIGINT handler for graceful termination.
102
+ #
103
+ # @return [void]
104
+ def setup_signal_handler
105
+ @previous_handler = trap("INT") do
106
+ stop
107
+ # Don't print newline in non-TTY mode to avoid extra output
108
+ @output.puts if tty?
109
+ end
110
+ end
111
+
112
+ # Sleeps for the specified duration, allowing interruption.
113
+ #
114
+ # Uses small sleep intervals to allow quick response to stop signal.
115
+ #
116
+ # @param duration [Integer] total sleep duration in seconds
117
+ # @return [void]
118
+ def sleep_interruptible(duration)
119
+ remaining = duration
120
+ while remaining > 0 && @running
121
+ chunk = [remaining, 0.1].min
122
+ sleep(chunk)
123
+ remaining -= chunk
124
+ end
125
+ end
126
+ end
127
+ end
128
+ end
129
+ end
@@ -0,0 +1,265 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pvectl
4
+ module Commands
5
+ # Shared functionality for irreversible batch commands (delete, template).
6
+ #
7
+ # Provides the generic flow: resolve resources → confirm → execute → format results.
8
+ # Submodules override hooks to customize behavior per operation.
9
+ #
10
+ # @example Including in an operation-specific module
11
+ # module TemplateCommand
12
+ # include IrreversibleCommand
13
+ #
14
+ # def self.included(base)
15
+ # base.extend(IrreversibleCommand::ClassMethods)
16
+ # end
17
+ # end
18
+ #
19
+ module IrreversibleCommand
20
+ # Class methods added when the module is included.
21
+ module ClassMethods
22
+ # Executes the command.
23
+ #
24
+ # @param resource_type [String, nil] resource type (vm, container)
25
+ # @param resource_ids [Array<String>, String, nil] resource identifiers
26
+ # @param options [Hash] command options
27
+ # @param global_options [Hash] global CLI options
28
+ # @return [Integer] exit code
29
+ def execute(resource_type, resource_ids, options, global_options)
30
+ new(resource_type, resource_ids, options, global_options).execute
31
+ end
32
+ end
33
+
34
+ # Hook called when module is included.
35
+ #
36
+ # @param base [Module] the module or class including this module
37
+ def self.included(base)
38
+ base.extend(ClassMethods)
39
+ end
40
+
41
+ # Initializes an irreversible command.
42
+ #
43
+ # @param resource_type [String, nil] resource type (vm, container)
44
+ # @param resource_ids [Array<String>, String, nil] resource identifiers
45
+ # @param options [Hash] command options
46
+ # @param global_options [Hash] global CLI options
47
+ def initialize(resource_type, resource_ids, options, global_options)
48
+ @resource_type = resource_type
49
+ @resource_ids = Array(resource_ids).compact
50
+ @options = options
51
+ @global_options = global_options
52
+ end
53
+
54
+ # Executes the command.
55
+ #
56
+ # @return [Integer] exit code
57
+ def execute
58
+ return usage_error("Resource type required (#{supported_types.join(', ')})") unless @resource_type
59
+ return usage_error("Unsupported resource: #{@resource_type}") unless supported_resource?
60
+
61
+ if @resource_ids.empty? && !@options[:all] && selector_strings.empty?
62
+ return usage_error("VMID, --all, or -l selector required")
63
+ end
64
+
65
+ perform_operation
66
+ end
67
+
68
+ private
69
+
70
+ # Returns the resource type symbol (:vm or :container).
71
+ #
72
+ # @return [Symbol] resource type
73
+ def resource_type_symbol
74
+ self.class::RESOURCE_TYPE
75
+ end
76
+
77
+ # Returns supported resource type strings.
78
+ #
79
+ # @return [Array<String>] supported types
80
+ def supported_types
81
+ self.class::SUPPORTED_RESOURCES
82
+ end
83
+
84
+ # Checks if resource type is supported.
85
+ #
86
+ # @return [Boolean] true if supported
87
+ def supported_resource?
88
+ supported_types.include?(@resource_type)
89
+ end
90
+
91
+ # Performs the operation. Override in submodule for custom flow.
92
+ #
93
+ # @return [Integer] exit code
94
+ def perform_operation
95
+ load_config
96
+ connection = Pvectl::Connection.new(@config)
97
+
98
+ resources = resolve_resources(connection)
99
+ return no_resources_found if resources.empty?
100
+ return ExitCodes::SUCCESS unless confirm_operation(resources)
101
+
102
+ results = perform_service_call(resources, connection)
103
+ output_results(results)
104
+ determine_exit_code(results)
105
+ rescue Pvectl::Config::ConfigNotFoundError,
106
+ Pvectl::Config::InvalidConfigError,
107
+ Pvectl::Config::ContextNotFoundError,
108
+ Pvectl::Config::ClusterNotFoundError,
109
+ Pvectl::Config::UserNotFoundError
110
+ raise
111
+ rescue StandardError => e
112
+ $stderr.puts "Error: #{e.message}"
113
+ ExitCodes::GENERAL_ERROR
114
+ end
115
+
116
+ # Resolves resources based on IDs, --all flag, or selectors.
117
+ #
118
+ # @param connection [Connection] API connection
119
+ # @return [Array<Models::Vm, Models::Container>] resolved resources
120
+ def resolve_resources(connection)
121
+ repo = build_repository(connection)
122
+
123
+ resources = if @options[:all]
124
+ repo.list(node: @options[:node])
125
+ elsif @resource_ids.any?
126
+ resolved = @resource_ids.map { |id| repo.get(id.to_i) }.compact
127
+ resolved = resolved.select { |r| r.node == @options[:node] } if @options[:node]
128
+ resolved
129
+ else
130
+ return [] if selector_strings.empty?
131
+ repo.list(node: @options[:node])
132
+ end
133
+
134
+ apply_selectors(resources)
135
+ end
136
+
137
+ # Builds the appropriate repository for the resource type.
138
+ #
139
+ # @param connection [Connection] API connection
140
+ # @return [Repositories::Base] repository
141
+ def build_repository(connection)
142
+ if resource_type_symbol == :vm
143
+ Pvectl::Repositories::Vm.new(connection)
144
+ else
145
+ Pvectl::Repositories::Container.new(connection)
146
+ end
147
+ end
148
+
149
+ # Returns selector strings from options.
150
+ #
151
+ # @return [Array<String>] selector strings
152
+ def selector_strings
153
+ Array(@options[:selector] || @options[:l])
154
+ end
155
+
156
+ # Applies selectors to resource collection.
157
+ #
158
+ # @param resources [Array] Resources to filter
159
+ # @return [Array] Filtered resources
160
+ def apply_selectors(resources)
161
+ return resources if selector_strings.empty?
162
+
163
+ selector_class = resource_type_symbol == :vm ? Selectors::Vm : Selectors::Container
164
+ selector = selector_class.parse_all(selector_strings)
165
+ selector.apply(resources)
166
+ end
167
+
168
+ # Confirms the operation with the user.
169
+ # Override in submodule for custom confirmation logic.
170
+ #
171
+ # @param resources [Array] Resources to operate on
172
+ # @return [Boolean] true if operation should proceed
173
+ def confirm_operation(resources)
174
+ raise NotImplementedError, "#{self.class} must implement #confirm_operation"
175
+ end
176
+
177
+ # Performs the actual service call for the operation.
178
+ # Must be implemented by the including module.
179
+ #
180
+ # @param resources [Array] resources to operate on
181
+ # @param connection [Connection] API connection
182
+ # @return [Array<Models::OperationResult>] results
183
+ def perform_service_call(resources, connection)
184
+ raise NotImplementedError, "#{self.class} must implement #perform_service_call"
185
+ end
186
+
187
+ # Builds the presenter for results output.
188
+ # Must be implemented by the including module.
189
+ #
190
+ # @return [Presenters::Base] presenter
191
+ def build_presenter
192
+ raise NotImplementedError, "#{self.class} must implement #build_presenter"
193
+ end
194
+
195
+ # Loads configuration from file or environment.
196
+ #
197
+ # @return [void]
198
+ def load_config
199
+ service = Pvectl::Config::Service.new
200
+ service.load(config: @global_options[:config])
201
+ @config = service.current_config
202
+ end
203
+
204
+ # Builds base service options from command options.
205
+ #
206
+ # @return [Hash] service options
207
+ def service_options
208
+ opts = {}
209
+ opts[:timeout] = @options[:timeout] if @options[:timeout]
210
+ opts[:async] = true if @options[:async]
211
+ opts[:force] = true if @options[:force]
212
+ opts[:fail_fast] = true if @options[:"fail-fast"]
213
+ opts
214
+ end
215
+
216
+ # Outputs operation results using the configured formatter.
217
+ #
218
+ # @param results [Array<Models::OperationResult>] operation results
219
+ # @return [void]
220
+ def output_results(results)
221
+ presenter = build_presenter
222
+ format = @global_options[:output] || "table"
223
+ color_flag = @global_options[:color]
224
+
225
+ formatter = Pvectl::Formatters::Registry.for(format)
226
+ output = formatter.format(results, presenter, color: color_flag)
227
+ puts output
228
+ end
229
+
230
+ # Determines exit code based on results.
231
+ #
232
+ # @param results [Array<Models::OperationResult>] operation results
233
+ # @return [Integer] exit code
234
+ def determine_exit_code(results)
235
+ return ExitCodes::SUCCESS if results.all?(&:successful?)
236
+ return ExitCodes::SUCCESS if results.all?(&:pending?)
237
+
238
+ ExitCodes::GENERAL_ERROR
239
+ end
240
+
241
+ # Outputs usage error and returns exit code.
242
+ #
243
+ # @param message [String] error message
244
+ # @return [Integer] exit code
245
+ def usage_error(message)
246
+ $stderr.puts "Error: #{message}"
247
+ ExitCodes::USAGE_ERROR
248
+ end
249
+
250
+ # Outputs no resources found error and returns exit code.
251
+ #
252
+ # @return [Integer] exit code
253
+ def no_resources_found
254
+ type_plural = resource_type_symbol == :vm ? "VMs" : "containers"
255
+ msg = if @options[:all] || selector_strings.any?
256
+ @options[:node] ? "No #{type_plural} found on node #{@options[:node]}" : "No #{type_plural} found matching criteria"
257
+ else
258
+ "No #{type_plural} found for given IDs"
259
+ end
260
+ $stderr.puts "Error: #{msg}"
261
+ ExitCodes::NOT_FOUND
262
+ end
263
+ end
264
+ end
265
+ end
@@ -0,0 +1,275 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pvectl
4
+ module Commands
5
+ module Logs
6
+ # Dispatcher for the `pvectl logs <resource_type> <id>` command.
7
+ #
8
+ # Routes requests to appropriate log handlers:
9
+ # - vm/ct: task history via Handlers::TaskLogs
10
+ # - node: syslog via Handlers::Syslog (or journal with --journal)
11
+ # - task: log lines via Handlers::TaskDetail
12
+ #
13
+ # @example Basic usage
14
+ # Commands::Logs::Command.execute("vm", "100", options, global_options)
15
+ #
16
+ class Command
17
+ # VM/CT resource types that use TaskLogs handler.
18
+ VM_CT_TYPES = %w[vm vms ct container containers cts].freeze
19
+
20
+ # Node resource types.
21
+ NODE_TYPES = %w[node nodes].freeze
22
+
23
+ # Registers the logs command with the CLI.
24
+ #
25
+ # @param cli [GLI::App] the CLI application object
26
+ # @return [void]
27
+ def self.register(cli)
28
+ cli.desc "Show logs for resources (task history, syslog, journal)"
29
+ cli.long_desc <<~HELP
30
+ Show logs and task history for cluster resources. The log source
31
+ depends on the resource type:
32
+
33
+ vm / container Task history (start, stop, backup, migrate, etc.)
34
+ node System log (syslog by default, --journal for systemd)
35
+ task Detailed log output for a specific task UPID
36
+
37
+ EXAMPLES
38
+ Node syslog (last 50 entries):
39
+ $ pvectl logs node pve1
40
+
41
+ Node systemd journal:
42
+ $ pvectl logs node pve1 --journal
43
+
44
+ Task history for a VM:
45
+ $ pvectl logs vm 100
46
+
47
+ Search VM tasks across all nodes:
48
+ $ pvectl logs vm 100 --all-nodes
49
+
50
+ Filter by task type and date:
51
+ $ pvectl logs vm 100 --type vzdump --since 2026-01-01
52
+
53
+ Detailed log for a specific task:
54
+ $ pvectl logs task UPID:pve1:000ABC:...
55
+
56
+ Filter node syslog by service:
57
+ $ pvectl logs node pve1 --service pvedaemon
58
+
59
+ Increase entry limit:
60
+ $ pvectl logs node pve1 --limit 200
61
+
62
+ NOTES
63
+ Default limit is 50 entries. Task detail (UPID) defaults to 512 lines.
64
+
65
+ --all-nodes searches across every cluster node for VM/CT tasks.
66
+ This is useful when a VM has been migrated between nodes.
67
+
68
+ Timestamps for --since and --until accept YYYY-MM-DD format
69
+ or Unix epoch seconds.
70
+
71
+ SEE ALSO
72
+ pvectl help get tasks List task history cluster-wide
73
+ pvectl help describe Detailed resource information
74
+ HELP
75
+ cli.arg_name "RESOURCE_TYPE ID"
76
+ cli.command :logs do |c|
77
+ c.desc "Maximum number of entries to show"
78
+ c.default_value 50
79
+ c.flag [:limit], type: Integer, arg_name: "N"
80
+
81
+ c.desc "Show entries since timestamp (YYYY-MM-DD or epoch)"
82
+ c.flag [:since], arg_name: "TIMESTAMP"
83
+
84
+ c.desc "Show entries until timestamp (YYYY-MM-DD or epoch)"
85
+ c.flag [:until], arg_name: "TIMESTAMP"
86
+
87
+ c.desc "Filter by task type (e.g., qmstart, qmstop, vzdump)"
88
+ c.flag [:type], arg_name: "TYPE"
89
+
90
+ c.desc "Filter by status (running, ok, error)"
91
+ c.flag [:status], arg_name: "STATUS"
92
+
93
+ c.desc "Filter by service name (syslog only)"
94
+ c.flag [:service], arg_name: "SERVICE"
95
+
96
+ c.desc "Use systemd journal instead of syslog (node only)"
97
+ c.switch [:journal], negatable: false
98
+
99
+ c.desc "Search across all cluster nodes (VM/CT only)"
100
+ c.switch [:"all-nodes"], negatable: false
101
+
102
+ c.action do |global_options, options, args|
103
+ resource_type = args[0]
104
+ resource_id = args[1]
105
+ exit_code = execute(resource_type, resource_id, options, global_options)
106
+ exit exit_code if exit_code != 0
107
+ end
108
+ end
109
+ end
110
+
111
+ # Executes the logs command.
112
+ #
113
+ # @param resource_type [String, nil] type (vm, ct, node, task)
114
+ # @param resource_id [String, nil] identifier (VMID, node name, UPID)
115
+ # @param options [Hash] command options
116
+ # @param global_options [Hash] global CLI options
117
+ # @return [Integer] exit code
118
+ def self.execute(resource_type, resource_id, options, global_options)
119
+ new(resource_type, resource_id, options, global_options).execute
120
+ end
121
+
122
+ # Creates a new Logs command instance.
123
+ #
124
+ # @param resource_type [String, nil] type of resource
125
+ # @param resource_id [String, nil] resource identifier
126
+ # @param options [Hash] command options
127
+ # @param global_options [Hash] global CLI options
128
+ # @param handler [Object, nil] override handler for testing
129
+ # @param journal_handler [Object, nil] override journal handler for testing
130
+ # @param registry [Class] registry for handler lookup
131
+ def initialize(resource_type, resource_id, options, global_options,
132
+ handler: nil, journal_handler: nil,
133
+ registry: Logs::ResourceRegistry)
134
+ @resource_type = resource_type
135
+ @resource_id = resource_id
136
+ @options = options
137
+ @global_options = global_options
138
+ @handler = handler
139
+ @journal_handler = journal_handler
140
+ @registry = registry
141
+ end
142
+
143
+ # Executes the logs operation.
144
+ #
145
+ # @return [Integer] exit code
146
+ def execute
147
+ return missing_resource_type_error if @resource_type.nil?
148
+ return missing_resource_id_error if @resource_id.nil? || @resource_id.empty?
149
+
150
+ handler = resolve_handler
151
+ return unknown_resource_error unless handler
152
+
153
+ models = fetch_data(handler)
154
+ output = format_output(models, handler.presenter)
155
+ puts output
156
+
157
+ ExitCodes::SUCCESS
158
+ rescue ResourceNotFoundError => e
159
+ $stderr.puts "Error: #{e.message}"
160
+ ExitCodes::NOT_FOUND
161
+ rescue Pvectl::Config::ConfigNotFoundError,
162
+ Pvectl::Config::InvalidConfigError,
163
+ Pvectl::Config::ContextNotFoundError,
164
+ Pvectl::Config::ClusterNotFoundError,
165
+ Pvectl::Config::UserNotFoundError => e
166
+ $stderr.puts "Error: #{e.message}"
167
+ ExitCodes::CONFIG_ERROR
168
+ rescue Timeout::Error, Errno::ECONNREFUSED, SocketError => e
169
+ $stderr.puts "Error: #{e.message}"
170
+ ExitCodes::CONNECTION_ERROR
171
+ end
172
+
173
+ private
174
+
175
+ # Resolves the handler based on resource type and flags.
176
+ #
177
+ # @return [Object, nil] handler instance
178
+ def resolve_handler
179
+ return @handler if @handler
180
+
181
+ if NODE_TYPES.include?(@resource_type) && @options[:journal]
182
+ @journal_handler || Handlers::Journal.new
183
+ else
184
+ @registry.for(@resource_type)
185
+ end
186
+ end
187
+
188
+ # Fetches data from the handler with appropriate parameters.
189
+ #
190
+ # @param handler [Object] the resolved handler
191
+ # @return [Array<Object>] models
192
+ def fetch_data(handler)
193
+ if VM_CT_TYPES.include?(@resource_type)
194
+ handler.list(
195
+ vmid: @resource_id.to_i,
196
+ resource_type: @resource_type,
197
+ all_nodes: @options[:"all-nodes"] || false,
198
+ limit: @options[:limit] || 50,
199
+ since: @options[:since],
200
+ until_time: @options[:until],
201
+ type_filter: @options[:type],
202
+ status_filter: @options[:status]
203
+ )
204
+ elsif NODE_TYPES.include?(@resource_type)
205
+ handler.list(
206
+ node: @resource_id,
207
+ limit: @options[:limit] || 50,
208
+ since: @options[:since],
209
+ until_time: @options[:until],
210
+ service: @options[:service]
211
+ )
212
+ elsif @resource_type == "task"
213
+ handler.list(
214
+ upid: @resource_id,
215
+ start: 0,
216
+ limit: @options[:limit] || 512
217
+ )
218
+ else
219
+ handler.list
220
+ end
221
+ end
222
+
223
+ # Outputs error for missing resource type argument.
224
+ #
225
+ # @return [Integer] USAGE_ERROR exit code
226
+ def missing_resource_type_error
227
+ $stderr.puts "Error: resource type is required"
228
+ $stderr.puts "Usage: pvectl logs RESOURCE_TYPE ID [options]"
229
+ $stderr.puts "Available resources: vm, ct, node, task"
230
+ ExitCodes::USAGE_ERROR
231
+ end
232
+
233
+ # Outputs error for missing resource ID argument.
234
+ #
235
+ # @return [Integer] USAGE_ERROR exit code
236
+ def missing_resource_id_error
237
+ $stderr.puts "Error: resource ID is required"
238
+ $stderr.puts "Usage: pvectl logs #{@resource_type} ID [options]"
239
+ ExitCodes::USAGE_ERROR
240
+ end
241
+
242
+ # Outputs error for unknown resource type.
243
+ #
244
+ # @return [Integer] USAGE_ERROR exit code
245
+ def unknown_resource_error
246
+ $stderr.puts "Unknown resource type: #{@resource_type}"
247
+ $stderr.puts "Available resources: vm, ct, node, task"
248
+ ExitCodes::USAGE_ERROR
249
+ end
250
+
251
+ # Formats models for output using the appropriate formatter.
252
+ #
253
+ # @param models [Array<Object>] collection of models
254
+ # @param presenter [Presenters::Base] presenter for the resource type
255
+ # @return [String] formatted output
256
+ def format_output(models, presenter)
257
+ format = @global_options[:output] || "table"
258
+ color_enabled = determine_color_enabled
259
+ formatter = Formatters::Registry.for(format)
260
+ formatter.format(models, presenter, color_enabled: color_enabled)
261
+ end
262
+
263
+ # Determines if color output should be enabled.
264
+ #
265
+ # @return [Boolean] true if color should be enabled
266
+ def determine_color_enabled
267
+ explicit = @global_options[:color]
268
+ return explicit unless explicit.nil?
269
+
270
+ $stdout.tty?
271
+ end
272
+ end
273
+ end
274
+ end
275
+ end