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,179 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pvectl
4
+ module Services
5
+ # Orchestrates LXC container creation operations.
6
+ #
7
+ # Handles auto-CTID allocation, parameter building (mapping rootfs/mountpoints/net
8
+ # configs to Proxmox API format), sync/async modes, and optional auto-start.
9
+ #
10
+ # @example Basic container creation
11
+ # service = CreateContainer.new(container_repository: ct_repo, task_repository: task_repo)
12
+ # result = service.execute(hostname: "web-ct", node: "pve1",
13
+ # ostemplate: "local:vztmpl/debian-12.tar.zst",
14
+ # cores: 2, memory: 2048)
15
+ #
16
+ # @example Async creation with auto-start
17
+ # service = CreateContainer.new(container_repository: ct_repo, task_repository: task_repo,
18
+ # options: { async: true, start: true })
19
+ # result = service.execute(ctid: 200, hostname: "db-ct", node: "pve1",
20
+ # ostemplate: "local:vztmpl/debian-12.tar.zst")
21
+ #
22
+ class CreateContainer
23
+ # @return [Integer] Default timeout for create operations (seconds)
24
+ DEFAULT_TIMEOUT = 300
25
+
26
+ # @return [Integer] Default timeout for start operations (seconds)
27
+ START_TIMEOUT = 60
28
+
29
+ # Creates a new CreateContainer service.
30
+ #
31
+ # @param container_repository [Repositories::Container] Container repository
32
+ # @param task_repository [Repositories::Task] Task repository
33
+ # @param options [Hash] Options (timeout, async, start)
34
+ def initialize(container_repository:, task_repository:, options: {})
35
+ @container_repository = container_repository
36
+ @task_repository = task_repository
37
+ @options = options
38
+ end
39
+
40
+ # Executes container creation operation.
41
+ #
42
+ # @param ctid [Integer, nil] Container identifier (auto-allocated if nil)
43
+ # @param hostname [String] Container hostname
44
+ # @param node [String] Target node
45
+ # @param ostemplate [String] OS template path
46
+ # @param cores [Integer, nil] Number of CPU cores
47
+ # @param memory [Integer, nil] Memory in MB
48
+ # @param swap [Integer, nil] Swap in MB
49
+ # @param rootfs [Hash, nil] Root filesystem config
50
+ # @param mountpoints [Array<Hash>, nil] Mountpoint configurations
51
+ # @param nets [Array<Hash>, nil] Network configurations
52
+ # @param privileged [Boolean, nil] Create privileged container
53
+ # @param features [String, nil] LXC features string
54
+ # @param password [String, nil] Root password
55
+ # @param ssh_public_keys [String, nil] SSH public keys
56
+ # @param onboot [Boolean, nil] Start on boot
57
+ # @param startup [String, nil] Startup order spec
58
+ # @param description [String, nil] Container description
59
+ # @param tags [String, nil] Tags (comma-separated)
60
+ # @param pool [String, nil] Resource pool
61
+ # @return [Models::ContainerOperationResult] Creation result
62
+ def execute(ctid: nil, hostname:, node:, ostemplate:, cores: nil, memory: nil,
63
+ swap: nil, rootfs: nil, mountpoints: nil, nets: nil, privileged: nil,
64
+ features: nil, password: nil, ssh_public_keys: nil, onboot: nil,
65
+ startup: nil, description: nil, tags: nil, pool: nil)
66
+ ctid ||= @container_repository.next_available_ctid
67
+
68
+ params = build_params(
69
+ hostname: hostname, ostemplate: ostemplate, cores: cores, memory: memory,
70
+ swap: swap, rootfs: rootfs, mountpoints: mountpoints, nets: nets,
71
+ privileged: privileged, features: features, password: password,
72
+ ssh_public_keys: ssh_public_keys, onboot: onboot, startup: startup,
73
+ description: description, tags: tags, pool: pool
74
+ )
75
+
76
+ upid = @container_repository.create(node, ctid, params)
77
+ resource_info = { ctid: ctid, hostname: hostname, node: node }
78
+
79
+ if @options[:async]
80
+ build_result(resource_info, task_upid: upid, success: :pending)
81
+ else
82
+ task = @task_repository.wait(upid, timeout: timeout)
83
+ start_container(ctid, node) if task.successful? && @options[:start]
84
+ build_result(resource_info, task: task, success: task.successful?)
85
+ end
86
+ rescue StandardError => e
87
+ build_result({ ctid: ctid, hostname: hostname, node: node },
88
+ success: false, error: e.message)
89
+ end
90
+
91
+ private
92
+
93
+ # Builds Proxmox API parameters from user-friendly options.
94
+ #
95
+ # Maps rootfs/mountpoint configs through {Parsers::LxcMountConfig.to_proxmox} and
96
+ # network configs through {Parsers::LxcNetConfig.to_proxmox}.
97
+ #
98
+ # @return [Hash] Proxmox API parameters
99
+ def build_params(hostname:, ostemplate:, cores:, memory:, swap:, rootfs:,
100
+ mountpoints:, nets:, privileged:, features:, password:,
101
+ ssh_public_keys:, onboot:, startup:, description:, tags:, pool:)
102
+ params = { hostname: hostname, ostemplate: ostemplate }
103
+ params[:cores] = cores if cores
104
+ params[:memory] = memory if memory
105
+ params[:swap] = swap if swap
106
+ params[:unprivileged] = privileged ? 0 : 1 unless privileged.nil?
107
+ params[:rootfs] = Parsers::LxcMountConfig.to_proxmox(rootfs) if rootfs
108
+ add_mountpoint_params(params, mountpoints) if mountpoints
109
+ add_net_params(params, nets) if nets
110
+ params[:features] = features if features
111
+ params[:password] = password if password
112
+ params[:"ssh-public-keys"] = ssh_public_keys if ssh_public_keys
113
+ params[:onboot] = onboot ? 1 : 0 unless onboot.nil?
114
+ params[:startup] = startup if startup
115
+ params[:description] = description if description
116
+ params[:tags] = tags if tags
117
+ params[:pool] = pool if pool
118
+ params
119
+ end
120
+
121
+ # Adds mountpoint parameters mapped to mp0, mp1, etc.
122
+ #
123
+ # @param params [Hash] Parameters hash to modify
124
+ # @param mountpoints [Array<Hash>] Mountpoint configurations
125
+ # @return [void]
126
+ def add_mountpoint_params(params, mountpoints)
127
+ mountpoints.each_with_index do |mp, index|
128
+ params[:"mp#{index}"] = Parsers::LxcMountConfig.to_proxmox(mp)
129
+ end
130
+ end
131
+
132
+ # Adds network parameters mapped to net0, net1, etc.
133
+ #
134
+ # @param params [Hash] Parameters hash to modify
135
+ # @param nets [Array<Hash>] Network configurations
136
+ # @return [void]
137
+ def add_net_params(params, nets)
138
+ nets.each_with_index do |net, index|
139
+ params[:"net#{index}"] = Parsers::LxcNetConfig.to_proxmox(net)
140
+ end
141
+ end
142
+
143
+ # Starts a container after successful creation.
144
+ #
145
+ # @param ctid [Integer] Container identifier
146
+ # @param node [String] Node name
147
+ # @return [void]
148
+ def start_container(ctid, node)
149
+ upid = @container_repository.start(ctid, node)
150
+ @task_repository.wait(upid, timeout: START_TIMEOUT)
151
+ end
152
+
153
+ # Returns configured timeout.
154
+ #
155
+ # @return [Integer] Timeout in seconds
156
+ def timeout
157
+ @options[:timeout] || DEFAULT_TIMEOUT
158
+ end
159
+
160
+ # Builds a ContainerOperationResult with the :create operation.
161
+ #
162
+ # Creates a minimal Container model for presenter compatibility.
163
+ #
164
+ # @param resource_info [Hash] Resource info (ctid, hostname, node)
165
+ # @param attrs [Hash] Additional result attributes
166
+ # @return [Models::ContainerOperationResult] Operation result
167
+ def build_result(resource_info, **attrs)
168
+ container = Models::Container.new(
169
+ vmid: resource_info[:ctid],
170
+ name: resource_info[:hostname],
171
+ node: resource_info[:node]
172
+ )
173
+ Models::ContainerOperationResult.new(
174
+ operation: :create, container: container, resource: resource_info, **attrs
175
+ )
176
+ end
177
+ end
178
+ end
179
+ end
@@ -0,0 +1,191 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pvectl
4
+ module Services
5
+ # Orchestrates VM creation operations.
6
+ #
7
+ # Handles auto-VMID allocation, parameter building (mapping disk/net/cloud-init
8
+ # configs to Proxmox API format), sync/async modes, and optional auto-start.
9
+ #
10
+ # @example Basic VM creation
11
+ # service = CreateVm.new(vm_repository: vm_repo, task_repository: task_repo)
12
+ # result = service.execute(name: "web-server", node: "pve1",
13
+ # cores: 4, memory: 4096,
14
+ # disks: [{ storage: "local-lvm", size: "32G" }])
15
+ #
16
+ # @example Async creation with auto-start
17
+ # service = CreateVm.new(vm_repository: vm_repo, task_repository: task_repo,
18
+ # options: { async: true, start: true })
19
+ # result = service.execute(vmid: 200, name: "db-server", node: "pve1",
20
+ # cores: 8, memory: 16384)
21
+ #
22
+ class CreateVm
23
+ # @return [Integer] Default timeout for create operations (seconds)
24
+ DEFAULT_TIMEOUT = 300
25
+
26
+ # @return [Integer] Default timeout for start operations (seconds)
27
+ START_TIMEOUT = 60
28
+
29
+ # Creates a new CreateVm service.
30
+ #
31
+ # @param vm_repository [Repositories::Vm] VM repository
32
+ # @param task_repository [Repositories::Task] Task repository
33
+ # @param options [Hash] Options (timeout, async, start)
34
+ def initialize(vm_repository:, task_repository:, options: {})
35
+ @vm_repository = vm_repository
36
+ @task_repository = task_repository
37
+ @options = options
38
+ end
39
+
40
+ # Executes VM creation operation.
41
+ #
42
+ # @param vmid [Integer, nil] VM identifier (auto-allocated if nil)
43
+ # @param name [String] VM name
44
+ # @param node [String] Target node
45
+ # @param cores [Integer, nil] Number of CPU cores
46
+ # @param sockets [Integer, nil] Number of CPU sockets
47
+ # @param cpu_type [String, nil] CPU type (e.g. "host", "kvm64")
48
+ # @param numa [Boolean, nil] Enable NUMA
49
+ # @param memory [Integer, nil] Memory in MB
50
+ # @param balloon [Integer, nil] Balloon memory in MB
51
+ # @param disks [Array<Hash>, nil] Disk configurations
52
+ # @param scsihw [String, nil] SCSI controller type
53
+ # @param cdrom [String, nil] CD-ROM ISO path
54
+ # @param nets [Array<Hash>, nil] Network configurations
55
+ # @param bios [String, nil] BIOS type (seabios or ovmf)
56
+ # @param boot_order [String, nil] Boot order (e.g. "scsi0;net0")
57
+ # @param machine [String, nil] Machine type (e.g. "q35")
58
+ # @param efidisk [String, nil] EFI disk specification
59
+ # @param cloud_init [Hash, nil] Cloud-init parameters
60
+ # @param agent [Boolean, nil] Enable QEMU guest agent
61
+ # @param ostype [String, nil] OS type (e.g. "l26", "win10")
62
+ # @param description [String, nil] VM description
63
+ # @param tags [String, nil] Tags (semicolon-separated)
64
+ # @param pool [String, nil] Resource pool
65
+ # @return [Models::VmOperationResult] Creation result
66
+ def execute(vmid: nil, name:, node:, cores: nil, sockets: nil, cpu_type: nil,
67
+ numa: nil, memory: nil, balloon: nil, disks: nil, scsihw: nil,
68
+ cdrom: nil, nets: nil, bios: nil, boot_order: nil, machine: nil,
69
+ efidisk: nil, cloud_init: nil, agent: nil, ostype: nil,
70
+ description: nil, tags: nil, pool: nil)
71
+ vmid ||= @vm_repository.next_available_vmid
72
+
73
+ params = build_params(
74
+ name: name, cores: cores, sockets: sockets, cpu_type: cpu_type,
75
+ numa: numa, memory: memory, balloon: balloon, disks: disks,
76
+ scsihw: scsihw, cdrom: cdrom, nets: nets, bios: bios,
77
+ boot_order: boot_order, machine: machine, efidisk: efidisk,
78
+ cloud_init: cloud_init, agent: agent, ostype: ostype,
79
+ description: description, tags: tags, pool: pool
80
+ )
81
+
82
+ upid = @vm_repository.create(node, vmid, params)
83
+ resource_info = { vmid: vmid, name: name, node: node }
84
+
85
+ if @options[:async]
86
+ build_result(resource_info, task_upid: upid, success: :pending)
87
+ else
88
+ task = @task_repository.wait(upid, timeout: timeout)
89
+ start_vm(vmid, node) if task.successful? && @options[:start]
90
+ build_result(resource_info, task: task, success: task.successful?)
91
+ end
92
+ rescue StandardError => e
93
+ build_result({ vmid: vmid, name: name, node: node },
94
+ success: false, error: e.message)
95
+ end
96
+
97
+ private
98
+
99
+ # Builds Proxmox API parameters from user-friendly options.
100
+ #
101
+ # Maps disk configs through {Parsers::DiskConfig.to_proxmox} and
102
+ # network configs through {Parsers::NetConfig.to_proxmox}.
103
+ #
104
+ # @return [Hash] Proxmox API parameters
105
+ def build_params(name:, cores:, sockets:, cpu_type:, numa:, memory:,
106
+ balloon:, disks:, scsihw:, cdrom:, nets:, bios:,
107
+ boot_order:, machine:, efidisk:, cloud_init:, agent:,
108
+ ostype:, description:, tags:, pool:)
109
+ params = { name: name }
110
+ params[:cores] = cores if cores
111
+ params[:sockets] = sockets if sockets
112
+ params[:cpu] = cpu_type if cpu_type
113
+ params[:numa] = numa ? 1 : 0 unless numa.nil?
114
+ params[:memory] = memory if memory
115
+ params[:balloon] = balloon if balloon
116
+ add_disk_params(params, disks) if disks
117
+ params[:scsihw] = scsihw if scsihw
118
+ params[:cdrom] = cdrom if cdrom
119
+ add_net_params(params, nets) if nets
120
+ params[:bios] = bios if bios
121
+ params[:boot] = "order=#{boot_order}" if boot_order
122
+ params[:machine] = machine if machine
123
+ params[:efidisk0] = efidisk if efidisk
124
+ params.merge!(cloud_init) if cloud_init
125
+ params[:agent] = agent ? "1" : "0" unless agent.nil?
126
+ params[:ostype] = ostype if ostype
127
+ params[:description] = description if description
128
+ params[:tags] = tags if tags
129
+ params[:pool] = pool if pool
130
+ params
131
+ end
132
+
133
+ # Adds disk parameters mapped to scsi0, scsi1, etc.
134
+ #
135
+ # @param params [Hash] Parameters hash to modify
136
+ # @param disks [Array<Hash>] Disk configurations
137
+ # @return [void]
138
+ def add_disk_params(params, disks)
139
+ disks.each_with_index do |disk, index|
140
+ params[:"scsi#{index}"] = Parsers::DiskConfig.to_proxmox(disk)
141
+ end
142
+ end
143
+
144
+ # Adds network parameters mapped to net0, net1, etc.
145
+ #
146
+ # @param params [Hash] Parameters hash to modify
147
+ # @param nets [Array<Hash>] Network configurations
148
+ # @return [void]
149
+ def add_net_params(params, nets)
150
+ nets.each_with_index do |net, index|
151
+ params[:"net#{index}"] = Parsers::NetConfig.to_proxmox(net)
152
+ end
153
+ end
154
+
155
+ # Starts a VM after successful creation.
156
+ #
157
+ # @param vmid [Integer] VM identifier
158
+ # @param node [String] Node name
159
+ # @return [void]
160
+ def start_vm(vmid, node)
161
+ upid = @vm_repository.start(vmid, node)
162
+ @task_repository.wait(upid, timeout: START_TIMEOUT)
163
+ end
164
+
165
+ # Returns configured timeout.
166
+ #
167
+ # @return [Integer] Timeout in seconds
168
+ def timeout
169
+ @options[:timeout] || DEFAULT_TIMEOUT
170
+ end
171
+
172
+ # Builds a VmOperationResult with the :create operation.
173
+ #
174
+ # Creates a minimal Vm model for presenter compatibility.
175
+ #
176
+ # @param resource_info [Hash] Resource info (vmid, name, node)
177
+ # @param attrs [Hash] Additional result attributes
178
+ # @return [Models::VmOperationResult] Operation result
179
+ def build_result(resource_info, **attrs)
180
+ vm = Models::Vm.new(
181
+ vmid: resource_info[:vmid],
182
+ name: resource_info[:name],
183
+ node: resource_info[:node]
184
+ )
185
+ Models::VmOperationResult.new(
186
+ operation: :create, vm: vm, resource: resource_info, **attrs
187
+ )
188
+ end
189
+ end
190
+ end
191
+ end
@@ -0,0 +1,125 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pvectl
4
+ module Services
5
+ # Orchestrates the interactive container configuration editing flow.
6
+ #
7
+ # Fetches current config, opens it in an editor as structured YAML,
8
+ # validates changes, computes a diff, and applies updates via the API.
9
+ # Supports dry-run mode and optimistic locking via digest.
10
+ #
11
+ # @example Basic usage
12
+ # service = EditContainer.new(container_repository: repo)
13
+ # result = service.execute(ctid: 200)
14
+ #
15
+ # @example Dry run with injected editor session
16
+ # service = EditContainer.new(container_repository: repo, editor_session: session,
17
+ # options: { dry_run: true })
18
+ # result = service.execute(ctid: 200)
19
+ #
20
+ class EditContainer
21
+ # Creates a new EditContainer service.
22
+ #
23
+ # @param container_repository [Repositories::Container] Container repository
24
+ # @param editor_session [EditorSession, nil] optional injected editor session
25
+ # @param options [Hash] options (dry_run)
26
+ def initialize(container_repository:, editor_session: nil, options: {})
27
+ @container_repository = container_repository
28
+ @editor_session = editor_session
29
+ @options = options
30
+ end
31
+
32
+ # Executes the interactive container edit flow.
33
+ #
34
+ # @param ctid [Integer] Container identifier
35
+ # @return [Models::ContainerOperationResult, nil] operation result, or nil if cancelled/no changes
36
+ def execute(ctid:)
37
+ container = @container_repository.get(ctid)
38
+ return not_found_result(ctid) unless container
39
+
40
+ config = @container_repository.fetch_config(container.node, ctid)
41
+ resource_info = { ctid: ctid, node: container.node, status: container.status }
42
+
43
+ yaml_content = ConfigSerializer.to_yaml(config, type: :container, resource: resource_info)
44
+
45
+ validator = ->(content) { ConfigSerializer.validate(content, type: :container) }
46
+ session = @editor_session || EditorSession.new(validator: validator)
47
+ edited = session.edit(yaml_content)
48
+
49
+ return nil unless edited
50
+
51
+ original_roundtrip = ConfigSerializer.from_yaml(yaml_content, type: :container)
52
+ edited_flat = ConfigSerializer.from_yaml(edited, type: :container)
53
+
54
+ violations = ConfigSerializer.readonly_violations(original_roundtrip, edited_flat, type: :container)
55
+ unless violations.empty?
56
+ return build_result(resource_info, success: false,
57
+ error: "Read-only fields cannot be changed: #{violations.join(', ')}")
58
+ end
59
+
60
+ changes = ConfigSerializer.diff(original_roundtrip, edited_flat)
61
+
62
+ if changes[:changed].empty? && changes[:added].empty? && changes[:removed].empty?
63
+ return nil
64
+ end
65
+
66
+ params = build_update_params(changes, config)
67
+
68
+ resource_info[:diff] = changes
69
+
70
+ if @options[:dry_run]
71
+ return build_result(resource_info, success: true)
72
+ end
73
+
74
+ @container_repository.update(ctid, container.node, params)
75
+ build_result(resource_info, success: true)
76
+ rescue StandardError => e
77
+ build_result({ ctid: ctid }, success: false, error: e.message)
78
+ end
79
+
80
+ private
81
+
82
+ # Builds API update parameters from a diff hash.
83
+ #
84
+ # Maps changed/added keys to their new values, removed keys to the
85
+ # Proxmox `delete` parameter, and includes digest for optimistic locking.
86
+ #
87
+ # @param changes [Hash] diff hash with :changed, :added, :removed
88
+ # @param original_config [Hash] original flat config (for digest)
89
+ # @return [Hash] Proxmox API parameters
90
+ def build_update_params(changes, original_config)
91
+ params = {}
92
+ changes[:changed].each { |key, (_old, new_val)| params[key] = new_val }
93
+ changes[:added].each { |key, val| params[key] = val }
94
+ unless changes[:removed].empty?
95
+ params[:delete] = changes[:removed].map(&:to_s).join(",")
96
+ end
97
+ params[:digest] = original_config[:digest] if original_config[:digest]
98
+ params
99
+ end
100
+
101
+ # Builds a ContainerOperationResult with the :edit operation.
102
+ #
103
+ # @param resource_info [Hash] resource info (ctid, node, status)
104
+ # @param attrs [Hash] additional result attributes
105
+ # @return [Models::ContainerOperationResult]
106
+ def build_result(resource_info, **attrs)
107
+ container = Models::Container.new(
108
+ vmid: resource_info[:ctid],
109
+ node: resource_info[:node]
110
+ )
111
+ Models::ContainerOperationResult.new(
112
+ operation: :edit, container: container, resource: resource_info, **attrs
113
+ )
114
+ end
115
+
116
+ # Builds a not-found error result.
117
+ #
118
+ # @param ctid [Integer] Container identifier
119
+ # @return [Models::ContainerOperationResult]
120
+ def not_found_result(ctid)
121
+ build_result({ ctid: ctid }, success: false, error: "Container #{ctid} not found")
122
+ end
123
+ end
124
+ end
125
+ end
@@ -0,0 +1,159 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pvectl
4
+ module Services
5
+ # Orchestrates the interactive editing flow for node DNS settings.
6
+ #
7
+ # Fetches current DNS config, presents it as YAML in an editor, computes
8
+ # a diff, and applies changes via the Proxmox API. The Proxmox PUT endpoint
9
+ # requires the `search` field — the service validates this before calling.
10
+ #
11
+ # @example Basic usage
12
+ # service = EditDns.new(dns_repository: repo)
13
+ # result = service.execute(node_name: "pve1")
14
+ #
15
+ # @example Dry run
16
+ # service = EditDns.new(dns_repository: repo, options: { dry_run: true })
17
+ # result = service.execute(node_name: "pve1")
18
+ #
19
+ class EditDns
20
+ # Editable keys exposed in the YAML editor (in order).
21
+ EDITABLE_KEYS = %i[search dns1 dns2 dns3].freeze
22
+
23
+ # Creates a new EditDns service.
24
+ #
25
+ # @param dns_repository [Repositories::Dns] DNS repository
26
+ # @param editor_session [EditorSession, nil] optional injected editor session
27
+ # @param options [Hash] options (dry_run)
28
+ def initialize(dns_repository:, editor_session: nil, options: {})
29
+ @dns_repository = dns_repository
30
+ @editor_session = editor_session
31
+ @options = options
32
+ end
33
+
34
+ # Executes the interactive DNS edit flow.
35
+ #
36
+ # @param node_name [String] node name
37
+ # @return [Models::NodeOperationResult, nil] result, or nil if cancelled/no changes
38
+ def execute(node_name:)
39
+ dns = @dns_repository.fetch(node_name)
40
+ resource_info = { node_name: node_name }
41
+
42
+ editable = build_editable(dns)
43
+ yaml_content = "# Node: #{node_name} — DNS configuration\n" \
44
+ "# 'search' is required by Proxmox. dns1/dns2/dns3 are optional.\n" +
45
+ editable.to_yaml
46
+
47
+ session = @editor_session || EditorSession.new
48
+ edited = session.edit(yaml_content)
49
+
50
+ return nil unless edited
51
+
52
+ cleaned = edited.lines.reject { |l| l.strip.start_with?("#") }.join
53
+ edited_config = YAML.safe_load(cleaned, symbolize_names: true) || {}
54
+
55
+ original_symbolized = editable.transform_keys(&:to_sym)
56
+ changes = compute_diff(original_symbolized, edited_config)
57
+
58
+ if changes[:changed].empty? && changes[:added].empty? && changes[:removed].empty?
59
+ return nil
60
+ end
61
+
62
+ validate!(edited_config)
63
+
64
+ resource_info[:diff] = changes
65
+
66
+ return build_result(resource_info, success: true) if @options[:dry_run]
67
+
68
+ @dns_repository.update(node_name, build_update_params(edited_config))
69
+ build_result(resource_info, success: true)
70
+ rescue StandardError => e
71
+ build_result({ node_name: node_name }, success: false, error: e.message)
72
+ end
73
+
74
+ private
75
+
76
+ # Builds editable hash (string-keyed) from a DnsConfig model.
77
+ #
78
+ # @param dns [Models::DnsConfig] current config
79
+ # @return [Hash{String=>String}] editable values
80
+ def build_editable(dns)
81
+ {
82
+ "search" => dns.search,
83
+ "dns1" => dns.dns1,
84
+ "dns2" => dns.dns2,
85
+ "dns3" => dns.dns3
86
+ }.compact
87
+ end
88
+
89
+ # Validates the edited config satisfies API requirements.
90
+ #
91
+ # @param edited [Hash] edited config with symbol keys
92
+ # @return [void]
93
+ # @raise [ArgumentError] if `search` is missing or empty
94
+ def validate!(edited)
95
+ search = edited[:search]
96
+ if search.nil? || search.to_s.strip.empty?
97
+ raise ArgumentError, "search field is required by Proxmox API"
98
+ end
99
+ end
100
+
101
+ # Computes diff between original and edited configs.
102
+ #
103
+ # @param original [Hash] original config (symbol keys)
104
+ # @param edited [Hash] edited config (symbol keys)
105
+ # @return [Hash] diff with :changed, :added, :removed
106
+ def compute_diff(original, edited)
107
+ changed = {}
108
+ added = {}
109
+ removed = []
110
+
111
+ edited.each do |key, value|
112
+ orig_value = original[key]
113
+ if orig_value.nil?
114
+ added[key] = value
115
+ elsif orig_value.to_s != value.to_s
116
+ changed[key] = [orig_value.to_s, value.to_s]
117
+ end
118
+ end
119
+
120
+ original.each_key do |key|
121
+ removed << key unless edited.key?(key)
122
+ end
123
+
124
+ { changed: changed, added: added, removed: removed }
125
+ end
126
+
127
+ # Builds API update parameters from edited config.
128
+ #
129
+ # The PUT endpoint requires `search` and accepts optional dns1-3.
130
+ # Removed keys are sent as empty strings to clear the field
131
+ # (Proxmox accepts empty strings as "unset" for optional dns* fields).
132
+ #
133
+ # @param edited [Hash] edited config (symbol keys)
134
+ # @return [Hash] API parameters with all editable keys present
135
+ def build_update_params(edited)
136
+ params = {}
137
+ EDITABLE_KEYS.each do |key|
138
+ value = edited[key]
139
+ params[key] = value.nil? ? "" : value
140
+ end
141
+ # search must always have a real value (validated above)
142
+ params[:search] = edited[:search]
143
+ params
144
+ end
145
+
146
+ # Builds a NodeOperationResult with the :edit operation.
147
+ #
148
+ # @param resource_info [Hash] resource info (node_name)
149
+ # @param attrs [Hash] additional result attributes
150
+ # @return [Models::NodeOperationResult]
151
+ def build_result(resource_info, **attrs)
152
+ node_model = Models::Node.new(name: resource_info[:node_name])
153
+ Models::NodeOperationResult.new(
154
+ operation: :edit, node_model: node_model, resource: resource_info, **attrs
155
+ )
156
+ end
157
+ end
158
+ end
159
+ end