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.
- checksums.yaml +7 -0
- data/.claude/rules/branch-before-changes.md +52 -0
- data/.claude/rules/documentation-updates.md +104 -0
- data/.claude/rules/git-workflow.md +84 -0
- data/.claude/rules/proxmox-api-docs.md +58 -0
- data/.claude/rules/rbs-signatures.md +80 -0
- data/.claude/rules/refactoring-as-design-option.md +35 -0
- data/.claude/scheduled_tasks.lock +1 -0
- data/.claude/settings.json +51 -0
- data/.mcp.json +8 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/CHANGELOG.md +138 -0
- data/CLAUDE.md +211 -0
- data/CODE_OF_CONDUCT.md +132 -0
- data/LICENSE.txt +21 -0
- data/README.md +143 -0
- data/Rakefile +8 -0
- data/docs/proxmox-api-update.sh +96 -0
- data/exe/pvectl +5 -0
- data/lib/pvectl/argv_preprocessor.rb +334 -0
- data/lib/pvectl/cli.rb +102 -0
- data/lib/pvectl/commands/apt.rb +389 -0
- data/lib/pvectl/commands/clone_container.rb +230 -0
- data/lib/pvectl/commands/clone_vm.rb +331 -0
- data/lib/pvectl/commands/cloudinit/command.rb +122 -0
- data/lib/pvectl/commands/cloudinit/dump.rb +94 -0
- data/lib/pvectl/commands/cloudinit/pending.rb +137 -0
- data/lib/pvectl/commands/cloudinit/regenerate.rb +79 -0
- data/lib/pvectl/commands/config/command.rb +65 -0
- data/lib/pvectl/commands/config/get_contexts.rb +68 -0
- data/lib/pvectl/commands/config/set_cluster.rb +103 -0
- data/lib/pvectl/commands/config/set_context.rb +136 -0
- data/lib/pvectl/commands/config/set_credentials.rb +181 -0
- data/lib/pvectl/commands/config/use_context.rb +69 -0
- data/lib/pvectl/commands/config/view.rb +67 -0
- data/lib/pvectl/commands/console.rb +93 -0
- data/lib/pvectl/commands/console_ct.rb +187 -0
- data/lib/pvectl/commands/console_vm.rb +187 -0
- data/lib/pvectl/commands/container_lifecycle_command.rb +77 -0
- data/lib/pvectl/commands/create_backup.rb +173 -0
- data/lib/pvectl/commands/create_container.rb +141 -0
- data/lib/pvectl/commands/create_resource_command.rb +244 -0
- data/lib/pvectl/commands/create_snapshot.rb +242 -0
- data/lib/pvectl/commands/create_vm.rb +267 -0
- data/lib/pvectl/commands/delete_backup.rb +139 -0
- data/lib/pvectl/commands/delete_command.rb +119 -0
- data/lib/pvectl/commands/delete_container.rb +30 -0
- data/lib/pvectl/commands/delete_snapshot.rb +248 -0
- data/lib/pvectl/commands/delete_vm.rb +127 -0
- data/lib/pvectl/commands/describe/command.rb +251 -0
- data/lib/pvectl/commands/edit_container.rb +56 -0
- data/lib/pvectl/commands/edit_dns.rb +149 -0
- data/lib/pvectl/commands/edit_hosts.rb +135 -0
- data/lib/pvectl/commands/edit_node.rb +54 -0
- data/lib/pvectl/commands/edit_resource_command.rb +180 -0
- data/lib/pvectl/commands/edit_vm.rb +154 -0
- data/lib/pvectl/commands/edit_volume.rb +189 -0
- data/lib/pvectl/commands/feature_command.rb +230 -0
- data/lib/pvectl/commands/feature_container.rb +21 -0
- data/lib/pvectl/commands/feature_vm.rb +94 -0
- data/lib/pvectl/commands/get/command.rb +360 -0
- data/lib/pvectl/commands/get/handlers/backups.rb +76 -0
- data/lib/pvectl/commands/get/handlers/capabilities.rb +107 -0
- data/lib/pvectl/commands/get/handlers/containers.rb +148 -0
- data/lib/pvectl/commands/get/handlers/disks.rb +107 -0
- data/lib/pvectl/commands/get/handlers/dns.rb +94 -0
- data/lib/pvectl/commands/get/handlers/hosts.rb +94 -0
- data/lib/pvectl/commands/get/handlers/nodes.rb +162 -0
- data/lib/pvectl/commands/get/handlers/services.rb +81 -0
- data/lib/pvectl/commands/get/handlers/snapshots.rb +97 -0
- data/lib/pvectl/commands/get/handlers/storage.rb +118 -0
- data/lib/pvectl/commands/get/handlers/subscription.rb +69 -0
- data/lib/pvectl/commands/get/handlers/tasks.rb +89 -0
- data/lib/pvectl/commands/get/handlers/templates.rb +175 -0
- data/lib/pvectl/commands/get/handlers/time.rb +118 -0
- data/lib/pvectl/commands/get/handlers/vms.rb +145 -0
- data/lib/pvectl/commands/get/handlers/volume.rb +134 -0
- data/lib/pvectl/commands/get/resource_handler.rb +63 -0
- data/lib/pvectl/commands/get/resource_registry.rb +18 -0
- data/lib/pvectl/commands/get/watch_loop.rb +129 -0
- data/lib/pvectl/commands/irreversible_command.rb +265 -0
- data/lib/pvectl/commands/logs/command.rb +275 -0
- data/lib/pvectl/commands/logs/handlers/journal.rb +46 -0
- data/lib/pvectl/commands/logs/handlers/syslog.rb +53 -0
- data/lib/pvectl/commands/logs/handlers/task_detail.rb +52 -0
- data/lib/pvectl/commands/logs/handlers/task_logs.rb +115 -0
- data/lib/pvectl/commands/logs/resource_handler.rb +46 -0
- data/lib/pvectl/commands/logs/resource_registry.rb +22 -0
- data/lib/pvectl/commands/migrate_command.rb +282 -0
- data/lib/pvectl/commands/migrate_container.rb +23 -0
- data/lib/pvectl/commands/migrate_vm.rb +122 -0
- data/lib/pvectl/commands/move_disk_command.rb +239 -0
- data/lib/pvectl/commands/move_disk_container.rb +21 -0
- data/lib/pvectl/commands/move_disk_vm.rb +127 -0
- data/lib/pvectl/commands/ping.rb +249 -0
- data/lib/pvectl/commands/pull.rb +342 -0
- data/lib/pvectl/commands/push.rb +352 -0
- data/lib/pvectl/commands/reset.rb +64 -0
- data/lib/pvectl/commands/resource_lifecycle_command.rb +277 -0
- data/lib/pvectl/commands/resource_registry.rb +73 -0
- data/lib/pvectl/commands/restart.rb +70 -0
- data/lib/pvectl/commands/restart_container.rb +18 -0
- data/lib/pvectl/commands/restore_backup.rb +236 -0
- data/lib/pvectl/commands/resume.rb +57 -0
- data/lib/pvectl/commands/rollback_snapshot.rb +228 -0
- data/lib/pvectl/commands/sendkey_vm.rb +205 -0
- data/lib/pvectl/commands/service.rb +293 -0
- data/lib/pvectl/commands/set_container.rb +50 -0
- data/lib/pvectl/commands/set_node.rb +52 -0
- data/lib/pvectl/commands/set_resource_command.rb +185 -0
- data/lib/pvectl/commands/set_vm.rb +136 -0
- data/lib/pvectl/commands/set_volume.rb +212 -0
- data/lib/pvectl/commands/shared_config_parsers.rb +126 -0
- data/lib/pvectl/commands/shared_flags.rb +155 -0
- data/lib/pvectl/commands/shutdown.rb +73 -0
- data/lib/pvectl/commands/shutdown_container.rb +18 -0
- data/lib/pvectl/commands/start.rb +79 -0
- data/lib/pvectl/commands/start_container.rb +18 -0
- data/lib/pvectl/commands/stop.rb +75 -0
- data/lib/pvectl/commands/stop_container.rb +18 -0
- data/lib/pvectl/commands/suspend.rb +64 -0
- data/lib/pvectl/commands/template_command.rb +205 -0
- data/lib/pvectl/commands/template_container.rb +27 -0
- data/lib/pvectl/commands/template_vm.rb +106 -0
- data/lib/pvectl/commands/top/command.rb +206 -0
- data/lib/pvectl/commands/top/handlers/containers.rb +61 -0
- data/lib/pvectl/commands/top/handlers/nodes.rb +61 -0
- data/lib/pvectl/commands/top/handlers/vms.rb +61 -0
- data/lib/pvectl/commands/top/resource_handler.rb +46 -0
- data/lib/pvectl/commands/top/resource_registry.rb +22 -0
- data/lib/pvectl/commands/unlink_disk_vm.rb +232 -0
- data/lib/pvectl/commands/vm_lifecycle_command.rb +77 -0
- data/lib/pvectl/commands/wakeonlan_node.rb +153 -0
- data/lib/pvectl/config/errors.rb +62 -0
- data/lib/pvectl/config/models/cluster.rb +180 -0
- data/lib/pvectl/config/models/context.rb +100 -0
- data/lib/pvectl/config/models/resolved_config.rb +171 -0
- data/lib/pvectl/config/models/user.rb +133 -0
- data/lib/pvectl/config/provider.rb +297 -0
- data/lib/pvectl/config/service.rb +300 -0
- data/lib/pvectl/config/store.rb +161 -0
- data/lib/pvectl/config/wizard.rb +309 -0
- data/lib/pvectl/config_serializer.rb +1034 -0
- data/lib/pvectl/connection/retry_handler.rb +161 -0
- data/lib/pvectl/connection.rb +157 -0
- data/lib/pvectl/console/terminal_session.rb +449 -0
- data/lib/pvectl/editor_session.rb +157 -0
- data/lib/pvectl/exit_codes.rb +43 -0
- data/lib/pvectl/formatters/base.rb +55 -0
- data/lib/pvectl/formatters/color_support.rb +90 -0
- data/lib/pvectl/formatters/json.rb +45 -0
- data/lib/pvectl/formatters/output_helper.rb +77 -0
- data/lib/pvectl/formatters/registry.rb +72 -0
- data/lib/pvectl/formatters/table.rb +235 -0
- data/lib/pvectl/formatters/wide.rb +93 -0
- data/lib/pvectl/formatters/yaml.rb +49 -0
- data/lib/pvectl/manifest_serializer.rb +142 -0
- data/lib/pvectl/models/apt_package.rb +107 -0
- data/lib/pvectl/models/backup.rb +173 -0
- data/lib/pvectl/models/base.rb +49 -0
- data/lib/pvectl/models/capability.rb +62 -0
- data/lib/pvectl/models/container.rb +205 -0
- data/lib/pvectl/models/container_operation_result.rb +27 -0
- data/lib/pvectl/models/dns_config.rb +54 -0
- data/lib/pvectl/models/hosts_file.rb +47 -0
- data/lib/pvectl/models/journal_entry.rb +16 -0
- data/lib/pvectl/models/network_interface.rb +85 -0
- data/lib/pvectl/models/node.rb +195 -0
- data/lib/pvectl/models/node_operation_result.rb +45 -0
- data/lib/pvectl/models/operation_result.rb +110 -0
- data/lib/pvectl/models/physical_disk.rb +193 -0
- data/lib/pvectl/models/service.rb +80 -0
- data/lib/pvectl/models/snapshot.rb +101 -0
- data/lib/pvectl/models/snapshot_description.rb +39 -0
- data/lib/pvectl/models/storage.rb +180 -0
- data/lib/pvectl/models/subscription.rb +87 -0
- data/lib/pvectl/models/syslog_entry.rb +17 -0
- data/lib/pvectl/models/task.rb +95 -0
- data/lib/pvectl/models/task_entry.rb +52 -0
- data/lib/pvectl/models/task_log_line.rb +17 -0
- data/lib/pvectl/models/time_config.rb +47 -0
- data/lib/pvectl/models/vm.rb +137 -0
- data/lib/pvectl/models/vm_operation_result.rb +27 -0
- data/lib/pvectl/models/volume.rb +133 -0
- data/lib/pvectl/models/volume_operation_result.rb +26 -0
- data/lib/pvectl/parsers/cloud_init_config.rb +92 -0
- data/lib/pvectl/parsers/disk_config.rb +97 -0
- data/lib/pvectl/parsers/lxc_mount_config.rb +98 -0
- data/lib/pvectl/parsers/lxc_net_config.rb +97 -0
- data/lib/pvectl/parsers/net_config.rb +95 -0
- data/lib/pvectl/parsers/smart_text.rb +42 -0
- data/lib/pvectl/plugin_loader.rb +157 -0
- data/lib/pvectl/presenters/apt_package.rb +99 -0
- data/lib/pvectl/presenters/backup.rb +128 -0
- data/lib/pvectl/presenters/base.rb +283 -0
- data/lib/pvectl/presenters/capability.rb +104 -0
- data/lib/pvectl/presenters/config/context.rb +80 -0
- data/lib/pvectl/presenters/container.rb +574 -0
- data/lib/pvectl/presenters/container_operation_result.rb +109 -0
- data/lib/pvectl/presenters/disk.rb +184 -0
- data/lib/pvectl/presenters/dns_config.rb +68 -0
- data/lib/pvectl/presenters/hosts_file.rb +61 -0
- data/lib/pvectl/presenters/journal_entry.rb +20 -0
- data/lib/pvectl/presenters/node.rb +762 -0
- data/lib/pvectl/presenters/node_operation_result.rb +50 -0
- data/lib/pvectl/presenters/operation_result.rb +61 -0
- data/lib/pvectl/presenters/service.rb +76 -0
- data/lib/pvectl/presenters/snapshot.rb +239 -0
- data/lib/pvectl/presenters/snapshot_operation_result.rb +125 -0
- data/lib/pvectl/presenters/storage.rb +329 -0
- data/lib/pvectl/presenters/subscription.rb +189 -0
- data/lib/pvectl/presenters/syslog_entry.rb +20 -0
- data/lib/pvectl/presenters/task_entry.rb +69 -0
- data/lib/pvectl/presenters/task_log_line.rb +20 -0
- data/lib/pvectl/presenters/template.rb +76 -0
- data/lib/pvectl/presenters/time_config.rb +86 -0
- data/lib/pvectl/presenters/top_container.rb +112 -0
- data/lib/pvectl/presenters/top_node.rb +115 -0
- data/lib/pvectl/presenters/top_presenter.rb +59 -0
- data/lib/pvectl/presenters/top_vm.rb +105 -0
- data/lib/pvectl/presenters/vm.rb +853 -0
- data/lib/pvectl/presenters/vm_operation_result.rb +109 -0
- data/lib/pvectl/presenters/volume.rb +136 -0
- data/lib/pvectl/presenters/volume_operation_result.rb +58 -0
- data/lib/pvectl/repositories/apt.rb +93 -0
- data/lib/pvectl/repositories/backup.rb +186 -0
- data/lib/pvectl/repositories/base.rb +110 -0
- data/lib/pvectl/repositories/capabilities.rb +96 -0
- data/lib/pvectl/repositories/container.rb +503 -0
- data/lib/pvectl/repositories/disk.rb +87 -0
- data/lib/pvectl/repositories/dns.rb +54 -0
- data/lib/pvectl/repositories/hosts.rb +63 -0
- data/lib/pvectl/repositories/journal.rb +23 -0
- data/lib/pvectl/repositories/node.rb +537 -0
- data/lib/pvectl/repositories/service.rb +139 -0
- data/lib/pvectl/repositories/snapshot.rb +133 -0
- data/lib/pvectl/repositories/storage.rb +302 -0
- data/lib/pvectl/repositories/subscription.rb +77 -0
- data/lib/pvectl/repositories/syslog.rb +25 -0
- data/lib/pvectl/repositories/task.rb +82 -0
- data/lib/pvectl/repositories/task_list.rb +30 -0
- data/lib/pvectl/repositories/task_log.rb +31 -0
- data/lib/pvectl/repositories/time_config.rb +53 -0
- data/lib/pvectl/repositories/vm.rb +616 -0
- data/lib/pvectl/repositories/volume.rb +306 -0
- data/lib/pvectl/selectors/base.rb +201 -0
- data/lib/pvectl/selectors/container.rb +116 -0
- data/lib/pvectl/selectors/disk.rb +59 -0
- data/lib/pvectl/selectors/vm.rb +116 -0
- data/lib/pvectl/selectors/volume.rb +59 -0
- data/lib/pvectl/services/backup.rb +209 -0
- data/lib/pvectl/services/clone_container.rb +260 -0
- data/lib/pvectl/services/clone_vm.rb +265 -0
- data/lib/pvectl/services/cloudinit.rb +96 -0
- data/lib/pvectl/services/console.rb +152 -0
- data/lib/pvectl/services/container_lifecycle.rb +124 -0
- data/lib/pvectl/services/create_container.rb +179 -0
- data/lib/pvectl/services/create_vm.rb +191 -0
- data/lib/pvectl/services/edit_container.rb +125 -0
- data/lib/pvectl/services/edit_dns.rb +159 -0
- data/lib/pvectl/services/edit_hosts.rb +78 -0
- data/lib/pvectl/services/edit_node.rb +147 -0
- data/lib/pvectl/services/edit_vm.rb +125 -0
- data/lib/pvectl/services/edit_volume.rb +224 -0
- data/lib/pvectl/services/get/resource_service.rb +98 -0
- data/lib/pvectl/services/move_disk.rb +132 -0
- data/lib/pvectl/services/pull_config.rb +94 -0
- data/lib/pvectl/services/push_config.rb +524 -0
- data/lib/pvectl/services/resize_volume.rb +253 -0
- data/lib/pvectl/services/resource_delete.rb +169 -0
- data/lib/pvectl/services/resource_migration.rb +170 -0
- data/lib/pvectl/services/sendkey.rb +108 -0
- data/lib/pvectl/services/service_lifecycle.rb +89 -0
- data/lib/pvectl/services/set_container.rb +128 -0
- data/lib/pvectl/services/set_node.rb +236 -0
- data/lib/pvectl/services/set_vm.rb +128 -0
- data/lib/pvectl/services/set_volume.rb +126 -0
- data/lib/pvectl/services/snapshot.rb +261 -0
- data/lib/pvectl/services/task_listing.rb +75 -0
- data/lib/pvectl/services/unlink_disk.rb +86 -0
- data/lib/pvectl/services/vm_lifecycle.rb +124 -0
- data/lib/pvectl/services/wakeonlan.rb +79 -0
- data/lib/pvectl/utils/resource_resolver.rb +80 -0
- data/lib/pvectl/version.rb +13 -0
- data/lib/pvectl/wizards/create_container.rb +105 -0
- data/lib/pvectl/wizards/create_vm.rb +98 -0
- data/lib/pvectl.rb +439 -0
- data/sig/external/gli.rbs +16 -0
- data/sig/external/proxmox_api.rbs +10 -0
- data/sig/pvectl/argv_preprocessor.rbs +53 -0
- data/sig/pvectl/cli.rbs +26 -0
- data/sig/pvectl/commands/apt.rbs +47 -0
- data/sig/pvectl/commands/clone_container.rbs +31 -0
- data/sig/pvectl/commands/clone_vm.rbs +33 -0
- data/sig/pvectl/commands/cloudinit/command.rbs +13 -0
- data/sig/pvectl/commands/cloudinit/dump.rbs +13 -0
- data/sig/pvectl/commands/cloudinit/pending.rbs +17 -0
- data/sig/pvectl/commands/cloudinit/regenerate.rbs +11 -0
- data/sig/pvectl/commands/config/command.rbs +9 -0
- data/sig/pvectl/commands/config/get_contexts.rbs +11 -0
- data/sig/pvectl/commands/config/set_cluster.rbs +11 -0
- data/sig/pvectl/commands/config/set_context.rbs +15 -0
- data/sig/pvectl/commands/config/set_credentials.rbs +15 -0
- data/sig/pvectl/commands/config/use_context.rbs +11 -0
- data/sig/pvectl/commands/config/view.rbs +11 -0
- data/sig/pvectl/commands/console.rbs +9 -0
- data/sig/pvectl/commands/console_ct.rbs +27 -0
- data/sig/pvectl/commands/console_vm.rbs +27 -0
- data/sig/pvectl/commands/container_lifecycle_command.rbs +25 -0
- data/sig/pvectl/commands/create_backup.rbs +29 -0
- data/sig/pvectl/commands/create_container.rbs +30 -0
- data/sig/pvectl/commands/create_resource_command.rbs +53 -0
- data/sig/pvectl/commands/create_snapshot.rbs +35 -0
- data/sig/pvectl/commands/create_vm.rbs +30 -0
- data/sig/pvectl/commands/delete_backup.rbs +25 -0
- data/sig/pvectl/commands/delete_command.rbs +45 -0
- data/sig/pvectl/commands/delete_container.rbs +11 -0
- data/sig/pvectl/commands/delete_snapshot.rbs +35 -0
- data/sig/pvectl/commands/delete_vm.rbs +13 -0
- data/sig/pvectl/commands/describe/command.rbs +27 -0
- data/sig/pvectl/commands/edit_container.rbs +17 -0
- data/sig/pvectl/commands/edit_dns.rbs +25 -0
- data/sig/pvectl/commands/edit_hosts.rbs +23 -0
- data/sig/pvectl/commands/edit_node.rbs +17 -0
- data/sig/pvectl/commands/edit_resource_command.rbs +35 -0
- data/sig/pvectl/commands/edit_vm.rbs +19 -0
- data/sig/pvectl/commands/edit_volume.rbs +24 -0
- data/sig/pvectl/commands/feature_command.rbs +43 -0
- data/sig/pvectl/commands/feature_container.rbs +10 -0
- data/sig/pvectl/commands/feature_vm.rbs +12 -0
- data/sig/pvectl/commands/get/command.rbs +42 -0
- data/sig/pvectl/commands/get/handlers/backups.rbs +23 -0
- data/sig/pvectl/commands/get/handlers/capabilities.rbs +29 -0
- data/sig/pvectl/commands/get/handlers/containers.rbs +35 -0
- data/sig/pvectl/commands/get/handlers/disks.rbs +27 -0
- data/sig/pvectl/commands/get/handlers/dns.rbs +25 -0
- data/sig/pvectl/commands/get/handlers/hosts.rbs +25 -0
- data/sig/pvectl/commands/get/handlers/nodes.rbs +33 -0
- data/sig/pvectl/commands/get/handlers/services.rbs +23 -0
- data/sig/pvectl/commands/get/handlers/snapshots.rbs +27 -0
- data/sig/pvectl/commands/get/handlers/storage.rbs +25 -0
- data/sig/pvectl/commands/get/handlers/subscription.rbs +25 -0
- data/sig/pvectl/commands/get/handlers/tasks.rbs +28 -0
- data/sig/pvectl/commands/get/handlers/templates.rbs +35 -0
- data/sig/pvectl/commands/get/handlers/time.rbs +29 -0
- data/sig/pvectl/commands/get/handlers/vms.rbs +35 -0
- data/sig/pvectl/commands/get/handlers/volume.rbs +27 -0
- data/sig/pvectl/commands/get/resource_handler.rbs +13 -0
- data/sig/pvectl/commands/get/resource_registry.rbs +8 -0
- data/sig/pvectl/commands/get/watch_loop.rbs +33 -0
- data/sig/pvectl/commands/irreversible_command.rbs +32 -0
- data/sig/pvectl/commands/logs/command.rbs +35 -0
- data/sig/pvectl/commands/logs/handlers/journal.rbs +21 -0
- data/sig/pvectl/commands/logs/handlers/syslog.rbs +21 -0
- data/sig/pvectl/commands/logs/handlers/task_detail.rbs +21 -0
- data/sig/pvectl/commands/logs/handlers/task_logs.rbs +35 -0
- data/sig/pvectl/commands/logs/resource_handler.rbs +11 -0
- data/sig/pvectl/commands/logs/resource_registry.rbs +8 -0
- data/sig/pvectl/commands/migrate_command.rbs +45 -0
- data/sig/pvectl/commands/migrate_container.rbs +11 -0
- data/sig/pvectl/commands/migrate_vm.rbs +13 -0
- data/sig/pvectl/commands/move_disk_command.rbs +43 -0
- data/sig/pvectl/commands/move_disk_container.rbs +11 -0
- data/sig/pvectl/commands/move_disk_vm.rbs +13 -0
- data/sig/pvectl/commands/ping.rbs +39 -0
- data/sig/pvectl/commands/pull.rbs +33 -0
- data/sig/pvectl/commands/push.rbs +32 -0
- data/sig/pvectl/commands/reset.rbs +11 -0
- data/sig/pvectl/commands/resource_lifecycle_command.rbs +55 -0
- data/sig/pvectl/commands/resource_registry.rbs +19 -0
- data/sig/pvectl/commands/restart.rbs +11 -0
- data/sig/pvectl/commands/restart_container.rbs +9 -0
- data/sig/pvectl/commands/restore_backup.rbs +27 -0
- data/sig/pvectl/commands/resume.rbs +11 -0
- data/sig/pvectl/commands/rollback_snapshot.rbs +31 -0
- data/sig/pvectl/commands/sendkey_vm.rbs +25 -0
- data/sig/pvectl/commands/service.rbs +38 -0
- data/sig/pvectl/commands/set_container.rbs +13 -0
- data/sig/pvectl/commands/set_node.rbs +13 -0
- data/sig/pvectl/commands/set_resource_command.rbs +25 -0
- data/sig/pvectl/commands/set_vm.rbs +15 -0
- data/sig/pvectl/commands/set_volume.rbs +24 -0
- data/sig/pvectl/commands/shared_config_parsers.rbs +19 -0
- data/sig/pvectl/commands/shared_flags.rbs +10 -0
- data/sig/pvectl/commands/shutdown.rbs +11 -0
- data/sig/pvectl/commands/shutdown_container.rbs +9 -0
- data/sig/pvectl/commands/start.rbs +11 -0
- data/sig/pvectl/commands/start_container.rbs +9 -0
- data/sig/pvectl/commands/stop.rbs +11 -0
- data/sig/pvectl/commands/stop_container.rbs +9 -0
- data/sig/pvectl/commands/suspend.rbs +11 -0
- data/sig/pvectl/commands/template_command.rbs +21 -0
- data/sig/pvectl/commands/template_container.rbs +10 -0
- data/sig/pvectl/commands/template_vm.rbs +12 -0
- data/sig/pvectl/commands/top/command.rbs +31 -0
- data/sig/pvectl/commands/top/handlers/containers.rbs +21 -0
- data/sig/pvectl/commands/top/handlers/nodes.rbs +21 -0
- data/sig/pvectl/commands/top/handlers/vms.rbs +21 -0
- data/sig/pvectl/commands/top/resource_handler.rbs +11 -0
- data/sig/pvectl/commands/top/resource_registry.rbs +8 -0
- data/sig/pvectl/commands/unlink_disk_vm.rbs +27 -0
- data/sig/pvectl/commands/vm_lifecycle_command.rbs +25 -0
- data/sig/pvectl/commands/wakeonlan_node.rbs +21 -0
- data/sig/pvectl/config/errors.rbs +24 -0
- data/sig/pvectl/config/models/cluster.rbs +39 -0
- data/sig/pvectl/config/models/context.rbs +23 -0
- data/sig/pvectl/config/models/resolved_config.rbs +51 -0
- data/sig/pvectl/config/models/user.rbs +31 -0
- data/sig/pvectl/config/provider.rbs +40 -0
- data/sig/pvectl/config/service.rbs +65 -0
- data/sig/pvectl/config/store.rbs +14 -0
- data/sig/pvectl/config/wizard.rbs +48 -0
- data/sig/pvectl/config_serializer.rbs +121 -0
- data/sig/pvectl/connection/retry_handler.rbs +31 -0
- data/sig/pvectl/connection.rbs +35 -0
- data/sig/pvectl/console/terminal_session.rbs +63 -0
- data/sig/pvectl/editor_session.rbs +33 -0
- data/sig/pvectl/exit_codes.rbs +19 -0
- data/sig/pvectl/formatters/base.rbs +13 -0
- data/sig/pvectl/formatters/color_support.rbs +13 -0
- data/sig/pvectl/formatters/json.rbs +7 -0
- data/sig/pvectl/formatters/output_helper.rbs +9 -0
- data/sig/pvectl/formatters/registry.rbs +13 -0
- data/sig/pvectl/formatters/table.rbs +25 -0
- data/sig/pvectl/formatters/wide.rbs +15 -0
- data/sig/pvectl/formatters/yaml.rbs +7 -0
- data/sig/pvectl/manifest_serializer.rbs +18 -0
- data/sig/pvectl/models/apt_package.rbs +26 -0
- data/sig/pvectl/models/backup.rbs +31 -0
- data/sig/pvectl/models/base.rbs +11 -0
- data/sig/pvectl/models/capability.rbs +16 -0
- data/sig/pvectl/models/container.rbs +44 -0
- data/sig/pvectl/models/container_operation_result.rbs +9 -0
- data/sig/pvectl/models/dns_config.rbs +15 -0
- data/sig/pvectl/models/hosts_file.rbs +13 -0
- data/sig/pvectl/models/journal_entry.rbs +10 -0
- data/sig/pvectl/models/network_interface.rbs +20 -0
- data/sig/pvectl/models/node.rbs +47 -0
- data/sig/pvectl/models/node_operation_result.rbs +12 -0
- data/sig/pvectl/models/operation_result.rbs +21 -0
- data/sig/pvectl/models/physical_disk.rbs +35 -0
- data/sig/pvectl/models/service.rbs +18 -0
- data/sig/pvectl/models/snapshot.rbs +21 -0
- data/sig/pvectl/models/snapshot_description.rbs +18 -0
- data/sig/pvectl/models/storage.rbs +39 -0
- data/sig/pvectl/models/subscription.rbs +24 -0
- data/sig/pvectl/models/syslog_entry.rbs +10 -0
- data/sig/pvectl/models/task.rbs +22 -0
- data/sig/pvectl/models/task_entry.rbs +24 -0
- data/sig/pvectl/models/task_log_line.rbs +10 -0
- data/sig/pvectl/models/time_config.rbs +12 -0
- data/sig/pvectl/models/vm.rbs +32 -0
- data/sig/pvectl/models/vm_operation_result.rbs +9 -0
- data/sig/pvectl/models/volume.rbs +29 -0
- data/sig/pvectl/models/volume_operation_result.rbs +9 -0
- data/sig/pvectl/parsers/cloud_init_config.rbs +15 -0
- data/sig/pvectl/parsers/disk_config.rbs +19 -0
- data/sig/pvectl/parsers/lxc_mount_config.rbs +19 -0
- data/sig/pvectl/parsers/lxc_net_config.rbs +19 -0
- data/sig/pvectl/parsers/net_config.rbs +19 -0
- data/sig/pvectl/parsers/smart_text.rbs +7 -0
- data/sig/pvectl/plugin_loader.rbs +25 -0
- data/sig/pvectl/presenters/apt_package.rbs +19 -0
- data/sig/pvectl/presenters/backup.rbs +25 -0
- data/sig/pvectl/presenters/base.rbs +41 -0
- data/sig/pvectl/presenters/capability.rbs +19 -0
- data/sig/pvectl/presenters/config/context.rbs +17 -0
- data/sig/pvectl/presenters/container.rbs +78 -0
- data/sig/pvectl/presenters/container_operation_result.rbs +19 -0
- data/sig/pvectl/presenters/disk.rbs +31 -0
- data/sig/pvectl/presenters/dns_config.rbs +13 -0
- data/sig/pvectl/presenters/hosts_file.rbs +13 -0
- data/sig/pvectl/presenters/journal_entry.rbs +11 -0
- data/sig/pvectl/presenters/node.rbs +118 -0
- data/sig/pvectl/presenters/node_operation_result.rbs +11 -0
- data/sig/pvectl/presenters/operation_result.rbs +17 -0
- data/sig/pvectl/presenters/service.rbs +15 -0
- data/sig/pvectl/presenters/snapshot.rbs +35 -0
- data/sig/pvectl/presenters/snapshot_operation_result.rbs +27 -0
- data/sig/pvectl/presenters/storage.rbs +59 -0
- data/sig/pvectl/presenters/subscription.rbs +36 -0
- data/sig/pvectl/presenters/syslog_entry.rbs +11 -0
- data/sig/pvectl/presenters/task_entry.rbs +21 -0
- data/sig/pvectl/presenters/task_log_line.rbs +11 -0
- data/sig/pvectl/presenters/template.rbs +15 -0
- data/sig/pvectl/presenters/time_config.rbs +19 -0
- data/sig/pvectl/presenters/top_container.rbs +17 -0
- data/sig/pvectl/presenters/top_node.rbs +17 -0
- data/sig/pvectl/presenters/top_presenter.rbs +13 -0
- data/sig/pvectl/presenters/top_vm.rbs +17 -0
- data/sig/pvectl/presenters/vm.rbs +91 -0
- data/sig/pvectl/presenters/vm_operation_result.rbs +19 -0
- data/sig/pvectl/presenters/volume.rbs +23 -0
- data/sig/pvectl/presenters/volume_operation_result.rbs +11 -0
- data/sig/pvectl/repositories/apt.rbs +17 -0
- data/sig/pvectl/repositories/backup.rbs +27 -0
- data/sig/pvectl/repositories/base.rbs +23 -0
- data/sig/pvectl/repositories/capabilities.rbs +20 -0
- data/sig/pvectl/repositories/container.rbs +63 -0
- data/sig/pvectl/repositories/disk.rbs +17 -0
- data/sig/pvectl/repositories/dns.rbs +13 -0
- data/sig/pvectl/repositories/hosts.rbs +13 -0
- data/sig/pvectl/repositories/journal.rbs +7 -0
- data/sig/pvectl/repositories/node.rbs +68 -0
- data/sig/pvectl/repositories/service.rbs +27 -0
- data/sig/pvectl/repositories/snapshot.rbs +19 -0
- data/sig/pvectl/repositories/storage.rbs +37 -0
- data/sig/pvectl/repositories/subscription.rbs +17 -0
- data/sig/pvectl/repositories/syslog.rbs +7 -0
- data/sig/pvectl/repositories/task.rbs +19 -0
- data/sig/pvectl/repositories/task_list.rbs +7 -0
- data/sig/pvectl/repositories/task_log.rbs +11 -0
- data/sig/pvectl/repositories/time_config.rbs +13 -0
- data/sig/pvectl/repositories/vm.rbs +85 -0
- data/sig/pvectl/repositories/volume.rbs +43 -0
- data/sig/pvectl/selectors/base.rbs +37 -0
- data/sig/pvectl/selectors/container.rbs +19 -0
- data/sig/pvectl/selectors/disk.rbs +13 -0
- data/sig/pvectl/selectors/vm.rbs +19 -0
- data/sig/pvectl/selectors/volume.rbs +13 -0
- data/sig/pvectl/services/backup.rbs +27 -0
- data/sig/pvectl/services/clone_container.rbs +35 -0
- data/sig/pvectl/services/clone_vm.rbs +35 -0
- data/sig/pvectl/services/cloudinit.rbs +19 -0
- data/sig/pvectl/services/console.rbs +23 -0
- data/sig/pvectl/services/container_lifecycle.rbs +26 -0
- data/sig/pvectl/services/create_container.rbs +64 -0
- data/sig/pvectl/services/create_vm.rbs +72 -0
- data/sig/pvectl/services/edit_container.rbs +17 -0
- data/sig/pvectl/services/edit_dns.rbs +23 -0
- data/sig/pvectl/services/edit_hosts.rbs +13 -0
- data/sig/pvectl/services/edit_node.rbs +21 -0
- data/sig/pvectl/services/edit_vm.rbs +17 -0
- data/sig/pvectl/services/edit_volume.rbs +18 -0
- data/sig/pvectl/services/get/resource_service.rbs +23 -0
- data/sig/pvectl/services/move_disk.rbs +21 -0
- data/sig/pvectl/services/pull_config.rbs +18 -0
- data/sig/pvectl/services/push_config.rbs +37 -0
- data/sig/pvectl/services/resize_volume.rbs +47 -0
- data/sig/pvectl/services/resource_delete.rbs +27 -0
- data/sig/pvectl/services/resource_migration.rbs +29 -0
- data/sig/pvectl/services/sendkey.rbs +19 -0
- data/sig/pvectl/services/service_lifecycle.rbs +17 -0
- data/sig/pvectl/services/set_container.rbs +14 -0
- data/sig/pvectl/services/set_node.rbs +23 -0
- data/sig/pvectl/services/set_vm.rbs +14 -0
- data/sig/pvectl/services/set_volume.rbs +12 -0
- data/sig/pvectl/services/snapshot.rbs +35 -0
- data/sig/pvectl/services/task_listing.rbs +13 -0
- data/sig/pvectl/services/unlink_disk.rbs +17 -0
- data/sig/pvectl/services/vm_lifecycle.rbs +26 -0
- data/sig/pvectl/services/wakeonlan.rbs +17 -0
- data/sig/pvectl/utils/resource_resolver.rbs +17 -0
- data/sig/pvectl/wizards/create_container.rbs +21 -0
- data/sig/pvectl/wizards/create_vm.rbs +21 -0
- data/sig/pvectl.rbs +9 -0
- metadata +675 -0
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Pvectl
|
|
4
|
+
module Services
|
|
5
|
+
# Orchestrates non-interactive volume property updates.
|
|
6
|
+
#
|
|
7
|
+
# Handles two types of changes:
|
|
8
|
+
# 1. Size changes — delegates to ResizeVolume service (irreversible)
|
|
9
|
+
# 2. Config changes (cache, discard, ssd, etc.) — rebuilds volume config string
|
|
10
|
+
#
|
|
11
|
+
# @example Resize only
|
|
12
|
+
# service = SetVolume.new(repository: vm_repo, resource_type: :vm)
|
|
13
|
+
# result = service.execute(id: 100, disk: "scsi0", params: { "size" => "+10G" }, node: "pve1")
|
|
14
|
+
#
|
|
15
|
+
# @example Config change
|
|
16
|
+
# service.execute(id: 100, disk: "scsi0", params: { "cache" => "writeback" }, node: "pve1")
|
|
17
|
+
#
|
|
18
|
+
# @example Mixed (size + config)
|
|
19
|
+
# service.execute(id: 100, disk: "scsi0",
|
|
20
|
+
# params: { "size" => "+10G", "cache" => "writeback" }, node: "pve1")
|
|
21
|
+
#
|
|
22
|
+
class SetVolume
|
|
23
|
+
# @param repository [Repositories::Vm, Repositories::Container] resource repository
|
|
24
|
+
# @param resource_type [Symbol] :vm or :container
|
|
25
|
+
def initialize(repository:, resource_type:)
|
|
26
|
+
@repository = repository
|
|
27
|
+
@resource_type = resource_type
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Executes the volume property update.
|
|
31
|
+
#
|
|
32
|
+
# Separates size param (delegated to ResizeVolume) from config params
|
|
33
|
+
# (rebuilt into the disk config string). Both can be applied in a single call.
|
|
34
|
+
#
|
|
35
|
+
# @param id [Integer] resource ID (VMID or CTID)
|
|
36
|
+
# @param disk [String] disk name (e.g., "scsi0", "rootfs")
|
|
37
|
+
# @param params [Hash] key-value pairs to set
|
|
38
|
+
# @param node [String] node name
|
|
39
|
+
# @return [Models::VolumeOperationResult] operation result
|
|
40
|
+
def execute(id:, disk:, params:, node:)
|
|
41
|
+
config = @repository.fetch_config(node, id)
|
|
42
|
+
disk_value = config[disk.to_sym]
|
|
43
|
+
|
|
44
|
+
unless disk_value
|
|
45
|
+
return build_result(id, disk, node, success: false,
|
|
46
|
+
error: "Volume '#{disk}' not found in config for resource #{id}")
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Separate size from config params (dup to avoid mutating caller's hash)
|
|
50
|
+
params = params.dup
|
|
51
|
+
size_param = params.delete("size") || params.delete(:size)
|
|
52
|
+
config_params = params
|
|
53
|
+
|
|
54
|
+
# Handle size change (resize)
|
|
55
|
+
if size_param
|
|
56
|
+
parsed_size = ResizeVolume.parse_size(size_param)
|
|
57
|
+
resize_service = ResizeVolume.new(repository: @repository)
|
|
58
|
+
resize_service.preflight(id, disk, parsed_size, node: node)
|
|
59
|
+
resize_service.perform(id, disk, parsed_size.raw, node: node)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Handle config changes (cache, discard, ssd, iothread, backup)
|
|
63
|
+
unless config_params.empty?
|
|
64
|
+
new_disk_value = rebuild_disk_config(disk_value, config_params)
|
|
65
|
+
@repository.update(id, node, { disk.to_sym => new_disk_value })
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
build_result(id, disk, node, success: true)
|
|
69
|
+
rescue ResizeVolume::VolumeNotFoundError, ResizeVolume::SizeTooSmallError, ArgumentError => e
|
|
70
|
+
build_result(id, disk, node, success: false, error: e.message)
|
|
71
|
+
rescue StandardError => e
|
|
72
|
+
build_result(id, disk, node, success: false, error: e.message)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
private
|
|
76
|
+
|
|
77
|
+
# Rebuilds a disk config string with updated properties.
|
|
78
|
+
#
|
|
79
|
+
# Config format: "storage:volume-id,key1=val1,key2=val2"
|
|
80
|
+
# Replaces existing keys and appends new ones.
|
|
81
|
+
#
|
|
82
|
+
# @param current_value [String] current disk config string
|
|
83
|
+
# @param updates [Hash] key-value pairs to update
|
|
84
|
+
# @return [String] updated config string
|
|
85
|
+
def rebuild_disk_config(current_value, updates)
|
|
86
|
+
parts = current_value.to_s.split(",")
|
|
87
|
+
base = parts.shift # "storage:volume-id"
|
|
88
|
+
|
|
89
|
+
# Parse existing key=value pairs
|
|
90
|
+
existing = {}
|
|
91
|
+
parts.each do |part|
|
|
92
|
+
key, value = part.split("=", 2)
|
|
93
|
+
existing[key] = value
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Apply updates
|
|
97
|
+
updates.each do |key, value|
|
|
98
|
+
existing[key.to_s] = value.to_s
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# Rebuild string
|
|
102
|
+
config_parts = existing.map { |k, v| "#{k}=#{v}" }
|
|
103
|
+
([base] + config_parts).join(",")
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# Builds a VolumeOperationResult with volume metadata.
|
|
107
|
+
#
|
|
108
|
+
# @param id [Integer] resource ID
|
|
109
|
+
# @param disk [String] disk name
|
|
110
|
+
# @param node [String] node name
|
|
111
|
+
# @param attrs [Hash] result attributes (:success, :error)
|
|
112
|
+
# @return [Models::VolumeOperationResult]
|
|
113
|
+
def build_result(id, disk, node, **attrs)
|
|
114
|
+
volume = Models::Volume.new(
|
|
115
|
+
name: disk,
|
|
116
|
+
resource_type: @resource_type.to_s,
|
|
117
|
+
resource_id: id,
|
|
118
|
+
node: node
|
|
119
|
+
)
|
|
120
|
+
Models::VolumeOperationResult.new(
|
|
121
|
+
operation: :set, volume: volume, **attrs
|
|
122
|
+
)
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
end
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Pvectl
|
|
4
|
+
module Services
|
|
5
|
+
# Orchestrates snapshot operations.
|
|
6
|
+
#
|
|
7
|
+
# Handles listing, creating, deleting and rolling back snapshots
|
|
8
|
+
# for VMs and containers with unified interface.
|
|
9
|
+
#
|
|
10
|
+
# @example Basic usage
|
|
11
|
+
# service = Snapshot.new(
|
|
12
|
+
# snapshot_repo: snapshot_repo,
|
|
13
|
+
# resource_resolver: resolver,
|
|
14
|
+
# task_repo: task_repo
|
|
15
|
+
# )
|
|
16
|
+
# snapshots = service.list([100, 101])
|
|
17
|
+
#
|
|
18
|
+
class Snapshot
|
|
19
|
+
DEFAULT_TIMEOUT = 60
|
|
20
|
+
|
|
21
|
+
# Creates a new Snapshot service.
|
|
22
|
+
#
|
|
23
|
+
# @param snapshot_repo [Repositories::Snapshot] Snapshot repository
|
|
24
|
+
# @param resource_resolver [Utils::ResourceResolver] Resource resolver
|
|
25
|
+
# @param task_repo [Repositories::Task] Task repository
|
|
26
|
+
# @param options [Hash] Options (timeout, async, fail_fast)
|
|
27
|
+
def initialize(snapshot_repo:, resource_resolver:, task_repo:, options: {})
|
|
28
|
+
@snapshot_repo = snapshot_repo
|
|
29
|
+
@resolver = resource_resolver
|
|
30
|
+
@task_repo = task_repo
|
|
31
|
+
@options = options
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Lists snapshots for given VMIDs.
|
|
35
|
+
#
|
|
36
|
+
# When vmids is empty, lists snapshots for all resources in the cluster.
|
|
37
|
+
#
|
|
38
|
+
# @param vmids [Array<Integer>] VM/container IDs (empty = all)
|
|
39
|
+
# @param node [String, nil] filter by node name
|
|
40
|
+
# @return [Array<Models::Snapshot>] all snapshots
|
|
41
|
+
def list(vmids, node: nil)
|
|
42
|
+
resources = resolve_resources(vmids)
|
|
43
|
+
resources = filter_by_node(resources, node)
|
|
44
|
+
return [] if resources.empty?
|
|
45
|
+
|
|
46
|
+
resources.flat_map do |r|
|
|
47
|
+
@snapshot_repo.list(r[:vmid], r[:node], r[:type])
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Describes a snapshot by name across given VMIDs.
|
|
52
|
+
#
|
|
53
|
+
# When vmids is empty, searches all resources in the cluster.
|
|
54
|
+
# Returns a SnapshotDescription with entries for each VM/CT that has the snapshot.
|
|
55
|
+
#
|
|
56
|
+
# @param vmids [Array<Integer>] VM/container IDs (empty = search all)
|
|
57
|
+
# @param name [String] snapshot name to find
|
|
58
|
+
# @param node [String, nil] filter by node name
|
|
59
|
+
# @return [Models::SnapshotDescription] description with entries per VM
|
|
60
|
+
# @raise [ResourceNotFoundError] when snapshot not found
|
|
61
|
+
def describe(vmids, name, node: nil)
|
|
62
|
+
resources = resolve_resources(vmids)
|
|
63
|
+
resources = filter_by_node(resources, node)
|
|
64
|
+
|
|
65
|
+
if resources.empty?
|
|
66
|
+
message = vmids.empty? ? "no resources found in cluster" : "resource #{vmids.first} not found"
|
|
67
|
+
raise ResourceNotFoundError, message
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
entries = build_describe_entries(resources, name)
|
|
71
|
+
|
|
72
|
+
if entries.empty?
|
|
73
|
+
message = vmids.empty? ? "snapshot '#{name}' not found in cluster" : "snapshot '#{name}' not found on VM #{vmids.join(', ')}"
|
|
74
|
+
raise ResourceNotFoundError, message
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
Models::SnapshotDescription.new(entries: entries)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Creates snapshots for given VMIDs, or all cluster resources when vmids is empty.
|
|
81
|
+
#
|
|
82
|
+
# When vmids is empty, creates snapshots for all resources in the cluster.
|
|
83
|
+
#
|
|
84
|
+
# @param vmids [Array<Integer>] VM/container IDs (empty = all)
|
|
85
|
+
# @param name [String] snapshot name
|
|
86
|
+
# @param description [String, nil] optional description
|
|
87
|
+
# @param vmstate [Boolean] save VM memory state
|
|
88
|
+
# @param node [String, nil] filter by node name
|
|
89
|
+
# @return [Array<Models::OperationResult>] results for each resource
|
|
90
|
+
def create(vmids, name:, description: nil, vmstate: false, node: nil)
|
|
91
|
+
resources = resolve_resources(vmids)
|
|
92
|
+
resources = filter_by_node(resources, node)
|
|
93
|
+
return [] if resources.empty?
|
|
94
|
+
|
|
95
|
+
execute_multi(resources, :create) do |r|
|
|
96
|
+
@snapshot_repo.create(r[:vmid], r[:node], r[:type], name: name, description: description, vmstate: vmstate)
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# Deletes snapshots from given VMIDs, or all cluster resources when vmids is empty.
|
|
101
|
+
#
|
|
102
|
+
# When vmids is empty, deletes snapshots from all resources in the cluster.
|
|
103
|
+
#
|
|
104
|
+
# @param vmids [Array<Integer>] VM/container IDs (empty = all)
|
|
105
|
+
# @param snapname [String] snapshot name to delete
|
|
106
|
+
# @param force [Boolean] force removal even if disk snapshot fails
|
|
107
|
+
# @param node [String, nil] filter by node name
|
|
108
|
+
# @return [Array<Models::OperationResult>] results for each resource
|
|
109
|
+
def delete(vmids, snapname, force: false, node: nil)
|
|
110
|
+
resources = resolve_resources(vmids)
|
|
111
|
+
resources = filter_by_node(resources, node)
|
|
112
|
+
return [] if resources.empty?
|
|
113
|
+
|
|
114
|
+
execute_multi(resources, :delete) do |r|
|
|
115
|
+
@snapshot_repo.delete(r[:vmid], r[:node], r[:type], snapname, force: force)
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# Deletes ALL snapshots from given VMIDs.
|
|
120
|
+
#
|
|
121
|
+
# When vmids is empty, deletes all snapshots from all resources in the cluster.
|
|
122
|
+
# Skips the "current" pseudo-snapshot.
|
|
123
|
+
#
|
|
124
|
+
# @param vmids [Array<Integer>] VM/container IDs (empty = all)
|
|
125
|
+
# @param node [String, nil] filter by node name
|
|
126
|
+
# @param force [Boolean] force removal even if disk snapshot fails
|
|
127
|
+
# @return [Array<Models::OperationResult>] results for each snapshot
|
|
128
|
+
def delete_all(vmids, node: nil, force: false)
|
|
129
|
+
resources = resolve_resources(vmids)
|
|
130
|
+
resources = filter_by_node(resources, node)
|
|
131
|
+
return [] if resources.empty?
|
|
132
|
+
|
|
133
|
+
results = []
|
|
134
|
+
|
|
135
|
+
resources.each do |r|
|
|
136
|
+
snapshots = @snapshot_repo.list(r[:vmid], r[:node], r[:type])
|
|
137
|
+
snapshots.reject! { |s| s.name == "current" }
|
|
138
|
+
|
|
139
|
+
snapshots.each do |snap|
|
|
140
|
+
result = execute_single(r, :delete) do
|
|
141
|
+
@snapshot_repo.delete(r[:vmid], r[:node], r[:type], snap.name, force: force)
|
|
142
|
+
end
|
|
143
|
+
results << result
|
|
144
|
+
|
|
145
|
+
break if @options[:fail_fast] && result.failed?
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
break if @options[:fail_fast] && results.last&.failed?
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
results
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
# Rolls back to a snapshot.
|
|
155
|
+
#
|
|
156
|
+
# @param vmid [Integer] VM/container ID
|
|
157
|
+
# @param snapname [String] snapshot name to rollback to
|
|
158
|
+
# @param start [Boolean] start after rollback (LXC only)
|
|
159
|
+
# @return [Models::OperationResult] result
|
|
160
|
+
def rollback(vmid, snapname, start: false)
|
|
161
|
+
resource = @resolver.resolve(vmid)
|
|
162
|
+
|
|
163
|
+
if resource.nil?
|
|
164
|
+
return Models::OperationResult.new(
|
|
165
|
+
resource: { vmid: vmid },
|
|
166
|
+
operation: :rollback,
|
|
167
|
+
success: false,
|
|
168
|
+
error: "Resource #{vmid} not found"
|
|
169
|
+
)
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
execute_single(resource, :rollback) do
|
|
173
|
+
@snapshot_repo.rollback(resource[:vmid], resource[:node], resource[:type], snapname, start: start)
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
private
|
|
178
|
+
|
|
179
|
+
# Resolves resources from VMIDs or returns all cluster resources.
|
|
180
|
+
#
|
|
181
|
+
# @param vmids [Array<Integer>] VM/container IDs (empty = resolve all)
|
|
182
|
+
# @return [Array<Hash>] resolved resources
|
|
183
|
+
def resolve_resources(vmids)
|
|
184
|
+
vmids.empty? ? @resolver.resolve_all : @resolver.resolve_multiple(vmids)
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
# Filters resources by node name.
|
|
188
|
+
#
|
|
189
|
+
# @param resources [Array<Hash>] resolved resources
|
|
190
|
+
# @param node [String, nil] node name to filter by (nil = no filter)
|
|
191
|
+
# @return [Array<Hash>] filtered resources
|
|
192
|
+
def filter_by_node(resources, node)
|
|
193
|
+
return resources unless node
|
|
194
|
+
|
|
195
|
+
resources.select { |r| r[:node] == node }
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
def build_describe_entries(resources, name)
|
|
199
|
+
entries = []
|
|
200
|
+
|
|
201
|
+
resources.each do |r|
|
|
202
|
+
siblings = @snapshot_repo.list(r[:vmid], r[:node], r[:type])
|
|
203
|
+
target = siblings.find { |s| s.name == name }
|
|
204
|
+
next unless target
|
|
205
|
+
|
|
206
|
+
entries << Models::SnapshotDescription::Entry.new(
|
|
207
|
+
snapshot: target,
|
|
208
|
+
siblings: siblings
|
|
209
|
+
)
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
entries
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
def execute_multi(resources, operation)
|
|
216
|
+
results = []
|
|
217
|
+
|
|
218
|
+
resources.each do |r|
|
|
219
|
+
result = execute_single(r, operation) { yield(r) }
|
|
220
|
+
results << result
|
|
221
|
+
|
|
222
|
+
break if @options[:fail_fast] && result.failed?
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
results
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
def execute_single(resource, operation)
|
|
229
|
+
upid = yield
|
|
230
|
+
|
|
231
|
+
if @options[:async]
|
|
232
|
+
Models::OperationResult.new(
|
|
233
|
+
resource: resource,
|
|
234
|
+
operation: operation,
|
|
235
|
+
task_upid: upid,
|
|
236
|
+
success: :pending
|
|
237
|
+
)
|
|
238
|
+
else
|
|
239
|
+
task = @task_repo.wait(upid, timeout: timeout)
|
|
240
|
+
Models::OperationResult.new(
|
|
241
|
+
resource: resource,
|
|
242
|
+
operation: operation,
|
|
243
|
+
task: task,
|
|
244
|
+
success: task.successful?
|
|
245
|
+
)
|
|
246
|
+
end
|
|
247
|
+
rescue StandardError => e
|
|
248
|
+
Models::OperationResult.new(
|
|
249
|
+
resource: resource,
|
|
250
|
+
operation: operation,
|
|
251
|
+
success: false,
|
|
252
|
+
error: e.message
|
|
253
|
+
)
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
def timeout
|
|
257
|
+
@options[:timeout] || DEFAULT_TIMEOUT
|
|
258
|
+
end
|
|
259
|
+
end
|
|
260
|
+
end
|
|
261
|
+
end
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Pvectl
|
|
4
|
+
module Services
|
|
5
|
+
# Service for listing tasks across one or all cluster nodes.
|
|
6
|
+
#
|
|
7
|
+
# Encapsulates multi-node iteration, merge, sort, and limit logic.
|
|
8
|
+
# Used by both Get::Handlers::Tasks and Logs::Handlers::TaskLogs.
|
|
9
|
+
#
|
|
10
|
+
# @example List all tasks cluster-wide
|
|
11
|
+
# service = TaskListing.new(task_list_repository: repo, node_repository: node_repo)
|
|
12
|
+
# tasks = service.list(limit: 20)
|
|
13
|
+
#
|
|
14
|
+
# @example List tasks on a specific node
|
|
15
|
+
# tasks = service.list(node: "pve1", type_filter: "vzdump")
|
|
16
|
+
#
|
|
17
|
+
class TaskListing
|
|
18
|
+
# Creates a new TaskListing service.
|
|
19
|
+
#
|
|
20
|
+
# @param task_list_repository [Repositories::TaskList] task list repository
|
|
21
|
+
# @param node_repository [Repositories::Node] node repository for cluster discovery
|
|
22
|
+
def initialize(task_list_repository:, node_repository:)
|
|
23
|
+
@task_list_repository = task_list_repository
|
|
24
|
+
@node_repository = node_repository
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Lists tasks, optionally filtered.
|
|
28
|
+
#
|
|
29
|
+
# When node is nil, iterates all cluster nodes and merges results
|
|
30
|
+
# sorted by starttime descending.
|
|
31
|
+
#
|
|
32
|
+
# @param node [String, nil] specific node or nil for all nodes
|
|
33
|
+
# @param vmid [Integer, nil] filter by VMID
|
|
34
|
+
# @param limit [Integer] max entries (default 50)
|
|
35
|
+
# @param since [String, nil] start time filter
|
|
36
|
+
# @param until_time [String, nil] end time filter
|
|
37
|
+
# @param type_filter [String, nil] task type filter
|
|
38
|
+
# @param status_filter [String, nil] status filter
|
|
39
|
+
# @return [Array<Models::TaskEntry>] task entries
|
|
40
|
+
def list(node: nil, vmid: nil, limit: 50, since: nil, until_time: nil,
|
|
41
|
+
type_filter: nil, status_filter: nil)
|
|
42
|
+
if node
|
|
43
|
+
@task_list_repository.list(
|
|
44
|
+
node: node, vmid: vmid, limit: limit, since: since,
|
|
45
|
+
until_time: until_time, type_filter: type_filter,
|
|
46
|
+
status_filter: status_filter
|
|
47
|
+
)
|
|
48
|
+
else
|
|
49
|
+
list_all_nodes(
|
|
50
|
+
vmid: vmid, limit: limit, since: since,
|
|
51
|
+
until_time: until_time, type_filter: type_filter,
|
|
52
|
+
status_filter: status_filter
|
|
53
|
+
)
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
private
|
|
58
|
+
|
|
59
|
+
# Iterates all cluster nodes and merges results.
|
|
60
|
+
#
|
|
61
|
+
# @return [Array<Models::TaskEntry>] merged and sorted entries
|
|
62
|
+
def list_all_nodes(vmid:, limit:, since:, until_time:, type_filter:, status_filter:)
|
|
63
|
+
nodes = @node_repository.list.map(&:name)
|
|
64
|
+
entries = nodes.flat_map do |node_name|
|
|
65
|
+
@task_list_repository.list(
|
|
66
|
+
node: node_name, vmid: vmid, limit: limit, since: since,
|
|
67
|
+
until_time: until_time, type_filter: type_filter,
|
|
68
|
+
status_filter: status_filter
|
|
69
|
+
)
|
|
70
|
+
end
|
|
71
|
+
entries.sort_by { |e| -(e.starttime || 0) }.first(limit)
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Pvectl
|
|
4
|
+
module Services
|
|
5
|
+
# Orchestrates disk unlinking from a VM configuration.
|
|
6
|
+
#
|
|
7
|
+
# Delegates to the VM repository to issue the unlink PUT request and
|
|
8
|
+
# wraps the outcome in a {Models::VmOperationResult}. The underlying
|
|
9
|
+
# Proxmox endpoint is synchronous and returns no UPID, so the result
|
|
10
|
+
# captures success/failure synchronously.
|
|
11
|
+
#
|
|
12
|
+
# @example Soft unlink (keeps volume as unused[n])
|
|
13
|
+
# service = UnlinkDisk.new(repository: vm_repo)
|
|
14
|
+
# result = service.execute(vmid: 100, node: "pve1", disk_ids: "scsi1")
|
|
15
|
+
#
|
|
16
|
+
# @example Hard delete via force
|
|
17
|
+
# service = UnlinkDisk.new(repository: vm_repo)
|
|
18
|
+
# result = service.execute(vmid: 100, node: "pve1", disk_ids: %w[scsi1 scsi2], force: true)
|
|
19
|
+
#
|
|
20
|
+
class UnlinkDisk
|
|
21
|
+
# Creates a new UnlinkDisk service.
|
|
22
|
+
#
|
|
23
|
+
# @param repository [Repositories::Vm] VM repository
|
|
24
|
+
def initialize(repository:)
|
|
25
|
+
@repository = repository
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Unlinks one or more disks from the VM configuration.
|
|
29
|
+
#
|
|
30
|
+
# @param vmid [Integer] VM identifier
|
|
31
|
+
# @param node [String] node name
|
|
32
|
+
# @param disk_ids [Array<String>, String] disk identifiers
|
|
33
|
+
# @param force [Boolean] physically remove the underlying volume(s)
|
|
34
|
+
# @return [Models::VmOperationResult] operation result
|
|
35
|
+
def execute(vmid:, node:, disk_ids:, force: false)
|
|
36
|
+
@repository.unlink_disks(node, vmid, disk_ids, force: force)
|
|
37
|
+
build_result(vmid, node, disk_ids, force, success: true)
|
|
38
|
+
rescue StandardError => e
|
|
39
|
+
build_result(vmid, node, disk_ids, force, success: false, error: e.message)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
private
|
|
43
|
+
|
|
44
|
+
# Builds a VmOperationResult.
|
|
45
|
+
#
|
|
46
|
+
# @param vmid [Integer] VM identifier
|
|
47
|
+
# @param node [String] node name
|
|
48
|
+
# @param disk_ids [Array<String>, String] disk identifiers
|
|
49
|
+
# @param force [Boolean] force flag
|
|
50
|
+
# @param attrs [Hash] additional result attributes (:success, :error)
|
|
51
|
+
# @return [Models::VmOperationResult]
|
|
52
|
+
def build_result(vmid, node, disk_ids, force, **attrs)
|
|
53
|
+
vm = fetch_vm(vmid) || Models::Vm.new(vmid: vmid, node: node)
|
|
54
|
+
Models::VmOperationResult.new(
|
|
55
|
+
operation: :unlink_disk,
|
|
56
|
+
vm: vm,
|
|
57
|
+
resource: {
|
|
58
|
+
vmid: vmid,
|
|
59
|
+
node: node,
|
|
60
|
+
disk_ids: format_disk_ids(disk_ids),
|
|
61
|
+
force: force
|
|
62
|
+
},
|
|
63
|
+
**attrs
|
|
64
|
+
)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Fetches the VM model for the result, swallowing any lookup errors.
|
|
68
|
+
#
|
|
69
|
+
# @param vmid [Integer] VM identifier
|
|
70
|
+
# @return [Models::Vm, nil] VM model or nil if unavailable
|
|
71
|
+
def fetch_vm(vmid)
|
|
72
|
+
@repository.get(vmid)
|
|
73
|
+
rescue StandardError
|
|
74
|
+
nil
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Formats disk_ids into a stable comma-separated representation.
|
|
78
|
+
#
|
|
79
|
+
# @param disk_ids [Array<String>, String] disk identifiers
|
|
80
|
+
# @return [String] comma-separated disk ID list
|
|
81
|
+
def format_disk_ids(disk_ids)
|
|
82
|
+
Array(disk_ids).flat_map { |id| id.to_s.split(",") }.map(&:strip).reject(&:empty?).join(",")
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Pvectl
|
|
4
|
+
module Services
|
|
5
|
+
# Orchestrates VM lifecycle operations.
|
|
6
|
+
#
|
|
7
|
+
# Handles execution of start/stop/shutdown/restart/reset/suspend/resume
|
|
8
|
+
# operations with sync/async modes, error handling, and result collection.
|
|
9
|
+
#
|
|
10
|
+
# @example Basic usage
|
|
11
|
+
# service = VmLifecycle.new(vm_repo, task_repo)
|
|
12
|
+
# results = service.execute(:start, [vm1, vm2])
|
|
13
|
+
# results.each { |r| puts "#{r.vm.vmid}: #{r.status_text}" }
|
|
14
|
+
#
|
|
15
|
+
class VmLifecycle
|
|
16
|
+
SYNC_OPERATIONS = %i[start stop reset resume].freeze
|
|
17
|
+
ASYNC_OPERATIONS = %i[shutdown restart suspend].freeze
|
|
18
|
+
ALL_OPERATIONS = (SYNC_OPERATIONS + ASYNC_OPERATIONS).freeze
|
|
19
|
+
|
|
20
|
+
DEFAULT_TIMEOUT = 60
|
|
21
|
+
|
|
22
|
+
# Creates a new VmLifecycle service.
|
|
23
|
+
#
|
|
24
|
+
# @param vm_repository [Repositories::Vm] VM repository
|
|
25
|
+
# @param task_repository [Repositories::Task] Task repository
|
|
26
|
+
# @param options [Hash] Options (timeout, async, wait, fail_fast)
|
|
27
|
+
def initialize(vm_repository, task_repository, options = {})
|
|
28
|
+
@vm_repository = vm_repository
|
|
29
|
+
@task_repository = task_repository
|
|
30
|
+
@options = options
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Executes a lifecycle operation on a list of VMs.
|
|
34
|
+
#
|
|
35
|
+
# @param operation [Symbol] Operation to execute
|
|
36
|
+
# @param vms [Array<Models::Vm>] VMs to operate on
|
|
37
|
+
# @return [Array<Models::OperationResult>] Results for each VM
|
|
38
|
+
def execute(operation, vms)
|
|
39
|
+
validate_operation!(operation)
|
|
40
|
+
|
|
41
|
+
results = []
|
|
42
|
+
vms.each do |vm|
|
|
43
|
+
result = execute_single(operation, vm)
|
|
44
|
+
results << result
|
|
45
|
+
|
|
46
|
+
break if @options[:fail_fast] && result.failed?
|
|
47
|
+
end
|
|
48
|
+
results
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
private
|
|
52
|
+
|
|
53
|
+
# Executes operation on a single VM.
|
|
54
|
+
#
|
|
55
|
+
# @param operation [Symbol] Operation
|
|
56
|
+
# @param vm [Models::Vm] VM
|
|
57
|
+
# @return [Models::OperationResult] Result
|
|
58
|
+
def execute_single(operation, vm)
|
|
59
|
+
task_upid = call_api(operation, vm)
|
|
60
|
+
|
|
61
|
+
if sync_mode?(operation)
|
|
62
|
+
task = @task_repository.wait(task_upid, timeout: timeout)
|
|
63
|
+
Models::VmOperationResult.new(
|
|
64
|
+
vm: vm,
|
|
65
|
+
operation: operation,
|
|
66
|
+
task: task,
|
|
67
|
+
success: task.successful?
|
|
68
|
+
)
|
|
69
|
+
else
|
|
70
|
+
Models::VmOperationResult.new(
|
|
71
|
+
vm: vm,
|
|
72
|
+
operation: operation,
|
|
73
|
+
task_upid: task_upid,
|
|
74
|
+
success: :pending
|
|
75
|
+
)
|
|
76
|
+
end
|
|
77
|
+
rescue StandardError => e
|
|
78
|
+
Models::VmOperationResult.new(
|
|
79
|
+
vm: vm,
|
|
80
|
+
operation: operation,
|
|
81
|
+
success: false,
|
|
82
|
+
error: e.message
|
|
83
|
+
)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Calls the appropriate API method.
|
|
87
|
+
#
|
|
88
|
+
# @param operation [Symbol] Operation
|
|
89
|
+
# @param vm [Models::Vm] VM
|
|
90
|
+
# @return [String] Task UPID
|
|
91
|
+
def call_api(operation, vm)
|
|
92
|
+
@vm_repository.send(operation, vm.vmid, vm.node)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# Determines if operation should run in sync mode.
|
|
96
|
+
#
|
|
97
|
+
# @param operation [Symbol] Operation
|
|
98
|
+
# @return [Boolean] true if sync mode
|
|
99
|
+
def sync_mode?(operation)
|
|
100
|
+
return false if @options[:async]
|
|
101
|
+
return true if @options[:wait]
|
|
102
|
+
|
|
103
|
+
SYNC_OPERATIONS.include?(operation)
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# Returns configured timeout.
|
|
107
|
+
#
|
|
108
|
+
# @return [Integer] Timeout in seconds
|
|
109
|
+
def timeout
|
|
110
|
+
@options[:timeout] || DEFAULT_TIMEOUT
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# Validates operation is supported.
|
|
114
|
+
#
|
|
115
|
+
# @param operation [Symbol] Operation
|
|
116
|
+
# @raise [ArgumentError] if operation is not supported
|
|
117
|
+
def validate_operation!(operation)
|
|
118
|
+
return if ALL_OPERATIONS.include?(operation)
|
|
119
|
+
|
|
120
|
+
raise ArgumentError, "Unknown operation: #{operation}. Valid: #{ALL_OPERATIONS.join(', ')}"
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|