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,265 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Pvectl
|
|
4
|
+
module Services
|
|
5
|
+
# Orchestrates VM clone operations.
|
|
6
|
+
#
|
|
7
|
+
# Handles validation, auto-generation of VMID/name, and sync/async modes.
|
|
8
|
+
# Supports both full clones and linked clones (templates only).
|
|
9
|
+
#
|
|
10
|
+
# @example Full clone with auto-generated VMID
|
|
11
|
+
# service = CloneVm.new(vm_repository: vm_repo, task_repository: task_repo)
|
|
12
|
+
# result = service.execute(vmid: 100)
|
|
13
|
+
#
|
|
14
|
+
# @example Linked clone to specific node
|
|
15
|
+
# service = CloneVm.new(vm_repository: vm_repo, task_repository: task_repo)
|
|
16
|
+
# result = service.execute(vmid: 100, linked: true, target_node: "pve2")
|
|
17
|
+
#
|
|
18
|
+
# @example Async clone with custom timeout
|
|
19
|
+
# service = CloneVm.new(vm_repository: vm_repo, task_repository: task_repo, options: { async: true })
|
|
20
|
+
# result = service.execute(vmid: 100, new_vmid: 200, name: "web-clone")
|
|
21
|
+
#
|
|
22
|
+
class CloneVm
|
|
23
|
+
DEFAULT_TIMEOUT = 300
|
|
24
|
+
|
|
25
|
+
# @return [Integer] Default timeout for start operations (seconds)
|
|
26
|
+
START_TIMEOUT = 60
|
|
27
|
+
|
|
28
|
+
# Creates a new CloneVm service.
|
|
29
|
+
#
|
|
30
|
+
# @param vm_repository [Repositories::Vm] VM repository
|
|
31
|
+
# @param task_repository [Repositories::Task] Task repository
|
|
32
|
+
# @param options [Hash] Options (timeout, async)
|
|
33
|
+
def initialize(vm_repository:, task_repository:, options: {})
|
|
34
|
+
@vm_repository = vm_repository
|
|
35
|
+
@task_repository = task_repository
|
|
36
|
+
@options = options
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Executes clone operation.
|
|
40
|
+
#
|
|
41
|
+
# Performs a two-step flow: clone the VM first, then optionally apply
|
|
42
|
+
# config updates via PUT /nodes/{node}/qemu/{vmid}/config.
|
|
43
|
+
#
|
|
44
|
+
# @param vmid [Integer] Source VM identifier
|
|
45
|
+
# @param node [String, nil] Source node (auto-detected from VM if nil)
|
|
46
|
+
# @param new_vmid [Integer, nil] New VMID (auto-selected if nil)
|
|
47
|
+
# @param name [String, nil] Name for clone (auto-generated if nil)
|
|
48
|
+
# @param target_node [String, nil] Target node for clone
|
|
49
|
+
# @param storage [String, nil] Target storage
|
|
50
|
+
# @param linked [Boolean] Linked clone (default: false, requires template)
|
|
51
|
+
# @param pool [String, nil] Resource pool
|
|
52
|
+
# @param description [String, nil] Description
|
|
53
|
+
# @param config_params [Hash] VM config parameters to apply after clone
|
|
54
|
+
# @return [Models::OperationResult] Clone result
|
|
55
|
+
def execute(vmid:, node: nil, new_vmid: nil, name: nil, target_node: nil,
|
|
56
|
+
storage: nil, linked: false, pool: nil, description: nil,
|
|
57
|
+
config_params: {})
|
|
58
|
+
source_vm = @vm_repository.get(vmid)
|
|
59
|
+
return vm_not_found_error(vmid) unless source_vm
|
|
60
|
+
|
|
61
|
+
if linked && !source_vm.template?
|
|
62
|
+
return linked_clone_error(source_vm)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
node ||= source_vm.node
|
|
66
|
+
new_vmid ||= @vm_repository.next_available_vmid
|
|
67
|
+
name ||= generate_name(source_vm)
|
|
68
|
+
|
|
69
|
+
clone_options = build_clone_options(
|
|
70
|
+
name: name, target_node: target_node, storage: storage,
|
|
71
|
+
linked: linked, pool: pool, description: description
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
upid = @vm_repository.clone(vmid, node, new_vmid, clone_options)
|
|
75
|
+
resource_info = { new_vmid: new_vmid, name: name, node: target_node || node }
|
|
76
|
+
|
|
77
|
+
if @options[:async]
|
|
78
|
+
Models::VmOperationResult.new(
|
|
79
|
+
vm: source_vm, operation: :clone,
|
|
80
|
+
task_upid: upid, success: :pending,
|
|
81
|
+
resource: resource_info
|
|
82
|
+
)
|
|
83
|
+
else
|
|
84
|
+
task = @task_repository.wait(upid, timeout: timeout)
|
|
85
|
+
|
|
86
|
+
unless task.successful?
|
|
87
|
+
return Models::VmOperationResult.new(
|
|
88
|
+
vm: source_vm, operation: :clone,
|
|
89
|
+
task: task, success: task.successful?,
|
|
90
|
+
resource: resource_info
|
|
91
|
+
)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
if config_params.any?
|
|
95
|
+
apply_config_update(source_vm, new_vmid, resource_info[:node], config_params, resource_info)
|
|
96
|
+
else
|
|
97
|
+
start_vm(new_vmid, resource_info[:node]) if @options[:start]
|
|
98
|
+
Models::VmOperationResult.new(
|
|
99
|
+
vm: source_vm, operation: :clone,
|
|
100
|
+
task: task, success: true,
|
|
101
|
+
resource: resource_info
|
|
102
|
+
)
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
rescue StandardError => e
|
|
106
|
+
Models::VmOperationResult.new(
|
|
107
|
+
vm: source_vm, operation: :clone,
|
|
108
|
+
success: false, error: e.message
|
|
109
|
+
)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
private
|
|
113
|
+
|
|
114
|
+
# Generates clone name from source VM.
|
|
115
|
+
#
|
|
116
|
+
# @param source_vm [Models::Vm] Source VM
|
|
117
|
+
# @return [String] Generated name
|
|
118
|
+
def generate_name(source_vm)
|
|
119
|
+
if source_vm.name && !source_vm.name.empty?
|
|
120
|
+
"#{source_vm.name}-clone"
|
|
121
|
+
else
|
|
122
|
+
"vm-#{source_vm.vmid}-clone"
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# Builds clone options hash for repository call.
|
|
127
|
+
#
|
|
128
|
+
# @param name [String] Clone name
|
|
129
|
+
# @param target_node [String, nil] Target node
|
|
130
|
+
# @param storage [String, nil] Target storage
|
|
131
|
+
# @param linked [Boolean] Linked clone flag
|
|
132
|
+
# @param pool [String, nil] Resource pool
|
|
133
|
+
# @param description [String, nil] Description
|
|
134
|
+
# @return [Hash] Clone options
|
|
135
|
+
def build_clone_options(name:, target_node:, storage:, linked:, pool:, description:)
|
|
136
|
+
opts = { name: name, full: !linked }
|
|
137
|
+
opts[:target] = target_node if target_node
|
|
138
|
+
opts[:storage] = storage if storage
|
|
139
|
+
opts[:pool] = pool if pool
|
|
140
|
+
opts[:description] = description if description
|
|
141
|
+
opts
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
# Applies config update to the cloned VM.
|
|
145
|
+
#
|
|
146
|
+
# Converts user-friendly params to Proxmox API format and calls
|
|
147
|
+
# the repository update method. Returns partial result on failure.
|
|
148
|
+
#
|
|
149
|
+
# @param source_vm [Models::Vm] Source VM
|
|
150
|
+
# @param new_vmid [Integer] Cloned VM identifier
|
|
151
|
+
# @param node [String] Target node for the cloned VM
|
|
152
|
+
# @param config_params [Hash] Config parameters to apply
|
|
153
|
+
# @param resource_info [Hash] Resource info for result
|
|
154
|
+
# @return [Models::VmOperationResult] Operation result
|
|
155
|
+
def apply_config_update(source_vm, new_vmid, node, config_params, resource_info)
|
|
156
|
+
api_params = build_config_api_params(config_params)
|
|
157
|
+
@vm_repository.update(new_vmid, node, api_params)
|
|
158
|
+
start_vm(new_vmid, node) if @options[:start]
|
|
159
|
+
Models::VmOperationResult.new(
|
|
160
|
+
vm: source_vm, operation: :clone,
|
|
161
|
+
success: true, resource: resource_info
|
|
162
|
+
)
|
|
163
|
+
rescue StandardError => e
|
|
164
|
+
Models::VmOperationResult.new(
|
|
165
|
+
vm: source_vm, operation: :clone,
|
|
166
|
+
success: :partial, resource: resource_info,
|
|
167
|
+
error: "Cloned successfully, but config update failed: #{e.message}"
|
|
168
|
+
)
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
# Builds Proxmox API parameters from user-friendly config options.
|
|
172
|
+
#
|
|
173
|
+
# Maps config keys to their Proxmox API equivalents. Does not include
|
|
174
|
+
# name, description, or pool (those belong to the clone step).
|
|
175
|
+
#
|
|
176
|
+
# @param config_params [Hash] User-friendly config parameters
|
|
177
|
+
# @return [Hash] Proxmox API parameters
|
|
178
|
+
def build_config_api_params(config_params)
|
|
179
|
+
params = {}
|
|
180
|
+
params[:cores] = config_params[:cores] if config_params[:cores]
|
|
181
|
+
params[:sockets] = config_params[:sockets] if config_params[:sockets]
|
|
182
|
+
params[:cpu] = config_params[:cpu_type] if config_params[:cpu_type]
|
|
183
|
+
params[:numa] = config_params[:numa] ? 1 : 0 unless config_params[:numa].nil?
|
|
184
|
+
params[:memory] = config_params[:memory] if config_params[:memory]
|
|
185
|
+
params[:balloon] = config_params[:balloon] if config_params[:balloon]
|
|
186
|
+
add_disk_params(params, config_params[:disks]) if config_params[:disks]
|
|
187
|
+
params[:scsihw] = config_params[:scsihw] if config_params[:scsihw]
|
|
188
|
+
params[:cdrom] = config_params[:cdrom] if config_params[:cdrom]
|
|
189
|
+
add_net_params(params, config_params[:nets]) if config_params[:nets]
|
|
190
|
+
params[:bios] = config_params[:bios] if config_params[:bios]
|
|
191
|
+
params[:boot] = "order=#{config_params[:boot_order]}" if config_params[:boot_order]
|
|
192
|
+
params[:machine] = config_params[:machine] if config_params[:machine]
|
|
193
|
+
params[:efidisk0] = config_params[:efidisk] if config_params[:efidisk]
|
|
194
|
+
params.merge!(config_params[:cloud_init]) if config_params[:cloud_init]
|
|
195
|
+
params[:agent] = config_params[:agent] ? "1" : "0" unless config_params[:agent].nil?
|
|
196
|
+
params[:ostype] = config_params[:ostype] if config_params[:ostype]
|
|
197
|
+
params[:tags] = config_params[:tags] if config_params[:tags]
|
|
198
|
+
params
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
# Adds disk parameters mapped to scsi0, scsi1, etc.
|
|
202
|
+
#
|
|
203
|
+
# @param params [Hash] Parameters hash to modify
|
|
204
|
+
# @param disks [Array<Hash>] Disk configurations
|
|
205
|
+
# @return [void]
|
|
206
|
+
def add_disk_params(params, disks)
|
|
207
|
+
disks.each_with_index do |disk, index|
|
|
208
|
+
params[:"scsi#{index}"] = Parsers::DiskConfig.to_proxmox(disk)
|
|
209
|
+
end
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
# Adds network parameters mapped to net0, net1, etc.
|
|
213
|
+
#
|
|
214
|
+
# @param params [Hash] Parameters hash to modify
|
|
215
|
+
# @param nets [Array<Hash>] Network configurations
|
|
216
|
+
# @return [void]
|
|
217
|
+
def add_net_params(params, nets)
|
|
218
|
+
nets.each_with_index do |net, index|
|
|
219
|
+
params[:"net#{index}"] = Parsers::NetConfig.to_proxmox(net)
|
|
220
|
+
end
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
# Starts a VM after successful clone and config update.
|
|
224
|
+
#
|
|
225
|
+
# @param vmid [Integer] VM identifier
|
|
226
|
+
# @param node [String] Node name
|
|
227
|
+
# @return [void]
|
|
228
|
+
def start_vm(vmid, node)
|
|
229
|
+
upid = @vm_repository.start(vmid, node)
|
|
230
|
+
@task_repository.wait(upid, timeout: START_TIMEOUT)
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
# Returns configured timeout.
|
|
234
|
+
#
|
|
235
|
+
# @return [Integer] Timeout in seconds
|
|
236
|
+
def timeout
|
|
237
|
+
@options[:timeout] || DEFAULT_TIMEOUT
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
# Returns error for VM not found.
|
|
241
|
+
#
|
|
242
|
+
# @param vmid [Integer] VM identifier
|
|
243
|
+
# @return [Models::OperationResult] Failed result
|
|
244
|
+
def vm_not_found_error(vmid)
|
|
245
|
+
Models::VmOperationResult.new(
|
|
246
|
+
operation: :clone,
|
|
247
|
+
success: false,
|
|
248
|
+
error: "VM #{vmid} not found"
|
|
249
|
+
)
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
# Returns error for linked clone of non-template VM.
|
|
253
|
+
#
|
|
254
|
+
# @param source_vm [Models::Vm] Source VM
|
|
255
|
+
# @return [Models::OperationResult] Failed result
|
|
256
|
+
def linked_clone_error(source_vm)
|
|
257
|
+
Models::VmOperationResult.new(
|
|
258
|
+
vm: source_vm, operation: :clone,
|
|
259
|
+
success: false,
|
|
260
|
+
error: "Linked clone requires VM to be a template. VM #{source_vm.vmid} is not a template"
|
|
261
|
+
)
|
|
262
|
+
end
|
|
263
|
+
end
|
|
264
|
+
end
|
|
265
|
+
end
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Pvectl
|
|
4
|
+
module Services
|
|
5
|
+
# Orchestrates cloud-init operations on VMs.
|
|
6
|
+
#
|
|
7
|
+
# Provides three operations:
|
|
8
|
+
# - +regenerate+ — rebuild the cloud-init ISO from current config
|
|
9
|
+
# - +pending+ — list configuration changes not yet applied
|
|
10
|
+
# - +dump+ — retrieve the auto-generated cloud-init YAML
|
|
11
|
+
#
|
|
12
|
+
# Operations are VM-only — LXC containers do not expose cloud-init
|
|
13
|
+
# endpoints. When the +vmid+ resolves to a non-QEMU resource, the
|
|
14
|
+
# service raises +Pvectl::ResourceNotFoundError+.
|
|
15
|
+
#
|
|
16
|
+
# @example Regenerate ISO for a VM
|
|
17
|
+
# service = Cloudinit.new(vm_repository: vm_repo, resource_resolver: resolver)
|
|
18
|
+
# service.regenerate(100)
|
|
19
|
+
#
|
|
20
|
+
# @example Dump generated user-data YAML
|
|
21
|
+
# yaml = service.dump(100, "user")
|
|
22
|
+
#
|
|
23
|
+
class Cloudinit
|
|
24
|
+
# Cloud-init config types supported by the +dump+ operation.
|
|
25
|
+
VALID_DUMP_TYPES = %w[user network meta].freeze
|
|
26
|
+
|
|
27
|
+
# Creates a new Cloudinit service.
|
|
28
|
+
#
|
|
29
|
+
# @param vm_repository [Repositories::Vm] VM repository
|
|
30
|
+
# @param resource_resolver [Utils::ResourceResolver] resolver for VMID -> node
|
|
31
|
+
def initialize(vm_repository:, resource_resolver:)
|
|
32
|
+
@vm_repository = vm_repository
|
|
33
|
+
@resolver = resource_resolver
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Regenerates the cloud-init ISO for the given VM.
|
|
37
|
+
#
|
|
38
|
+
# @param vmid [Integer] VM identifier
|
|
39
|
+
# @param node [String, nil] explicit node name (skips resolver)
|
|
40
|
+
# @return [Hash{Symbol => untyped}] +{ vmid: Integer, node: String }+
|
|
41
|
+
# @raise [Pvectl::ResourceNotFoundError] when VM does not exist or is not QEMU
|
|
42
|
+
def regenerate(vmid, node: nil)
|
|
43
|
+
node ||= resolve_node!(vmid)
|
|
44
|
+
@vm_repository.cloudinit_regenerate(node, vmid)
|
|
45
|
+
{ vmid: vmid, node: node }
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Returns pending cloud-init configuration changes.
|
|
49
|
+
#
|
|
50
|
+
# @param vmid [Integer] VM identifier
|
|
51
|
+
# @param node [String, nil] explicit node name (skips resolver)
|
|
52
|
+
# @return [Array<Hash{Symbol => untyped}>] pending entries
|
|
53
|
+
# @raise [Pvectl::ResourceNotFoundError] when VM does not exist or is not QEMU
|
|
54
|
+
def pending(vmid, node: nil)
|
|
55
|
+
node ||= resolve_node!(vmid)
|
|
56
|
+
@vm_repository.cloudinit_pending(node, vmid)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Dumps the auto-generated cloud-init configuration.
|
|
60
|
+
#
|
|
61
|
+
# @param vmid [Integer] VM identifier
|
|
62
|
+
# @param type [String] one of +"user"+, +"network"+, +"meta"+
|
|
63
|
+
# @param node [String, nil] explicit node name (skips resolver)
|
|
64
|
+
# @return [String] raw cloud-init YAML/text
|
|
65
|
+
# @raise [ArgumentError] when +type+ is not a valid dump type
|
|
66
|
+
# @raise [Pvectl::ResourceNotFoundError] when VM does not exist or is not QEMU
|
|
67
|
+
def dump(vmid, type, node: nil)
|
|
68
|
+
unless VALID_DUMP_TYPES.include?(type)
|
|
69
|
+
raise ArgumentError, "Invalid cloud-init dump type: #{type.inspect} " \
|
|
70
|
+
"(valid: #{VALID_DUMP_TYPES.join(', ')})"
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
node ||= resolve_node!(vmid)
|
|
74
|
+
@vm_repository.cloudinit_dump(node, vmid, type)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
private
|
|
78
|
+
|
|
79
|
+
# Resolves a VMID to its node, ensuring the resource is a QEMU VM.
|
|
80
|
+
#
|
|
81
|
+
# @param vmid [Integer] VM identifier
|
|
82
|
+
# @return [String] node name
|
|
83
|
+
# @raise [Pvectl::ResourceNotFoundError] when not found or not a VM
|
|
84
|
+
def resolve_node!(vmid)
|
|
85
|
+
resolved = @resolver.resolve(vmid)
|
|
86
|
+
raise Pvectl::ResourceNotFoundError, "VM #{vmid} not found" if resolved.nil?
|
|
87
|
+
|
|
88
|
+
unless resolved[:type] == :qemu
|
|
89
|
+
raise Pvectl::ResourceNotFoundError, "Resource #{vmid} is not a VM (cloud-init is VM-only)"
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
resolved[:node]
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "uri"
|
|
4
|
+
require "rest_client"
|
|
5
|
+
require "json"
|
|
6
|
+
|
|
7
|
+
module Pvectl
|
|
8
|
+
module Services
|
|
9
|
+
# Orchestrates an interactive console session to a VM or container.
|
|
10
|
+
#
|
|
11
|
+
# Handles: authentication (session ticket), termproxy setup,
|
|
12
|
+
# WebSocket URL construction, and terminal session lifecycle.
|
|
13
|
+
#
|
|
14
|
+
# @example Basic usage
|
|
15
|
+
# service = Console.new
|
|
16
|
+
# service.run(resource: vm, resource_path: "qemu/100", server: "https://pve1:8006",
|
|
17
|
+
# username: "root@pam", password: "secret", verify_ssl: true)
|
|
18
|
+
#
|
|
19
|
+
class Console
|
|
20
|
+
# Raised when the target resource is not in a running state.
|
|
21
|
+
class ResourceNotRunningError < Pvectl::Error; end
|
|
22
|
+
|
|
23
|
+
# Raised when authentication fails.
|
|
24
|
+
class AuthenticationError < Pvectl::Error; end
|
|
25
|
+
|
|
26
|
+
# Builds the WebSocket URL for vncwebsocket endpoint.
|
|
27
|
+
#
|
|
28
|
+
# @param server [String] Proxmox server URL (e.g. "https://pve1:8006")
|
|
29
|
+
# @param node [String] node name
|
|
30
|
+
# @param resource_path [String] resource path (e.g. "qemu/100" or "lxc/200")
|
|
31
|
+
# @param port [Integer] termproxy port
|
|
32
|
+
# @param ticket [String] PVEVNC ticket
|
|
33
|
+
# @return [String] full WebSocket URL
|
|
34
|
+
def build_websocket_url(server:, node:, resource_path:, port:, ticket:)
|
|
35
|
+
uri = URI.parse(server)
|
|
36
|
+
scheme = uri.scheme == "https" ? "wss" : "ws"
|
|
37
|
+
host = uri.host
|
|
38
|
+
ws_port = uri.port || 8006
|
|
39
|
+
encoded_ticket = URI.encode_www_form_component(ticket)
|
|
40
|
+
|
|
41
|
+
"#{scheme}://#{host}:#{ws_port}/api2/json/nodes/#{node}/#{resource_path}/vncwebsocket" \
|
|
42
|
+
"?port=#{port}&vncticket=#{encoded_ticket}"
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Validates that the resource is in a running state.
|
|
46
|
+
#
|
|
47
|
+
# @param resource [Models::Vm, Models::Container] resource to check
|
|
48
|
+
# @return [void]
|
|
49
|
+
# @raise [ResourceNotRunningError] if resource is not running
|
|
50
|
+
def validate_resource_running!(resource)
|
|
51
|
+
return if resource.status == "running"
|
|
52
|
+
|
|
53
|
+
raise ResourceNotRunningError,
|
|
54
|
+
"Resource #{resource.vmid} is not running (status: #{resource.status})"
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Runs a console session end-to-end.
|
|
58
|
+
#
|
|
59
|
+
# All API calls (authenticate, termproxy) use the same session-based
|
|
60
|
+
# REST client to ensure PVEVNC ticket and PVEAuthCookie share the
|
|
61
|
+
# same identity. Using a token-based connection for termproxy would
|
|
62
|
+
# generate a ticket bound to the token identity (e.g., "root@pam!pvectl"),
|
|
63
|
+
# which is rejected by the WebSocket endpoint expecting a session cookie
|
|
64
|
+
# for "root@pam".
|
|
65
|
+
#
|
|
66
|
+
# @param resource [Models::Vm, Models::Container] target resource
|
|
67
|
+
# @param resource_path [String] API path segment ("qemu/{vmid}" or "lxc/{ctid}")
|
|
68
|
+
# @param server [String] Proxmox server URL
|
|
69
|
+
# @param username [String] username for auth
|
|
70
|
+
# @param password [String] password for auth
|
|
71
|
+
# @param verify_ssl [Boolean] SSL verification flag
|
|
72
|
+
# @return [void]
|
|
73
|
+
def run(resource:, resource_path:, server:, username:, password:, verify_ssl:)
|
|
74
|
+
validate_resource_running!(resource)
|
|
75
|
+
|
|
76
|
+
# All operations use one session-authenticated REST client
|
|
77
|
+
api_url = "#{server}/api2/json/"
|
|
78
|
+
session_client = RestClient::Resource.new(api_url, verify_ssl: verify_ssl)
|
|
79
|
+
|
|
80
|
+
# 1. Authenticate
|
|
81
|
+
auth = authenticate_with_client(session_client, username, password)
|
|
82
|
+
|
|
83
|
+
# 2. Open termproxy (using session ticket, not API token)
|
|
84
|
+
termproxy_data = open_termproxy(
|
|
85
|
+
session_client, auth,
|
|
86
|
+
node: resource.node, resource_path: resource_path
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
# 3. Build websocket URL
|
|
90
|
+
url = build_websocket_url(
|
|
91
|
+
server: server,
|
|
92
|
+
node: resource.node,
|
|
93
|
+
resource_path: resource_path,
|
|
94
|
+
port: termproxy_data[:port],
|
|
95
|
+
ticket: termproxy_data[:ticket]
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
# 4. Run terminal session
|
|
99
|
+
session = Pvectl::Console::TerminalSession.new(
|
|
100
|
+
url: url,
|
|
101
|
+
cookie: "PVEAuthCookie=#{auth[:ticket]}",
|
|
102
|
+
user: termproxy_data[:user],
|
|
103
|
+
ticket: termproxy_data[:ticket],
|
|
104
|
+
verify_ssl: verify_ssl
|
|
105
|
+
)
|
|
106
|
+
session.run
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
private
|
|
110
|
+
|
|
111
|
+
# Authenticates using a provided REST client and returns session data.
|
|
112
|
+
#
|
|
113
|
+
# @param client [RestClient::Resource] clean REST client
|
|
114
|
+
# @param username [String] Proxmox username
|
|
115
|
+
# @param password [String] password
|
|
116
|
+
# @return [Hash] { ticket:, csrf_token: }
|
|
117
|
+
# @raise [AuthenticationError] on failure
|
|
118
|
+
def authenticate_with_client(client, username, password)
|
|
119
|
+
response = client["access/ticket"].post(username: username, password: password)
|
|
120
|
+
data = JSON.parse(response.body, symbolize_names: true)[:data]
|
|
121
|
+
|
|
122
|
+
{
|
|
123
|
+
ticket: data[:ticket],
|
|
124
|
+
csrf_token: data[:CSRFPreventionToken]
|
|
125
|
+
}
|
|
126
|
+
rescue StandardError => e
|
|
127
|
+
raise AuthenticationError, "Authentication failed: #{e.message}"
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
# Opens a termproxy session using session auth credentials.
|
|
131
|
+
#
|
|
132
|
+
# @param client [RestClient::Resource] REST client
|
|
133
|
+
# @param auth [Hash] session auth with :ticket and :csrf_token
|
|
134
|
+
# @param node [String] Proxmox node name
|
|
135
|
+
# @param resource_path [String] e.g. "qemu/100" or "lxc/200"
|
|
136
|
+
# @return [Hash] { port:, ticket:, user: }
|
|
137
|
+
def open_termproxy(client, auth, node:, resource_path:)
|
|
138
|
+
endpoint = "nodes/#{node}/#{resource_path}/termproxy"
|
|
139
|
+
response = client[endpoint].post(
|
|
140
|
+
{},
|
|
141
|
+
cookies: { PVEAuthCookie: auth[:ticket] },
|
|
142
|
+
CSRFPreventionToken: auth[:csrf_token]
|
|
143
|
+
)
|
|
144
|
+
data = JSON.parse(response.body, symbolize_names: true)[:data]
|
|
145
|
+
|
|
146
|
+
{ port: data[:port], ticket: data[:ticket], user: data[:user] }
|
|
147
|
+
rescue StandardError => e
|
|
148
|
+
raise AuthenticationError, "Termproxy failed: #{e.message}"
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
end
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Pvectl
|
|
4
|
+
module Services
|
|
5
|
+
# Orchestrates container lifecycle operations.
|
|
6
|
+
#
|
|
7
|
+
# Handles execution of start/stop/shutdown/restart operations
|
|
8
|
+
# with sync/async modes, error handling, and result collection.
|
|
9
|
+
#
|
|
10
|
+
# @example Basic usage
|
|
11
|
+
# service = ContainerLifecycle.new(ct_repo, task_repo)
|
|
12
|
+
# results = service.execute(:start, [ct1, ct2])
|
|
13
|
+
# results.each { |r| puts "#{r.container.vmid}: #{r.status_text}" }
|
|
14
|
+
#
|
|
15
|
+
class ContainerLifecycle
|
|
16
|
+
SYNC_OPERATIONS = %i[start stop].freeze
|
|
17
|
+
ASYNC_OPERATIONS = %i[shutdown restart].freeze
|
|
18
|
+
ALL_OPERATIONS = (SYNC_OPERATIONS + ASYNC_OPERATIONS).freeze
|
|
19
|
+
|
|
20
|
+
DEFAULT_TIMEOUT = 60
|
|
21
|
+
|
|
22
|
+
# Creates a new ContainerLifecycle service.
|
|
23
|
+
#
|
|
24
|
+
# @param container_repository [Repositories::Container] Container repository
|
|
25
|
+
# @param task_repository [Repositories::Task] Task repository
|
|
26
|
+
# @param options [Hash] Options (timeout, async, wait, fail_fast)
|
|
27
|
+
def initialize(container_repository, task_repository, options = {})
|
|
28
|
+
@container_repository = container_repository
|
|
29
|
+
@task_repository = task_repository
|
|
30
|
+
@options = options
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Executes a lifecycle operation on a list of containers.
|
|
34
|
+
#
|
|
35
|
+
# @param operation [Symbol] Operation to execute
|
|
36
|
+
# @param containers [Array<Models::Container>] Containers to operate on
|
|
37
|
+
# @return [Array<Models::ContainerOperationResult>] Results for each container
|
|
38
|
+
def execute(operation, containers)
|
|
39
|
+
validate_operation!(operation)
|
|
40
|
+
|
|
41
|
+
results = []
|
|
42
|
+
containers.each do |container|
|
|
43
|
+
result = execute_single(operation, container)
|
|
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 container.
|
|
54
|
+
#
|
|
55
|
+
# @param operation [Symbol] Operation
|
|
56
|
+
# @param container [Models::Container] Container
|
|
57
|
+
# @return [Models::ContainerOperationResult] Result
|
|
58
|
+
def execute_single(operation, container)
|
|
59
|
+
task_upid = call_api(operation, container)
|
|
60
|
+
|
|
61
|
+
if sync_mode?(operation)
|
|
62
|
+
task = @task_repository.wait(task_upid, timeout: timeout)
|
|
63
|
+
Models::ContainerOperationResult.new(
|
|
64
|
+
container: container,
|
|
65
|
+
operation: operation,
|
|
66
|
+
task: task,
|
|
67
|
+
success: task.successful?
|
|
68
|
+
)
|
|
69
|
+
else
|
|
70
|
+
Models::ContainerOperationResult.new(
|
|
71
|
+
container: container,
|
|
72
|
+
operation: operation,
|
|
73
|
+
task_upid: task_upid,
|
|
74
|
+
success: :pending
|
|
75
|
+
)
|
|
76
|
+
end
|
|
77
|
+
rescue StandardError => e
|
|
78
|
+
Models::ContainerOperationResult.new(
|
|
79
|
+
container: container,
|
|
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 container [Models::Container] Container
|
|
90
|
+
# @return [String] Task UPID
|
|
91
|
+
def call_api(operation, container)
|
|
92
|
+
@container_repository.send(operation, container.vmid, container.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
|