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,249 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pvectl
4
+ module Commands
5
+ # Handler for the `pvectl ping` command.
6
+ #
7
+ # Verifies connectivity to the Proxmox cluster by calling the version
8
+ # endpoint and measuring response time. Provides a quick health check
9
+ # without detailed resource information.
10
+ #
11
+ # @example Basic usage
12
+ # pvectl ping
13
+ # # Output: OK - Connected to pve1.example.com
14
+ #
15
+ # @example Wide output with latency
16
+ # pvectl ping -o wide
17
+ # # Output: OK - Connected to pve1.example.com | Latency: 45ms
18
+ #
19
+ # @example JSON output for scripts
20
+ # pvectl ping -o json
21
+ # # Output: {"status":"ok","server":"pve1.example.com","latency_ms":45}
22
+ #
23
+ class Ping
24
+ # Registers the ping command with the CLI.
25
+ #
26
+ # @param cli [GLI::App] the CLI application object
27
+ # @return [void]
28
+ def self.register(cli)
29
+ cli.desc "Check connectivity to Proxmox cluster"
30
+ cli.long_desc <<~HELP
31
+ Verify connectivity to the Proxmox cluster by calling the version
32
+ API endpoint and measuring response time.
33
+
34
+ EXAMPLES
35
+ Basic connectivity check:
36
+ $ pvectl ping
37
+
38
+ Wide output with latency:
39
+ $ pvectl ping -o wide
40
+
41
+ JSON output for scripting:
42
+ $ pvectl ping -o json
43
+
44
+ NOTES
45
+ Uses the configured context (or PROXMOX_HOST environment variable)
46
+ to determine which server to ping.
47
+
48
+ Exit code 0 = connected, exit code 4 = connection error.
49
+
50
+ SEE ALSO
51
+ pvectl help config Manage cluster configuration
52
+ pvectl help get nodes List cluster nodes
53
+ HELP
54
+ cli.command :ping do |c|
55
+ c.action do |global_options, _options, _args|
56
+ exit_code = execute(global_options)
57
+ exit exit_code if exit_code != 0
58
+ end
59
+ end
60
+ end
61
+
62
+ # Executes the ping command.
63
+ #
64
+ # @param global_options [Hash] global CLI options
65
+ # - :config [String, nil] path to config file
66
+ # - :output [String] output format (table, wide, json, yaml)
67
+ # - :color [Boolean, nil] explicit color flag
68
+ # @return [Integer] exit code (0 for success, 4 for connection error)
69
+ def self.execute(global_options)
70
+ new(global_options).execute
71
+ end
72
+
73
+ # Creates a new Ping command instance.
74
+ #
75
+ # @param global_options [Hash] global CLI options
76
+ def initialize(global_options)
77
+ @global_options = global_options
78
+ @output_format = global_options[:output] || "table"
79
+ @color_flag = global_options[:color]
80
+ @config = nil
81
+ end
82
+
83
+ # Executes the ping operation.
84
+ #
85
+ # @return [Integer] exit code
86
+ def execute
87
+ load_config
88
+ connection = Pvectl::Connection.new(@config)
89
+
90
+ result = measure_ping(connection)
91
+ output_result(result, @config.server)
92
+
93
+ ExitCodes::SUCCESS
94
+ rescue Pvectl::Config::ConfigNotFoundError,
95
+ Pvectl::Config::InvalidConfigError,
96
+ Pvectl::Config::ContextNotFoundError,
97
+ Pvectl::Config::ClusterNotFoundError,
98
+ Pvectl::Config::UserNotFoundError => e
99
+ # Re-raise config errors to be handled by CLI error handler
100
+ raise
101
+ rescue Timeout::Error
102
+ output_error("Connection timed out", server_url)
103
+ ExitCodes::CONNECTION_ERROR
104
+ rescue Errno::ECONNREFUSED
105
+ output_error("Connection refused", server_url)
106
+ ExitCodes::CONNECTION_ERROR
107
+ rescue SocketError => e
108
+ output_error(e.message, server_url)
109
+ ExitCodes::CONNECTION_ERROR
110
+ rescue StandardError => e
111
+ output_error(e.message, server_url)
112
+ ExitCodes::CONNECTION_ERROR
113
+ end
114
+
115
+ private
116
+
117
+ attr_reader :global_options, :output_format, :color_flag
118
+
119
+ # Loads configuration from service.
120
+ #
121
+ # @return [Config::Models::ResolvedConfig] the resolved configuration
122
+ # @raise [Config::ConfigNotFoundError] if config not found
123
+ def load_config
124
+ service = Pvectl::Config::Service.new
125
+ service.load(config: global_options[:config])
126
+ @config = service.current_config
127
+ end
128
+
129
+ # Returns the server URL, or "unknown" if config not loaded.
130
+ #
131
+ # @return [String] server URL or "unknown"
132
+ def server_url
133
+ @config&.server || "unknown"
134
+ end
135
+
136
+ # Measures ping latency by timing the version API call.
137
+ #
138
+ # @param connection [Connection] the connection to use
139
+ # @return [Hash] result with :latency_ms
140
+ def measure_ping(connection)
141
+ start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
142
+ connection.version
143
+ end_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
144
+
145
+ {
146
+ latency_ms: ((end_time - start_time) * 1000).round
147
+ }
148
+ end
149
+
150
+ # Outputs the result based on the selected format.
151
+ #
152
+ # @param result [Hash] the ping result
153
+ # @param server [String] the server URL
154
+ # @return [void]
155
+ def output_result(result, server)
156
+ case output_format
157
+ when "json"
158
+ output_json(result, server)
159
+ when "yaml"
160
+ output_yaml(result, server)
161
+ when "wide"
162
+ output_wide(result, server)
163
+ else
164
+ output_simple(server)
165
+ end
166
+ end
167
+
168
+ # Outputs simple text format.
169
+ #
170
+ # @param server [String] the server URL
171
+ # @return [void]
172
+ def output_simple(server)
173
+ pastel = Formatters::ColorSupport.pastel(explicit_flag: color_flag)
174
+ puts "#{pastel.green('OK')} - Connected to #{extract_host(server)}"
175
+ end
176
+
177
+ # Outputs wide text format with latency.
178
+ #
179
+ # @param result [Hash] the ping result
180
+ # @param server [String] the server URL
181
+ # @return [void]
182
+ def output_wide(result, server)
183
+ pastel = Formatters::ColorSupport.pastel(explicit_flag: color_flag)
184
+ puts "#{pastel.green('OK')} - Connected to #{extract_host(server)} | " \
185
+ "Latency: #{result[:latency_ms]}ms"
186
+ end
187
+
188
+ # Outputs JSON format.
189
+ #
190
+ # @param result [Hash] the ping result
191
+ # @param server [String] the server URL
192
+ # @return [void]
193
+ def output_json(result, server)
194
+ require "json"
195
+ puts JSON.pretty_generate({
196
+ status: "ok",
197
+ server: extract_host(server),
198
+ latency_ms: result[:latency_ms]
199
+ })
200
+ end
201
+
202
+ # Outputs YAML format.
203
+ #
204
+ # @param result [Hash] the ping result
205
+ # @param server [String] the server URL
206
+ # @return [void]
207
+ def output_yaml(result, server)
208
+ require "yaml"
209
+ puts YAML.dump({
210
+ "status" => "ok",
211
+ "server" => extract_host(server),
212
+ "latency_ms" => result[:latency_ms]
213
+ })
214
+ end
215
+
216
+ # Outputs error message.
217
+ #
218
+ # @param message [String] the error message
219
+ # @param server [String] the server URL
220
+ # @return [void]
221
+ def output_error(message, server)
222
+ pastel = Formatters::ColorSupport.pastel(explicit_flag: color_flag)
223
+ host = extract_host(server)
224
+
225
+ case output_format
226
+ when "json"
227
+ require "json"
228
+ puts JSON.pretty_generate({ status: "error", server: host, error: message })
229
+ when "yaml"
230
+ require "yaml"
231
+ puts YAML.dump({ "status" => "error", "server" => host, "error" => message })
232
+ else
233
+ $stderr.puts "#{pastel.red('ERROR')} - Cannot connect to #{host}: #{message}"
234
+ end
235
+ end
236
+
237
+ # Extracts hostname from server URL.
238
+ #
239
+ # @param server_url [String] the full server URL
240
+ # @return [String] the hostname
241
+ def extract_host(server_url)
242
+ uri = URI.parse(server_url)
243
+ uri.host
244
+ rescue StandardError
245
+ server_url
246
+ end
247
+ end
248
+ end
249
+ end
@@ -0,0 +1,342 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "fileutils"
4
+
5
+ module Pvectl
6
+ module Commands
7
+ # Pull command -- exports resource configuration from the Proxmox cluster
8
+ # as kubectl-like YAML manifest files.
9
+ #
10
+ # @example Register with CLI
11
+ # Pull.register(cli)
12
+ class Pull
13
+ RESOURCE_TYPES = {
14
+ "vm" => :vm,
15
+ "vms" => :vm,
16
+ "container" => :container,
17
+ "containers" => :container,
18
+ "ct" => :container
19
+ }.freeze
20
+
21
+ FILE_PREFIXES = {
22
+ vm: "vm",
23
+ container: "ct"
24
+ }.freeze
25
+
26
+ # Registers the pull command with the CLI.
27
+ #
28
+ # @param cli [GLI::App] the CLI application object
29
+ # @return [void]
30
+ def self.register(cli)
31
+ cli.desc "Pull resource configuration to YAML manifest"
32
+ cli.long_desc <<~HELP
33
+ DESCRIPTION
34
+ Exports resource configuration from the Proxmox cluster as kubectl-like
35
+ YAML manifest files. Supports single resources, multiple IDs, selectors,
36
+ and bulk export with --all.
37
+
38
+ When writing to files (-f), shows a diff of changes and asks for
39
+ confirmation before overwriting existing files. Use --yes to skip
40
+ confirmation or --dry-run to preview changes without writing.
41
+
42
+ EXAMPLES
43
+ $ pvectl pull vm 100
44
+ $ pvectl pull vm 100 -f vm-100.yaml
45
+ $ pvectl pull vm 100 -f vm-100.yaml --dry-run
46
+ $ pvectl pull vm 100 101 102 -f ./manifests/
47
+ $ pvectl pull vm --all -f ./manifests/ --yes
48
+ $ pvectl pull vm -f ./manifests/
49
+ $ pvectl pull vm -l tags=prod -f ./manifests/
50
+ $ pvectl pull container 200
51
+
52
+ NOTES
53
+ Without -f, YAML is printed to stdout (pipe-friendly).
54
+ With -f, shows diff against existing files and asks to confirm.
55
+ With --all or -l, -f must point to a directory.
56
+ When -f points to a directory with existing manifests and no IDs
57
+ are given, IDs are inferred from file names (vm-{vmid}.yaml).
58
+ File naming convention: vm-{vmid}.yaml or ct-{vmid}.yaml.
59
+
60
+ SEE ALSO
61
+ push, get, describe, edit
62
+ HELP
63
+
64
+ cli.command :pull do |c|
65
+ c.flag [:f, :file], desc: "Output file or directory"
66
+ c.flag [:l, :selector], desc: "Filter by selector (e.g. tags=prod,status=running)", multiple: true
67
+ c.switch [:all], desc: "Pull all resources of given type", negatable: false
68
+ c.switch [:y, :yes], desc: "Auto-confirm without prompting", negatable: false
69
+ c.switch [:"dry-run"], desc: "Show diff without writing files", negatable: false
70
+ c.flag [:node], desc: "Limit to specific node"
71
+
72
+ c.action do |global_options, options, args|
73
+ Pull.new(args, options, global_options).execute
74
+ end
75
+ end
76
+ end
77
+
78
+ # @param args [Array<String>] command arguments
79
+ # @param options [Hash] command options
80
+ # @param global_options [Hash] global CLI options
81
+ def initialize(args, options, global_options)
82
+ @args = args
83
+ @options = options
84
+ @global_options = global_options
85
+ end
86
+
87
+ # Executes the pull command.
88
+ #
89
+ # @return [Integer] exit code
90
+ def execute
91
+ resource_type_str = @args.shift
92
+ return usage_error("Resource type is required (vm, container)") unless resource_type_str
93
+
94
+ type = RESOURCE_TYPES[resource_type_str.downcase]
95
+ return usage_error("Unknown resource type '#{resource_type_str}'. Valid: vm, container") unless type
96
+
97
+ ids = @args.map(&:to_i)
98
+ all = @options[:all]
99
+ node = @options[:node]
100
+ output = @options[:file]
101
+
102
+ # Auto-infer IDs from existing manifest files in directory
103
+ if ids.empty? && !all && @options[:selector].nil? && output && directory_output?(output)
104
+ ids = infer_ids_from_directory(output, type)
105
+ end
106
+
107
+ if ids.empty? && !all && @options[:selector].nil?
108
+ return usage_error("Provide resource IDs, --all, or -l selector")
109
+ end
110
+
111
+ if (all || @options[:selector]) && output && !directory_output?(output)
112
+ return usage_error("--all and -l require -f to be a directory (end with /)")
113
+ end
114
+
115
+ selector = build_selector(type)
116
+
117
+ load_config
118
+ connection = Pvectl::Connection.new(@config)
119
+ service = build_service(connection)
120
+
121
+ result = service.execute(type: type, ids: ids, all: all, node: node, selector: selector)
122
+
123
+ result[:errors].each { |e| $stderr.puts "Error: #{e}" }
124
+
125
+ write_output(result[:manifests], type, output)
126
+
127
+ result[:errors].empty? ? ExitCodes::SUCCESS : ExitCodes::GENERAL_ERROR
128
+ rescue Pvectl::Config::ConfigNotFoundError,
129
+ Pvectl::Config::InvalidConfigError,
130
+ Pvectl::Config::ContextNotFoundError,
131
+ Pvectl::Config::ClusterNotFoundError,
132
+ Pvectl::Config::UserNotFoundError
133
+ raise
134
+ rescue StandardError => e
135
+ $stderr.puts "Error: #{e.message}"
136
+ ExitCodes::GENERAL_ERROR
137
+ end
138
+
139
+ private
140
+
141
+ def build_service(connection)
142
+ vm_repo = Pvectl::Repositories::Vm.new(connection)
143
+ ct_repo = Pvectl::Repositories::Container.new(connection)
144
+ Pvectl::Services::PullConfig.new(
145
+ vm_repository: vm_repo,
146
+ container_repository: ct_repo
147
+ )
148
+ end
149
+
150
+ def build_selector(type)
151
+ expressions = @options[:selector]
152
+ return nil if expressions.nil? || (expressions.is_a?(Array) && expressions.empty?)
153
+
154
+ selector_class = type == :container ? Selectors::Container : Selectors::Vm
155
+ selector_class.new(Array(expressions))
156
+ end
157
+
158
+ # Writes pull results to stdout or files.
159
+ # For file output, shows diff against existing files and asks for confirmation.
160
+ #
161
+ # @param manifests [Array<Hash>] pulled manifests with :yaml and :vmid
162
+ # @param type [Symbol] :vm or :container
163
+ # @param output [String, nil] output file/directory or nil for stdout
164
+ # @return [void]
165
+ def write_output(manifests, type, output)
166
+ return if manifests.empty?
167
+
168
+ if output.nil?
169
+ # stdout mode -- no diff, just print
170
+ manifests.each { |m| $stdout.puts m[:yaml] }
171
+ return
172
+ end
173
+
174
+ # File output mode -- build operations, show diff, confirm, write
175
+ operations = build_file_operations(manifests, type, output)
176
+ actionable = operations.reject { |op| op[:action] == :unchanged }
177
+
178
+ if actionable.empty?
179
+ $stdout.puts "No changes."
180
+ return
181
+ end
182
+
183
+ display_pull_plan(operations, type)
184
+
185
+ if @options[:"dry-run"]
186
+ $stdout.puts "\n(dry-run mode -- no files written)"
187
+ return
188
+ end
189
+
190
+ unless @options[:yes]
191
+ $stdout.print "\nWrite #{actionable.length} file(s)? [y/N] "
192
+ answer = $stdin.gets&.strip&.downcase
193
+ unless answer == "y" || answer == "yes"
194
+ $stdout.puts "Cancelled."
195
+ return
196
+ end
197
+ end
198
+
199
+ apply_file_operations(operations)
200
+ end
201
+
202
+ # Builds a list of file operations (create/update/unchanged) for each manifest.
203
+ #
204
+ # @param manifests [Array<Hash>] pulled manifests
205
+ # @param type [Symbol] :vm or :container
206
+ # @param output [String] output file or directory path
207
+ # @return [Array<Hash>] operation hashes with :action, :path, :vmid, :yaml, :diff
208
+ def build_file_operations(manifests, type, output)
209
+ if manifests.length == 1 && !directory_output?(output)
210
+ [build_operation(manifests.first, output, type)]
211
+ else
212
+ prefix = FILE_PREFIXES[type]
213
+ manifests.map do |m|
214
+ filename = "#{prefix}-#{m[:vmid]}.yaml"
215
+ path = File.join(output, filename)
216
+ build_operation(m, path, type)
217
+ end
218
+ end
219
+ end
220
+
221
+ # Builds a single file operation by comparing new YAML with existing file.
222
+ #
223
+ # @param manifest [Hash] manifest with :yaml and :vmid
224
+ # @param path [String] target file path
225
+ # @param type [Symbol] :vm or :container
226
+ # @return [Hash] operation hash
227
+ def build_operation(manifest, path, type)
228
+ new_yaml = manifest[:yaml]
229
+
230
+ unless File.file?(path)
231
+ return { action: :create, path: path, vmid: manifest[:vmid], yaml: new_yaml }
232
+ end
233
+
234
+ old_yaml = File.read(path)
235
+ return { action: :unchanged, path: path, vmid: manifest[:vmid] } if old_yaml == new_yaml
236
+
237
+ diff = compute_manifest_diff(old_yaml, new_yaml, type)
238
+ { action: :update, path: path, vmid: manifest[:vmid], yaml: new_yaml, diff: diff }
239
+ end
240
+
241
+ # Computes a flat config diff between old and new manifest YAML strings.
242
+ #
243
+ # @param old_yaml [String] existing file content
244
+ # @param new_yaml [String] new content from server
245
+ # @param type [Symbol] :vm or :container
246
+ # @return [Hash, nil] diff hash or nil on parse error
247
+ def compute_manifest_diff(old_yaml, new_yaml, type)
248
+ old_manifest = ManifestSerializer.from_yaml(old_yaml)
249
+ new_manifest = ManifestSerializer.from_yaml(new_yaml)
250
+ old_flat = ConfigSerializer.from_nested(old_manifest[:spec], type: type)
251
+ new_flat = ConfigSerializer.from_nested(new_manifest[:spec], type: type)
252
+ ConfigSerializer.diff(old_flat, new_flat)
253
+ rescue StandardError
254
+ nil
255
+ end
256
+
257
+ # Displays the pull plan with diffs for each file operation.
258
+ #
259
+ # @param operations [Array<Hash>] file operations
260
+ # @param type [Symbol] :vm or :container
261
+ # @return [void]
262
+ def display_pull_plan(operations, type)
263
+ label = type == :container ? "Container" : "VM"
264
+ operations.each do |op|
265
+ case op[:action]
266
+ when :create
267
+ $stdout.puts "\n#{label} #{op[:vmid]} -- NEW (#{File.basename(op[:path])})"
268
+ when :update
269
+ $stdout.puts "\n#{label} #{op[:vmid]} -- UPDATE (#{File.basename(op[:path])}):"
270
+ if op[:diff] && diff_has_changes?(op[:diff])
271
+ $stdout.puts ConfigSerializer.format_diff(op[:diff])
272
+ else
273
+ $stdout.puts " (metadata changed)"
274
+ end
275
+ when :unchanged
276
+ $stderr.puts "Info: #{File.basename(op[:path])}: no changes"
277
+ end
278
+ end
279
+ end
280
+
281
+ # Checks if a diff hash has any actual changes.
282
+ #
283
+ # @param diff [Hash] diff hash from ConfigSerializer.diff
284
+ # @return [Boolean]
285
+ def diff_has_changes?(diff)
286
+ diff[:changed].any? || diff[:added].any? || diff[:removed].any?
287
+ end
288
+
289
+ # Writes files for all actionable operations.
290
+ #
291
+ # @param operations [Array<Hash>] file operations
292
+ # @return [void]
293
+ def apply_file_operations(operations)
294
+ written = 0
295
+ operations.each do |op|
296
+ next if op[:action] == :unchanged
297
+
298
+ dir = File.dirname(op[:path])
299
+ FileUtils.mkdir_p(dir) unless File.directory?(dir)
300
+ File.write(op[:path], op[:yaml])
301
+ written += 1
302
+ end
303
+ $stderr.puts "Written #{written} manifest(s)"
304
+ end
305
+
306
+ # Infers resource IDs from existing manifest files in a directory.
307
+ # Scans for files matching {prefix}-{vmid}.yaml pattern.
308
+ #
309
+ # @param directory [String] path to directory
310
+ # @param type [Symbol] :vm or :container
311
+ # @return [Array<Integer>] extracted VMIDs
312
+ def infer_ids_from_directory(directory, type)
313
+ return [] unless File.directory?(directory)
314
+
315
+ prefix = FILE_PREFIXES[type]
316
+ pattern = File.join(directory, "#{prefix}-*.yaml")
317
+ Dir.glob(pattern).filter_map do |path|
318
+ basename = File.basename(path, ".yaml")
319
+ match = basename.match(/\A#{Regexp.escape(prefix)}-(\d+)\z/)
320
+ match[1].to_i if match
321
+ end.sort
322
+ end
323
+
324
+ def directory_output?(path)
325
+ return false if path.nil?
326
+
327
+ path.end_with?("/") || File.directory?(path)
328
+ end
329
+
330
+ def load_config
331
+ service = Pvectl::Config::Service.new
332
+ service.load(config: @global_options[:config])
333
+ @config = service.current_config
334
+ end
335
+
336
+ def usage_error(message)
337
+ $stderr.puts "Error: #{message}"
338
+ ExitCodes::USAGE_ERROR
339
+ end
340
+ end
341
+ end
342
+ end