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,96 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pvectl
4
+ module Repositories
5
+ # Repository for Proxmox node capabilities.
6
+ #
7
+ # Aggregates feature information from the two most useful capability
8
+ # endpoints — QEMU CPU models and QEMU machine types — into a flat
9
+ # collection of Capability models. The cpu-flags and migration
10
+ # capability endpoints are intentionally out of scope for the initial
11
+ # implementation (YAGNI); they can be added when there is a concrete
12
+ # consumer need.
13
+ #
14
+ # @example List capabilities for a node
15
+ # repo = Capabilities.new(connection)
16
+ # caps = repo.list(node: "pve1")
17
+ # caps.each { |c| puts "#{c.kind}: #{c.name}" }
18
+ #
19
+ # @see Pvectl::Models::Capability
20
+ #
21
+ class Capabilities < Base
22
+ # CPU capability endpoint path (without leading slash).
23
+ CPU_ENDPOINT = "capabilities/qemu/cpu"
24
+
25
+ # Machine type capability endpoint path (without leading slash).
26
+ MACHINES_ENDPOINT = "capabilities/qemu/machines"
27
+
28
+ # Lists capabilities for a node.
29
+ #
30
+ # Combines `GET /nodes/{node}/capabilities/qemu/cpu` and
31
+ # `GET /nodes/{node}/capabilities/qemu/machines`. Each entry becomes
32
+ # a Capability instance — CPU models first, then machine types.
33
+ #
34
+ # @param node [String] cluster node name (required)
35
+ # @return [Array<Models::Capability>]
36
+ # @raise [ArgumentError] when `node` is nil/empty
37
+ # @raise [StandardError] propagated from the API on auth/connection failure
38
+ def list(node:)
39
+ raise ArgumentError, "node is required" if node.nil? || node.to_s.empty?
40
+
41
+ cpus(node) + machines(node)
42
+ end
43
+
44
+ private
45
+
46
+ # Fetches CPU model capabilities.
47
+ #
48
+ # @param node [String]
49
+ # @return [Array<Models::Capability>]
50
+ def cpus(node)
51
+ response = connection.client["nodes/#{node}/#{CPU_ENDPOINT}"].get
52
+ unwrap(response).map { |data| build_cpu(data, node) }
53
+ end
54
+
55
+ # Fetches machine type capabilities.
56
+ #
57
+ # @param node [String]
58
+ # @return [Array<Models::Capability>]
59
+ def machines(node)
60
+ response = connection.client["nodes/#{node}/#{MACHINES_ENDPOINT}"].get
61
+ unwrap(response).map { |data| build_machine(data, node) }
62
+ end
63
+
64
+ # Builds a CPU capability model.
65
+ #
66
+ # @param data [Hash] one entry from the CPU endpoint
67
+ # @param node [String]
68
+ # @return [Models::Capability]
69
+ def build_cpu(data, node)
70
+ Models::Capability.new(
71
+ node_name: node,
72
+ kind: :cpu,
73
+ name: data[:name],
74
+ vendor: data[:vendor],
75
+ custom: data[:custom] || false
76
+ )
77
+ end
78
+
79
+ # Builds a machine type capability model.
80
+ #
81
+ # @param data [Hash] one entry from the machines endpoint
82
+ # @param node [String]
83
+ # @return [Models::Capability]
84
+ def build_machine(data, node)
85
+ Models::Capability.new(
86
+ node_name: node,
87
+ kind: :machine,
88
+ name: data[:id],
89
+ machine_type: data[:type],
90
+ version: data[:version],
91
+ changes: data[:changes]
92
+ )
93
+ end
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,503 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "task_list"
4
+
5
+ module Pvectl
6
+ module Repositories
7
+ # Repository for LXC containers.
8
+ #
9
+ # Uses the `/cluster/resources` API endpoint to list containers across the cluster.
10
+ # Filters to only include LXC containers (excludes QEMU VMs).
11
+ #
12
+ # @example Listing all containers
13
+ # repo = Container.new(connection)
14
+ # containers = repo.list
15
+ # containers.each { |ct| puts "#{ct.vmid}: #{ct.name}" }
16
+ #
17
+ # @example Listing containers on a specific node
18
+ # containers = repo.list(node: "pve-node1")
19
+ #
20
+ # @example Getting a single container
21
+ # ct = repo.get(100)
22
+ # puts ct.name if ct
23
+ #
24
+ # @see Pvectl::Models::Container Container model
25
+ # @see Pvectl::Connection API connection
26
+ #
27
+ class Container < Base
28
+ # Lists all containers in the cluster.
29
+ #
30
+ # Uses `/cluster/resources?type=lxc` endpoint for efficient cluster-wide
31
+ # listing. Filters to only include LXC containers (type == "lxc").
32
+ #
33
+ # @param node [String, nil] filter by node name
34
+ # @return [Array<Models::Container>] collection of Container models
35
+ def list(node: nil)
36
+ response = connection.client["cluster/resources"].get(params: { type: "vm" })
37
+ containers = unwrap(response).select { |r| r[:type] == "lxc" }
38
+ containers = containers.select { |r| r[:node] == node } if node
39
+ containers.map { |data| build_model(data) }
40
+ end
41
+
42
+ # Gets a single container by CTID.
43
+ #
44
+ # @param ctid [Integer, String] container identifier
45
+ # @return [Models::Container, nil] Container model or nil if not found
46
+ def get(ctid)
47
+ list.find { |ct| ct.vmid == ctid.to_i }
48
+ end
49
+
50
+ # Describes a container with comprehensive details from multiple API endpoints.
51
+ #
52
+ # @param ctid [Integer, String] container identifier
53
+ # @return [Models::Container, nil] Container model with full details, or nil if not found
54
+ def describe(ctid)
55
+ ctid = ctid.to_i
56
+
57
+ basic_data = find_container_basic_data(ctid)
58
+ return nil if basic_data.nil?
59
+
60
+ node = basic_data[:node]
61
+
62
+ config = fetch_config(node, ctid)
63
+ status = fetch_status(node, ctid)
64
+ snapshots = fetch_snapshots(node, ctid)
65
+ tasks = fetch_tasks(node, ctid)
66
+ firewall = fetch_firewall(node, ctid)
67
+
68
+ build_describe_model(basic_data, config, status, snapshots, tasks, firewall)
69
+ end
70
+
71
+ # Deletes a container from the cluster.
72
+ #
73
+ # @param ctid [Integer, String] Container identifier
74
+ # @param node [String] Node name
75
+ # @param destroy_disks [Boolean] destroy unreferenced disks (default: true)
76
+ # @param purge [Boolean] remove from HA, replication, backups (default: false)
77
+ # @param force [Boolean] force removal (default: false)
78
+ # @return [String] Task UPID
79
+ def delete(ctid, node, destroy_disks: true, purge: false, force: false)
80
+ params = {}
81
+ params["destroy-unreferenced-disks"] = 1 if destroy_disks
82
+ params[:purge] = 1 if purge
83
+ params[:force] = 1 if force
84
+
85
+ connection.client["nodes/#{node}/lxc/#{ctid}"].delete(params)
86
+ end
87
+
88
+ # Starts a container.
89
+ #
90
+ # @param ctid [Integer, String] Container identifier
91
+ # @param node [String] Node name
92
+ # @return [String] Task UPID
93
+ def start(ctid, node)
94
+ connection.client["nodes/#{node}/lxc/#{ctid}/status/start"].post
95
+ end
96
+
97
+ # Stops a container (hard stop).
98
+ #
99
+ # @param ctid [Integer, String] Container identifier
100
+ # @param node [String] Node name
101
+ # @return [String] Task UPID
102
+ def stop(ctid, node)
103
+ connection.client["nodes/#{node}/lxc/#{ctid}/status/stop"].post
104
+ end
105
+
106
+ # Shuts down a container gracefully.
107
+ #
108
+ # @param ctid [Integer, String] Container identifier
109
+ # @param node [String] Node name
110
+ # @return [String] Task UPID
111
+ def shutdown(ctid, node)
112
+ connection.client["nodes/#{node}/lxc/#{ctid}/status/shutdown"].post
113
+ end
114
+
115
+ # Clones a container to create a new container.
116
+ #
117
+ # Posts to `/nodes/{node}/lxc/{ctid}/clone` with the specified parameters.
118
+ # Note: LXC API uses `hostname` parameter (not `name` like QEMU).
119
+ #
120
+ # @param ctid [Integer, String] source container identifier
121
+ # @param node [String] source node name
122
+ # @param new_ctid [Integer] CTID for the new cloned container
123
+ # @param options [Hash] optional clone parameters
124
+ # @option options [String] :hostname hostname for the new container
125
+ # @option options [String] :target target node for the clone
126
+ # @option options [String] :storage target storage for the clone
127
+ # @option options [Boolean] :full full clone (true) or linked clone (false)
128
+ # @option options [String] :description description for the new container
129
+ # @option options [String] :pool resource pool for the new container
130
+ # @return [String] Task UPID
131
+ def clone(ctid, node, new_ctid, options = {})
132
+ params = { newid: new_ctid }
133
+ params[:hostname] = options[:hostname] if options[:hostname]
134
+ params[:target] = options[:target] if options[:target]
135
+ params[:storage] = options[:storage] if options[:storage]
136
+ params[:full] = options[:full] ? 1 : 0 if options.key?(:full)
137
+ params[:description] = options[:description] if options[:description]
138
+ params[:pool] = options[:pool] if options[:pool]
139
+
140
+ connection.client["nodes/#{node}/lxc/#{ctid}/clone"].post(params)
141
+ end
142
+
143
+ # Converts a container to a template.
144
+ #
145
+ # This is an irreversible operation. The container will become read-only
146
+ # and can only be used as a source for cloning.
147
+ #
148
+ # @param ctid [Integer, String] Container identifier
149
+ # @param node [String] Node name
150
+ # @return [void]
151
+ def convert_to_template(ctid, node)
152
+ connection.client["nodes/#{node}/lxc/#{ctid}/template"].post({})
153
+ end
154
+
155
+ # Migrates a container to another node.
156
+ #
157
+ # @param ctid [Integer, String] container identifier
158
+ # @param node [String] current node name
159
+ # @param params [Hash] migration parameters (:target, :online, :restart, :targetstorage)
160
+ # @return [String] Task UPID
161
+ # @raise [ArgumentError] if node name or ctid format is invalid
162
+ def migrate(ctid, node, params = {})
163
+ unless node.match?(/\A[a-z][a-z0-9-]*\z/)
164
+ raise ArgumentError, "Invalid node name: #{node}"
165
+ end
166
+ unless ctid.is_a?(Integer) && ctid.positive?
167
+ raise ArgumentError, "Invalid CTID: #{ctid}"
168
+ end
169
+
170
+ connection.client["nodes/#{node}/lxc/#{ctid}/migrate"].post(params)
171
+ end
172
+
173
+ # Moves a container volume to a different storage on the same node.
174
+ #
175
+ # POSTs to +/nodes/{node}/lxc/{ctid}/move_volume+ with the volume identifier
176
+ # and target storage. The operation is asynchronous — the returned UPID
177
+ # can be polled via Repositories::Task to track completion.
178
+ #
179
+ # @param ctid [Integer, String] container identifier
180
+ # @param node [String] node name where the container currently resides
181
+ # @param volume [String] volume identifier (e.g., "rootfs", "mp0")
182
+ # @param target_storage [String] destination storage ID
183
+ # @param delete [Boolean] delete the source volume after copy (default: false)
184
+ # @param bwlimit [Integer, nil] I/O bandwidth limit in KiB/s
185
+ # @return [String] Task UPID
186
+ #
187
+ # @example Move rootfs to a different storage
188
+ # repo.move_volume(200, "pve1", "rootfs", "local-lvm")
189
+ # #=> "UPID:pve1:..."
190
+ #
191
+ # @example Move and delete source volume
192
+ # repo.move_volume(200, "pve1", "mp0", "ceph-pool", delete: true)
193
+ def move_volume(ctid, node, volume, target_storage, delete: false, bwlimit: nil)
194
+ params = { volume: volume, storage: target_storage }
195
+ params[:delete] = 1 if delete
196
+ params[:bwlimit] = bwlimit if bwlimit
197
+
198
+ connection.client["nodes/#{node}/lxc/#{ctid}/move_volume"].post(params)
199
+ end
200
+
201
+ # Checks whether a feature (clone, snapshot, copy) is available for a container.
202
+ #
203
+ # Calls +GET /nodes/{node}/lxc/{vmid}/feature+ with the feature and
204
+ # optional snapshot name. The Proxmox LXC API returns +hasFeature+ (0/1).
205
+ # Unlike the QEMU variant, the LXC endpoint does not return a +nodes+ array;
206
+ # this method always returns +nodes: []+ for shape compatibility.
207
+ #
208
+ # @param ctid [Integer, String] container identifier
209
+ # @param node [String] node currently hosting the container
210
+ # @param feature [String] feature name (one of: clone, snapshot, copy)
211
+ # @param snapname [String, nil] snapshot name (required for some checks)
212
+ # @return [Hash] result with :available (Boolean) and :nodes (Array<String>)
213
+ #
214
+ # @example Check whether container 200 can be cloned
215
+ # repo.feature_available?(200, "pve1", "clone")
216
+ # #=> { available: true, nodes: [] }
217
+ def feature_available?(ctid, node, feature, snapname: nil)
218
+ params = { feature: feature }
219
+ params[:snapname] = snapname unless snapname.nil?
220
+
221
+ response = connection.client["nodes/#{node}/lxc/#{ctid}/feature"].get(params: params)
222
+ data = extract_data(response)
223
+
224
+ {
225
+ available: data[:hasFeature].to_i == 1,
226
+ nodes: Array(data[:nodes])
227
+ }
228
+ end
229
+
230
+ # Returns the next available CTID from the Proxmox cluster.
231
+ #
232
+ # Uses the +/cluster/nextid+ API endpoint which performs server-side allocation.
233
+ # This is more reliable than client-side scanning because it detects stale
234
+ # config files that don't appear in +/cluster/resources+.
235
+ #
236
+ # @return [Integer] next available CTID
237
+ def next_available_ctid
238
+ connection.client["cluster/nextid"].get.to_i
239
+ end
240
+
241
+ # Restarts a container (reboot).
242
+ #
243
+ # @param ctid [Integer, String] Container identifier
244
+ # @param node [String] Node name
245
+ # @return [String] Task UPID
246
+ def restart(ctid, node)
247
+ connection.client["nodes/#{node}/lxc/#{ctid}/status/reboot"].post
248
+ end
249
+
250
+ # Opens a terminal proxy session for a container.
251
+ #
252
+ # @param ctid [Integer, String] Container identifier
253
+ # @param node [String] Node name
254
+ # @return [Hash] termproxy data with :port, :ticket, :user keys
255
+ def termproxy(ctid, node)
256
+ response = connection.client["nodes/#{node}/lxc/#{ctid}/termproxy"].post({})
257
+ extract_data(response)
258
+ end
259
+
260
+ # Creates a new LXC container on the specified node.
261
+ #
262
+ # Posts to `/nodes/{node}/lxc` with the container configuration parameters.
263
+ # The ctid is merged into params automatically.
264
+ #
265
+ # @param node [String] target node name
266
+ # @param ctid [Integer] container identifier
267
+ # @param params [Hash] container configuration parameters (hostname, ostemplate, etc.)
268
+ # @return [String] Task UPID
269
+ #
270
+ # @example Create a basic container
271
+ # repo.create("pve1", 200, { hostname: "web-ct", ostemplate: "local:vztmpl/debian-12.tar.zst" })
272
+ # #=> "UPID:pve1:..."
273
+ def create(node, ctid, params = {})
274
+ api_params = params.merge(vmid: ctid)
275
+ connection.client["nodes/#{node}/lxc"].post(api_params)
276
+ end
277
+
278
+ # Updates an existing LXC container configuration.
279
+ #
280
+ # PUTs to +/nodes/{node}/lxc/{ctid}/config+ with configuration parameters.
281
+ # This is a synchronous operation — changes are applied immediately.
282
+ #
283
+ # @param ctid [Integer, String] container identifier
284
+ # @param node [String] node name
285
+ # @param params [Hash] container configuration parameters to update
286
+ # @return [nil]
287
+ def update(ctid, node, params = {})
288
+ connection.client["nodes/#{node}/lxc/#{ctid}/config"].put(params)
289
+ end
290
+
291
+ # Resizes a container disk.
292
+ #
293
+ # PUTs to +/nodes/{node}/lxc/{ctid}/resize+ with disk identifier
294
+ # and new size. This is a synchronous, irreversible operation —
295
+ # Proxmox does not support shrinking disks.
296
+ #
297
+ # @param ctid [Integer, String] container identifier
298
+ # @param node [String] node name
299
+ # @param disk [String] disk identifier (e.g., "rootfs", "mp0")
300
+ # @param size [String] new size, absolute ("50G") or relative ("+10G")
301
+ # @return [nil]
302
+ def resize(ctid, node, disk:, size:)
303
+ connection.client["nodes/#{node}/lxc/#{ctid}/resize"].put({ disk: disk, size: size })
304
+ end
305
+
306
+ # Fetches container configuration.
307
+ #
308
+ # @param node [String] node name
309
+ # @param ctid [Integer] container identifier
310
+ # @return [Hash] config data
311
+ def fetch_config(node, ctid)
312
+ resp = connection.client["nodes/#{node}/lxc/#{ctid}/config"].get
313
+ extract_data(resp)
314
+ rescue StandardError
315
+ {}
316
+ end
317
+
318
+ protected
319
+
320
+ # Builds Container model from API response data.
321
+ #
322
+ # @param data [Hash] API response hash
323
+ # @return [Models::Container] Container model instance
324
+ def build_model(data)
325
+ Models::Container.new(
326
+ vmid: data[:vmid],
327
+ name: data[:name],
328
+ status: data[:status],
329
+ node: data[:node],
330
+ cpu: data[:cpu],
331
+ maxcpu: data[:maxcpu],
332
+ mem: data[:mem],
333
+ maxmem: data[:maxmem],
334
+ swap: data[:swap],
335
+ maxswap: data[:maxswap],
336
+ disk: data[:disk],
337
+ maxdisk: data[:maxdisk],
338
+ uptime: data[:uptime],
339
+ template: data[:template],
340
+ tags: data[:tags],
341
+ pool: data[:pool],
342
+ lock: data[:lock],
343
+ netin: data[:netin],
344
+ netout: data[:netout],
345
+ type: data[:type]
346
+ )
347
+ end
348
+
349
+ private
350
+
351
+ # Finds container basic data from cluster resources.
352
+ #
353
+ # @param ctid [Integer] container identifier
354
+ # @return [Hash, nil] container data or nil if not found
355
+ def find_container_basic_data(ctid)
356
+ response = connection.client["cluster/resources"].get(params: { type: "vm" })
357
+ unwrap(response).find { |r| r[:type] == "lxc" && r[:vmid] == ctid }
358
+ end
359
+
360
+ # Fetches container runtime status.
361
+ #
362
+ # @param node [String] node name
363
+ # @param ctid [Integer] container identifier
364
+ # @return [Hash] status data
365
+ def fetch_status(node, ctid)
366
+ resp = connection.client["nodes/#{node}/lxc/#{ctid}/status/current"].get
367
+ extract_data(resp)
368
+ rescue StandardError
369
+ {}
370
+ end
371
+
372
+ # Fetches container snapshots.
373
+ #
374
+ # @param node [String] node name
375
+ # @param ctid [Integer] container identifier
376
+ # @return [Array<Hash>] snapshots list (excluding "current")
377
+ def fetch_snapshots(node, ctid)
378
+ resp = connection.client["nodes/#{node}/lxc/#{ctid}/snapshot"].get
379
+ unwrap(resp).reject { |s| s[:name] == "current" }
380
+ rescue StandardError
381
+ []
382
+ end
383
+
384
+ # Fetches firewall configuration (options, rules, aliases, IP sets).
385
+ #
386
+ # @param node [String] node name
387
+ # @param ctid [Integer] container identifier
388
+ # @return [Hash] firewall data with :options, :rules, :aliases, :ipset keys
389
+ def fetch_firewall(node, ctid)
390
+ base = "nodes/#{node}/lxc/#{ctid}/firewall"
391
+ options = (extract_data(connection.client["#{base}/options"].get) rescue {})
392
+ rules = (unwrap(connection.client["#{base}/rules"].get) rescue [])
393
+ aliases_data = (unwrap(connection.client["#{base}/aliases"].get) rescue [])
394
+ ipset = (unwrap(connection.client["#{base}/ipset"].get) rescue [])
395
+ { options: options, rules: rules, aliases: aliases_data, ipset: ipset }
396
+ rescue StandardError
397
+ {}
398
+ end
399
+
400
+ # Fetches recent task history for the container.
401
+ #
402
+ # @param node [String] node name
403
+ # @param ctid [Integer] container identifier
404
+ # @param limit [Integer] max entries (default 10)
405
+ # @return [Array<Models::TaskEntry>] recent tasks
406
+ def fetch_tasks(node, ctid, limit: 10)
407
+ task_list_repo = TaskList.new(connection)
408
+ task_list_repo.list(node: node, vmid: ctid, limit: limit)
409
+ rescue StandardError
410
+ []
411
+ end
412
+
413
+ # Extracts network interfaces from config.
414
+ # Network interfaces are stored as net0, net1, etc. keys.
415
+ #
416
+ # @param config [Hash] container config
417
+ # @return [Array<Hash>] parsed network interfaces
418
+ def extract_network_interfaces(config)
419
+ interfaces = []
420
+ config.each do |key, value|
421
+ next unless key.to_s.match?(/^net\d+$/)
422
+
423
+ parsed = parse_network_interface(key.to_s, value)
424
+ interfaces << parsed if parsed
425
+ end
426
+ interfaces
427
+ end
428
+
429
+ # Parses a single network interface string.
430
+ #
431
+ # @param key [String] interface key (e.g., "net0")
432
+ # @param value [String] interface configuration string
433
+ # @return [Hash] parsed interface data
434
+ def parse_network_interface(key, value)
435
+ return nil unless value.is_a?(String)
436
+
437
+ interface = { id: key }
438
+
439
+ # Parse key=value pairs from the interface string
440
+ value.split(",").each do |part|
441
+ k, v = part.split("=", 2)
442
+ interface[k.to_sym] = v if k && v
443
+ end
444
+
445
+ interface
446
+ end
447
+
448
+ # Builds Container model with describe-specific attributes.
449
+ #
450
+ # @param basic_data [Hash] basic container data from cluster/resources
451
+ # @param config [Hash] container config from /nodes/{node}/lxc/{ctid}/config
452
+ # @param status [Hash] container status from /nodes/{node}/lxc/{ctid}/status/current
453
+ # @param snapshots [Array<Hash>] container snapshots
454
+ # @return [Models::Container] Container model
455
+ def build_describe_model(basic_data, config, status, snapshots = [], tasks = [], firewall = {})
456
+ network_interfaces = extract_network_interfaces(config)
457
+
458
+ Models::Container.new(
459
+ # Basic attributes from cluster/resources
460
+ vmid: basic_data[:vmid],
461
+ name: basic_data[:name],
462
+ status: basic_data[:status],
463
+ node: basic_data[:node],
464
+ type: basic_data[:type],
465
+ cpu: basic_data[:cpu],
466
+ maxcpu: basic_data[:maxcpu],
467
+ mem: basic_data[:mem],
468
+ maxmem: basic_data[:maxmem],
469
+ swap: basic_data[:swap],
470
+ maxswap: basic_data[:maxswap],
471
+ disk: basic_data[:disk],
472
+ maxdisk: basic_data[:maxdisk],
473
+ uptime: basic_data[:uptime],
474
+ template: basic_data[:template],
475
+ tags: basic_data[:tags],
476
+ pool: basic_data[:pool],
477
+ lock: basic_data[:lock],
478
+ netin: basic_data[:netin],
479
+ netout: basic_data[:netout],
480
+
481
+ # Config attributes (kept for backward compat)
482
+ ostype: config[:ostype],
483
+ arch: config[:arch],
484
+ unprivileged: config[:unprivileged],
485
+ features: config[:features],
486
+ rootfs: config[:rootfs],
487
+ description: config[:description],
488
+ hostname: config[:hostname],
489
+
490
+ # Status attributes
491
+ pid: status[:pid],
492
+ ha: status[:ha],
493
+
494
+ # Parsed network interfaces
495
+ network_interfaces: network_interfaces,
496
+
497
+ # Raw API data for comprehensive describe
498
+ describe_data: { config: config, status: status, snapshots: snapshots, tasks: tasks, firewall: firewall }
499
+ )
500
+ end
501
+ end
502
+ end
503
+ end
@@ -0,0 +1,87 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pvectl
4
+ module Repositories
5
+ # Repository for physical disks on Proxmox nodes.
6
+ #
7
+ # Uses the `/nodes/{node}/disks/list` API endpoint to fetch
8
+ # physical disk information per node.
9
+ #
10
+ # @example Listing all disks across the cluster
11
+ # repo = Disk.new(connection)
12
+ # disks = repo.list
13
+ # disks.each { |d| puts "#{d.node}: #{d.devpath} (#{d.type})" }
14
+ #
15
+ # @example Listing disks on a specific node
16
+ # disks = repo.list(node: "pve1")
17
+ #
18
+ # @see Pvectl::Models::PhysicalDisk PhysicalDisk model
19
+ # @see Pvectl::Connection API connection
20
+ #
21
+ class Disk < Base
22
+ # Lists physical disks, optionally filtered by node.
23
+ #
24
+ # When node is nil, iterates over all online nodes in the cluster.
25
+ # When node is specified, queries only that node.
26
+ #
27
+ # @param node [String, nil] filter by node name
28
+ # @return [Array<Models::PhysicalDisk>] collection of PhysicalDisk models
29
+ def list(node: nil)
30
+ if node
31
+ disks_for_node(node)
32
+ else
33
+ online_nodes.flat_map { |node_name| disks_for_node(node_name) }
34
+ end
35
+ end
36
+
37
+ # Fetches SMART data for a specific disk on a node.
38
+ #
39
+ # @param node_name [String] node name
40
+ # @param disk_path [String] device path (e.g., "/dev/nvme0n1")
41
+ # @return [Hash{Symbol => untyped}] SMART data with keys: :health, :type, :attributes, :text
42
+ def smart(node_name, disk_path)
43
+ response = connection.client["nodes/#{node_name}/disks/smart"].get(params: { disk: disk_path })
44
+ extract_data(response)
45
+ rescue StandardError
46
+ {}
47
+ end
48
+
49
+ protected
50
+
51
+ # Builds PhysicalDisk model from API response data.
52
+ #
53
+ # @param data [Hash] API response hash
54
+ # @return [Models::PhysicalDisk] PhysicalDisk model instance
55
+ def build_model(data)
56
+ Models::PhysicalDisk.new(data)
57
+ end
58
+
59
+ private
60
+
61
+ # Fetches disks for a single node.
62
+ #
63
+ # @param node_name [String] node name
64
+ # @return [Array<Models::PhysicalDisk>] disks on that node
65
+ def disks_for_node(node_name)
66
+ response = connection.client["nodes/#{node_name}/disks/list"].get
67
+ disks_data = unwrap(response)
68
+ disks_data.map { |data| build_model(data.merge(node: node_name)) }
69
+ rescue StandardError
70
+ []
71
+ end
72
+
73
+ # Fetches list of online node names.
74
+ #
75
+ # @return [Array<String>] online node names
76
+ def online_nodes
77
+ response = connection.client["nodes"].get
78
+ nodes_data = unwrap(response)
79
+ nodes_data
80
+ .select { |n| n[:status] == "online" }
81
+ .map { |n| n[:node] || n[:name] }
82
+ rescue StandardError
83
+ []
84
+ end
85
+ end
86
+ end
87
+ end