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,253 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Pvectl
|
|
4
|
+
module Services
|
|
5
|
+
# Orchestrates volume resize operations for VMs and containers.
|
|
6
|
+
#
|
|
7
|
+
# Handles size parsing, preflight validation (current size comparison),
|
|
8
|
+
# and execution via the repository interface. Works polymorphically with
|
|
9
|
+
# both VM and Container repositories (they share #fetch_config and #resize).
|
|
10
|
+
#
|
|
11
|
+
# Two-phase operation:
|
|
12
|
+
# 1. {#preflight} — validates volume exists, computes new size, checks constraints
|
|
13
|
+
# 2. {#perform} — executes the actual resize via repository
|
|
14
|
+
#
|
|
15
|
+
# @example Basic resize flow
|
|
16
|
+
# parsed = ResizeVolume.parse_size("+10G")
|
|
17
|
+
# service = ResizeVolume.new(repository: vm_repo)
|
|
18
|
+
# info = service.preflight(100, "scsi0", parsed, node: "pve1")
|
|
19
|
+
# result = service.perform(100, "scsi0", parsed.raw, node: "pve1")
|
|
20
|
+
#
|
|
21
|
+
class ResizeVolume
|
|
22
|
+
# Raised when the specified volume key is not found in resource config.
|
|
23
|
+
class VolumeNotFoundError < StandardError; end
|
|
24
|
+
|
|
25
|
+
# Raised when absolute size is not larger than current disk size.
|
|
26
|
+
class SizeTooSmallError < StandardError; end
|
|
27
|
+
|
|
28
|
+
# Parsed size representation returned by {.parse_size}.
|
|
29
|
+
#
|
|
30
|
+
# @!attribute [r] relative
|
|
31
|
+
# @return [Boolean] true if size is relative (prefixed with +)
|
|
32
|
+
# @!attribute [r] value
|
|
33
|
+
# @return [String] clean size value without + prefix (e.g., "10G")
|
|
34
|
+
# @!attribute [r] raw
|
|
35
|
+
# @return [String] original size string for API (e.g., "+10G")
|
|
36
|
+
ParsedSize = Struct.new(:relative, :value, :raw, keyword_init: true) do
|
|
37
|
+
# Whether this is a relative size (increment).
|
|
38
|
+
#
|
|
39
|
+
# @return [Boolean]
|
|
40
|
+
def relative?
|
|
41
|
+
relative
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Size regex: optional +, digits with optional decimal, optional T/G/M/K suffix.
|
|
46
|
+
SIZE_PATTERN = /\A(\+)?(\d+(?:\.\d+)?)([TGMK])?\z/i
|
|
47
|
+
|
|
48
|
+
# Multipliers for converting units to megabytes (MB as base unit).
|
|
49
|
+
UNIT_MULTIPLIERS = {
|
|
50
|
+
"T" => 1024 * 1024,
|
|
51
|
+
"G" => 1024,
|
|
52
|
+
"M" => 1,
|
|
53
|
+
"K" => 1.0 / 1024
|
|
54
|
+
}.freeze
|
|
55
|
+
|
|
56
|
+
# Parses a size string into a {ParsedSize}.
|
|
57
|
+
#
|
|
58
|
+
# Accepts formats like "10G", "+10G", "1.5T", "512M", "+100".
|
|
59
|
+
# Suffix is uppercased. No suffix means raw number.
|
|
60
|
+
#
|
|
61
|
+
# @param size_str [String] size string to parse
|
|
62
|
+
# @return [ParsedSize] parsed size components
|
|
63
|
+
# @raise [ArgumentError] if format is invalid, empty, or negative
|
|
64
|
+
#
|
|
65
|
+
# @example Relative size
|
|
66
|
+
# ResizeVolume.parse_size("+10G")
|
|
67
|
+
# #=> ParsedSize(relative: true, value: "10G", raw: "+10G")
|
|
68
|
+
#
|
|
69
|
+
# @example Absolute size
|
|
70
|
+
# ResizeVolume.parse_size("50G")
|
|
71
|
+
# #=> ParsedSize(relative: false, value: "50G", raw: "50G")
|
|
72
|
+
def self.parse_size(size_str)
|
|
73
|
+
raise ArgumentError, "Size cannot be empty" if size_str.nil? || size_str.strip.empty?
|
|
74
|
+
|
|
75
|
+
match = SIZE_PATTERN.match(size_str.strip)
|
|
76
|
+
raise ArgumentError, "Invalid size format: #{size_str}" unless match
|
|
77
|
+
|
|
78
|
+
plus, number, suffix = match.captures
|
|
79
|
+
suffix = suffix&.upcase
|
|
80
|
+
|
|
81
|
+
raise ArgumentError, "Size must be positive: #{size_str}" if number.to_f <= 0
|
|
82
|
+
|
|
83
|
+
clean_value = "#{number}#{suffix}"
|
|
84
|
+
raw_value = "#{plus}#{number}#{suffix}"
|
|
85
|
+
|
|
86
|
+
ParsedSize.new(
|
|
87
|
+
relative: !plus.nil?,
|
|
88
|
+
value: clean_value,
|
|
89
|
+
raw: raw_value
|
|
90
|
+
)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Creates a new ResizeVolume service.
|
|
94
|
+
#
|
|
95
|
+
# @param repository [Repositories::Vm, Repositories::Container] resource repository
|
|
96
|
+
def initialize(repository:)
|
|
97
|
+
@repository = repository
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# Validates the resize operation and returns size information.
|
|
101
|
+
#
|
|
102
|
+
# Checks that the disk exists in the resource config, extracts current
|
|
103
|
+
# size, calculates new size, and validates constraints (absolute must
|
|
104
|
+
# be larger than current).
|
|
105
|
+
#
|
|
106
|
+
# @param id [Integer] resource identifier (VMID or CTID)
|
|
107
|
+
# @param disk [String] disk key (e.g., "scsi0", "rootfs", "mp0")
|
|
108
|
+
# @param parsed_size [ParsedSize] parsed size from {.parse_size}
|
|
109
|
+
# @param node [String] node name
|
|
110
|
+
# @return [Hash] preflight info with :disk, :current_size, :new_size
|
|
111
|
+
# @raise [VolumeNotFoundError] if volume not in config or size not extractable
|
|
112
|
+
# @raise [SizeTooSmallError] if absolute size <= current size
|
|
113
|
+
def preflight(id, disk, parsed_size, node:)
|
|
114
|
+
config = @repository.fetch_config(node, id)
|
|
115
|
+
current_size = extract_disk_size(config, disk, id)
|
|
116
|
+
new_size = calculate_new_size(current_size, parsed_size)
|
|
117
|
+
validate_new_size!(current_size, new_size, parsed_size)
|
|
118
|
+
|
|
119
|
+
{ disk: disk, current_size: current_size, new_size: new_size }
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# Executes the disk resize via repository.
|
|
123
|
+
#
|
|
124
|
+
# @param id [Integer] resource identifier (VMID or CTID)
|
|
125
|
+
# @param disk [String] disk key
|
|
126
|
+
# @param raw_size [String] size string for API (e.g., "+10G", "50G")
|
|
127
|
+
# @param node [String] node name
|
|
128
|
+
# @return [Models::OperationResult] operation result
|
|
129
|
+
def perform(id, disk, raw_size, node:)
|
|
130
|
+
@repository.resize(id, node, disk: disk, size: raw_size)
|
|
131
|
+
Models::OperationResult.new(
|
|
132
|
+
operation: :resize_volume,
|
|
133
|
+
success: true,
|
|
134
|
+
resource: { id: id, node: node, disk: disk, size: raw_size }
|
|
135
|
+
)
|
|
136
|
+
rescue StandardError => e
|
|
137
|
+
Models::OperationResult.new(
|
|
138
|
+
operation: :resize_volume,
|
|
139
|
+
success: false,
|
|
140
|
+
error: e.message,
|
|
141
|
+
resource: { id: id, node: node, disk: disk }
|
|
142
|
+
)
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
private
|
|
146
|
+
|
|
147
|
+
# Extracts the disk size from a config value string.
|
|
148
|
+
#
|
|
149
|
+
# Config values have formats like:
|
|
150
|
+
# - VM: "local-lvm:vm-100-disk-0,size=32G"
|
|
151
|
+
# - Container: "local-lvm:subvol-100-disk-0,size=8G"
|
|
152
|
+
# - Rootfs: "local-lvm:subvol-100-disk-0,size=8G"
|
|
153
|
+
#
|
|
154
|
+
# @param config [Hash] resource configuration
|
|
155
|
+
# @param disk [String] disk key to look up
|
|
156
|
+
# @param id [Integer] resource ID (for error messages)
|
|
157
|
+
# @return [String] current size (e.g., "32G")
|
|
158
|
+
# @raise [VolumeNotFoundError] if volume not found or size not extractable
|
|
159
|
+
def extract_disk_size(config, disk, id)
|
|
160
|
+
disk_value = config[disk.to_sym]
|
|
161
|
+
raise VolumeNotFoundError, "Volume '#{disk}' not found in config for resource #{id}" unless disk_value
|
|
162
|
+
|
|
163
|
+
size_match = disk_value.to_s.match(/size=(\d+(?:\.\d+)?[TGMK]?)/i)
|
|
164
|
+
raise VolumeNotFoundError, "Cannot determine size for volume '#{disk}' on resource #{id}" unless size_match
|
|
165
|
+
|
|
166
|
+
size_match[1]
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
# Calculates the new size after resize.
|
|
170
|
+
#
|
|
171
|
+
# For relative sizes, adds the increment to current size (converting
|
|
172
|
+
# units as needed). For absolute sizes, returns the parsed value directly.
|
|
173
|
+
#
|
|
174
|
+
# @param current_size [String] current size (e.g., "32G")
|
|
175
|
+
# @param parsed_size [ParsedSize] parsed size
|
|
176
|
+
# @return [String] new size (e.g., "42G")
|
|
177
|
+
def calculate_new_size(current_size, parsed_size)
|
|
178
|
+
if parsed_size.relative?
|
|
179
|
+
current_num, current_suffix = parse_size_components(current_size)
|
|
180
|
+
add_num, add_suffix = parse_size_components(parsed_size.value)
|
|
181
|
+
|
|
182
|
+
converted_add = convert_to_unit(add_num, add_suffix, current_suffix)
|
|
183
|
+
new_num = current_num + converted_add
|
|
184
|
+
|
|
185
|
+
format_size(new_num, current_suffix)
|
|
186
|
+
else
|
|
187
|
+
parsed_size.value
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
# Validates that the new size is larger than current for absolute resizes.
|
|
192
|
+
#
|
|
193
|
+
# Relative sizes always pass (Proxmox enforces positive increments).
|
|
194
|
+
# Absolute sizes must be strictly larger than current.
|
|
195
|
+
#
|
|
196
|
+
# @param current_size [String] current size
|
|
197
|
+
# @param new_size [String] new size
|
|
198
|
+
# @param parsed_size [ParsedSize] parsed size (to check if relative)
|
|
199
|
+
# @raise [SizeTooSmallError] if absolute size <= current
|
|
200
|
+
def validate_new_size!(current_size, new_size, parsed_size)
|
|
201
|
+
return if parsed_size.relative?
|
|
202
|
+
|
|
203
|
+
if size_to_bytes(new_size) <= size_to_bytes(current_size)
|
|
204
|
+
raise SizeTooSmallError,
|
|
205
|
+
"New size #{new_size} must be larger than current size #{current_size}"
|
|
206
|
+
end
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
# Parses size string into numeric value and unit suffix.
|
|
210
|
+
#
|
|
211
|
+
# @param size [String] size string (e.g., "32G", "1.5T", "100")
|
|
212
|
+
# @return [Array(Float, String)] number and suffix (defaults to "G")
|
|
213
|
+
def parse_size_components(size)
|
|
214
|
+
match = size.to_s.match(/\A(\d+(?:\.\d+)?)([TGMK])?\z/i)
|
|
215
|
+
return [0.0, "G"] unless match
|
|
216
|
+
|
|
217
|
+
[match[1].to_f, (match[2] || "G").upcase]
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
# Converts a value from one unit to another using MB as base.
|
|
221
|
+
#
|
|
222
|
+
# @param value [Float] numeric value
|
|
223
|
+
# @param from_unit [String] source unit (T, G, M, K)
|
|
224
|
+
# @param to_unit [String] target unit (T, G, M, K)
|
|
225
|
+
# @return [Float] converted value
|
|
226
|
+
def convert_to_unit(value, from_unit, to_unit)
|
|
227
|
+
mb_value = value * UNIT_MULTIPLIERS.fetch(from_unit, 1024)
|
|
228
|
+
mb_value / UNIT_MULTIPLIERS.fetch(to_unit, 1024)
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
# Formats a numeric value and suffix into a size string.
|
|
232
|
+
#
|
|
233
|
+
# Produces integer format when possible (e.g., "42G" not "42.0G").
|
|
234
|
+
#
|
|
235
|
+
# @param value [Float] numeric value
|
|
236
|
+
# @param suffix [String] unit suffix
|
|
237
|
+
# @return [String] formatted size (e.g., "42G")
|
|
238
|
+
def format_size(value, suffix)
|
|
239
|
+
formatted = value == value.to_i ? value.to_i.to_s : format("%.1f", value)
|
|
240
|
+
"#{formatted}#{suffix}"
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
# Converts a size string to bytes for comparison.
|
|
244
|
+
#
|
|
245
|
+
# @param size [String] size string (e.g., "32G")
|
|
246
|
+
# @return [Float] size in bytes
|
|
247
|
+
def size_to_bytes(size)
|
|
248
|
+
num, suffix = parse_size_components(size)
|
|
249
|
+
num * UNIT_MULTIPLIERS.fetch(suffix, 1024) * 1024 * 1024 # MB to bytes
|
|
250
|
+
end
|
|
251
|
+
end
|
|
252
|
+
end
|
|
253
|
+
end
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Pvectl
|
|
4
|
+
module Services
|
|
5
|
+
# Orchestrates deletion of VMs and containers.
|
|
6
|
+
#
|
|
7
|
+
# Handles validation, force-stop of running resources, and async/sync modes.
|
|
8
|
+
#
|
|
9
|
+
# @example Delete stopped VMs
|
|
10
|
+
# service = ResourceDelete.new(vm_repository: vm_repo, container_repository: ct_repo, task_repository: task_repo)
|
|
11
|
+
# results = service.execute(:vm, [vm1, vm2])
|
|
12
|
+
#
|
|
13
|
+
# @example Delete with force (stops running VMs first)
|
|
14
|
+
# service = ResourceDelete.new(..., options: { force: true })
|
|
15
|
+
# results = service.execute(:vm, [running_vm])
|
|
16
|
+
#
|
|
17
|
+
class ResourceDelete
|
|
18
|
+
DEFAULT_TIMEOUT = 60
|
|
19
|
+
|
|
20
|
+
# Creates a new ResourceDelete service.
|
|
21
|
+
#
|
|
22
|
+
# @param vm_repository [Repositories::Vm] VM repository
|
|
23
|
+
# @param container_repository [Repositories::Container] Container repository
|
|
24
|
+
# @param task_repository [Repositories::Task] Task repository
|
|
25
|
+
# @param options [Hash] Options (force, keep_disks, purge, timeout, async, fail_fast)
|
|
26
|
+
def initialize(vm_repository:, container_repository:, task_repository:, options: {})
|
|
27
|
+
@vm_repository = vm_repository
|
|
28
|
+
@container_repository = container_repository
|
|
29
|
+
@task_repository = task_repository
|
|
30
|
+
@options = options
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Executes delete operation on resources.
|
|
34
|
+
#
|
|
35
|
+
# @param resource_type [Symbol] :vm or :container
|
|
36
|
+
# @param resources [Array<Models::Vm, Models::Container>] Resources to delete
|
|
37
|
+
# @return [Array<Models::OperationResult>] Results for each resource
|
|
38
|
+
def execute(resource_type, resources)
|
|
39
|
+
@resource_type = resource_type
|
|
40
|
+
results = []
|
|
41
|
+
|
|
42
|
+
resources.each do |resource|
|
|
43
|
+
result = delete_single(resource)
|
|
44
|
+
results << result
|
|
45
|
+
|
|
46
|
+
break if @options[:fail_fast] && result.failed?
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
results
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
private
|
|
53
|
+
|
|
54
|
+
# Deletes a single resource.
|
|
55
|
+
#
|
|
56
|
+
# @param resource [Models::Vm, Models::Container] Resource to delete
|
|
57
|
+
# @return [Models::OperationResult] Result
|
|
58
|
+
def delete_single(resource)
|
|
59
|
+
# Check if running
|
|
60
|
+
if resource.status == "running"
|
|
61
|
+
return running_error(resource) unless @options[:force]
|
|
62
|
+
|
|
63
|
+
stop_result = stop_resource(resource)
|
|
64
|
+
return stop_result if stop_result.failed?
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Perform delete
|
|
68
|
+
perform_delete(resource)
|
|
69
|
+
rescue StandardError => e
|
|
70
|
+
build_result(resource,
|
|
71
|
+
operation: :delete,
|
|
72
|
+
success: false,
|
|
73
|
+
error: e.message
|
|
74
|
+
)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Returns error for running resource.
|
|
78
|
+
#
|
|
79
|
+
# @param resource [Models::Vm, Models::Container] Resource
|
|
80
|
+
# @return [Models::OperationResult] Failed result
|
|
81
|
+
def running_error(resource)
|
|
82
|
+
type_name = @resource_type == :vm ? "VM" : "Container"
|
|
83
|
+
build_result(resource,
|
|
84
|
+
operation: :delete,
|
|
85
|
+
success: false,
|
|
86
|
+
error: "#{type_name} #{resource.vmid} is running. Stop it first or use --force"
|
|
87
|
+
)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# Stops a running resource.
|
|
91
|
+
#
|
|
92
|
+
# @param resource [Models::Vm, Models::Container] Resource
|
|
93
|
+
# @return [Models::OperationResult] Result
|
|
94
|
+
def stop_resource(resource)
|
|
95
|
+
repo = repository_for(@resource_type)
|
|
96
|
+
upid = repo.stop(resource.vmid, resource.node)
|
|
97
|
+
task = @task_repository.wait(upid, timeout: timeout)
|
|
98
|
+
|
|
99
|
+
if task.successful?
|
|
100
|
+
build_result(resource, operation: :stop, task: task, success: true)
|
|
101
|
+
else
|
|
102
|
+
build_result(resource,
|
|
103
|
+
operation: :delete,
|
|
104
|
+
success: false,
|
|
105
|
+
error: "Failed to stop: #{task.exitstatus}"
|
|
106
|
+
)
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# Performs the actual delete operation.
|
|
111
|
+
#
|
|
112
|
+
# @param resource [Models::Vm, Models::Container] Resource
|
|
113
|
+
# @return [Models::OperationResult] Result
|
|
114
|
+
def perform_delete(resource)
|
|
115
|
+
repo = repository_for(@resource_type)
|
|
116
|
+
delete_opts = {
|
|
117
|
+
destroy_disks: !@options[:keep_disks],
|
|
118
|
+
purge: @options[:purge] || false,
|
|
119
|
+
force: false
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
upid = repo.delete(resource.vmid, resource.node, **delete_opts)
|
|
123
|
+
|
|
124
|
+
if @options[:async]
|
|
125
|
+
build_result(resource,
|
|
126
|
+
operation: :delete,
|
|
127
|
+
task_upid: upid,
|
|
128
|
+
success: :pending
|
|
129
|
+
)
|
|
130
|
+
else
|
|
131
|
+
task = @task_repository.wait(upid, timeout: timeout)
|
|
132
|
+
build_result(resource,
|
|
133
|
+
operation: :delete,
|
|
134
|
+
task: task,
|
|
135
|
+
success: task.successful?
|
|
136
|
+
)
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# Builds typed OperationResult for the current resource type.
|
|
141
|
+
#
|
|
142
|
+
# @param resource [Models::Vm, Models::Container] Resource
|
|
143
|
+
# @param attrs [Hash] Result attributes
|
|
144
|
+
# @return [Models::VmOperationResult, Models::ContainerOperationResult] Typed result
|
|
145
|
+
def build_result(resource, **attrs)
|
|
146
|
+
if @resource_type == :vm
|
|
147
|
+
Models::VmOperationResult.new(vm: resource, **attrs)
|
|
148
|
+
else
|
|
149
|
+
Models::ContainerOperationResult.new(container: resource, **attrs)
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
# Returns the appropriate repository for resource type.
|
|
154
|
+
#
|
|
155
|
+
# @param type [Symbol] :vm or :container
|
|
156
|
+
# @return [Repositories::Vm, Repositories::Container] Repository
|
|
157
|
+
def repository_for(type)
|
|
158
|
+
type == :vm ? @vm_repository : @container_repository
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
# Returns configured timeout.
|
|
162
|
+
#
|
|
163
|
+
# @return [Integer] Timeout in seconds
|
|
164
|
+
def timeout
|
|
165
|
+
@options[:timeout] || DEFAULT_TIMEOUT
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
end
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Pvectl
|
|
4
|
+
module Services
|
|
5
|
+
# Orchestrates migration of VMs and containers between cluster nodes.
|
|
6
|
+
#
|
|
7
|
+
# Single service for both resource types, parameterized by resource_type.
|
|
8
|
+
# Async mode (default): returns UPID immediately, no blocking.
|
|
9
|
+
# Sync mode (--wait): polls Task until completion or timeout.
|
|
10
|
+
#
|
|
11
|
+
# @example Migrate VMs async (default)
|
|
12
|
+
# service = ResourceMigration.new(vm_repository: vm_repo, container_repository: ct_repo, task_repository: task_repo)
|
|
13
|
+
# results = service.execute(:vm, [vm1, vm2], target: "pve2")
|
|
14
|
+
#
|
|
15
|
+
# @example Migrate with sync wait
|
|
16
|
+
# service = ResourceMigration.new(..., options: { wait: true })
|
|
17
|
+
# results = service.execute(:vm, [vm], target: "pve2")
|
|
18
|
+
#
|
|
19
|
+
class ResourceMigration
|
|
20
|
+
DEFAULT_TIMEOUT = 600
|
|
21
|
+
|
|
22
|
+
# Creates a new ResourceMigration service.
|
|
23
|
+
#
|
|
24
|
+
# @param vm_repository [Repositories::Vm] VM repository
|
|
25
|
+
# @param container_repository [Repositories::Container] Container repository
|
|
26
|
+
# @param task_repository [Repositories::Task] Task repository
|
|
27
|
+
# @param options [Hash] Options (online, restart, target_storage, timeout, wait, fail_fast)
|
|
28
|
+
def initialize(vm_repository:, container_repository:, task_repository:, options: {})
|
|
29
|
+
@vm_repository = vm_repository
|
|
30
|
+
@container_repository = container_repository
|
|
31
|
+
@task_repository = task_repository
|
|
32
|
+
@options = options
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Executes migration operation on resources.
|
|
36
|
+
#
|
|
37
|
+
# @param resource_type [Symbol] :vm or :container
|
|
38
|
+
# @param resources [Array<Models::Vm, Models::Container>] Resources to migrate
|
|
39
|
+
# @param target [String] Target node name
|
|
40
|
+
# @return [Array<Models::OperationResult>] Results for each resource
|
|
41
|
+
def execute(resource_type, resources, target:)
|
|
42
|
+
@resource_type = resource_type
|
|
43
|
+
|
|
44
|
+
migratable, skipped = partition_by_target(resources, target)
|
|
45
|
+
report_skipped(skipped, target)
|
|
46
|
+
return all_on_target_results(target) if migratable.empty?
|
|
47
|
+
|
|
48
|
+
results = []
|
|
49
|
+
migratable.each do |resource|
|
|
50
|
+
result = migrate_single(resource, target)
|
|
51
|
+
results << result
|
|
52
|
+
|
|
53
|
+
break if @options[:fail_fast] && result.failed?
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
results
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
private
|
|
60
|
+
|
|
61
|
+
# Partitions resources into migratable and already-on-target groups.
|
|
62
|
+
#
|
|
63
|
+
# @param resources [Array] Resources to partition
|
|
64
|
+
# @param target [String] Target node name
|
|
65
|
+
# @return [Array<Array, Array>] [migratable, skipped]
|
|
66
|
+
def partition_by_target(resources, target)
|
|
67
|
+
resources.partition { |r| r.node != target }
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Reports skipped resources to stderr.
|
|
71
|
+
#
|
|
72
|
+
# @param skipped [Array] Resources already on target
|
|
73
|
+
# @param target [String] Target node name
|
|
74
|
+
# @return [void]
|
|
75
|
+
def report_skipped(skipped, target)
|
|
76
|
+
type_name = @resource_type == :vm ? "VM" : "container"
|
|
77
|
+
skipped.each do |r|
|
|
78
|
+
$stderr.puts "Skipping #{type_name} #{r.vmid} (already on #{target})"
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Handles case when all resources are already on target.
|
|
83
|
+
#
|
|
84
|
+
# @param target [String] Target node name
|
|
85
|
+
# @return [Array] Empty results array
|
|
86
|
+
def all_on_target_results(target)
|
|
87
|
+
$stderr.puts "All resources are already on target node #{target}"
|
|
88
|
+
[]
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# Migrates a single resource.
|
|
92
|
+
#
|
|
93
|
+
# @param resource [Models::Vm, Models::Container] Resource to migrate
|
|
94
|
+
# @param target [String] Target node name
|
|
95
|
+
# @return [Models::OperationResult] Result
|
|
96
|
+
def migrate_single(resource, target)
|
|
97
|
+
repo = repository_for(@resource_type)
|
|
98
|
+
params = build_migrate_params(target)
|
|
99
|
+
upid = repo.migrate(resource.vmid, resource.node, params)
|
|
100
|
+
|
|
101
|
+
if @options[:wait]
|
|
102
|
+
task = @task_repository.wait(upid, timeout: timeout)
|
|
103
|
+
build_result(resource,
|
|
104
|
+
operation: :migrate,
|
|
105
|
+
task: task,
|
|
106
|
+
success: task.successful?
|
|
107
|
+
)
|
|
108
|
+
else
|
|
109
|
+
build_result(resource,
|
|
110
|
+
operation: :migrate,
|
|
111
|
+
task_upid: upid,
|
|
112
|
+
success: :pending
|
|
113
|
+
)
|
|
114
|
+
end
|
|
115
|
+
rescue StandardError => e
|
|
116
|
+
build_result(resource,
|
|
117
|
+
operation: :migrate,
|
|
118
|
+
success: false,
|
|
119
|
+
error: e.message
|
|
120
|
+
)
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# Builds migration parameters for the API call.
|
|
124
|
+
#
|
|
125
|
+
# @param target [String] Target node name
|
|
126
|
+
# @return [Hash] Migration parameters
|
|
127
|
+
def build_migrate_params(target)
|
|
128
|
+
params = { target: target }
|
|
129
|
+
|
|
130
|
+
if @options[:online]
|
|
131
|
+
params[:online] = 1
|
|
132
|
+
params[:"with-local-disks"] = 1 if @resource_type == :vm
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
params[:restart] = 1 if @options[:restart] && @resource_type == :container
|
|
136
|
+
params[:targetstorage] = @options[:target_storage] if @options[:target_storage]
|
|
137
|
+
|
|
138
|
+
params
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
# Builds typed OperationResult for the current resource type.
|
|
142
|
+
#
|
|
143
|
+
# @param resource [Models::Vm, Models::Container] Resource
|
|
144
|
+
# @param attrs [Hash] Result attributes
|
|
145
|
+
# @return [Models::VmOperationResult, Models::ContainerOperationResult] Typed result
|
|
146
|
+
def build_result(resource, **attrs)
|
|
147
|
+
if @resource_type == :vm
|
|
148
|
+
Models::VmOperationResult.new(vm: resource, **attrs)
|
|
149
|
+
else
|
|
150
|
+
Models::ContainerOperationResult.new(container: resource, **attrs)
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
# Returns the appropriate repository for resource type.
|
|
155
|
+
#
|
|
156
|
+
# @param type [Symbol] :vm or :container
|
|
157
|
+
# @return [Repositories::Vm, Repositories::Container] Repository
|
|
158
|
+
def repository_for(type)
|
|
159
|
+
type == :vm ? @vm_repository : @container_repository
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
# Returns configured timeout.
|
|
163
|
+
#
|
|
164
|
+
# @return [Integer] Timeout in seconds
|
|
165
|
+
def timeout
|
|
166
|
+
@options[:timeout] || DEFAULT_TIMEOUT
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
end
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Pvectl
|
|
4
|
+
module Services
|
|
5
|
+
# Orchestrates QEMU monitor key-send operations for a single VM.
|
|
6
|
+
#
|
|
7
|
+
# Resolves the VM (and its node) via the repository, validates that the
|
|
8
|
+
# target VM is running, and forwards the key sequence verbatim to the
|
|
9
|
+
# Proxmox API. The key string is passed through unmodified — interpretation
|
|
10
|
+
# is delegated to QEMU's qcode parser.
|
|
11
|
+
#
|
|
12
|
+
# @example Sending Ctrl+Alt+Delete to a running VM
|
|
13
|
+
# service = Sendkey.new(vm_repository: vm_repo)
|
|
14
|
+
# result = service.execute(vmid: 100, key: "ctrl-alt-delete")
|
|
15
|
+
# result.successful? #=> true
|
|
16
|
+
#
|
|
17
|
+
class Sendkey
|
|
18
|
+
# Creates a new Sendkey service.
|
|
19
|
+
#
|
|
20
|
+
# @param vm_repository [Repositories::Vm] VM repository for lookup and key send
|
|
21
|
+
def initialize(vm_repository:)
|
|
22
|
+
@vm_repository = vm_repository
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Sends a QEMU key sequence to a VM.
|
|
26
|
+
#
|
|
27
|
+
# Looks up the VM (the +node+ kwarg is honored when provided, otherwise
|
|
28
|
+
# the node is taken from the resolved VM). Returns a failed
|
|
29
|
+
# +VmOperationResult+ when the VM cannot be found, is not running, or
|
|
30
|
+
# the underlying API call raises.
|
|
31
|
+
#
|
|
32
|
+
# @param vmid [Integer, String] VM identifier
|
|
33
|
+
# @param key [String] QEMU qcode key sequence (e.g., "ctrl-alt-delete")
|
|
34
|
+
# @param node [String, nil] optional node override
|
|
35
|
+
# @return [Models::VmOperationResult] result of the operation
|
|
36
|
+
def execute(vmid:, key:, node: nil)
|
|
37
|
+
vmid_int = vmid.to_i
|
|
38
|
+
|
|
39
|
+
vm = @vm_repository.get(vmid_int)
|
|
40
|
+
return not_found_result(vmid_int) unless vm
|
|
41
|
+
|
|
42
|
+
target_node = node || vm.node
|
|
43
|
+
return not_running_result(vm, target_node, key) unless vm.status == "running"
|
|
44
|
+
|
|
45
|
+
@vm_repository.sendkey(vmid_int, target_node, key)
|
|
46
|
+
|
|
47
|
+
success_result(vm, target_node, key)
|
|
48
|
+
rescue StandardError => e
|
|
49
|
+
failure_result(vm, target_node || vm&.node, key, e.message)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
private
|
|
53
|
+
|
|
54
|
+
# @param vmid [Integer]
|
|
55
|
+
# @return [Models::VmOperationResult]
|
|
56
|
+
def not_found_result(vmid)
|
|
57
|
+
Models::VmOperationResult.new(
|
|
58
|
+
operation: :sendkey,
|
|
59
|
+
success: false,
|
|
60
|
+
error: "VM #{vmid} not found",
|
|
61
|
+
resource: { vmid: vmid }
|
|
62
|
+
)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# @param vm [Models::Vm]
|
|
66
|
+
# @param node [String]
|
|
67
|
+
# @param key [String]
|
|
68
|
+
# @return [Models::VmOperationResult]
|
|
69
|
+
def not_running_result(vm, node, key)
|
|
70
|
+
Models::VmOperationResult.new(
|
|
71
|
+
vm: vm,
|
|
72
|
+
operation: :sendkey,
|
|
73
|
+
success: false,
|
|
74
|
+
error: "VM #{vm.vmid} is not running (status: #{vm.status})",
|
|
75
|
+
resource: { vmid: vm.vmid, node: node, key: key }
|
|
76
|
+
)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# @param vm [Models::Vm]
|
|
80
|
+
# @param node [String]
|
|
81
|
+
# @param key [String]
|
|
82
|
+
# @return [Models::VmOperationResult]
|
|
83
|
+
def success_result(vm, node, key)
|
|
84
|
+
Models::VmOperationResult.new(
|
|
85
|
+
vm: vm,
|
|
86
|
+
operation: :sendkey,
|
|
87
|
+
success: true,
|
|
88
|
+
resource: { vmid: vm.vmid, node: node, key: key }
|
|
89
|
+
)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# @param vm [Models::Vm, nil]
|
|
93
|
+
# @param node [String, nil]
|
|
94
|
+
# @param key [String]
|
|
95
|
+
# @param message [String]
|
|
96
|
+
# @return [Models::VmOperationResult]
|
|
97
|
+
def failure_result(vm, node, key, message)
|
|
98
|
+
Models::VmOperationResult.new(
|
|
99
|
+
vm: vm,
|
|
100
|
+
operation: :sendkey,
|
|
101
|
+
success: false,
|
|
102
|
+
error: message,
|
|
103
|
+
resource: { vmid: vm&.vmid, node: node, key: key }
|
|
104
|
+
)
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|