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,92 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pvectl
4
+ module Parsers
5
+ # Parses and converts cloud-init configuration strings for Proxmox VMs.
6
+ #
7
+ # CloudInitConfig handles the conversion between user-friendly key=value
8
+ # cloud-init specifications and the parameter names required by the Proxmox API.
9
+ # Unlike DiskConfig and NetConfig, all keys are optional.
10
+ #
11
+ # @example Parsing a cloud-init config string
12
+ # config = CloudInitConfig.parse("user=admin,password=secret,ip=dhcp")
13
+ # config[:user] #=> "admin"
14
+ # config[:password] #=> "secret"
15
+ # config[:ip] #=> "dhcp"
16
+ #
17
+ # @example Converting to Proxmox API parameters
18
+ # config = { user: "admin", ip: "dhcp" }
19
+ # CloudInitConfig.to_proxmox_params(config)
20
+ # #=> { ciuser: "admin", ipconfig0: "ip=dhcp" }
21
+ #
22
+ class CloudInitConfig
23
+ # All recognized cloud-init configuration keys.
24
+ VALID_KEYS = %w[user password sshkeys ip gw nameserver searchdomain].freeze
25
+
26
+ # Parses a comma-separated key=value cloud-init config string into a Hash.
27
+ #
28
+ # @param string [String] cloud-init config in "key=value,key=value" format
29
+ # @return [Hash<Symbol, String>] parsed configuration
30
+ # @raise [ArgumentError] if unknown keys are present
31
+ #
32
+ # @example
33
+ # CloudInitConfig.parse("user=admin,ip=dhcp,nameserver=8.8.8.8")
34
+ # #=> { user: "admin", ip: "dhcp", nameserver: "8.8.8.8" }
35
+ def self.parse(string)
36
+ pairs = string.split(",").map { |pair| pair.strip.split("=", 2).map(&:strip) }
37
+ config = pairs.to_h { |k, v| [k.to_sym, v] }
38
+
39
+ validate!(config)
40
+ config
41
+ end
42
+
43
+ # Converts a parsed cloud-init config Hash to Proxmox API parameter names.
44
+ #
45
+ # Maps user-friendly keys to their Proxmox API equivalents:
46
+ # - +user+ becomes +ciuser+
47
+ # - +password+ becomes +cipassword+
48
+ # - +sshkeys+ stays +sshkeys+
49
+ # - +ip+ becomes +ipconfig0+ with "ip=" prefix
50
+ # - +nameserver+ stays +nameserver+
51
+ # - +searchdomain+ stays +searchdomain+
52
+ #
53
+ # @param config [Hash<Symbol, String>] parsed cloud-init configuration
54
+ # @return [Hash<Symbol, String>] Proxmox API parameters
55
+ #
56
+ # @example Minimal config
57
+ # CloudInitConfig.to_proxmox_params({ user: "admin" })
58
+ # #=> { ciuser: "admin" }
59
+ #
60
+ # @example With IP and nameserver
61
+ # CloudInitConfig.to_proxmox_params({ ip: "dhcp", nameserver: "8.8.8.8" })
62
+ # #=> { ipconfig0: "ip=dhcp", nameserver: "8.8.8.8" }
63
+ def self.to_proxmox_params(config)
64
+ params = {}
65
+ params[:ciuser] = config[:user] if config[:user]
66
+ params[:cipassword] = config[:password] if config[:password]
67
+ params[:sshkeys] = config[:sshkeys] if config[:sshkeys]
68
+ if config[:ip]
69
+ ip_str = "ip=#{config[:ip]}"
70
+ ip_str += ",gw=#{config[:gw]}" if config[:gw]
71
+ params[:ipconfig0] = ip_str
72
+ end
73
+ params[:nameserver] = config[:nameserver] if config[:nameserver]
74
+ params[:searchdomain] = config[:searchdomain] if config[:searchdomain]
75
+ params
76
+ end
77
+
78
+ # Validates that config contains only known keys.
79
+ #
80
+ # @param config [Hash<Symbol, String>] parsed cloud-init configuration
81
+ # @return [void]
82
+ # @raise [ArgumentError] if unknown keys are present
83
+ def self.validate!(config)
84
+ unknown = config.keys.map(&:to_s) - VALID_KEYS
85
+ unless unknown.empty?
86
+ raise ArgumentError, "Unknown cloud-init config key(s): #{unknown.join(', ')}"
87
+ end
88
+ end
89
+ private_class_method :validate!
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,97 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pvectl
4
+ module Parsers
5
+ # Parses and formats disk configuration strings for Proxmox VMs.
6
+ #
7
+ # DiskConfig handles the conversion between user-friendly key=value
8
+ # disk specifications and the format required by the Proxmox API.
9
+ #
10
+ # @example Parsing a disk config string
11
+ # config = DiskConfig.parse("storage=local-lvm,size=32G,format=qcow2")
12
+ # config[:storage] #=> "local-lvm"
13
+ # config[:size] #=> "32G"
14
+ # config[:format] #=> "qcow2"
15
+ #
16
+ # @example Converting to Proxmox API format
17
+ # config = { storage: "local-lvm", size: "32G", format: "qcow2" }
18
+ # DiskConfig.to_proxmox(config) #=> "local-lvm:32,format=qcow2"
19
+ #
20
+ class DiskConfig
21
+ # All recognized disk configuration keys.
22
+ VALID_KEYS = %w[storage size format cache discard ssd iothread backup].freeze
23
+
24
+ # Keys that must be present in every disk configuration.
25
+ REQUIRED_KEYS = %w[storage size].freeze
26
+
27
+ # Optional flags appended to the Proxmox API string.
28
+ OPTIONAL_FLAGS = %w[cache discard ssd iothread backup].freeze
29
+
30
+ # Parses a comma-separated key=value disk config string into a Hash.
31
+ #
32
+ # @param string [String] disk config in "key=value,key=value" format
33
+ # @return [Hash<Symbol, String>] parsed configuration
34
+ # @raise [ArgumentError] if unknown keys are present or required keys are missing
35
+ #
36
+ # @example
37
+ # DiskConfig.parse("storage=local-lvm,size=32G")
38
+ # #=> { storage: "local-lvm", size: "32G" }
39
+ def self.parse(string)
40
+ pairs = string.split(",").map { |pair| pair.strip.split("=", 2).map(&:strip) }
41
+ config = pairs.to_h { |k, v| [k.to_sym, v] }
42
+
43
+ validate!(config)
44
+ config
45
+ end
46
+
47
+ # Converts a parsed disk config Hash to a Proxmox API string.
48
+ #
49
+ # The Proxmox API expects disk specifications in the format
50
+ # "storage:size,format=fmt,flag=val". Size is extracted as a
51
+ # numeric value (without the "G" suffix). Format defaults to "raw"
52
+ # when not specified.
53
+ #
54
+ # @param config [Hash<Symbol, String>] parsed disk configuration
55
+ # @return [String] Proxmox API-compatible disk string
56
+ #
57
+ # @example Minimal config
58
+ # DiskConfig.to_proxmox({ storage: "local-lvm", size: "32G" })
59
+ # #=> "local-lvm:32,format=raw"
60
+ #
61
+ # @example With optional flags
62
+ # DiskConfig.to_proxmox({ storage: "local-lvm", size: "32G", cache: "writeback" })
63
+ # #=> "local-lvm:32,format=raw,cache=writeback"
64
+ def self.to_proxmox(config)
65
+ size_num = config[:size].to_s.gsub(/[^0-9]/, "")
66
+ format = config[:format] || "raw"
67
+ parts = ["#{config[:storage]}:#{size_num}", "format=#{format}"]
68
+
69
+ OPTIONAL_FLAGS.each do |flag|
70
+ parts << "#{flag}=#{config[flag.to_sym]}" if config[flag.to_sym]
71
+ end
72
+
73
+ parts.join(",")
74
+ end
75
+
76
+ # Validates that config contains only known keys and all required keys.
77
+ #
78
+ # @param config [Hash<Symbol, String>] parsed disk configuration
79
+ # @return [void]
80
+ # @raise [ArgumentError] if unknown keys are present or required keys are missing
81
+ def self.validate!(config)
82
+ unknown = config.keys.map(&:to_s) - VALID_KEYS
83
+ unless unknown.empty?
84
+ raise ArgumentError, "Unknown disk config key(s): #{unknown.join(', ')}"
85
+ end
86
+
87
+ REQUIRED_KEYS.each do |key|
88
+ value = config[key.to_sym]
89
+ if value.nil? || value.strip.empty?
90
+ raise ArgumentError, "Missing required disk config key: #{key}"
91
+ end
92
+ end
93
+ end
94
+ private_class_method :validate!
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,98 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pvectl
4
+ module Parsers
5
+ # Parses and formats LXC mount configurations for Proxmox containers.
6
+ #
7
+ # Handles rootfs and mountpoint (mp0, mp1, ...) configuration strings.
8
+ # Format: key=value pairs separated by commas.
9
+ #
10
+ # @example Parsing a rootfs config
11
+ # config = LxcMountConfig.parse("storage=local-lvm,size=8G")
12
+ # config[:storage] #=> "local-lvm"
13
+ # config[:size] #=> "8G"
14
+ #
15
+ # @example Converting to Proxmox API format
16
+ # LxcMountConfig.to_proxmox({ storage: "local-lvm", size: "8G" })
17
+ # #=> "local-lvm:8"
18
+ #
19
+ # LxcMountConfig.to_proxmox({ storage: "local-lvm", size: "32G", mp: "/mnt/data" })
20
+ # #=> "local-lvm:32,mp=/mnt/data"
21
+ #
22
+ class LxcMountConfig
23
+ # All recognized mount configuration keys.
24
+ VALID_KEYS = %w[storage size mp acl backup quota replicate ro shared].freeze
25
+
26
+ # Keys that must be present in every mount configuration.
27
+ REQUIRED_KEYS = %w[storage size].freeze
28
+
29
+ # Optional flags appended to the Proxmox API string.
30
+ OPTIONAL_FLAGS = %w[acl backup quota replicate ro shared].freeze
31
+
32
+ # Parses a comma-separated key=value mount config string into a Hash.
33
+ #
34
+ # @param string [String] mount config in "key=value,key=value" format
35
+ # @return [Hash<Symbol, String>] parsed configuration
36
+ # @raise [ArgumentError] if unknown keys are present or required keys are missing
37
+ #
38
+ # @example
39
+ # LxcMountConfig.parse("storage=local-lvm,size=8G")
40
+ # #=> { storage: "local-lvm", size: "8G" }
41
+ def self.parse(string)
42
+ pairs = string.split(",").map { |pair| pair.strip.split("=", 2).map(&:strip) }
43
+ config = pairs.to_h { |k, v| [k.to_sym, v] }
44
+
45
+ validate!(config)
46
+ config
47
+ end
48
+
49
+ # Converts a parsed mount config Hash to a Proxmox API string.
50
+ #
51
+ # The Proxmox API expects mount specifications in the format
52
+ # "storage:size[,mp=/path][,flag=val]". Size is extracted as a
53
+ # numeric value (without the "G" suffix).
54
+ #
55
+ # @param config [Hash<Symbol, String>] parsed mount configuration
56
+ # @return [String] Proxmox API-compatible mount string
57
+ #
58
+ # @example Rootfs config
59
+ # LxcMountConfig.to_proxmox({ storage: "local-lvm", size: "8G" })
60
+ # #=> "local-lvm:8"
61
+ #
62
+ # @example Mountpoint with path and flags
63
+ # LxcMountConfig.to_proxmox({ storage: "local-lvm", size: "32G", mp: "/mnt/data", backup: "1" })
64
+ # #=> "local-lvm:32,mp=/mnt/data,backup=1"
65
+ def self.to_proxmox(config)
66
+ size_num = config[:size].to_s.gsub(/[^0-9]/, "")
67
+ parts = ["#{config[:storage]}:#{size_num}"]
68
+ parts << "mp=#{config[:mp]}" if config[:mp]
69
+
70
+ OPTIONAL_FLAGS.each do |flag|
71
+ parts << "#{flag}=#{config[flag.to_sym]}" if config[flag.to_sym]
72
+ end
73
+
74
+ parts.join(",")
75
+ end
76
+
77
+ # Validates that config contains only known keys and all required keys.
78
+ #
79
+ # @param config [Hash<Symbol, String>] parsed mount configuration
80
+ # @return [void]
81
+ # @raise [ArgumentError] if unknown keys are present or required keys are missing
82
+ def self.validate!(config)
83
+ unknown = config.keys.map(&:to_s) - VALID_KEYS
84
+ unless unknown.empty?
85
+ raise ArgumentError, "Unknown mount config key(s): #{unknown.join(', ')}"
86
+ end
87
+
88
+ REQUIRED_KEYS.each do |key|
89
+ value = config[key.to_sym]
90
+ if value.nil? || value.strip.empty?
91
+ raise ArgumentError, "Missing required mount config key: #{key}"
92
+ end
93
+ end
94
+ end
95
+ private_class_method :validate!
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,97 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pvectl
4
+ module Parsers
5
+ # Parses and formats LXC network configurations for Proxmox containers.
6
+ #
7
+ # LXC network format differs from QEMU: uses name/type instead of model,
8
+ # and supports IP configuration directly in the network spec.
9
+ #
10
+ # @example Parsing a net config string
11
+ # config = LxcNetConfig.parse("bridge=vmbr0,name=eth0,ip=dhcp")
12
+ # config[:bridge] #=> "vmbr0"
13
+ # config[:name] #=> "eth0"
14
+ # config[:ip] #=> "dhcp"
15
+ #
16
+ # @example Converting to Proxmox API format
17
+ # LxcNetConfig.to_proxmox({ bridge: "vmbr0", ip: "dhcp" })
18
+ # #=> "name=eth0,bridge=vmbr0,ip=dhcp,type=veth"
19
+ #
20
+ class LxcNetConfig
21
+ # All recognized LXC network configuration keys.
22
+ VALID_KEYS = %w[bridge name ip gw ip6 gw6 tag firewall mtu rate type].freeze
23
+
24
+ # Keys that must be present in every network configuration.
25
+ REQUIRED_KEYS = %w[bridge].freeze
26
+
27
+ # Optional flags appended to the Proxmox API string.
28
+ OPTIONAL_FLAGS = %w[ip gw ip6 gw6 tag firewall mtu rate].freeze
29
+
30
+ # Parses a comma-separated key=value LXC net config string into a Hash.
31
+ #
32
+ # @param string [String] net config in "key=value,key=value" format
33
+ # @return [Hash<Symbol, String>] parsed configuration
34
+ # @raise [ArgumentError] if unknown keys are present or required keys are missing
35
+ #
36
+ # @example
37
+ # LxcNetConfig.parse("bridge=vmbr0,name=eth0,ip=dhcp")
38
+ # #=> { bridge: "vmbr0", name: "eth0", ip: "dhcp" }
39
+ def self.parse(string)
40
+ pairs = string.split(",").map { |pair| pair.strip.split("=", 2).map(&:strip) }
41
+ config = pairs.to_h { |k, v| [k.to_sym, v] }
42
+
43
+ validate!(config)
44
+ config
45
+ end
46
+
47
+ # Converts a parsed LXC net config Hash to a Proxmox API string.
48
+ #
49
+ # The Proxmox API expects LXC network specifications in the format
50
+ # "name=eth0,bridge=vmbr0,[flags],type=veth". Name defaults to "eth0"
51
+ # and type defaults to "veth" when not specified.
52
+ #
53
+ # @param config [Hash<Symbol, String>] parsed network configuration
54
+ # @return [String] Proxmox API-compatible network string
55
+ #
56
+ # @example Minimal config
57
+ # LxcNetConfig.to_proxmox({ bridge: "vmbr0" })
58
+ # #=> "name=eth0,bridge=vmbr0,type=veth"
59
+ #
60
+ # @example With IP and gateway
61
+ # LxcNetConfig.to_proxmox({ bridge: "vmbr0", ip: "10.0.0.5/24", gw: "10.0.0.1" })
62
+ # #=> "name=eth0,bridge=vmbr0,ip=10.0.0.5/24,gw=10.0.0.1,type=veth"
63
+ def self.to_proxmox(config)
64
+ name = config[:name] || "eth0"
65
+ type = config[:type] || "veth"
66
+ parts = ["name=#{name}", "bridge=#{config[:bridge]}"]
67
+
68
+ OPTIONAL_FLAGS.each do |flag|
69
+ parts << "#{flag}=#{config[flag.to_sym]}" if config[flag.to_sym]
70
+ end
71
+
72
+ parts << "type=#{type}"
73
+ parts.join(",")
74
+ end
75
+
76
+ # Validates that config contains only known keys and all required keys.
77
+ #
78
+ # @param config [Hash<Symbol, String>] parsed network configuration
79
+ # @return [void]
80
+ # @raise [ArgumentError] if unknown keys are present or required keys are missing
81
+ def self.validate!(config)
82
+ unknown = config.keys.map(&:to_s) - VALID_KEYS
83
+ unless unknown.empty?
84
+ raise ArgumentError, "Unknown LXC net config key(s): #{unknown.join(', ')}"
85
+ end
86
+
87
+ REQUIRED_KEYS.each do |key|
88
+ value = config[key.to_sym]
89
+ if value.nil? || value.strip.empty?
90
+ raise ArgumentError, "Missing required LXC net config key: #{key}"
91
+ end
92
+ end
93
+ end
94
+ private_class_method :validate!
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,95 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pvectl
4
+ module Parsers
5
+ # Parses and formats network configuration strings for Proxmox VMs.
6
+ #
7
+ # NetConfig handles the conversion between user-friendly key=value
8
+ # network specifications and the format required by the Proxmox API.
9
+ #
10
+ # @example Parsing a net config string
11
+ # config = NetConfig.parse("bridge=vmbr0,model=virtio,tag=100")
12
+ # config[:bridge] #=> "vmbr0"
13
+ # config[:model] #=> "virtio"
14
+ # config[:tag] #=> "100"
15
+ #
16
+ # @example Converting to Proxmox API format
17
+ # config = { bridge: "vmbr0", tag: "100" }
18
+ # NetConfig.to_proxmox(config) #=> "virtio,bridge=vmbr0,tag=100"
19
+ #
20
+ class NetConfig
21
+ # All recognized network configuration keys.
22
+ VALID_KEYS = %w[bridge model tag firewall mtu queues].freeze
23
+
24
+ # Keys that must be present in every network configuration.
25
+ REQUIRED_KEYS = %w[bridge].freeze
26
+
27
+ # Optional flags appended to the Proxmox API string.
28
+ OPTIONAL_FLAGS = %w[tag firewall mtu queues].freeze
29
+
30
+ # Parses a comma-separated key=value net config string into a Hash.
31
+ #
32
+ # @param string [String] net config in "key=value,key=value" format
33
+ # @return [Hash<Symbol, String>] parsed configuration
34
+ # @raise [ArgumentError] if unknown keys are present or required keys are missing
35
+ #
36
+ # @example
37
+ # NetConfig.parse("bridge=vmbr0,model=virtio,tag=100")
38
+ # #=> { bridge: "vmbr0", model: "virtio", tag: "100" }
39
+ def self.parse(string)
40
+ pairs = string.split(",").map { |pair| pair.strip.split("=", 2).map(&:strip) }
41
+ config = pairs.to_h { |k, v| [k.to_sym, v] }
42
+
43
+ validate!(config)
44
+ config
45
+ end
46
+
47
+ # Converts a parsed net config Hash to a Proxmox API string.
48
+ #
49
+ # The Proxmox API expects network specifications in the format
50
+ # "model,bridge=name,flag=val". Model defaults to "virtio"
51
+ # when not specified.
52
+ #
53
+ # @param config [Hash<Symbol, String>] parsed network configuration
54
+ # @return [String] Proxmox API-compatible network string
55
+ #
56
+ # @example Minimal config
57
+ # NetConfig.to_proxmox({ bridge: "vmbr0" })
58
+ # #=> "virtio,bridge=vmbr0"
59
+ #
60
+ # @example With optional flags
61
+ # NetConfig.to_proxmox({ bridge: "vmbr0", tag: "100", firewall: "1" })
62
+ # #=> "virtio,bridge=vmbr0,tag=100,firewall=1"
63
+ def self.to_proxmox(config)
64
+ model = config[:model] || "virtio"
65
+ parts = [model, "bridge=#{config[:bridge]}"]
66
+
67
+ OPTIONAL_FLAGS.each do |flag|
68
+ parts << "#{flag}=#{config[flag.to_sym]}" if config[flag.to_sym]
69
+ end
70
+
71
+ parts.join(",")
72
+ end
73
+
74
+ # Validates that config contains only known keys and all required keys.
75
+ #
76
+ # @param config [Hash<Symbol, String>] parsed network configuration
77
+ # @return [void]
78
+ # @raise [ArgumentError] if unknown keys are present or required keys are missing
79
+ def self.validate!(config)
80
+ unknown = config.keys.map(&:to_s) - VALID_KEYS
81
+ unless unknown.empty?
82
+ raise ArgumentError, "Unknown net config key(s): #{unknown.join(', ')}"
83
+ end
84
+
85
+ REQUIRED_KEYS.each do |key|
86
+ value = config[key.to_sym]
87
+ if value.nil? || value.strip.empty?
88
+ raise ArgumentError, "Missing required net config key: #{key}"
89
+ end
90
+ end
91
+ end
92
+ private_class_method :validate!
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pvectl
4
+ module Parsers
5
+ # Parses smartctl text output (NVMe/SAS) into structured key-value pairs.
6
+ #
7
+ # NVMe and SAS disks return SMART data as plain text from the Proxmox API
8
+ # (field +text+ in +GET /nodes/{node}/disks/smart+). This parser extracts
9
+ # +Key: Value+ lines into an Array of Hashes suitable for table display.
10
+ #
11
+ # @example Parsing NVMe SMART text
12
+ # text = "Critical Warning: 0x00\nTemperature: 34 Celsius\n"
13
+ # Pvectl::Parsers::SmartText.parse(text)
14
+ # # => [{ "Attribute" => "Critical Warning", "Value" => "0x00" },
15
+ # # { "Attribute" => "Temperature", "Value" => "34 Celsius" }]
16
+ #
17
+ class SmartText
18
+ # Parses smartctl text output into structured attributes.
19
+ #
20
+ # Splits each line on the first colon. Lines without a colon or
21
+ # without a non-empty value after the colon are skipped.
22
+ # Uses String#split instead of regex to avoid ReDoS risk.
23
+ #
24
+ # @param text [String, nil] raw smartctl text output
25
+ # @return [Array<Hash{String => String}>] parsed attributes
26
+ def self.parse(text)
27
+ return [] if text.nil? || text.empty?
28
+
29
+ text.each_line.filter_map { |line|
30
+ key, value = line.strip.split(":", 2)
31
+ next unless value
32
+
33
+ key = key.strip
34
+ value = value.strip
35
+ next if key.empty? || value.empty?
36
+
37
+ { "Attribute" => key, "Value" => value }
38
+ }
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,157 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pvectl
4
+ # Discovers and loads commands from built-in sources and external plugins.
5
+ #
6
+ # Loading order:
7
+ # 1. Built-in commands (BUILTIN_COMMANDS list)
8
+ # 2. Gem-based plugins (pvectl-plugin-* gems)
9
+ # 3. Directory-based plugins (~/.pvectl/plugins/*.rb)
10
+ #
11
+ # Order matters: built-in commands register first (including their
12
+ # ResourceRegistries), then plugins can extend those registries.
13
+ #
14
+ # @example Plugin registration from a gem
15
+ # Pvectl::PluginLoader.register_plugin(MyPlugin::Command)
16
+ #
17
+ class PluginLoader
18
+ # Built-in commands that ship with pvectl.
19
+ # Each must implement .register(cli).
20
+ BUILTIN_COMMANDS = [
21
+ Commands::Ping,
22
+ Commands::Config::Command,
23
+ Commands::Cloudinit,
24
+ Commands::Get::Command,
25
+ Commands::Top::Command,
26
+ Commands::Logs::Command,
27
+ Commands::Describe::Command,
28
+ Commands::Start,
29
+ Commands::Stop,
30
+ Commands::Shutdown,
31
+ Commands::Restart,
32
+ Commands::Reset,
33
+ Commands::Suspend,
34
+ Commands::Resume,
35
+ Commands::CreateVm,
36
+ Commands::DeleteVm,
37
+ Commands::EditVm,
38
+ Commands::SetVm,
39
+ Commands::CloneVm,
40
+ Commands::MigrateVm,
41
+ Commands::MoveDiskVm,
42
+ Commands::FeatureVm,
43
+ Commands::TemplateVm,
44
+ Commands::RollbackSnapshot,
45
+ Commands::RestoreBackup,
46
+ Commands::Console,
47
+ Commands::SendkeyVm,
48
+ Commands::Pull,
49
+ Commands::Push,
50
+ Commands::Service,
51
+ Commands::Apt,
52
+ Commands::WakeonlanNode,
53
+ Commands::UnlinkDiskVm,
54
+ ].freeze
55
+
56
+ @registered_plugins = []
57
+
58
+ class << self
59
+ # Registers an external plugin command for loading.
60
+ #
61
+ # @param klass [Class] plugin class that implements .register(cli)
62
+ # @return [void]
63
+ def register_plugin(klass)
64
+ @registered_plugins << klass
65
+ end
66
+
67
+ # Returns currently registered plugins (for testing).
68
+ #
69
+ # @return [Array<Class>] registered plugin classes
70
+ def registered_plugins
71
+ @registered_plugins.dup
72
+ end
73
+
74
+ # Loads all commands: built-in, gem plugins, directory plugins.
75
+ #
76
+ # @param cli [GLI::App] the CLI application object
77
+ # @return [void]
78
+ def load_all(cli)
79
+ load_builtins(cli)
80
+ load_gem_plugins(cli)
81
+ load_directory_plugins(cli)
82
+ end
83
+
84
+ # Loads built-in commands.
85
+ #
86
+ # @param cli [GLI::App] the CLI application object
87
+ # @return [void]
88
+ def load_builtins(cli)
89
+ BUILTIN_COMMANDS.each { |cmd| cmd.register(cli) }
90
+ end
91
+
92
+ # Discovers and loads gem-based plugins.
93
+ #
94
+ # Searches for gems matching pvectl-plugin-* via
95
+ # Gem.find_files("pvectl_plugin/register").
96
+ #
97
+ # @param cli [GLI::App] the CLI application object
98
+ # @return [void]
99
+ def load_gem_plugins(cli)
100
+ Gem.find_files("pvectl_plugin/register").each do |register_file|
101
+ require register_file
102
+ rescue StandardError => e
103
+ warn "Warning: Failed to load plugin #{register_file}: #{e.message}"
104
+ warn e.backtrace.join("\n") if ENV["GLI_DEBUG"] == "true"
105
+ end
106
+ flush_registered_plugins(cli)
107
+ end
108
+
109
+ # Discovers and loads directory-based plugins.
110
+ #
111
+ # Scans ~/.pvectl/plugins/*.rb for plugin files.
112
+ #
113
+ # @param cli [GLI::App] the CLI application object
114
+ # @return [void]
115
+ def load_directory_plugins(cli)
116
+ return unless Dir.exist?(directory_plugins_path)
117
+
118
+ Dir.glob(File.join(directory_plugins_path, "*.rb")).sort.each do |plugin_file|
119
+ require plugin_file
120
+ rescue StandardError => e
121
+ warn "Warning: Failed to load plugin #{plugin_file}: #{e.message}"
122
+ warn e.backtrace.join("\n") if ENV["GLI_DEBUG"] == "true"
123
+ end
124
+ flush_registered_plugins(cli)
125
+ end
126
+
127
+ # Returns the directory path for local plugins.
128
+ #
129
+ # @return [String] plugins directory path
130
+ def directory_plugins_path
131
+ File.expand_path("~/.pvectl/plugins")
132
+ end
133
+
134
+ # Calls register on all queued plugins and clears the queue.
135
+ #
136
+ # @param cli [GLI::App] the CLI application object
137
+ # @return [void]
138
+ def flush_registered_plugins(cli)
139
+ @registered_plugins.each do |klass|
140
+ klass.register(cli)
141
+ rescue StandardError => e
142
+ warn "Warning: Failed to register plugin #{klass}: #{e.message}"
143
+ warn e.backtrace.join("\n") if ENV["GLI_DEBUG"] == "true"
144
+ end
145
+ @registered_plugins.clear
146
+ end
147
+
148
+ # Resets state (for testing).
149
+ #
150
+ # @return [void]
151
+ # @api private
152
+ def reset!
153
+ @registered_plugins = []
154
+ end
155
+ end
156
+ end
157
+ end