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,116 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Pvectl
|
|
4
|
+
module Selectors
|
|
5
|
+
# Selector for filtering VMs.
|
|
6
|
+
#
|
|
7
|
+
# Extends Base with VM-specific field extraction.
|
|
8
|
+
# Supports: status, tags, pool, name, template.
|
|
9
|
+
#
|
|
10
|
+
# @example Filter running VMs
|
|
11
|
+
# selector = Vm.parse("status=running")
|
|
12
|
+
# running_vms = selector.apply(all_vms)
|
|
13
|
+
#
|
|
14
|
+
# @example Filter by multiple criteria
|
|
15
|
+
# selector = Vm.parse("status=running,tags=prod")
|
|
16
|
+
# filtered = selector.apply(all_vms)
|
|
17
|
+
#
|
|
18
|
+
# @example Filter by name pattern
|
|
19
|
+
# selector = Vm.parse("name=~web-*")
|
|
20
|
+
# web_vms = selector.apply(all_vms)
|
|
21
|
+
#
|
|
22
|
+
class Vm < Base
|
|
23
|
+
SUPPORTED_FIELDS = %w[status tags pool name template].freeze
|
|
24
|
+
|
|
25
|
+
# Applies selector to VM collection.
|
|
26
|
+
#
|
|
27
|
+
# @param vms [Array<Models::Vm>] VMs to filter
|
|
28
|
+
# @return [Array<Models::Vm>] Filtered VMs
|
|
29
|
+
def apply(vms)
|
|
30
|
+
return vms if empty?
|
|
31
|
+
|
|
32
|
+
vms.select { |vm| matches?(vm) }
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
protected
|
|
36
|
+
|
|
37
|
+
# Extracts field value from VM model.
|
|
38
|
+
#
|
|
39
|
+
# @param vm [Models::Vm] VM model
|
|
40
|
+
# @param field [String] Field name (status, tags, pool, name, template)
|
|
41
|
+
# @return [String, nil] Field value
|
|
42
|
+
# @raise [ArgumentError] if field is not supported
|
|
43
|
+
def extract_value(vm, field)
|
|
44
|
+
case field
|
|
45
|
+
when "status"
|
|
46
|
+
vm.status
|
|
47
|
+
when "tags"
|
|
48
|
+
vm.tags
|
|
49
|
+
when "pool"
|
|
50
|
+
vm.pool
|
|
51
|
+
when "name"
|
|
52
|
+
vm.name
|
|
53
|
+
when "template"
|
|
54
|
+
vm.template? ? "yes" : "no"
|
|
55
|
+
else
|
|
56
|
+
raise ArgumentError, "Unknown field: #{field}. Supported: #{SUPPORTED_FIELDS.join(', ')}"
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Override to handle tags specially.
|
|
61
|
+
# Tags in Proxmox are semicolon-separated: "tag1;tag2;tag3"
|
|
62
|
+
# Selector "tags=prod" should match if "prod" is one of the tags.
|
|
63
|
+
#
|
|
64
|
+
# @param vm [Models::Vm] VM model
|
|
65
|
+
# @param condition [Hash] Condition
|
|
66
|
+
# @return [Boolean] true if matches
|
|
67
|
+
def match_condition?(vm, condition)
|
|
68
|
+
return match_tags_condition?(vm, condition) if condition[:field] == "tags"
|
|
69
|
+
return super(vm, normalize_boolean_condition(condition)) if condition[:field] == "template"
|
|
70
|
+
|
|
71
|
+
super
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
private
|
|
75
|
+
|
|
76
|
+
# Normalizes boolean condition values for template field.
|
|
77
|
+
# Accepts: yes/no, true/false, 1/0
|
|
78
|
+
#
|
|
79
|
+
# @param condition [Hash] Condition with :value key
|
|
80
|
+
# @return [Hash] Condition with normalized value
|
|
81
|
+
def normalize_boolean_condition(condition)
|
|
82
|
+
normalized = case condition[:value]
|
|
83
|
+
when "true", "1" then "yes"
|
|
84
|
+
when "false", "0" then "no"
|
|
85
|
+
else condition[:value]
|
|
86
|
+
end
|
|
87
|
+
condition.merge(value: normalized)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# Special matching for tags field.
|
|
91
|
+
# Proxmox tags are semicolon-separated, so we check if the value
|
|
92
|
+
# is contained in the tag list.
|
|
93
|
+
#
|
|
94
|
+
# @param vm [Models::Vm] VM model
|
|
95
|
+
# @param condition [Hash] Condition
|
|
96
|
+
# @return [Boolean] true if matches
|
|
97
|
+
def match_tags_condition?(vm, condition)
|
|
98
|
+
tags_string = vm.tags || ""
|
|
99
|
+
tag_list = tags_string.split(";").map(&:strip)
|
|
100
|
+
|
|
101
|
+
case condition[:operator]
|
|
102
|
+
when :eq
|
|
103
|
+
tag_list.include?(condition[:value])
|
|
104
|
+
when :neq
|
|
105
|
+
!tag_list.include?(condition[:value])
|
|
106
|
+
when :match
|
|
107
|
+
tag_list.any? { |tag| wildcard_match?(tag, condition[:value]) }
|
|
108
|
+
when :in
|
|
109
|
+
(tag_list & condition[:value]).any?
|
|
110
|
+
else
|
|
111
|
+
false
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Pvectl
|
|
4
|
+
module Selectors
|
|
5
|
+
# Selector for filtering virtual disk volumes.
|
|
6
|
+
#
|
|
7
|
+
# Extends Base with volume-specific field extraction.
|
|
8
|
+
# Supports: format, storage, node, content, resource_type, name.
|
|
9
|
+
#
|
|
10
|
+
# @example Filter raw volumes only
|
|
11
|
+
# selector = Volume.parse("format=raw")
|
|
12
|
+
# raw_vols = selector.apply(all_volumes)
|
|
13
|
+
#
|
|
14
|
+
# @example Filter volumes on a specific storage and node
|
|
15
|
+
# selector = Volume.parse("storage=local-lvm,node=pve1")
|
|
16
|
+
# filtered = selector.apply(all_volumes)
|
|
17
|
+
#
|
|
18
|
+
class Volume < Base
|
|
19
|
+
SUPPORTED_FIELDS = %w[format storage node content resource_type name].freeze
|
|
20
|
+
|
|
21
|
+
# Applies selector to volume collection.
|
|
22
|
+
#
|
|
23
|
+
# @param volumes [Array<Models::Volume>] volumes to filter
|
|
24
|
+
# @return [Array<Models::Volume>] filtered volumes
|
|
25
|
+
def apply(volumes)
|
|
26
|
+
return volumes if empty?
|
|
27
|
+
|
|
28
|
+
volumes.select { |vol| matches?(vol) }
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
protected
|
|
32
|
+
|
|
33
|
+
# Extracts field value from Volume model.
|
|
34
|
+
#
|
|
35
|
+
# @param vol [Models::Volume] volume model
|
|
36
|
+
# @param field [String] field name
|
|
37
|
+
# @return [String, nil] field value
|
|
38
|
+
# @raise [ArgumentError] if field is not supported
|
|
39
|
+
def extract_value(vol, field)
|
|
40
|
+
case field
|
|
41
|
+
when "format"
|
|
42
|
+
vol.format
|
|
43
|
+
when "storage"
|
|
44
|
+
vol.storage
|
|
45
|
+
when "node"
|
|
46
|
+
vol.node
|
|
47
|
+
when "content"
|
|
48
|
+
vol.content
|
|
49
|
+
when "resource_type"
|
|
50
|
+
vol.resource_type
|
|
51
|
+
when "name"
|
|
52
|
+
vol.name
|
|
53
|
+
else
|
|
54
|
+
raise ArgumentError, "Unknown field: #{field}. Supported: #{SUPPORTED_FIELDS.join(', ')}"
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Pvectl
|
|
4
|
+
module Services
|
|
5
|
+
# Orchestrates backup operations.
|
|
6
|
+
#
|
|
7
|
+
# Handles creating, listing, deleting, and restoring backups
|
|
8
|
+
# for VMs and containers with multi-ID support and error handling.
|
|
9
|
+
#
|
|
10
|
+
# @example Basic usage
|
|
11
|
+
# service = Backup.new(
|
|
12
|
+
# backup_repo: backup_repo,
|
|
13
|
+
# resource_resolver: resolver,
|
|
14
|
+
# task_repo: task_repo
|
|
15
|
+
# )
|
|
16
|
+
# backups = service.list(vmid: 100)
|
|
17
|
+
#
|
|
18
|
+
class Backup
|
|
19
|
+
DEFAULT_TIMEOUT = 300 # 5 minutes for backups
|
|
20
|
+
|
|
21
|
+
# Creates a new Backup service.
|
|
22
|
+
#
|
|
23
|
+
# @param backup_repo [Repositories::Backup] Backup repository
|
|
24
|
+
# @param resource_resolver [Utils::ResourceResolver] Resource resolver
|
|
25
|
+
# @param task_repo [Repositories::Task] Task repository
|
|
26
|
+
# @param options [Hash] Options (timeout, async, fail_fast)
|
|
27
|
+
def initialize(backup_repo:, resource_resolver:, task_repo:, options: {})
|
|
28
|
+
@backup_repo = backup_repo
|
|
29
|
+
@resolver = resource_resolver
|
|
30
|
+
@task_repo = task_repo
|
|
31
|
+
@options = options
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Lists backups with optional filtering.
|
|
35
|
+
#
|
|
36
|
+
# @param vmid [Integer, nil] filter by VM ID
|
|
37
|
+
# @param storage [String, nil] filter by storage
|
|
38
|
+
# @return [Array<Models::Backup>]
|
|
39
|
+
def list(vmid: nil, storage: nil)
|
|
40
|
+
@backup_repo.list(vmid: vmid, storage: storage)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Creates backups for multiple VMs.
|
|
44
|
+
#
|
|
45
|
+
# @param vmids [Array<Integer>] VM IDs
|
|
46
|
+
# @param storage [String] target storage
|
|
47
|
+
# @param mode [String] backup mode (snapshot/suspend/stop)
|
|
48
|
+
# @param compress [String] compression (zstd/gzip/lzo/0)
|
|
49
|
+
# @param notes [String, nil] backup notes
|
|
50
|
+
# @param protected [Boolean] protect backup
|
|
51
|
+
# @return [Array<Models::OperationResult>]
|
|
52
|
+
def create(vmids, storage:, mode: "snapshot", compress: "zstd", notes: nil, protected: false)
|
|
53
|
+
resources = @resolver.resolve_multiple(vmids)
|
|
54
|
+
return [] if resources.empty?
|
|
55
|
+
|
|
56
|
+
execute_multi(resources, :create) do |resource|
|
|
57
|
+
@backup_repo.create(
|
|
58
|
+
resource[:vmid],
|
|
59
|
+
resource[:node],
|
|
60
|
+
storage: storage,
|
|
61
|
+
mode: mode,
|
|
62
|
+
compress: compress,
|
|
63
|
+
notes: notes,
|
|
64
|
+
protected: protected
|
|
65
|
+
)
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Deletes a backup.
|
|
70
|
+
#
|
|
71
|
+
# @param volid [String] backup volume ID
|
|
72
|
+
# @return [Models::OperationResult]
|
|
73
|
+
def delete(volid)
|
|
74
|
+
node = find_node_for_volid(volid)
|
|
75
|
+
|
|
76
|
+
upid = @backup_repo.delete(volid, node)
|
|
77
|
+
|
|
78
|
+
if @options[:async]
|
|
79
|
+
Models::OperationResult.new(
|
|
80
|
+
resource: { volid: volid, node: node },
|
|
81
|
+
operation: :delete,
|
|
82
|
+
task_upid: upid,
|
|
83
|
+
success: :pending
|
|
84
|
+
)
|
|
85
|
+
else
|
|
86
|
+
task = @task_repo.wait(upid, timeout: timeout)
|
|
87
|
+
Models::OperationResult.new(
|
|
88
|
+
resource: { volid: volid, node: node },
|
|
89
|
+
operation: :delete,
|
|
90
|
+
task: task,
|
|
91
|
+
success: task.successful?
|
|
92
|
+
)
|
|
93
|
+
end
|
|
94
|
+
rescue StandardError => e
|
|
95
|
+
Models::OperationResult.new(
|
|
96
|
+
resource: { volid: volid },
|
|
97
|
+
operation: :delete,
|
|
98
|
+
success: false,
|
|
99
|
+
error: e.message
|
|
100
|
+
)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# Restores a backup to a VM/container.
|
|
104
|
+
#
|
|
105
|
+
# @param volid [String] backup volume ID
|
|
106
|
+
# @param vmid [Integer] target VM ID
|
|
107
|
+
# @param storage [String, nil] target storage
|
|
108
|
+
# @param force [Boolean] overwrite existing
|
|
109
|
+
# @param start [Boolean] start after restore
|
|
110
|
+
# @param unique [Boolean] regenerate unique properties
|
|
111
|
+
# @return [Models::OperationResult]
|
|
112
|
+
def restore(volid, vmid:, storage: nil, force: false, start: false, unique: false)
|
|
113
|
+
node = find_node_for_volid(volid)
|
|
114
|
+
|
|
115
|
+
upid = @backup_repo.restore(
|
|
116
|
+
volid,
|
|
117
|
+
node,
|
|
118
|
+
vmid: vmid,
|
|
119
|
+
storage: storage,
|
|
120
|
+
force: force,
|
|
121
|
+
start: start,
|
|
122
|
+
unique: unique
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
if @options[:async]
|
|
126
|
+
Models::OperationResult.new(
|
|
127
|
+
resource: { volid: volid, vmid: vmid, node: node },
|
|
128
|
+
operation: :restore,
|
|
129
|
+
task_upid: upid,
|
|
130
|
+
success: :pending
|
|
131
|
+
)
|
|
132
|
+
else
|
|
133
|
+
task = @task_repo.wait(upid, timeout: timeout)
|
|
134
|
+
Models::OperationResult.new(
|
|
135
|
+
resource: { volid: volid, vmid: vmid, node: node },
|
|
136
|
+
operation: :restore,
|
|
137
|
+
task: task,
|
|
138
|
+
success: task.successful?
|
|
139
|
+
)
|
|
140
|
+
end
|
|
141
|
+
rescue StandardError => e
|
|
142
|
+
Models::OperationResult.new(
|
|
143
|
+
resource: { volid: volid, vmid: vmid },
|
|
144
|
+
operation: :restore,
|
|
145
|
+
success: false,
|
|
146
|
+
error: e.message
|
|
147
|
+
)
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
private
|
|
151
|
+
|
|
152
|
+
def execute_multi(resources, operation)
|
|
153
|
+
results = []
|
|
154
|
+
|
|
155
|
+
resources.each do |resource|
|
|
156
|
+
result = execute_single(resource, operation) { yield(resource) }
|
|
157
|
+
results << result
|
|
158
|
+
|
|
159
|
+
break if @options[:fail_fast] && result.failed?
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
results
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
def execute_single(resource, operation)
|
|
166
|
+
upid = yield
|
|
167
|
+
|
|
168
|
+
if @options[:async]
|
|
169
|
+
Models::OperationResult.new(
|
|
170
|
+
resource: resource,
|
|
171
|
+
operation: operation,
|
|
172
|
+
task_upid: upid,
|
|
173
|
+
success: :pending
|
|
174
|
+
)
|
|
175
|
+
else
|
|
176
|
+
task = @task_repo.wait(upid, timeout: timeout)
|
|
177
|
+
Models::OperationResult.new(
|
|
178
|
+
resource: resource,
|
|
179
|
+
operation: operation,
|
|
180
|
+
task: task,
|
|
181
|
+
success: task.successful?
|
|
182
|
+
)
|
|
183
|
+
end
|
|
184
|
+
rescue StandardError => e
|
|
185
|
+
Models::OperationResult.new(
|
|
186
|
+
resource: resource,
|
|
187
|
+
operation: operation,
|
|
188
|
+
success: false,
|
|
189
|
+
error: e.message
|
|
190
|
+
)
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
def timeout
|
|
194
|
+
@options[:timeout] || DEFAULT_TIMEOUT
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
# Finds the node for a backup by searching all backups.
|
|
198
|
+
#
|
|
199
|
+
# @param volid [String] backup volume ID
|
|
200
|
+
# @return [String] node name
|
|
201
|
+
# @raise [StandardError] if backup not found
|
|
202
|
+
def find_node_for_volid(volid)
|
|
203
|
+
backups = @backup_repo.list
|
|
204
|
+
backup = backups.find { |b| b.volid == volid }
|
|
205
|
+
backup&.node || raise("Backup not found: #{volid}")
|
|
206
|
+
end
|
|
207
|
+
end
|
|
208
|
+
end
|
|
209
|
+
end
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Pvectl
|
|
4
|
+
module Services
|
|
5
|
+
# Orchestrates container clone operations.
|
|
6
|
+
#
|
|
7
|
+
# Handles validation, auto-generation of CTID/hostname, and sync/async modes.
|
|
8
|
+
# Supports both full clones and linked clones (templates only).
|
|
9
|
+
#
|
|
10
|
+
# @example Full clone with auto-generated CTID
|
|
11
|
+
# service = CloneContainer.new(container_repository: ct_repo, task_repository: task_repo)
|
|
12
|
+
# result = service.execute(ctid: 100)
|
|
13
|
+
#
|
|
14
|
+
# @example Linked clone to specific node
|
|
15
|
+
# service = CloneContainer.new(container_repository: ct_repo, task_repository: task_repo)
|
|
16
|
+
# result = service.execute(ctid: 100, linked: true, target_node: "pve2")
|
|
17
|
+
#
|
|
18
|
+
# @example Async clone with custom timeout
|
|
19
|
+
# service = CloneContainer.new(container_repository: ct_repo, task_repository: task_repo, options: { async: true })
|
|
20
|
+
# result = service.execute(ctid: 100, new_ctid: 200, hostname: "web-clone")
|
|
21
|
+
#
|
|
22
|
+
class CloneContainer
|
|
23
|
+
DEFAULT_TIMEOUT = 300
|
|
24
|
+
|
|
25
|
+
# @return [Integer] Default timeout for start operations (seconds)
|
|
26
|
+
START_TIMEOUT = 60
|
|
27
|
+
|
|
28
|
+
# Creates a new CloneContainer service.
|
|
29
|
+
#
|
|
30
|
+
# @param container_repository [Repositories::Container] Container repository
|
|
31
|
+
# @param task_repository [Repositories::Task] Task repository
|
|
32
|
+
# @param options [Hash] Options (timeout, async)
|
|
33
|
+
def initialize(container_repository:, task_repository:, options: {})
|
|
34
|
+
@container_repository = container_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 container first, then optionally apply
|
|
42
|
+
# config updates via PUT /nodes/{node}/lxc/{ctid}/config.
|
|
43
|
+
#
|
|
44
|
+
# @param ctid [Integer] Source container identifier
|
|
45
|
+
# @param node [String, nil] Source node (auto-detected from container if nil)
|
|
46
|
+
# @param new_ctid [Integer, nil] New CTID (auto-selected if nil)
|
|
47
|
+
# @param hostname [String, nil] Hostname 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] Container config parameters to apply after clone
|
|
54
|
+
# @return [Models::ContainerOperationResult] Clone result
|
|
55
|
+
def execute(ctid:, node: nil, new_ctid: nil, hostname: nil, target_node: nil,
|
|
56
|
+
storage: nil, linked: false, pool: nil, description: nil,
|
|
57
|
+
config_params: {})
|
|
58
|
+
source_ct = @container_repository.get(ctid)
|
|
59
|
+
return container_not_found_error(ctid) unless source_ct
|
|
60
|
+
|
|
61
|
+
if linked && !source_ct.template?
|
|
62
|
+
return linked_clone_error(source_ct)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
node ||= source_ct.node
|
|
66
|
+
new_ctid ||= @container_repository.next_available_ctid
|
|
67
|
+
hostname ||= generate_hostname(source_ct)
|
|
68
|
+
|
|
69
|
+
clone_options = build_clone_options(
|
|
70
|
+
hostname: hostname, target_node: target_node, storage: storage,
|
|
71
|
+
linked: linked, pool: pool, description: description
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
upid = @container_repository.clone(ctid, node, new_ctid, clone_options)
|
|
75
|
+
resource_info = { new_ctid: new_ctid, hostname: hostname, node: target_node || node }
|
|
76
|
+
|
|
77
|
+
if @options[:async]
|
|
78
|
+
Models::ContainerOperationResult.new(
|
|
79
|
+
container: source_ct, 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::ContainerOperationResult.new(
|
|
88
|
+
container: source_ct, 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_ct, new_ctid, resource_info[:node], config_params, resource_info)
|
|
96
|
+
else
|
|
97
|
+
start_container(new_ctid, resource_info[:node]) if @options[:start]
|
|
98
|
+
Models::ContainerOperationResult.new(
|
|
99
|
+
container: source_ct, operation: :clone,
|
|
100
|
+
task: task, success: true,
|
|
101
|
+
resource: resource_info
|
|
102
|
+
)
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
rescue StandardError => e
|
|
106
|
+
Models::ContainerOperationResult.new(
|
|
107
|
+
container: source_ct, operation: :clone,
|
|
108
|
+
success: false, error: e.message
|
|
109
|
+
)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
private
|
|
113
|
+
|
|
114
|
+
# Generates clone hostname from source container.
|
|
115
|
+
#
|
|
116
|
+
# @param source_ct [Models::Container] Source container
|
|
117
|
+
# @return [String] Generated hostname
|
|
118
|
+
def generate_hostname(source_ct)
|
|
119
|
+
if source_ct.name && !source_ct.name.empty?
|
|
120
|
+
"#{source_ct.name}-clone"
|
|
121
|
+
else
|
|
122
|
+
"ct-#{source_ct.vmid}-clone"
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# Builds clone options hash for repository call.
|
|
127
|
+
#
|
|
128
|
+
# @param hostname [String] Clone hostname
|
|
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(hostname:, target_node:, storage:, linked:, pool:, description:)
|
|
136
|
+
opts = { hostname: hostname, 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 container.
|
|
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_ct [Models::Container] Source container
|
|
150
|
+
# @param new_ctid [Integer] Cloned container identifier
|
|
151
|
+
# @param node [String] Target node for the cloned container
|
|
152
|
+
# @param config_params [Hash] Config parameters to apply
|
|
153
|
+
# @param resource_info [Hash] Resource info for result
|
|
154
|
+
# @return [Models::ContainerOperationResult] Operation result
|
|
155
|
+
def apply_config_update(source_ct, new_ctid, node, config_params, resource_info)
|
|
156
|
+
api_params = build_ct_config_api_params(config_params)
|
|
157
|
+
@container_repository.update(new_ctid, node, api_params)
|
|
158
|
+
start_container(new_ctid, node) if @options[:start]
|
|
159
|
+
Models::ContainerOperationResult.new(
|
|
160
|
+
container: source_ct, operation: :clone,
|
|
161
|
+
success: true, resource: resource_info
|
|
162
|
+
)
|
|
163
|
+
rescue StandardError => e
|
|
164
|
+
Models::ContainerOperationResult.new(
|
|
165
|
+
container: source_ct, 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 container config options.
|
|
172
|
+
#
|
|
173
|
+
# Maps config keys to their Proxmox API equivalents. Does not include
|
|
174
|
+
# hostname, ostemplate, 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_ct_config_api_params(config_params)
|
|
179
|
+
params = {}
|
|
180
|
+
params[:cores] = config_params[:cores] if config_params[:cores]
|
|
181
|
+
params[:memory] = config_params[:memory] if config_params[:memory]
|
|
182
|
+
params[:swap] = config_params[:swap] if config_params[:swap]
|
|
183
|
+
params[:unprivileged] = config_params[:privileged] ? 0 : 1 unless config_params[:privileged].nil?
|
|
184
|
+
params[:rootfs] = Parsers::LxcMountConfig.to_proxmox(config_params[:rootfs]) if config_params[:rootfs]
|
|
185
|
+
add_mountpoint_params(params, config_params[:mountpoints]) if config_params[:mountpoints]
|
|
186
|
+
add_net_params(params, config_params[:nets]) if config_params[:nets]
|
|
187
|
+
params[:features] = config_params[:features] if config_params[:features]
|
|
188
|
+
params[:password] = config_params[:password] if config_params[:password]
|
|
189
|
+
params[:"ssh-public-keys"] = config_params[:ssh_public_keys] if config_params[:ssh_public_keys]
|
|
190
|
+
params[:onboot] = config_params[:onboot] ? 1 : 0 unless config_params[:onboot].nil?
|
|
191
|
+
params[:startup] = config_params[:startup] if config_params[:startup]
|
|
192
|
+
params[:tags] = config_params[:tags] if config_params[:tags]
|
|
193
|
+
params
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
# Adds mountpoint parameters mapped to mp0, mp1, etc.
|
|
197
|
+
#
|
|
198
|
+
# @param params [Hash] Parameters hash to modify
|
|
199
|
+
# @param mountpoints [Array<Hash>] Mountpoint configurations
|
|
200
|
+
# @return [void]
|
|
201
|
+
def add_mountpoint_params(params, mountpoints)
|
|
202
|
+
mountpoints.each_with_index do |mp, index|
|
|
203
|
+
params[:"mp#{index}"] = Parsers::LxcMountConfig.to_proxmox(mp)
|
|
204
|
+
end
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
# Adds network parameters mapped to net0, net1, etc.
|
|
208
|
+
#
|
|
209
|
+
# @param params [Hash] Parameters hash to modify
|
|
210
|
+
# @param nets [Array<Hash>] Network configurations
|
|
211
|
+
# @return [void]
|
|
212
|
+
def add_net_params(params, nets)
|
|
213
|
+
nets.each_with_index do |net, index|
|
|
214
|
+
params[:"net#{index}"] = Parsers::LxcNetConfig.to_proxmox(net)
|
|
215
|
+
end
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
# Starts a container after successful clone and config update.
|
|
219
|
+
#
|
|
220
|
+
# @param ctid [Integer] Container identifier
|
|
221
|
+
# @param node [String] Node name
|
|
222
|
+
# @return [void]
|
|
223
|
+
def start_container(ctid, node)
|
|
224
|
+
upid = @container_repository.start(ctid, node)
|
|
225
|
+
@task_repository.wait(upid, timeout: START_TIMEOUT)
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
# Returns configured timeout.
|
|
229
|
+
#
|
|
230
|
+
# @return [Integer] Timeout in seconds
|
|
231
|
+
def timeout
|
|
232
|
+
@options[:timeout] || DEFAULT_TIMEOUT
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
# Returns error for container not found.
|
|
236
|
+
#
|
|
237
|
+
# @param ctid [Integer] Container identifier
|
|
238
|
+
# @return [Models::ContainerOperationResult] Failed result
|
|
239
|
+
def container_not_found_error(ctid)
|
|
240
|
+
Models::ContainerOperationResult.new(
|
|
241
|
+
operation: :clone,
|
|
242
|
+
success: false,
|
|
243
|
+
error: "Container #{ctid} not found"
|
|
244
|
+
)
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
# Returns error for linked clone of non-template container.
|
|
248
|
+
#
|
|
249
|
+
# @param source_ct [Models::Container] Source container
|
|
250
|
+
# @return [Models::ContainerOperationResult] Failed result
|
|
251
|
+
def linked_clone_error(source_ct)
|
|
252
|
+
Models::ContainerOperationResult.new(
|
|
253
|
+
container: source_ct, operation: :clone,
|
|
254
|
+
success: false,
|
|
255
|
+
error: "Linked clone requires container to be a template. Container #{source_ct.vmid} is not a template"
|
|
256
|
+
)
|
|
257
|
+
end
|
|
258
|
+
end
|
|
259
|
+
end
|
|
260
|
+
end
|