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,331 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pvectl
4
+ module Commands
5
+ # Handler for the `pvectl clone vm` command.
6
+ #
7
+ # Clones a VM by VMID, supporting full and linked clones,
8
+ # custom name, target node, storage, pool, and description.
9
+ # No batch operations - clones exactly one VM at a time.
10
+ #
11
+ # @example Full clone with auto-generated VMID
12
+ # pvectl clone vm 100
13
+ #
14
+ # @example Clone with custom name and target VMID
15
+ # pvectl clone vm 100 --vmid 200 --name web-clone
16
+ #
17
+ # @example Linked clone to different node
18
+ # pvectl clone vm 100 --linked --target pve2
19
+ #
20
+ class CloneVm
21
+ include SharedConfigParsers
22
+
23
+ # Registers the clone command with the CLI.
24
+ #
25
+ # @param cli [GLI::App] the CLI application object
26
+ # @return [void]
27
+ def self.register(cli)
28
+ cli.desc "Clone a resource"
29
+ cli.long_desc <<~HELP
30
+ Clone a virtual machine or container, optionally modifying the
31
+ configuration of the clone (CPU, memory, disks, network).
32
+
33
+ Supports full clones (independent copy) and linked clones (shares
34
+ base image with source — requires source to be a template).
35
+
36
+ EXAMPLES
37
+ Clone a VM to the same node:
38
+ $ pvectl clone vm 100 --name web-clone
39
+
40
+ Clone to a different node:
41
+ $ pvectl clone vm 100 --name web-prod --target pve2
42
+
43
+ Clone with modified configuration:
44
+ $ pvectl clone vm 100 --name web-prod --cores 4 --memory 8192
45
+
46
+ Linked clone (thin provisioning, requires template):
47
+ $ pvectl clone vm 100 --linked --name thin-clone
48
+
49
+ Clone a container with new network config:
50
+ $ pvectl clone ct 200 --name db-clone --memory 4096 --net bridge=vmbr1
51
+
52
+ Clone with explicit new ID:
53
+ $ pvectl clone vm 100 --newid 150 --name web-test
54
+
55
+ NOTES
56
+ Config modification is a two-step process: clone first, then update
57
+ configuration via the Proxmox API. If the config update fails, the
58
+ clone still exists but with the original configuration.
59
+
60
+ Linked clones share the base disk with the source. They are faster
61
+ to create and use less storage, but the source cannot be deleted.
62
+
63
+ If --name is not specified, Proxmox auto-generates a name.
64
+
65
+ SEE ALSO
66
+ pvectl help create Create new VMs/containers from scratch
67
+ pvectl help migrate Move resources between nodes
68
+ pvectl help template Convert to template for linked clones
69
+ HELP
70
+ cli.arg_name "RESOURCE_TYPE ID"
71
+ cli.command :clone do |c|
72
+ c.desc "Name/hostname for the new resource"
73
+ c.flag [:name, :n], arg_name: "NAME"
74
+
75
+ c.desc "ID for the new resource (auto-selected if not specified)"
76
+ c.flag [:newid], type: Integer, arg_name: "ID"
77
+
78
+ c.desc "Target node for the clone"
79
+ c.flag [:target, :t], arg_name: "NODE"
80
+
81
+ c.desc "Target storage for the clone"
82
+ c.flag [:storage, :s], arg_name: "STORAGE"
83
+
84
+ c.desc "Create a linked clone (requires source to be a template)"
85
+ c.switch [:linked], negatable: false
86
+
87
+ c.desc "Resource pool for the new resource"
88
+ c.flag [:pool, :p], arg_name: "POOL"
89
+
90
+ c.desc "Description for the new resource"
91
+ c.flag [:description, :d], arg_name: "DESCRIPTION"
92
+
93
+ c.desc "Timeout in seconds for sync operations (default: 300)"
94
+ c.flag [:timeout], type: Integer, arg_name: "SECONDS"
95
+
96
+ c.desc "Async mode (return task ID immediately)"
97
+ c.switch [:async], negatable: false
98
+
99
+ c.desc "Skip confirmation prompt"
100
+ c.switch [:yes, :y], negatable: false
101
+
102
+ # Shared config flags for VM/container modification after clone
103
+ SharedFlags.common_config(c)
104
+ SharedFlags.vm_config(c)
105
+ SharedFlags.container_config(c)
106
+
107
+ c.action do |global_options, options, args|
108
+ resource_type = args.shift
109
+
110
+ exit_code = case resource_type
111
+ when "vm"
112
+ Commands::CloneVm.execute(args, options, global_options)
113
+ when "container", "ct"
114
+ Commands::CloneContainer.execute(args, options, global_options)
115
+ else
116
+ $stderr.puts "Error: Unknown resource type: #{resource_type}"
117
+ $stderr.puts "Valid types: vm, container, ct"
118
+ ExitCodes::USAGE_ERROR
119
+ end
120
+
121
+ exit exit_code if exit_code != 0
122
+ end
123
+ end
124
+ end
125
+
126
+ # Executes the clone VM command.
127
+ #
128
+ # @param args [Array<String>] command arguments (VMID)
129
+ # @param options [Hash] command options
130
+ # @param global_options [Hash] global CLI options
131
+ # @return [Integer] exit code
132
+ def self.execute(args, options, global_options)
133
+ new(args, options, global_options).execute
134
+ end
135
+
136
+ # Initializes a clone VM command.
137
+ #
138
+ # @param args [Array<String>] command arguments
139
+ # @param options [Hash] command options
140
+ # @param global_options [Hash] global CLI options
141
+ def initialize(args, options, global_options)
142
+ @args = args
143
+ @options = options
144
+ @global_options = global_options
145
+ end
146
+
147
+ # Executes the clone VM command.
148
+ #
149
+ # Builds config params from shared flags, validates async+config
150
+ # compatibility, and delegates to the clone operation.
151
+ #
152
+ # @return [Integer] exit code
153
+ def execute
154
+ vmid = @args.first
155
+ return usage_error("Source VMID required") unless vmid
156
+
157
+ config_params = build_vm_config_params
158
+
159
+ if @options[:async] && !config_params.empty?
160
+ return usage_error("Config flags require sync mode (remove --async)")
161
+ end
162
+
163
+ perform_clone(vmid.to_i, config_params)
164
+ end
165
+
166
+ private
167
+
168
+ # Performs the clone operation.
169
+ #
170
+ # When config params are present, displays a summary and prompts
171
+ # for confirmation before proceeding. Passes config_params to the
172
+ # service for the two-step clone+configure flow.
173
+ #
174
+ # @param vmid [Integer] source VM identifier
175
+ # @param config_params [Hash] VM config parameters to apply after clone
176
+ # @return [Integer] exit code
177
+ def perform_clone(vmid, config_params)
178
+ unless config_params.empty?
179
+ return ExitCodes::SUCCESS if display_clone_summary(vmid, config_params) == :cancelled
180
+ end
181
+
182
+ load_config
183
+ connection = Pvectl::Connection.new(@config)
184
+
185
+ vm_repo = Pvectl::Repositories::Vm.new(connection)
186
+ task_repo = Pvectl::Repositories::Task.new(connection)
187
+
188
+ service = Pvectl::Services::CloneVm.new(
189
+ vm_repository: vm_repo,
190
+ task_repository: task_repo,
191
+ options: service_options
192
+ )
193
+
194
+ result = service.execute(
195
+ vmid: vmid,
196
+ new_vmid: @options[:newid]&.to_i,
197
+ name: @options[:name],
198
+ target_node: @options[:target],
199
+ storage: @options[:storage],
200
+ linked: @options[:linked],
201
+ pool: @options[:pool],
202
+ description: @options[:description],
203
+ config_params: config_params
204
+ )
205
+
206
+ print_progress(result) if !@options[:async] && result.vm
207
+
208
+ output_result(result)
209
+ result.failed? ? ExitCodes::GENERAL_ERROR : ExitCodes::SUCCESS
210
+ rescue Pvectl::Config::ConfigNotFoundError,
211
+ Pvectl::Config::InvalidConfigError,
212
+ Pvectl::Config::ContextNotFoundError,
213
+ Pvectl::Config::ClusterNotFoundError,
214
+ Pvectl::Config::UserNotFoundError
215
+ raise
216
+ rescue StandardError => e
217
+ $stderr.puts "Error: #{e.message}"
218
+ ExitCodes::GENERAL_ERROR
219
+ end
220
+
221
+ # Prints progress message for sync mode.
222
+ #
223
+ # @param result [Models::OperationResult] clone result
224
+ # @return [void]
225
+ def print_progress(result)
226
+ source = result.vm
227
+ new_name = result.resource&.dig(:name) || "clone"
228
+ new_id = result.resource&.dig(:new_vmid)
229
+ $stderr.puts "Cloning VM #{source.vmid} (#{source.name || 'unnamed'}) to #{new_id} (#{new_name})..."
230
+ $stderr.puts ""
231
+ end
232
+
233
+ # Loads configuration from file or environment.
234
+ #
235
+ # @return [void]
236
+ def load_config
237
+ service = Pvectl::Config::Service.new
238
+ service.load(config: @global_options[:config])
239
+ @config = service.current_config
240
+ end
241
+
242
+ # Builds service options from command options.
243
+ #
244
+ # @return [Hash] service options
245
+ def service_options
246
+ opts = {}
247
+ opts[:timeout] = @options[:timeout] if @options[:timeout]
248
+ opts[:async] = true if @options[:async]
249
+ opts[:start] = true if @options[:start]
250
+ opts
251
+ end
252
+
253
+ # Outputs operation result using the configured formatter.
254
+ #
255
+ # @param result [Models::OperationResult] operation result
256
+ # @return [void]
257
+ def output_result(result)
258
+ presenter = Pvectl::Presenters::VmOperationResult.new
259
+ format = @global_options[:output] || "table"
260
+ color_flag = @global_options[:color]
261
+
262
+ formatter = Pvectl::Formatters::Registry.for(format)
263
+ output = formatter.format([result], presenter, color: color_flag)
264
+ puts output
265
+ end
266
+
267
+ # Displays clone summary with config changes and prompts for confirmation.
268
+ #
269
+ # Only called when config params are present. Shows source/target info
270
+ # and the config changes that will be applied after cloning.
271
+ #
272
+ # @param vmid [Integer] source VM identifier
273
+ # @param config_params [Hash] config parameters to display
274
+ # @return [Symbol, nil] +:cancelled+ if user declines, +nil+ otherwise
275
+ def display_clone_summary(vmid, config_params)
276
+ $stdout.puts ""
277
+ $stdout.puts " Clone VM - Summary"
278
+ $stdout.puts " #{'─' * 40}"
279
+ $stdout.puts " Source: #{vmid}"
280
+ $stdout.puts " New ID: #{@options[:newid] || '(auto)'}"
281
+ $stdout.puts " Name: #{@options[:name] || '(auto)'}"
282
+ target_display = @options[:target] ? "→ #{@options[:target]}" : "(same)"
283
+ $stdout.puts " Node: #{target_display}"
284
+ $stdout.puts " Storage: #{@options[:storage]}" if @options[:storage]
285
+ display_config_changes(config_params)
286
+ $stdout.puts " #{'─' * 40}"
287
+ $stdout.puts ""
288
+
289
+ return nil if @options[:yes]
290
+
291
+ $stdout.print "Clone and configure this VM? [y/N] "
292
+ $stdout.flush
293
+ answer = $stdin.gets&.strip&.downcase
294
+ answer == "y" ? nil : :cancelled
295
+ end
296
+
297
+ # Displays the config changes section of the clone summary.
298
+ #
299
+ # @param params [Hash] config parameters
300
+ # @return [void]
301
+ def display_config_changes(params)
302
+ $stdout.puts " ── Config changes #{'─' * 23}"
303
+ $stdout.puts " CPU: #{params[:cores]} cores" if params[:cores]
304
+ $stdout.puts " Sockets: #{params[:sockets]}" if params[:sockets]
305
+ $stdout.puts " Memory: #{params[:memory]} MB" if params[:memory]
306
+ if params[:disks]
307
+ params[:disks].each_with_index do |d, i|
308
+ $stdout.puts " Disk#{i}: #{d[:storage]}, #{d[:size]}"
309
+ end
310
+ end
311
+ if params[:nets]
312
+ params[:nets].each_with_index do |n, i|
313
+ $stdout.puts " Net#{i}: #{n[:bridge]}"
314
+ end
315
+ end
316
+ $stdout.puts " OS Type: #{params[:ostype]}" if params[:ostype]
317
+ $stdout.puts " Agent: enabled" if params[:agent]
318
+ $stdout.puts " Tags: #{params[:tags]}" if params[:tags]
319
+ end
320
+
321
+ # Outputs usage error and returns exit code.
322
+ #
323
+ # @param message [String] error message
324
+ # @return [Integer] exit code
325
+ def usage_error(message)
326
+ $stderr.puts "Error: #{message}"
327
+ ExitCodes::USAGE_ERROR
328
+ end
329
+ end
330
+ end
331
+ end
@@ -0,0 +1,122 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pvectl
4
+ module Commands
5
+ # Namespace for the +pvectl cloudinit+ command group.
6
+ #
7
+ # Provides three subcommands that operate on cloud-init configuration
8
+ # of QEMU VMs:
9
+ #
10
+ # - +regenerate vm <id>+ — rebuild the cloud-init ISO
11
+ # - +pending vm <id>+ — list pending configuration changes
12
+ # - +dump vm <id> <type>+ — print generated cloud-init YAML
13
+ #
14
+ # All subcommands share a common service-construction helper and a
15
+ # unified error-mapping path that distinguishes usage errors from
16
+ # not-found and connection errors.
17
+ module Cloudinit
18
+ # Registers the cloudinit command group with the CLI.
19
+ #
20
+ # @param cli [GLI::App] the CLI application object
21
+ # @return [void]
22
+ def self.register(cli)
23
+ cli.desc "Manage cloud-init configuration for VMs"
24
+ cli.long_desc <<~HELP
25
+ DESCRIPTION
26
+ Manage cloud-init configuration for QEMU virtual machines.
27
+ Cloud-init lets you configure users, SSH keys, network, and
28
+ other guest-side settings without baking them into the
29
+ template image.
30
+
31
+ Cloud-init is a VM-only feature — LXC containers do not
32
+ expose cloud-init endpoints.
33
+
34
+ SUBCOMMANDS
35
+ cloudinit regenerate vm ID Rebuild the cloud-init ISO
36
+ cloudinit pending vm ID List pending changes
37
+ cloudinit dump vm ID TYPE Print generated YAML
38
+
39
+ EXAMPLES
40
+ Apply pending cloud-init changes:
41
+ $ pvectl cloudinit regenerate vm 100
42
+
43
+ Preview what would change on next regenerate:
44
+ $ pvectl cloudinit pending vm 100
45
+
46
+ Inspect the user-data that the guest will see:
47
+ $ pvectl cloudinit dump vm 100 user
48
+
49
+ NOTES
50
+ +TYPE+ for +dump+ must be one of: +user+, +network+, +meta+.
51
+
52
+ SEE ALSO
53
+ pvectl help edit vm Edit cloud-init keys (cipassword, sshkeys, etc.)
54
+ HELP
55
+ cli.command :cloudinit do |c|
56
+ c.desc "Source node (skips VMID lookup)"
57
+ c.flag [:node, :n], arg_name: "NODE"
58
+
59
+ Regenerate.register_subcommand(c)
60
+ Pending.register_subcommand(c)
61
+ Dump.register_subcommand(c)
62
+ end
63
+ end
64
+
65
+ # Builds a fresh +Services::Cloudinit+ from the active configuration
66
+ # and yields it to the caller. Maps known exceptions to pvectl exit
67
+ # codes — anything else bubbles up to +CLI.on_error+.
68
+ #
69
+ # @param global_options [Hash] global CLI options (includes :config)
70
+ # @yieldparam service [Pvectl::Services::Cloudinit] cloudinit service
71
+ # @return [Integer] exit code
72
+ def self.with_service(global_options)
73
+ config_service = Pvectl::Config::Service.new
74
+ config_service.load(config: global_options[:config])
75
+ config = config_service.current_config
76
+
77
+ connection = Pvectl::Connection.new(config)
78
+ service = Pvectl::Services::Cloudinit.new(
79
+ vm_repository: Pvectl::Repositories::Vm.new(connection),
80
+ resource_resolver: Pvectl::Utils::ResourceResolver.new(connection)
81
+ )
82
+
83
+ yield service
84
+ Pvectl::ExitCodes::SUCCESS
85
+ rescue Pvectl::ResourceNotFoundError => e
86
+ $stderr.puts "Error: #{e.message}"
87
+ Pvectl::ExitCodes::NOT_FOUND
88
+ rescue ArgumentError => e
89
+ $stderr.puts "Error: #{e.message}"
90
+ Pvectl::ExitCodes::USAGE_ERROR
91
+ rescue Pvectl::Config::ConfigNotFoundError,
92
+ Pvectl::Config::InvalidConfigError,
93
+ Pvectl::Config::ContextNotFoundError,
94
+ Pvectl::Config::ClusterNotFoundError,
95
+ Pvectl::Config::UserNotFoundError
96
+ raise
97
+ rescue StandardError => e
98
+ $stderr.puts "Error: #{e.message}"
99
+ Pvectl::ExitCodes::GENERAL_ERROR
100
+ end
101
+
102
+ # Prints a usage error and returns the standard usage exit code.
103
+ #
104
+ # @param message [String] message printed to stderr
105
+ # @return [Integer] usage error exit code
106
+ def self.usage_error(message)
107
+ $stderr.puts "Error: #{message}"
108
+ Pvectl::ExitCodes::USAGE_ERROR
109
+ end
110
+
111
+ # Prints the standard "unknown resource type" error.
112
+ #
113
+ # @param resource_type [String] the unrecognised resource type
114
+ # @return [Integer] usage error exit code
115
+ def self.unknown_resource_type(resource_type)
116
+ $stderr.puts "Error: Unknown or invalid resource type for cloudinit: #{resource_type}"
117
+ $stderr.puts "Valid types: vm"
118
+ Pvectl::ExitCodes::USAGE_ERROR
119
+ end
120
+ end
121
+ end
122
+ end
@@ -0,0 +1,94 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pvectl
4
+ module Commands
5
+ module Cloudinit
6
+ # Handler for the `pvectl cloudinit dump vm <id> <type>` subcommand.
7
+ #
8
+ # Prints the generated cloud-init configuration to stdout as raw
9
+ # YAML/text. The +type+ argument selects which document is dumped:
10
+ # +user+ (user-data), +network+ (network-config), or +meta+
11
+ # (meta-data).
12
+ #
13
+ # @example Usage
14
+ # pvectl cloudinit dump vm 100 user
15
+ # pvectl cloudinit dump vm 100 network
16
+ #
17
+ class Dump
18
+ # Registers the dump subcommand under the cloudinit parent.
19
+ #
20
+ # @param parent [GLI::Command] parent cloudinit command
21
+ # @return [void]
22
+ def self.register_subcommand(parent)
23
+ parent.desc "Dump generated cloud-init configuration for a VM"
24
+ parent.long_desc <<~HELP
25
+ DESCRIPTION
26
+ Print the auto-generated cloud-init configuration that would
27
+ be served to the guest. The +TYPE+ argument selects which
28
+ cloud-init document to display:
29
+
30
+ user User-data (#cloud-config YAML)
31
+ network Network-config (cloud-init network spec)
32
+ meta Meta-data (instance-id, hostname)
33
+
34
+ EXAMPLES
35
+ Dump user-data:
36
+ $ pvectl cloudinit dump vm 100 user
37
+
38
+ Dump network-config:
39
+ $ pvectl cloudinit dump vm 100 network
40
+
41
+ Pipe to a file:
42
+ $ pvectl cloudinit dump vm 100 user > user-data.yml
43
+
44
+ NOTES
45
+ The output is the raw payload returned by Proxmox — no
46
+ additional formatting is applied. This means +-o json+ is
47
+ ignored for this subcommand.
48
+
49
+ SEE ALSO
50
+ pvectl help cloudinit regenerate Rebuild the ISO
51
+ pvectl help cloudinit pending Show pending changes
52
+ HELP
53
+ parent.arg_name "RESOURCE_TYPE ID TYPE"
54
+ parent.command :dump do |c|
55
+ c.action do |global_options, options, args|
56
+ exit_code = execute(args, options, global_options)
57
+ exit exit_code if exit_code != 0
58
+ end
59
+ end
60
+ end
61
+
62
+ # Valid cloud-init dump types.
63
+ VALID_TYPES = %w[user network meta].freeze
64
+
65
+ # Executes the dump subcommand.
66
+ #
67
+ # @param args [Array<String>] command arguments (RESOURCE_TYPE, ID, TYPE)
68
+ # @param options [Hash] command-local options
69
+ # @param global_options [Hash] global CLI options
70
+ # @return [Integer] exit code
71
+ def self.execute(args, options, global_options)
72
+ resource_type = args[0]
73
+ vmid_arg = args[1]
74
+ type = args[2]
75
+
76
+ return Cloudinit.usage_error("Resource type required (vm)") unless resource_type
77
+ return Cloudinit.usage_error("VMID is required") unless vmid_arg
78
+ return Cloudinit.usage_error("Config TYPE is required (user, network, meta)") unless type
79
+ return Cloudinit.unknown_resource_type(resource_type) unless resource_type == "vm"
80
+ unless VALID_TYPES.include?(type)
81
+ return Cloudinit.usage_error(
82
+ "Invalid config type: #{type} (valid: #{VALID_TYPES.join(', ')})"
83
+ )
84
+ end
85
+
86
+ Cloudinit.with_service(global_options) do |service|
87
+ yaml = service.dump(vmid_arg.to_i, type, node: options[:node])
88
+ $stdout.puts yaml
89
+ end
90
+ end
91
+ end
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,137 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pvectl
4
+ module Commands
5
+ module Cloudinit
6
+ # Handler for the `pvectl cloudinit pending vm <id>` subcommand.
7
+ #
8
+ # Lists cloud-init configuration entries currently differing from
9
+ # the values used to build the active ISO. Each entry contains a
10
+ # key, the current value, the pending value, and a delete flag.
11
+ #
12
+ # @example Usage
13
+ # pvectl cloudinit pending vm 100
14
+ # pvectl cloudinit pending vm 100 -o json
15
+ #
16
+ class Pending
17
+ # Registers the pending subcommand under the cloudinit parent.
18
+ #
19
+ # @param parent [GLI::Command] parent cloudinit command
20
+ # @return [void]
21
+ def self.register_subcommand(parent)
22
+ parent.desc "List pending cloud-init configuration changes"
23
+ parent.long_desc <<~HELP
24
+ DESCRIPTION
25
+ Show cloud-init configuration entries that differ from the
26
+ values currently embedded in the active cloud-init ISO. Use
27
+ this to preview what will change on the next regeneration.
28
+
29
+ EXAMPLES
30
+ Show pending changes for VM 100:
31
+ $ pvectl cloudinit pending vm 100
32
+
33
+ JSON output for scripting:
34
+ $ pvectl cloudinit pending vm 100 -o json
35
+
36
+ NOTES
37
+ An empty list means the active ISO is in sync with the
38
+ current Proxmox configuration.
39
+
40
+ Entries with the +action+ column set to +delete+ are about
41
+ to be removed from the ISO.
42
+
43
+ SEE ALSO
44
+ pvectl help cloudinit regenerate Apply pending changes
45
+ pvectl help cloudinit dump Inspect generated YAML
46
+ HELP
47
+ parent.arg_name "RESOURCE_TYPE ID"
48
+ parent.command :pending do |c|
49
+ c.action do |global_options, options, args|
50
+ exit_code = execute(args, options, global_options)
51
+ exit exit_code if exit_code != 0
52
+ end
53
+ end
54
+ end
55
+
56
+ # Executes the pending subcommand.
57
+ #
58
+ # @param args [Array<String>] command arguments
59
+ # @param options [Hash] command-local options
60
+ # @param global_options [Hash] global CLI options
61
+ # @return [Integer] exit code
62
+ def self.execute(args, options, global_options)
63
+ resource_type = args[0]
64
+ vmid_arg = args[1]
65
+
66
+ return Cloudinit.usage_error("Resource type required (vm)") unless resource_type
67
+ return Cloudinit.usage_error("VMID is required") unless vmid_arg
68
+ return Cloudinit.unknown_resource_type(resource_type) unless resource_type == "vm"
69
+
70
+ Cloudinit.with_service(global_options) do |service|
71
+ entries = service.pending(vmid_arg.to_i, node: options[:node])
72
+ render(entries, global_options)
73
+ end
74
+ end
75
+
76
+ # Renders pending entries via the configured output formatter.
77
+ #
78
+ # For +table+ output, prints a flat 4-column table (key, current,
79
+ # pending, action) or a friendly "no pending changes" notice when
80
+ # the list is empty. For +json+/+yaml+ output, emits the raw
81
+ # collection so it can be parsed downstream.
82
+ #
83
+ # @param entries [Array<Hash>] pending entries from the service
84
+ # @param global_options [Hash] global CLI options
85
+ # @return [void]
86
+ def self.render(entries, global_options)
87
+ format = global_options[:output] || "table"
88
+
89
+ case format
90
+ when "json"
91
+ require "json"
92
+ $stdout.puts JSON.pretty_generate(entries)
93
+ when "yaml"
94
+ require "yaml"
95
+ $stdout.puts entries.map { |e| stringify_keys(e) }.to_yaml
96
+ else
97
+ render_table(entries)
98
+ end
99
+ end
100
+
101
+ # Renders pending entries as a plain text table.
102
+ #
103
+ # @param entries [Array<Hash>] pending entries
104
+ # @return [void]
105
+ def self.render_table(entries)
106
+ if entries.empty?
107
+ $stdout.puts "No pending cloud-init changes."
108
+ return
109
+ end
110
+
111
+ rows = entries.map do |e|
112
+ action = e[:delete].to_i.positive? ? "delete" : (e[:pending] ? "update" : "-")
113
+ [e[:key].to_s, (e[:value] || "-").to_s, (e[:pending] || "-").to_s, action]
114
+ end
115
+
116
+ headers = %w[KEY CURRENT PENDING ACTION]
117
+ widths = headers.each_with_index.map do |h, i|
118
+ [h.length, *rows.map { |r| r[i].length }].max
119
+ end
120
+
121
+ $stdout.puts headers.each_with_index.map { |h, i| h.ljust(widths[i]) }.join(" ")
122
+ rows.each do |row|
123
+ $stdout.puts row.each_with_index.map { |v, i| v.ljust(widths[i]) }.join(" ")
124
+ end
125
+ end
126
+
127
+ # Stringifies hash keys for YAML output consistency.
128
+ #
129
+ # @param hash [Hash] hash with symbol or string keys
130
+ # @return [Hash] hash with string keys
131
+ def self.stringify_keys(hash)
132
+ hash.each_with_object({}) { |(k, v), out| out[k.to_s] = v }
133
+ end
134
+ end
135
+ end
136
+ end
137
+ end