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,352 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pvectl
4
+ module Commands
5
+ # Push command -- applies YAML manifests to the Proxmox cluster.
6
+ # Creates resources that don't exist, updates those that do.
7
+ #
8
+ # @example Register with CLI
9
+ # Push.register(cli)
10
+ class Push
11
+ RESOURCE_TYPES = {
12
+ "vm" => :vm,
13
+ "vms" => :vm,
14
+ "container" => :container,
15
+ "containers" => :container,
16
+ "ct" => :container
17
+ }.freeze
18
+
19
+ # Registers the push command with the CLI.
20
+ #
21
+ # @param cli [GLI::App] the CLI application object
22
+ # @return [void]
23
+ def self.register(cli)
24
+ cli.desc "Push YAML manifest to cluster (create or update)"
25
+ cli.long_desc <<~HELP
26
+ DESCRIPTION
27
+ Applies resource configuration from YAML manifest files to the
28
+ Proxmox cluster. Creates resources that don't exist, updates those
29
+ that do. Shows a diff and asks for confirmation before applying.
30
+
31
+ EXAMPLES
32
+ $ pvectl push vm -f vm-100.yaml
33
+ $ pvectl push vm -f ./manifests/
34
+ $ pvectl push -f ./manifests/
35
+ $ pvectl push vm -f vm-100.yaml --dry-run
36
+ $ pvectl push vm -f vm-100.yaml --yes
37
+ $ pvectl push vm -f vm-new.yaml # no vmid → auto-assign
38
+ $ pvectl pull vm 100 | pvectl push --yes
39
+ $ cat vm-100.yaml | pvectl push vm --dry-run
40
+
41
+ NOTES
42
+ Without -f, reads YAML from stdin (pipe-friendly).
43
+ With -f, reads from file or directory (repeatable).
44
+ Without resource type, reads kind from each manifest.
45
+ If metadata.vmid is omitted, a new VMID is auto-assigned
46
+ and the source YAML file is updated with the assigned ID.
47
+ Stdin mode requires --yes or --dry-run (no interactive prompt).
48
+ With --yes, skips confirmation (useful for CI/CD).
49
+ With --dry-run, shows diff without applying changes.
50
+
51
+ SEE ALSO
52
+ pull, edit, create, delete
53
+ HELP
54
+
55
+ cli.command :push do |c|
56
+ c.flag [:f, :file], desc: "YAML file or directory to push", multiple: true
57
+ c.switch [:y, :yes], desc: "Auto-confirm without prompting", negatable: false
58
+ c.switch [:"dry-run"], desc: "Show diff without applying", negatable: false
59
+
60
+ c.action do |global_options, options, args|
61
+ Push.new(args, options, global_options).execute
62
+ end
63
+ end
64
+ end
65
+
66
+ # @param args [Array<String>] command arguments
67
+ # @param options [Hash] command options
68
+ # @param global_options [Hash] global CLI options
69
+ def initialize(args, options, global_options)
70
+ @args = args
71
+ @options = options
72
+ @global_options = global_options
73
+ @stdin_mode = false
74
+ end
75
+
76
+ # Executes the push command.
77
+ #
78
+ # @return [Integer] exit code
79
+ def execute
80
+ args = @args.dup
81
+ filter_type = parse_resource_type(args)
82
+
83
+ unless args.empty?
84
+ return usage_error("Unexpected arguments: #{args.join(', ')}. Use -f to specify files.")
85
+ end
86
+
87
+ yaml_contents = read_input
88
+ return usage_error("No YAML content provided. Use -f <path> or pipe YAML to stdin.") if yaml_contents.empty?
89
+
90
+ load_config
91
+ connection = Pvectl::Connection.new(@config)
92
+ service, pull_service = build_services(connection)
93
+
94
+ result = service.prepare_batch(yaml_contents, filter_type: filter_type)
95
+
96
+ # Report errors and skipped
97
+ result[:errors].each { |e| $stderr.puts "Error: #{e}" }
98
+ result[:skipped].each { |s| $stderr.puts "Info: #{s}" }
99
+
100
+ if result[:plans].empty?
101
+ if result[:errors].empty?
102
+ $stdout.puts "No changes to apply."
103
+ end
104
+
105
+ # Refresh unchanged file-backed manifests with server-assigned values
106
+ # (MAC addresses, volume names, UUIDs, etc.)
107
+ refresh_unchanged_manifests(result[:unchanged] || [], pull_service)
108
+
109
+ return result[:errors].empty? ? ExitCodes::SUCCESS : ExitCodes::GENERAL_ERROR
110
+ end
111
+
112
+ # Display plans
113
+ display_plans(result[:plans])
114
+
115
+ if @options[:"dry-run"]
116
+ $stdout.puts "\n(dry-run mode -- no changes applied)"
117
+ return ExitCodes::SUCCESS
118
+ end
119
+
120
+ # Confirm unless --yes
121
+ unless @options[:yes]
122
+ if @stdin_mode
123
+ return usage_error("Stdin mode requires --yes or --dry-run (no interactive prompt available)")
124
+ end
125
+ $stdout.print "\nApply #{result[:plans].length} change(s)? [y/N] "
126
+ answer = $stdin.gets&.strip&.downcase
127
+ unless answer == "y" || answer == "yes"
128
+ $stdout.puts "Cancelled."
129
+ return ExitCodes::SUCCESS
130
+ end
131
+ end
132
+
133
+ # Apply
134
+ apply_result = service.apply(result[:plans])
135
+
136
+ apply_result[:results].each do |r|
137
+ if r[:success]
138
+ $stdout.puts "#{r[:action].capitalize}d #{type_label_for(r)} #{r[:vmid]} successfully."
139
+ else
140
+ $stderr.puts "Error: Failed to #{r[:action]} #{r[:vmid]}: #{r[:error]}"
141
+ end
142
+ end
143
+
144
+ # Refresh source manifest files with current server state
145
+ refresh_manifests(apply_result[:results], result[:plans], pull_service)
146
+ refresh_unchanged_manifests(result[:unchanged] || [], pull_service)
147
+
148
+ apply_result[:errors].empty? ? ExitCodes::SUCCESS : ExitCodes::GENERAL_ERROR
149
+ rescue Pvectl::Config::ConfigNotFoundError,
150
+ Pvectl::Config::InvalidConfigError,
151
+ Pvectl::Config::ContextNotFoundError,
152
+ Pvectl::Config::ClusterNotFoundError,
153
+ Pvectl::Config::UserNotFoundError
154
+ raise
155
+ rescue StandardError => e
156
+ $stderr.puts "Error: #{e.message}"
157
+ ExitCodes::GENERAL_ERROR
158
+ end
159
+
160
+ private
161
+
162
+ # Reads YAML input from -f flag (files/directories) or stdin.
163
+ #
164
+ # @return [Array<Hash>] array of { filename: String, content: String }
165
+ def read_input
166
+ file_flag = @options[:file]
167
+ if file_flag && !file_flag.empty?
168
+ @stdin_mode = false
169
+ collect_yaml_contents(Array(file_flag))
170
+ else
171
+ @stdin_mode = true
172
+ read_stdin
173
+ end
174
+ end
175
+
176
+ # Reads YAML content from stdin.
177
+ #
178
+ # @return [Array<Hash>] array of { filename: String, content: String }
179
+ def read_stdin
180
+ if $stdin.tty?
181
+ $stderr.puts "Error: No input. Use -f <path> or pipe YAML to stdin."
182
+ return []
183
+ end
184
+ content = $stdin.read
185
+ return [] if content.nil? || content.strip.empty?
186
+
187
+ [{ filename: "stdin", content: content }]
188
+ end
189
+
190
+ # Parses and removes the optional resource type from the argument list.
191
+ #
192
+ # @param args [Array<String>] argument list (modified in place)
193
+ # @return [Symbol, nil] :vm or :container, or nil if first arg is not a type
194
+ def parse_resource_type(args)
195
+ return nil if args.empty?
196
+
197
+ first = args.first.downcase
198
+ if RESOURCE_TYPES.key?(first)
199
+ args.shift
200
+ RESOURCE_TYPES[first]
201
+ end
202
+ end
203
+
204
+ # Collects YAML file contents from given paths (files or directories).
205
+ #
206
+ # @param paths [Array<String>] file or directory paths
207
+ # @return [Array<Hash>] array of { filename: String, content: String, path: String? }
208
+ def collect_yaml_contents(paths)
209
+ contents = []
210
+ paths.each do |path|
211
+ if File.directory?(path)
212
+ Dir.glob(File.join(path, "*.{yaml,yml}")).sort.each do |file|
213
+ contents << { filename: File.basename(file), content: File.read(file), path: File.expand_path(file) }
214
+ end
215
+ elsif File.file?(path)
216
+ contents << { filename: File.basename(path), content: File.read(path), path: File.expand_path(path) }
217
+ else
218
+ $stderr.puts "Error: File not found: #{path}"
219
+ end
220
+ end
221
+ contents
222
+ end
223
+
224
+ # Displays push plans with diffs to stdout.
225
+ #
226
+ # @param plans [Array<Hash>] prepared push plans
227
+ # @return [void]
228
+ def display_plans(plans)
229
+ plans.each do |plan|
230
+ label = plan[:type] == :container ? "Container" : "VM"
231
+ if plan[:action] == :update
232
+ $stdout.puts "\n#{label} #{plan[:vmid]} (#{plan[:node]}) -- UPDATE:"
233
+ $stdout.puts ConfigSerializer.format_diff(plan[:diff])
234
+ if plan[:resize_ops]&.any?
235
+ plan[:resize_ops].each do |op|
236
+ $stdout.puts " (disk resize: #{op[:disk]} -> #{op[:size]})"
237
+ end
238
+ end
239
+ elsif plan[:action] == :create
240
+ id_note = plan[:auto_id] ? " (auto-assigned)" : ""
241
+ $stdout.puts "\n#{label} #{plan[:vmid]}#{id_note} (#{plan[:node]}) -- CREATE:"
242
+ plan[:params].each do |key, val|
243
+ $stdout.puts " + #{key}: #{val}"
244
+ end
245
+ end
246
+ end
247
+ end
248
+
249
+ # Returns a human-readable type label for a result hash.
250
+ #
251
+ # @param result [Hash] apply result with optional :type key
252
+ # @return [String] "VM" or "Container"
253
+ def type_label_for(result)
254
+ result[:type] == :container ? "Container" : "VM"
255
+ end
256
+
257
+ # Refreshes source manifest files with the current server state after
258
+ # successful apply. Re-pulls each resource and overwrites the source file,
259
+ # ensuring local manifests include server-assigned values (volume names,
260
+ # MAC addresses, etc.).
261
+ #
262
+ # @param results [Array<Hash>] apply results
263
+ # @param plans [Array<Hash>] original plans (same order as results)
264
+ # @param pull_service [Services::PullConfig] pull service for re-pulling
265
+ # @return [void]
266
+ def refresh_manifests(results, plans, pull_service)
267
+ results.each_with_index do |r, idx|
268
+ next unless r[:success]
269
+
270
+ plan = plans[idx]
271
+ source_path = plan[:source_path]
272
+ next unless source_path && File.file?(source_path)
273
+
274
+ refresh_manifest_file(source_path, plan[:type], plan[:vmid], pull_service)
275
+ end
276
+ end
277
+
278
+ # Refreshes file-backed manifests that had no changes to apply.
279
+ # Re-pulls each resource to fill in server-assigned values (MAC addresses,
280
+ # volume names, UUIDs, etc.) that may not be in the local manifest.
281
+ #
282
+ # @param unchanged [Array<Hash>] unchanged entries from prepare_batch
283
+ # @param pull_service [Services::PullConfig] pull service for re-pulling
284
+ # @return [void]
285
+ def refresh_unchanged_manifests(unchanged, pull_service)
286
+ unchanged.each do |entry|
287
+ next unless entry[:source_path] && File.file?(entry[:source_path])
288
+
289
+ refresh_manifest_file(entry[:source_path], entry[:type], entry[:vmid], pull_service)
290
+ end
291
+ end
292
+
293
+ # Re-pulls a single resource and writes the updated YAML to the source file.
294
+ #
295
+ # @param path [String] source manifest file path
296
+ # @param type [Symbol] :vm or :container
297
+ # @param vmid [Integer] resource ID
298
+ # @param pull_service [Services::PullConfig] pull service
299
+ # @return [void]
300
+ def refresh_manifest_file(path, type, vmid, pull_service)
301
+ pull_result = pull_service.execute(type: type, ids: [vmid])
302
+ return if pull_result[:manifests].empty?
303
+
304
+ File.write(path, pull_result[:manifests].first[:yaml])
305
+ $stderr.puts "Refreshed #{path}"
306
+ rescue StandardError => e
307
+ $stderr.puts "Warning: Could not refresh #{path}: #{e.message}"
308
+ end
309
+
310
+ # Builds the PushConfig and PullConfig services with shared repositories.
311
+ #
312
+ # @param connection [Connection] API connection
313
+ # @return [Array(Services::PushConfig, Services::PullConfig)]
314
+ def build_services(connection)
315
+ vm_repo = Pvectl::Repositories::Vm.new(connection)
316
+ ct_repo = Pvectl::Repositories::Container.new(connection)
317
+ task_repo = Pvectl::Repositories::Task.new(connection)
318
+
319
+ push_service = Pvectl::Services::PushConfig.new(
320
+ vm_repository: vm_repo,
321
+ container_repository: ct_repo,
322
+ task_repository: task_repo
323
+ )
324
+
325
+ pull_service = Pvectl::Services::PullConfig.new(
326
+ vm_repository: vm_repo,
327
+ container_repository: ct_repo
328
+ )
329
+
330
+ [push_service, pull_service]
331
+ end
332
+
333
+ # Loads configuration from file/env.
334
+ #
335
+ # @return [void]
336
+ def load_config
337
+ service = Pvectl::Config::Service.new
338
+ service.load(config: @global_options[:config])
339
+ @config = service.current_config
340
+ end
341
+
342
+ # Prints a usage error and returns the USAGE_ERROR exit code.
343
+ #
344
+ # @param message [String] error message
345
+ # @return [Integer] USAGE_ERROR exit code
346
+ def usage_error(message)
347
+ $stderr.puts "Error: #{message}"
348
+ ExitCodes::USAGE_ERROR
349
+ end
350
+ end
351
+ end
352
+ end
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pvectl
4
+ module Commands
5
+ # Handler for the `pvectl reset` command.
6
+ #
7
+ # Resets one or more virtual machines (hard reset).
8
+ #
9
+ # @example Reset a single VM
10
+ # pvectl reset vm 100
11
+ #
12
+ # @example Reset with JSON output
13
+ # pvectl reset vm 100 -o json
14
+ #
15
+ class Reset
16
+ include VmLifecycleCommand
17
+
18
+ # Registers the reset command with the CLI.
19
+ #
20
+ # @param cli [GLI::App] the CLI application object
21
+ # @return [void]
22
+ def self.register(cli)
23
+ cli.desc "Reset virtual machines (hard reset)"
24
+ cli.long_desc <<~HELP
25
+ Hard reset one or more virtual machines. Equivalent to pressing the
26
+ physical reset button — the VM is immediately restarted without
27
+ graceful OS shutdown.
28
+
29
+ Only available for VMs. Containers do not support hard reset.
30
+
31
+ EXAMPLES
32
+ Hard reset a VM:
33
+ $ pvectl reset vm 100
34
+
35
+ Reset multiple VMs:
36
+ $ pvectl reset vm 100 101 102
37
+
38
+ NOTES
39
+ May cause data loss or filesystem corruption. Use 'pvectl restart'
40
+ for a graceful reboot instead.
41
+
42
+ Not available for containers — use 'pvectl restart ct' instead.
43
+
44
+ SEE ALSO
45
+ pvectl help restart Graceful reboot (VMs and containers)
46
+ pvectl help stop Hard stop without restart
47
+ HELP
48
+ cli.arg_name "RESOURCE_TYPE [ID...]"
49
+ cli.command :reset do |c|
50
+ SharedFlags.lifecycle(c)
51
+
52
+ c.action do |global_options, options, args|
53
+ resource_type = args.shift
54
+ resource_ids = args
55
+ exit_code = execute(resource_type, resource_ids, options, global_options)
56
+ exit exit_code if exit_code != 0
57
+ end
58
+ end
59
+ end
60
+
61
+ OPERATION = :reset
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,277 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pvectl
4
+ module Commands
5
+ # Shared functionality for lifecycle commands across resource types.
6
+ #
7
+ # Template method pattern: provides common flow (validate, resolve,
8
+ # confirm, execute, output) while specialization modules define
9
+ # resource-specific hooks.
10
+ #
11
+ # @abstract Include a specialization module (VmLifecycleCommand,
12
+ # ContainerLifecycleCommand) instead of this one directly.
13
+ #
14
+ # @example Specialization module pattern
15
+ # module VmLifecycleCommand
16
+ # def self.included(base)
17
+ # base.include(ResourceLifecycleCommand)
18
+ # end
19
+ #
20
+ # private
21
+ # def supported_resources = %w[vm]
22
+ # def resource_label = "VM"
23
+ # # ...
24
+ # end
25
+ #
26
+ module ResourceLifecycleCommand
27
+ # Class methods added when the module is included.
28
+ module ClassMethods
29
+ # Executes the lifecycle command.
30
+ #
31
+ # @param resource_type [String, nil] resource type (vm, ct)
32
+ # @param resource_ids [Array<String>, String, nil] resource identifiers
33
+ # @param options [Hash] command options
34
+ # @param global_options [Hash] global CLI options
35
+ # @return [Integer] exit code
36
+ def execute(resource_type, resource_ids, options, global_options)
37
+ new(resource_type, resource_ids, options, global_options).execute
38
+ end
39
+ end
40
+
41
+ # Hook called when module is included.
42
+ #
43
+ # @param base [Class] the class including this module
44
+ def self.included(base)
45
+ base.extend(ClassMethods)
46
+ end
47
+
48
+ # Initializes a lifecycle command.
49
+ #
50
+ # @param resource_type [String, nil] resource type
51
+ # @param resource_ids [Array<String>, String, nil] resource identifiers
52
+ # @param options [Hash] command options
53
+ # @param global_options [Hash] global CLI options
54
+ def initialize(resource_type, resource_ids, options, global_options)
55
+ @resource_type = resource_type
56
+ @resource_ids = Array(resource_ids).compact
57
+ @options = options
58
+ @global_options = global_options
59
+ end
60
+
61
+ # Executes the lifecycle command.
62
+ #
63
+ # @return [Integer] exit code
64
+ def execute
65
+ return usage_error("Resource type required (#{supported_resources.join(', ')})") unless @resource_type
66
+ return usage_error("Unsupported resource: #{@resource_type}") unless supported_resources.include?(@resource_type)
67
+
68
+ if @resource_ids.empty? && !@options[:all] && selector_strings.empty?
69
+ return usage_error("#{resource_id_label}, --all, or -l selector required")
70
+ end
71
+
72
+ perform_operation
73
+ end
74
+
75
+ private
76
+
77
+ # --- Template methods (override in specialization) ---
78
+
79
+ # @return [Array<String>] supported resource type strings
80
+ def supported_resources
81
+ raise NotImplementedError, "#{self.class} must implement #supported_resources"
82
+ end
83
+
84
+ # @return [String] human label for resource ("VM", "container")
85
+ def resource_label
86
+ raise NotImplementedError, "#{self.class} must implement #resource_label"
87
+ end
88
+
89
+ # @return [String] human label for resource ID ("VMID", "CTID")
90
+ def resource_id_label
91
+ raise NotImplementedError, "#{self.class} must implement #resource_id_label"
92
+ end
93
+
94
+ # @return [Object] repository for this resource type
95
+ def build_repository(connection)
96
+ raise NotImplementedError, "#{self.class} must implement #build_repository"
97
+ end
98
+
99
+ # @return [Object] lifecycle service
100
+ def build_service(repo, task_repo, options)
101
+ raise NotImplementedError, "#{self.class} must implement #build_service"
102
+ end
103
+
104
+ # @return [Object] presenter for results
105
+ def build_presenter
106
+ raise NotImplementedError, "#{self.class} must implement #build_presenter"
107
+ end
108
+
109
+ # @return [Object] selector for filtering
110
+ def build_selector(strings)
111
+ raise NotImplementedError, "#{self.class} must implement #build_selector"
112
+ end
113
+
114
+ # --- Shared implementation ---
115
+
116
+ # Performs the lifecycle operation.
117
+ #
118
+ # @return [Integer] exit code
119
+ def perform_operation
120
+ load_config
121
+ connection = Pvectl::Connection.new(@config)
122
+
123
+ repo = build_repository(connection)
124
+ task_repo = Pvectl::Repositories::Task.new(connection)
125
+
126
+ resources = resolve_resources(repo)
127
+ return no_resources_found if resources.empty?
128
+ return Pvectl::ExitCodes::SUCCESS unless confirm_operation(resources)
129
+
130
+ service = build_service(repo, task_repo, service_options)
131
+ results = service.execute(operation_name, resources)
132
+
133
+ output_results(results)
134
+ determine_exit_code(results)
135
+ rescue Pvectl::Config::ConfigNotFoundError,
136
+ Pvectl::Config::InvalidConfigError,
137
+ Pvectl::Config::ContextNotFoundError,
138
+ Pvectl::Config::ClusterNotFoundError,
139
+ Pvectl::Config::UserNotFoundError
140
+ raise
141
+ rescue StandardError => e
142
+ $stderr.puts "Error: #{e.message}"
143
+ Pvectl::ExitCodes::GENERAL_ERROR
144
+ end
145
+
146
+ # Resolves resources based on resource_ids, --all flag, or selectors.
147
+ #
148
+ # @param repo [Object] resource repository
149
+ # @return [Array<Object>] resolved resources
150
+ def resolve_resources(repo)
151
+ resources = if @options[:all]
152
+ repo.list(node: @options[:node])
153
+ elsif @resource_ids.any?
154
+ resolved = @resource_ids.map { |id| repo.get(id.to_i) }.compact
155
+ resolved = resolved.select { |r| r.node == @options[:node] } if @options[:node]
156
+ resolved
157
+ else
158
+ return [] if selector_strings.empty?
159
+
160
+ repo.list(node: @options[:node])
161
+ end
162
+
163
+ apply_selectors(resources)
164
+ end
165
+
166
+ # Returns selector strings from options.
167
+ #
168
+ # @return [Array<String>] selector strings
169
+ def selector_strings
170
+ Array(@options[:selector] || @options[:l])
171
+ end
172
+
173
+ # Applies selectors to resource collection.
174
+ #
175
+ # @param resources [Array<Object>] resources to filter
176
+ # @return [Array<Object>] filtered resources
177
+ def apply_selectors(resources)
178
+ return resources if selector_strings.empty?
179
+
180
+ selector = build_selector(selector_strings)
181
+ selector.apply(resources)
182
+ end
183
+
184
+ # Confirms multi-resource operation with user.
185
+ #
186
+ # @param resources [Array<Object>] resources to operate on
187
+ # @return [Boolean] true if operation should proceed
188
+ def confirm_operation(resources)
189
+ return true if resources.size == 1
190
+ return true if @options[:yes]
191
+
192
+ $stdout.puts "You are about to #{operation_name} #{resources.size} #{resource_label}s:"
193
+ resources.each { |r| $stdout.puts " - #{r.vmid} (#{r.name || 'unnamed'}) on #{r.node}" }
194
+ $stdout.puts ""
195
+ $stdout.print "Proceed? [y/N]: "
196
+
197
+ response = $stdin.gets&.strip&.downcase
198
+ %w[y yes].include?(response)
199
+ end
200
+
201
+ # Returns the operation name for this command.
202
+ #
203
+ # @return [Symbol] operation name
204
+ def operation_name
205
+ self.class::OPERATION
206
+ end
207
+
208
+ # Loads configuration from file or environment.
209
+ #
210
+ # @return [void]
211
+ def load_config
212
+ service = Pvectl::Config::Service.new
213
+ service.load(config: @global_options[:config])
214
+ @config = service.current_config
215
+ end
216
+
217
+ # Builds service options from command options.
218
+ #
219
+ # @return [Hash] service options
220
+ def service_options
221
+ opts = {}
222
+ opts[:timeout] = @options[:timeout] if @options[:timeout]
223
+ opts[:async] = true if @options[:async]
224
+ opts[:wait] = true if @options[:wait]
225
+ opts[:fail_fast] = true if @options[:"fail-fast"]
226
+ opts
227
+ end
228
+
229
+ # Outputs operation results using the configured formatter.
230
+ #
231
+ # @param results [Array<Object>] operation results
232
+ # @return [void]
233
+ def output_results(results)
234
+ presenter = build_presenter
235
+ format = @global_options[:output] || "table"
236
+ color_flag = @global_options[:color]
237
+
238
+ formatter = Pvectl::Formatters::Registry.for(format)
239
+ output = formatter.format(results, presenter, color: color_flag)
240
+ puts output
241
+ end
242
+
243
+ # Determines exit code based on results.
244
+ #
245
+ # @param results [Array<Object>] operation results
246
+ # @return [Integer] exit code
247
+ def determine_exit_code(results)
248
+ return Pvectl::ExitCodes::SUCCESS if results.all?(&:successful?)
249
+ return Pvectl::ExitCodes::SUCCESS if results.all?(&:pending?)
250
+
251
+ Pvectl::ExitCodes::GENERAL_ERROR
252
+ end
253
+
254
+ # Outputs usage error and returns exit code.
255
+ #
256
+ # @param message [String] error message
257
+ # @return [Integer] exit code
258
+ def usage_error(message)
259
+ $stderr.puts "Error: #{message}"
260
+ Pvectl::ExitCodes::USAGE_ERROR
261
+ end
262
+
263
+ # Outputs no resources found error and returns exit code.
264
+ #
265
+ # @return [Integer] exit code
266
+ def no_resources_found
267
+ msg = if @options[:all] || selector_strings.any?
268
+ @options[:node] ? "No #{resource_label}s found on node #{@options[:node]}" : "No #{resource_label}s found matching criteria"
269
+ else
270
+ "No #{resource_label}s found for given #{resource_id_label}s"
271
+ end
272
+ $stderr.puts "Error: #{msg}"
273
+ Pvectl::ExitCodes::NOT_FOUND
274
+ end
275
+ end
276
+ end
277
+ end