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,139 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pvectl
4
+ module Repositories
5
+ # Repository for systemd services on Proxmox nodes.
6
+ #
7
+ # Wraps the `/nodes/{node}/services` API endpoints. Provides listing of
8
+ # services on a node and lifecycle operations (start/stop/restart/reload)
9
+ # which return Proxmox task UPIDs.
10
+ #
11
+ # @example Listing services on a node
12
+ # repo = Service.new(connection)
13
+ # services = repo.list(node: "pve1")
14
+ # services.each { |s| puts "#{s.service}: #{s.active_state}" }
15
+ #
16
+ # @example Restarting a service
17
+ # upid = repo.restart("pve1", "pveproxy")
18
+ #
19
+ # @see Pvectl::Models::Service Service model
20
+ # @see Pvectl::Connection API connection
21
+ #
22
+ class Service < Base
23
+ # Lists systemd services on a node.
24
+ #
25
+ # When node is nil, iterates over all online nodes in the cluster.
26
+ # When node is specified, queries only that node.
27
+ #
28
+ # @param node [String, nil] node name (or nil for all online nodes)
29
+ # @return [Array<Models::Service>] collection of Service models
30
+ def list(node: nil)
31
+ if node
32
+ services_for_node(node)
33
+ else
34
+ online_nodes.flat_map { |node_name| services_for_node(node_name) }
35
+ end
36
+ end
37
+
38
+ # Reads single service state.
39
+ #
40
+ # @param node [String] node name
41
+ # @param service [String] service identifier (e.g., "pveproxy")
42
+ # @return [Models::Service, nil] service model or nil on error
43
+ def state(node, service)
44
+ resp = connection.client["nodes/#{node}/services/#{service}/state"].get
45
+ data = extract_data(resp)
46
+ return nil if data.nil? || data.empty?
47
+
48
+ build_model(data.merge(node: node))
49
+ rescue StandardError
50
+ nil
51
+ end
52
+
53
+ # Starts a service. Returns the task UPID.
54
+ #
55
+ # @param node [String] node name
56
+ # @param service [String] service identifier
57
+ # @return [String] task UPID
58
+ def start(node, service)
59
+ post_action(node, service, "start")
60
+ end
61
+
62
+ # Stops a service. Returns the task UPID.
63
+ #
64
+ # @param node [String] node name
65
+ # @param service [String] service identifier
66
+ # @return [String] task UPID
67
+ def stop(node, service)
68
+ post_action(node, service, "stop")
69
+ end
70
+
71
+ # Hard-restarts a service. Returns the task UPID.
72
+ #
73
+ # @param node [String] node name
74
+ # @param service [String] service identifier
75
+ # @return [String] task UPID
76
+ def restart(node, service)
77
+ post_action(node, service, "restart")
78
+ end
79
+
80
+ # Reloads a service (falls back to restart if unsupported). Returns the task UPID.
81
+ #
82
+ # @param node [String] node name
83
+ # @param service [String] service identifier
84
+ # @return [String] task UPID
85
+ def reload(node, service)
86
+ post_action(node, service, "reload")
87
+ end
88
+
89
+ protected
90
+
91
+ # Builds Service model from API response data.
92
+ #
93
+ # @param data [Hash] API response hash
94
+ # @return [Models::Service] Service model instance
95
+ def build_model(data)
96
+ Models::Service.new(data)
97
+ end
98
+
99
+ private
100
+
101
+ # POSTs a lifecycle action and extracts the UPID from the response.
102
+ #
103
+ # @param node [String] node name
104
+ # @param service [String] service identifier
105
+ # @param action [String] one of "start", "stop", "restart", "reload"
106
+ # @return [String] task UPID
107
+ def post_action(node, service, action)
108
+ resp = connection.client["nodes/#{node}/services/#{service}/#{action}"].post({})
109
+ data = extract_data(resp)
110
+ data.is_a?(String) ? data : data.to_s
111
+ end
112
+
113
+ # Fetches services for a single node.
114
+ #
115
+ # @param node_name [String] node name
116
+ # @return [Array<Models::Service>] services on that node
117
+ def services_for_node(node_name)
118
+ response = connection.client["nodes/#{node_name}/services"].get
119
+ services_data = unwrap(response)
120
+ services_data.map { |data| build_model(data.merge(node: node_name)) }
121
+ rescue StandardError
122
+ []
123
+ end
124
+
125
+ # Fetches list of online node names.
126
+ #
127
+ # @return [Array<String>] online node names
128
+ def online_nodes
129
+ response = connection.client["nodes"].get
130
+ nodes_data = unwrap(response)
131
+ nodes_data
132
+ .select { |n| n[:status] == "online" }
133
+ .map { |n| n[:node] || n[:name] }
134
+ rescue StandardError
135
+ []
136
+ end
137
+ end
138
+ end
139
+ end
@@ -0,0 +1,133 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pvectl
4
+ module Repositories
5
+ # Repository for VM/container snapshots.
6
+ #
7
+ # Handles listing snapshots for both QEMU VMs and LXC containers.
8
+ # Filters out the "current" snapshot which represents the live state.
9
+ #
10
+ # @example Listing snapshots for a VM
11
+ # repo = Snapshot.new(connection)
12
+ # snapshots = repo.list(100, "pve1", :qemu)
13
+ # snapshots.each { |s| puts "#{s.name}: #{s.description}" }
14
+ #
15
+ # @example Listing snapshots for a container
16
+ # snapshots = repo.list(101, "pve1", :lxc)
17
+ #
18
+ # @see Pvectl::Models::Snapshot Snapshot model
19
+ # @see Pvectl::Connection API connection
20
+ #
21
+ class Snapshot < Base
22
+ # Lists all snapshots for a VM or container.
23
+ #
24
+ # Uses `/nodes/{node}/qemu/{vmid}/snapshot` for VMs or
25
+ # `/nodes/{node}/lxc/{vmid}/snapshot` for containers.
26
+ # Filters out the "current" snapshot which represents live state.
27
+ #
28
+ # @param vmid [Integer, String] VM or container identifier
29
+ # @param node [String] node name where the resource resides
30
+ # @param resource_type [Symbol] :qemu for VMs, :lxc for containers
31
+ # @return [Array<Models::Snapshot>] collection of snapshot models
32
+ def list(vmid, node, resource_type)
33
+ endpoint = resource_endpoint(resource_type)
34
+ response = connection.client["nodes/#{node}/#{endpoint}/#{vmid}/snapshot"].get
35
+
36
+ response
37
+ .reject { |s| s[:name] == "current" }
38
+ .map { |data| build_model(data, vmid, node, resource_type) }
39
+ rescue StandardError
40
+ []
41
+ end
42
+
43
+ # Creates a snapshot for a VM or container.
44
+ #
45
+ # Uses `POST /nodes/{node}/qemu|lxc/{vmid}/snapshot` with parameters:
46
+ # - snapname: Name of the snapshot
47
+ # - description: Optional description
48
+ # - vmstate: Include VM RAM state (QEMU only, ignored for LXC)
49
+ #
50
+ # @param vmid [Integer, String] VM or container identifier
51
+ # @param node [String] node name where the resource resides
52
+ # @param resource_type [Symbol] :qemu for VMs, :lxc for containers
53
+ # @param name [String] name for the snapshot
54
+ # @param description [String, nil] optional description
55
+ # @param vmstate [Boolean] include RAM state (QEMU only)
56
+ # @return [String] UPID of the snapshot creation task
57
+ def create(vmid, node, resource_type, name:, description: nil, vmstate: false)
58
+ endpoint = resource_endpoint(resource_type)
59
+ params = { snapname: name }
60
+ params[:description] = description if description
61
+ params[:vmstate] = vmstate if vmstate && resource_type == :qemu
62
+
63
+ connection.client["nodes/#{node}/#{endpoint}/#{vmid}/snapshot"].post(params)
64
+ end
65
+
66
+ # Deletes a snapshot.
67
+ #
68
+ # Uses `DELETE /nodes/{node}/qemu|lxc/{vmid}/snapshot/{snapname}`.
69
+ #
70
+ # @param vmid [Integer, String] VM or container identifier
71
+ # @param node [String] node name where the resource resides
72
+ # @param resource_type [Symbol] :qemu for VMs, :lxc for containers
73
+ # @param snapname [String] name of the snapshot to delete
74
+ # @param force [Boolean] force deletion even if snapshot is referenced
75
+ # @return [String] UPID of the delete task
76
+ def delete(vmid, node, resource_type, snapname, force: false)
77
+ endpoint = resource_endpoint(resource_type)
78
+ params = {}
79
+ params[:force] = true if force
80
+
81
+ connection.client["nodes/#{node}/#{endpoint}/#{vmid}/snapshot/#{snapname}"].delete(params)
82
+ end
83
+
84
+ # Rolls back to a snapshot.
85
+ #
86
+ # Uses `POST /nodes/{node}/qemu|lxc/{vmid}/snapshot/{snapname}/rollback`.
87
+ #
88
+ # @param vmid [Integer, String] VM or container identifier
89
+ # @param node [String] node name where the resource resides
90
+ # @param resource_type [Symbol] :qemu for VMs, :lxc for containers
91
+ # @param snapname [String] name of the snapshot to rollback to
92
+ # @param start [Boolean] start VM/container after rollback
93
+ # @return [String] UPID of the rollback task
94
+ def rollback(vmid, node, resource_type, snapname, start: false)
95
+ endpoint = resource_endpoint(resource_type)
96
+ params = {}
97
+ params[:start] = true if start
98
+
99
+ connection.client["nodes/#{node}/#{endpoint}/#{vmid}/snapshot/#{snapname}/rollback"].post(params)
100
+ end
101
+
102
+ private
103
+
104
+ # Returns the API endpoint prefix for the resource type.
105
+ #
106
+ # @param resource_type [Symbol] :qemu or :lxc
107
+ # @return [String] "qemu" or "lxc"
108
+ def resource_endpoint(resource_type)
109
+ resource_type == :lxc ? "lxc" : "qemu"
110
+ end
111
+
112
+ # Builds Snapshot model from API response data.
113
+ #
114
+ # @param data [Hash] API response hash
115
+ # @param vmid [Integer, String] VM/container ID
116
+ # @param node [String] node name
117
+ # @param resource_type [Symbol] :qemu or :lxc
118
+ # @return [Models::Snapshot] snapshot model instance
119
+ def build_model(data, vmid, node, resource_type)
120
+ Models::Snapshot.new(
121
+ name: data[:name],
122
+ snaptime: data[:snaptime],
123
+ description: data[:description],
124
+ vmstate: data[:vmstate],
125
+ parent: data[:parent],
126
+ vmid: vmid,
127
+ node: node,
128
+ resource_type: resource_type
129
+ )
130
+ end
131
+ end
132
+ end
133
+ end
@@ -0,0 +1,302 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pvectl
4
+ module Repositories
5
+ # Repository for Proxmox cluster storage pools.
6
+ #
7
+ # Uses the `/cluster/resources?type=storage` API endpoint for cluster-wide view.
8
+ # Handles aggregation of shared storage (deduplication by name).
9
+ #
10
+ # @example Listing all storage pools
11
+ # repo = Storage.new(connection)
12
+ # storage_pools = repo.list
13
+ # storage_pools.each { |s| puts "#{s.name}: #{s.plugintype}" }
14
+ #
15
+ # @example Listing storage on a specific node
16
+ # storage_pools = repo.list(node: "pve1")
17
+ #
18
+ # @see Pvectl::Models::Storage Storage model
19
+ # @see Pvectl::Connection API connection
20
+ #
21
+ class Storage < Base
22
+ # Lists all storage pools in the cluster.
23
+ #
24
+ # Uses `/cluster/resources?type=storage` endpoint for cluster-wide view.
25
+ # Aggregates shared storage by keeping first entry per storage name.
26
+ #
27
+ # @param node [String, nil] filter by node name
28
+ # @return [Array<Models::Storage>] collection of Storage models
29
+ def list(node: nil)
30
+ response = connection.client["cluster/resources"].get(params: { type: "storage" })
31
+ storage_data = normalize_response(response)
32
+
33
+ # Aggregate shared storage (deduplicate by name, keep first entry)
34
+ aggregated = aggregate_storage(storage_data)
35
+
36
+ # Filter by node if specified
37
+ if node
38
+ aggregated = aggregated.select { |data| data[:node] == node || data[:shared] == 1 }
39
+ end
40
+
41
+ aggregated.map { |data| build_model(data) }
42
+ end
43
+
44
+ # Gets a single storage pool by name.
45
+ #
46
+ # @param name [String] storage pool name
47
+ # @return [Models::Storage, nil] Storage model or nil if not found
48
+ def get(name)
49
+ list.find { |s| s.name == name }
50
+ end
51
+
52
+ # Lists all instances of a storage by name.
53
+ #
54
+ # For shared storage: returns single instance.
55
+ # For local storage: returns all instances (one per node).
56
+ #
57
+ # @param name [String] storage name
58
+ # @return [Array<Models::Storage>] array of storage instances
59
+ def list_instances(name)
60
+ response = connection.client["cluster/resources"].get(params: { type: "storage" })
61
+ storage_data = normalize_response(response)
62
+ storage_data.select { |s| s[:storage] == name }.map { |data| build_model(data) }
63
+ end
64
+
65
+ # Gets storage for a specific node.
66
+ #
67
+ # @param name [String] storage name
68
+ # @param node [String] node name
69
+ # @return [Models::Storage, nil] Storage model or nil if not found
70
+ def get_for_node(name, node)
71
+ list_instances(name).find { |s| s.node == node }
72
+ end
73
+
74
+ # Describes a storage with comprehensive details from multiple API endpoints.
75
+ #
76
+ # Fetches:
77
+ # - Basic storage info from cluster resources (via get or get_for_node)
78
+ # - Configuration from /storage/{name}
79
+ # - Status from /nodes/{node}/storage/{name}/status
80
+ # - Content (volumes) from /nodes/{node}/storage/{name}/content
81
+ #
82
+ # @param name [String] storage name
83
+ # @param node [String, nil] specific node for local storage
84
+ # @return [Models::Storage, nil] Storage model with full details, or nil if not found
85
+ def describe(name, node: nil)
86
+ storage = node ? get_for_node(name, node) : get(name)
87
+ return nil unless storage
88
+
89
+ # GET /storage/{name} - configuration
90
+ config = fetch_storage_config(name)
91
+
92
+ # Find active node for this storage
93
+ node = find_node_for_storage(name, storage)
94
+
95
+ # GET /nodes/{node}/storage/{name}/status (if node available)
96
+ status = node ? fetch_storage_status(node, name) : {}
97
+
98
+ # GET /nodes/{node}/storage/{name}/content (volumes)
99
+ content = node ? fetch_storage_content(node, name) : []
100
+
101
+ build_describe_model(storage, config, status, content)
102
+ end
103
+
104
+ # Lists storage pools for a specific node.
105
+ #
106
+ # Uses `/nodes/{node}/storage` endpoint which returns detailed
107
+ # per-node storage information including avail, enabled, active flags.
108
+ #
109
+ # @param node_name [String] node name
110
+ # @return [Array<Models::Storage>] collection of Storage models
111
+ def list_for_node(node_name)
112
+ response = connection.client["nodes/#{node_name}/storage"].get
113
+ storage_data = normalize_response(response)
114
+
115
+ storage_data.map { |data| build_model_from_node_api(data, node_name) }
116
+ end
117
+
118
+ protected
119
+
120
+ # Builds Storage model from API response data.
121
+ #
122
+ # @param data [Hash] API response hash
123
+ # @return [Models::Storage] Storage model instance
124
+ def build_model(data)
125
+ Models::Storage.new(
126
+ name: data[:storage],
127
+ plugintype: data[:plugintype],
128
+ status: data[:status],
129
+ node: data[:node],
130
+ disk: data[:disk],
131
+ maxdisk: data[:maxdisk],
132
+ content: data[:content],
133
+ shared: data[:shared]
134
+ )
135
+ end
136
+
137
+ # Builds Storage model from /nodes/{node}/storage API response.
138
+ #
139
+ # Maps node-specific API fields to model attributes:
140
+ # - type -> plugintype
141
+ # - used -> disk
142
+ # - total -> maxdisk
143
+ # - active -> status (derived)
144
+ #
145
+ # @param data [Hash] API response hash
146
+ # @param node_name [String] node name (not in response, passed as param)
147
+ # @return [Models::Storage] Storage model instance
148
+ def build_model_from_node_api(data, node_name)
149
+ Models::Storage.new(
150
+ name: data[:storage],
151
+ plugintype: data[:type],
152
+ node: node_name,
153
+ disk: data[:used],
154
+ maxdisk: data[:total],
155
+ avail: data[:avail],
156
+ content: data[:content],
157
+ enabled: data[:enabled],
158
+ active: data[:active],
159
+ shared: 0 # /nodes/{node}/storage doesn't return shared flag
160
+ )
161
+ end
162
+
163
+ private
164
+
165
+ # Fetches storage configuration from /storage/{name}.
166
+ #
167
+ # @param name [String] storage name
168
+ # @return [Hash] configuration data or empty hash on error
169
+ def fetch_storage_config(name)
170
+ response = connection.client["storage/#{name}"].get
171
+ extract_data(response)
172
+ rescue StandardError
173
+ {}
174
+ end
175
+
176
+ # Fetches storage status from /nodes/{node}/storage/{name}/status.
177
+ #
178
+ # @param node [String] node name
179
+ # @param name [String] storage name
180
+ # @return [Hash] status data or empty hash on error
181
+ def fetch_storage_status(node, name)
182
+ response = connection.client["nodes/#{node}/storage/#{name}/status"].get
183
+ extract_data(response)
184
+ rescue StandardError
185
+ {}
186
+ end
187
+
188
+ # Fetches storage content (volumes) from /nodes/{node}/storage/{name}/content.
189
+ #
190
+ # @param node [String] node name
191
+ # @param name [String] storage name
192
+ # @return [Array<Hash>] volumes array or empty array on error
193
+ def fetch_storage_content(node, name)
194
+ response = connection.client["nodes/#{node}/storage/#{name}/content"].get
195
+ unwrap(response)
196
+ rescue StandardError
197
+ []
198
+ end
199
+
200
+ # Finds an active node where this storage is accessible.
201
+ #
202
+ # For local storage: uses the node it belongs to.
203
+ # For shared storage: finds first online node in the cluster.
204
+ #
205
+ # @param name [String] storage name
206
+ # @param storage [Models::Storage] storage model
207
+ # @return [String, nil] node name or nil if unavailable
208
+ def find_node_for_storage(name, storage)
209
+ # For local storage, use the node it belongs to
210
+ return storage.node unless storage.shared?
211
+
212
+ # For shared storage, find first available online node
213
+ nodes_response = connection.client["nodes"].get
214
+ nodes = unwrap(nodes_response)
215
+ online_node = nodes.find { |n| n[:status] == "online" }
216
+ online_node&.dig(:node)
217
+ rescue StandardError
218
+ nil
219
+ end
220
+
221
+ # Builds Storage model with comprehensive describe data.
222
+ #
223
+ # Merges data from basic storage, config, status, and content endpoints.
224
+ #
225
+ # @param storage [Models::Storage] base storage model
226
+ # @param config [Hash] configuration from /storage/{name}
227
+ # @param status [Hash] status from /nodes/{node}/storage/{name}/status
228
+ # @param content [Array<Hash>] volumes from /content endpoint
229
+ # @return [Models::Storage] complete storage model
230
+ def build_describe_model(storage, config, status, content)
231
+ Models::Storage.new(
232
+ # Basic attributes from list
233
+ name: storage.name,
234
+ plugintype: storage.plugintype,
235
+ status: storage.status,
236
+ node: storage.node,
237
+ disk: storage.disk,
238
+ maxdisk: storage.maxdisk,
239
+ content: storage.content,
240
+ shared: storage.shared,
241
+
242
+ # Config attributes
243
+ path: config[:path],
244
+ server: config[:server],
245
+ export: config[:export],
246
+ pool: config[:pool],
247
+ vgname: config[:vgname],
248
+ thinpool: config[:thinpool],
249
+ nodes: config[:nodes],
250
+ "prune-backups": config[:"prune-backups"],
251
+ maxfiles: config[:maxfiles],
252
+
253
+ # Status attributes (override if available)
254
+ avail: status[:avail] || storage.avail,
255
+ enabled: status[:enabled] || storage.enabled,
256
+ active: status[:active] || storage.active_flag,
257
+
258
+ # Content (volumes)
259
+ volumes: content
260
+ )
261
+ end
262
+
263
+ # Normalizes API response to array format.
264
+ #
265
+ # @param response [Array, Hash] API response
266
+ # @return [Array<Hash>] array of storage data hashes
267
+ def normalize_response(response)
268
+ if response.is_a?(Array)
269
+ response
270
+ elsif response.is_a?(Hash) && response[:data]
271
+ response[:data]
272
+ else
273
+ response.to_a
274
+ end
275
+ end
276
+
277
+ # Aggregates storage data by name.
278
+ # For shared storage, keeps first entry (data is identical across nodes).
279
+ # For local storage, keeps all entries.
280
+ #
281
+ # @param storage_data [Array<Hash>] raw storage data from API
282
+ # @return [Array<Hash>] aggregated storage data
283
+ def aggregate_storage(storage_data)
284
+ seen = {}
285
+ storage_data.each do |data|
286
+ name = data[:storage]
287
+ next if name.nil?
288
+
289
+ # For shared storage, keep only first entry
290
+ if data[:shared] == 1
291
+ seen[name] ||= data
292
+ else
293
+ # For local storage, include all (unique by name+node)
294
+ key = "#{name}:#{data[:node]}"
295
+ seen[key] ||= data
296
+ end
297
+ end
298
+ seen.values
299
+ end
300
+ end
301
+ end
302
+ end
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pvectl
4
+ module Repositories
5
+ # Repository for Proxmox subscription information per node.
6
+ #
7
+ # Wraps the `GET /nodes/{node}/subscription` endpoint and aggregates
8
+ # subscription records across all nodes in the cluster.
9
+ #
10
+ # @example Listing subscriptions across the cluster
11
+ # repo = Subscription.new(connection)
12
+ # repo.list.each { |s| puts "#{s.node}: #{s.status} #{s.level}" }
13
+ #
14
+ # @see Pvectl::Models::Subscription
15
+ #
16
+ class Subscription < Base
17
+ # Lists subscription records for cluster nodes.
18
+ #
19
+ # Returns one Subscription per online node. Offline / unreachable
20
+ # nodes return a Subscription with status="unreachable" so the output
21
+ # remains stable instead of silently dropping rows.
22
+ #
23
+ # @param node [String, nil] filter to a single node (otherwise all online nodes)
24
+ # @return [Array<Models::Subscription>]
25
+ def list(node: nil)
26
+ node_names = node ? [node] : online_node_names
27
+ node_names.map { |name| fetch_for(name) }
28
+ end
29
+
30
+ # Fetches the subscription record for a single node.
31
+ #
32
+ # @param node [String] node name
33
+ # @return [Models::Subscription]
34
+ def get(node)
35
+ fetch_for(node)
36
+ end
37
+
38
+ protected
39
+
40
+ # Builds a Subscription model from API data plus the node name.
41
+ #
42
+ # @param data [Hash] API response payload (already extracted from :data wrapper)
43
+ # @param node_name [String] node the record belongs to
44
+ # @return [Models::Subscription]
45
+ def build_model(data, node_name)
46
+ attrs = (data || {}).merge(node: node_name)
47
+ Models::Subscription.new(attrs)
48
+ end
49
+
50
+ private
51
+
52
+ # Returns names of all online nodes in the cluster.
53
+ #
54
+ # @return [Array<String>]
55
+ def online_node_names
56
+ response = connection.client["nodes"].get
57
+ nodes = unwrap(response)
58
+ nodes
59
+ .select { |n| n[:status].nil? || n[:status] == "online" }
60
+ .map { |n| n[:node] || n[:name] }
61
+ .compact
62
+ end
63
+
64
+ # Fetches one node's subscription, recovering from API errors gracefully.
65
+ #
66
+ # @param node_name [String]
67
+ # @return [Models::Subscription]
68
+ def fetch_for(node_name)
69
+ response = connection.client["nodes/#{node_name}/subscription"].get
70
+ data = extract_data(response)
71
+ build_model(data, node_name)
72
+ rescue StandardError => e
73
+ build_model({ status: "unreachable", message: e.message }, node_name)
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pvectl
4
+ module Repositories
5
+ # Repository for reading node syslog.
6
+ # Uses GET /nodes/{node}/syslog endpoint.
7
+ class Syslog < Base
8
+ # @param node [String] node name (required)
9
+ # @param limit [Integer] max entries (default 50)
10
+ # @param since [String, nil] start timestamp
11
+ # @param until_time [String, nil] end timestamp
12
+ # @param service [String, nil] filter by service name
13
+ # @return [Array<Models::SyslogEntry>]
14
+ def list(node:, limit: 50, since: nil, until_time: nil, service: nil)
15
+ params = { limit: limit }
16
+ params[:since] = since if since
17
+ params[:until] = until_time if until_time
18
+ params[:service] = service if service
19
+
20
+ response = connection.client["nodes/#{node}/syslog"].get(params: params)
21
+ models_from(response, Models::SyslogEntry)
22
+ end
23
+ end
24
+ end
25
+ end