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,352 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Pvectl
|
|
4
|
+
module Commands
|
|
5
|
+
# Push command -- applies YAML manifests to the Proxmox cluster.
|
|
6
|
+
# Creates resources that don't exist, updates those that do.
|
|
7
|
+
#
|
|
8
|
+
# @example Register with CLI
|
|
9
|
+
# Push.register(cli)
|
|
10
|
+
class Push
|
|
11
|
+
RESOURCE_TYPES = {
|
|
12
|
+
"vm" => :vm,
|
|
13
|
+
"vms" => :vm,
|
|
14
|
+
"container" => :container,
|
|
15
|
+
"containers" => :container,
|
|
16
|
+
"ct" => :container
|
|
17
|
+
}.freeze
|
|
18
|
+
|
|
19
|
+
# Registers the push command with the CLI.
|
|
20
|
+
#
|
|
21
|
+
# @param cli [GLI::App] the CLI application object
|
|
22
|
+
# @return [void]
|
|
23
|
+
def self.register(cli)
|
|
24
|
+
cli.desc "Push YAML manifest to cluster (create or update)"
|
|
25
|
+
cli.long_desc <<~HELP
|
|
26
|
+
DESCRIPTION
|
|
27
|
+
Applies resource configuration from YAML manifest files to the
|
|
28
|
+
Proxmox cluster. Creates resources that don't exist, updates those
|
|
29
|
+
that do. Shows a diff and asks for confirmation before applying.
|
|
30
|
+
|
|
31
|
+
EXAMPLES
|
|
32
|
+
$ pvectl push vm -f vm-100.yaml
|
|
33
|
+
$ pvectl push vm -f ./manifests/
|
|
34
|
+
$ pvectl push -f ./manifests/
|
|
35
|
+
$ pvectl push vm -f vm-100.yaml --dry-run
|
|
36
|
+
$ pvectl push vm -f vm-100.yaml --yes
|
|
37
|
+
$ pvectl push vm -f vm-new.yaml # no vmid → auto-assign
|
|
38
|
+
$ pvectl pull vm 100 | pvectl push --yes
|
|
39
|
+
$ cat vm-100.yaml | pvectl push vm --dry-run
|
|
40
|
+
|
|
41
|
+
NOTES
|
|
42
|
+
Without -f, reads YAML from stdin (pipe-friendly).
|
|
43
|
+
With -f, reads from file or directory (repeatable).
|
|
44
|
+
Without resource type, reads kind from each manifest.
|
|
45
|
+
If metadata.vmid is omitted, a new VMID is auto-assigned
|
|
46
|
+
and the source YAML file is updated with the assigned ID.
|
|
47
|
+
Stdin mode requires --yes or --dry-run (no interactive prompt).
|
|
48
|
+
With --yes, skips confirmation (useful for CI/CD).
|
|
49
|
+
With --dry-run, shows diff without applying changes.
|
|
50
|
+
|
|
51
|
+
SEE ALSO
|
|
52
|
+
pull, edit, create, delete
|
|
53
|
+
HELP
|
|
54
|
+
|
|
55
|
+
cli.command :push do |c|
|
|
56
|
+
c.flag [:f, :file], desc: "YAML file or directory to push", multiple: true
|
|
57
|
+
c.switch [:y, :yes], desc: "Auto-confirm without prompting", negatable: false
|
|
58
|
+
c.switch [:"dry-run"], desc: "Show diff without applying", negatable: false
|
|
59
|
+
|
|
60
|
+
c.action do |global_options, options, args|
|
|
61
|
+
Push.new(args, options, global_options).execute
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# @param args [Array<String>] command arguments
|
|
67
|
+
# @param options [Hash] command options
|
|
68
|
+
# @param global_options [Hash] global CLI options
|
|
69
|
+
def initialize(args, options, global_options)
|
|
70
|
+
@args = args
|
|
71
|
+
@options = options
|
|
72
|
+
@global_options = global_options
|
|
73
|
+
@stdin_mode = false
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Executes the push command.
|
|
77
|
+
#
|
|
78
|
+
# @return [Integer] exit code
|
|
79
|
+
def execute
|
|
80
|
+
args = @args.dup
|
|
81
|
+
filter_type = parse_resource_type(args)
|
|
82
|
+
|
|
83
|
+
unless args.empty?
|
|
84
|
+
return usage_error("Unexpected arguments: #{args.join(', ')}. Use -f to specify files.")
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
yaml_contents = read_input
|
|
88
|
+
return usage_error("No YAML content provided. Use -f <path> or pipe YAML to stdin.") if yaml_contents.empty?
|
|
89
|
+
|
|
90
|
+
load_config
|
|
91
|
+
connection = Pvectl::Connection.new(@config)
|
|
92
|
+
service, pull_service = build_services(connection)
|
|
93
|
+
|
|
94
|
+
result = service.prepare_batch(yaml_contents, filter_type: filter_type)
|
|
95
|
+
|
|
96
|
+
# Report errors and skipped
|
|
97
|
+
result[:errors].each { |e| $stderr.puts "Error: #{e}" }
|
|
98
|
+
result[:skipped].each { |s| $stderr.puts "Info: #{s}" }
|
|
99
|
+
|
|
100
|
+
if result[:plans].empty?
|
|
101
|
+
if result[:errors].empty?
|
|
102
|
+
$stdout.puts "No changes to apply."
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# Refresh unchanged file-backed manifests with server-assigned values
|
|
106
|
+
# (MAC addresses, volume names, UUIDs, etc.)
|
|
107
|
+
refresh_unchanged_manifests(result[:unchanged] || [], pull_service)
|
|
108
|
+
|
|
109
|
+
return result[:errors].empty? ? ExitCodes::SUCCESS : ExitCodes::GENERAL_ERROR
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# Display plans
|
|
113
|
+
display_plans(result[:plans])
|
|
114
|
+
|
|
115
|
+
if @options[:"dry-run"]
|
|
116
|
+
$stdout.puts "\n(dry-run mode -- no changes applied)"
|
|
117
|
+
return ExitCodes::SUCCESS
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# Confirm unless --yes
|
|
121
|
+
unless @options[:yes]
|
|
122
|
+
if @stdin_mode
|
|
123
|
+
return usage_error("Stdin mode requires --yes or --dry-run (no interactive prompt available)")
|
|
124
|
+
end
|
|
125
|
+
$stdout.print "\nApply #{result[:plans].length} change(s)? [y/N] "
|
|
126
|
+
answer = $stdin.gets&.strip&.downcase
|
|
127
|
+
unless answer == "y" || answer == "yes"
|
|
128
|
+
$stdout.puts "Cancelled."
|
|
129
|
+
return ExitCodes::SUCCESS
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# Apply
|
|
134
|
+
apply_result = service.apply(result[:plans])
|
|
135
|
+
|
|
136
|
+
apply_result[:results].each do |r|
|
|
137
|
+
if r[:success]
|
|
138
|
+
$stdout.puts "#{r[:action].capitalize}d #{type_label_for(r)} #{r[:vmid]} successfully."
|
|
139
|
+
else
|
|
140
|
+
$stderr.puts "Error: Failed to #{r[:action]} #{r[:vmid]}: #{r[:error]}"
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
# Refresh source manifest files with current server state
|
|
145
|
+
refresh_manifests(apply_result[:results], result[:plans], pull_service)
|
|
146
|
+
refresh_unchanged_manifests(result[:unchanged] || [], pull_service)
|
|
147
|
+
|
|
148
|
+
apply_result[:errors].empty? ? ExitCodes::SUCCESS : ExitCodes::GENERAL_ERROR
|
|
149
|
+
rescue Pvectl::Config::ConfigNotFoundError,
|
|
150
|
+
Pvectl::Config::InvalidConfigError,
|
|
151
|
+
Pvectl::Config::ContextNotFoundError,
|
|
152
|
+
Pvectl::Config::ClusterNotFoundError,
|
|
153
|
+
Pvectl::Config::UserNotFoundError
|
|
154
|
+
raise
|
|
155
|
+
rescue StandardError => e
|
|
156
|
+
$stderr.puts "Error: #{e.message}"
|
|
157
|
+
ExitCodes::GENERAL_ERROR
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
private
|
|
161
|
+
|
|
162
|
+
# Reads YAML input from -f flag (files/directories) or stdin.
|
|
163
|
+
#
|
|
164
|
+
# @return [Array<Hash>] array of { filename: String, content: String }
|
|
165
|
+
def read_input
|
|
166
|
+
file_flag = @options[:file]
|
|
167
|
+
if file_flag && !file_flag.empty?
|
|
168
|
+
@stdin_mode = false
|
|
169
|
+
collect_yaml_contents(Array(file_flag))
|
|
170
|
+
else
|
|
171
|
+
@stdin_mode = true
|
|
172
|
+
read_stdin
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
# Reads YAML content from stdin.
|
|
177
|
+
#
|
|
178
|
+
# @return [Array<Hash>] array of { filename: String, content: String }
|
|
179
|
+
def read_stdin
|
|
180
|
+
if $stdin.tty?
|
|
181
|
+
$stderr.puts "Error: No input. Use -f <path> or pipe YAML to stdin."
|
|
182
|
+
return []
|
|
183
|
+
end
|
|
184
|
+
content = $stdin.read
|
|
185
|
+
return [] if content.nil? || content.strip.empty?
|
|
186
|
+
|
|
187
|
+
[{ filename: "stdin", content: content }]
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
# Parses and removes the optional resource type from the argument list.
|
|
191
|
+
#
|
|
192
|
+
# @param args [Array<String>] argument list (modified in place)
|
|
193
|
+
# @return [Symbol, nil] :vm or :container, or nil if first arg is not a type
|
|
194
|
+
def parse_resource_type(args)
|
|
195
|
+
return nil if args.empty?
|
|
196
|
+
|
|
197
|
+
first = args.first.downcase
|
|
198
|
+
if RESOURCE_TYPES.key?(first)
|
|
199
|
+
args.shift
|
|
200
|
+
RESOURCE_TYPES[first]
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
# Collects YAML file contents from given paths (files or directories).
|
|
205
|
+
#
|
|
206
|
+
# @param paths [Array<String>] file or directory paths
|
|
207
|
+
# @return [Array<Hash>] array of { filename: String, content: String, path: String? }
|
|
208
|
+
def collect_yaml_contents(paths)
|
|
209
|
+
contents = []
|
|
210
|
+
paths.each do |path|
|
|
211
|
+
if File.directory?(path)
|
|
212
|
+
Dir.glob(File.join(path, "*.{yaml,yml}")).sort.each do |file|
|
|
213
|
+
contents << { filename: File.basename(file), content: File.read(file), path: File.expand_path(file) }
|
|
214
|
+
end
|
|
215
|
+
elsif File.file?(path)
|
|
216
|
+
contents << { filename: File.basename(path), content: File.read(path), path: File.expand_path(path) }
|
|
217
|
+
else
|
|
218
|
+
$stderr.puts "Error: File not found: #{path}"
|
|
219
|
+
end
|
|
220
|
+
end
|
|
221
|
+
contents
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
# Displays push plans with diffs to stdout.
|
|
225
|
+
#
|
|
226
|
+
# @param plans [Array<Hash>] prepared push plans
|
|
227
|
+
# @return [void]
|
|
228
|
+
def display_plans(plans)
|
|
229
|
+
plans.each do |plan|
|
|
230
|
+
label = plan[:type] == :container ? "Container" : "VM"
|
|
231
|
+
if plan[:action] == :update
|
|
232
|
+
$stdout.puts "\n#{label} #{plan[:vmid]} (#{plan[:node]}) -- UPDATE:"
|
|
233
|
+
$stdout.puts ConfigSerializer.format_diff(plan[:diff])
|
|
234
|
+
if plan[:resize_ops]&.any?
|
|
235
|
+
plan[:resize_ops].each do |op|
|
|
236
|
+
$stdout.puts " (disk resize: #{op[:disk]} -> #{op[:size]})"
|
|
237
|
+
end
|
|
238
|
+
end
|
|
239
|
+
elsif plan[:action] == :create
|
|
240
|
+
id_note = plan[:auto_id] ? " (auto-assigned)" : ""
|
|
241
|
+
$stdout.puts "\n#{label} #{plan[:vmid]}#{id_note} (#{plan[:node]}) -- CREATE:"
|
|
242
|
+
plan[:params].each do |key, val|
|
|
243
|
+
$stdout.puts " + #{key}: #{val}"
|
|
244
|
+
end
|
|
245
|
+
end
|
|
246
|
+
end
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
# Returns a human-readable type label for a result hash.
|
|
250
|
+
#
|
|
251
|
+
# @param result [Hash] apply result with optional :type key
|
|
252
|
+
# @return [String] "VM" or "Container"
|
|
253
|
+
def type_label_for(result)
|
|
254
|
+
result[:type] == :container ? "Container" : "VM"
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
# Refreshes source manifest files with the current server state after
|
|
258
|
+
# successful apply. Re-pulls each resource and overwrites the source file,
|
|
259
|
+
# ensuring local manifests include server-assigned values (volume names,
|
|
260
|
+
# MAC addresses, etc.).
|
|
261
|
+
#
|
|
262
|
+
# @param results [Array<Hash>] apply results
|
|
263
|
+
# @param plans [Array<Hash>] original plans (same order as results)
|
|
264
|
+
# @param pull_service [Services::PullConfig] pull service for re-pulling
|
|
265
|
+
# @return [void]
|
|
266
|
+
def refresh_manifests(results, plans, pull_service)
|
|
267
|
+
results.each_with_index do |r, idx|
|
|
268
|
+
next unless r[:success]
|
|
269
|
+
|
|
270
|
+
plan = plans[idx]
|
|
271
|
+
source_path = plan[:source_path]
|
|
272
|
+
next unless source_path && File.file?(source_path)
|
|
273
|
+
|
|
274
|
+
refresh_manifest_file(source_path, plan[:type], plan[:vmid], pull_service)
|
|
275
|
+
end
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
# Refreshes file-backed manifests that had no changes to apply.
|
|
279
|
+
# Re-pulls each resource to fill in server-assigned values (MAC addresses,
|
|
280
|
+
# volume names, UUIDs, etc.) that may not be in the local manifest.
|
|
281
|
+
#
|
|
282
|
+
# @param unchanged [Array<Hash>] unchanged entries from prepare_batch
|
|
283
|
+
# @param pull_service [Services::PullConfig] pull service for re-pulling
|
|
284
|
+
# @return [void]
|
|
285
|
+
def refresh_unchanged_manifests(unchanged, pull_service)
|
|
286
|
+
unchanged.each do |entry|
|
|
287
|
+
next unless entry[:source_path] && File.file?(entry[:source_path])
|
|
288
|
+
|
|
289
|
+
refresh_manifest_file(entry[:source_path], entry[:type], entry[:vmid], pull_service)
|
|
290
|
+
end
|
|
291
|
+
end
|
|
292
|
+
|
|
293
|
+
# Re-pulls a single resource and writes the updated YAML to the source file.
|
|
294
|
+
#
|
|
295
|
+
# @param path [String] source manifest file path
|
|
296
|
+
# @param type [Symbol] :vm or :container
|
|
297
|
+
# @param vmid [Integer] resource ID
|
|
298
|
+
# @param pull_service [Services::PullConfig] pull service
|
|
299
|
+
# @return [void]
|
|
300
|
+
def refresh_manifest_file(path, type, vmid, pull_service)
|
|
301
|
+
pull_result = pull_service.execute(type: type, ids: [vmid])
|
|
302
|
+
return if pull_result[:manifests].empty?
|
|
303
|
+
|
|
304
|
+
File.write(path, pull_result[:manifests].first[:yaml])
|
|
305
|
+
$stderr.puts "Refreshed #{path}"
|
|
306
|
+
rescue StandardError => e
|
|
307
|
+
$stderr.puts "Warning: Could not refresh #{path}: #{e.message}"
|
|
308
|
+
end
|
|
309
|
+
|
|
310
|
+
# Builds the PushConfig and PullConfig services with shared repositories.
|
|
311
|
+
#
|
|
312
|
+
# @param connection [Connection] API connection
|
|
313
|
+
# @return [Array(Services::PushConfig, Services::PullConfig)]
|
|
314
|
+
def build_services(connection)
|
|
315
|
+
vm_repo = Pvectl::Repositories::Vm.new(connection)
|
|
316
|
+
ct_repo = Pvectl::Repositories::Container.new(connection)
|
|
317
|
+
task_repo = Pvectl::Repositories::Task.new(connection)
|
|
318
|
+
|
|
319
|
+
push_service = Pvectl::Services::PushConfig.new(
|
|
320
|
+
vm_repository: vm_repo,
|
|
321
|
+
container_repository: ct_repo,
|
|
322
|
+
task_repository: task_repo
|
|
323
|
+
)
|
|
324
|
+
|
|
325
|
+
pull_service = Pvectl::Services::PullConfig.new(
|
|
326
|
+
vm_repository: vm_repo,
|
|
327
|
+
container_repository: ct_repo
|
|
328
|
+
)
|
|
329
|
+
|
|
330
|
+
[push_service, pull_service]
|
|
331
|
+
end
|
|
332
|
+
|
|
333
|
+
# Loads configuration from file/env.
|
|
334
|
+
#
|
|
335
|
+
# @return [void]
|
|
336
|
+
def load_config
|
|
337
|
+
service = Pvectl::Config::Service.new
|
|
338
|
+
service.load(config: @global_options[:config])
|
|
339
|
+
@config = service.current_config
|
|
340
|
+
end
|
|
341
|
+
|
|
342
|
+
# Prints a usage error and returns the USAGE_ERROR exit code.
|
|
343
|
+
#
|
|
344
|
+
# @param message [String] error message
|
|
345
|
+
# @return [Integer] USAGE_ERROR exit code
|
|
346
|
+
def usage_error(message)
|
|
347
|
+
$stderr.puts "Error: #{message}"
|
|
348
|
+
ExitCodes::USAGE_ERROR
|
|
349
|
+
end
|
|
350
|
+
end
|
|
351
|
+
end
|
|
352
|
+
end
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Pvectl
|
|
4
|
+
module Commands
|
|
5
|
+
# Handler for the `pvectl reset` command.
|
|
6
|
+
#
|
|
7
|
+
# Resets one or more virtual machines (hard reset).
|
|
8
|
+
#
|
|
9
|
+
# @example Reset a single VM
|
|
10
|
+
# pvectl reset vm 100
|
|
11
|
+
#
|
|
12
|
+
# @example Reset with JSON output
|
|
13
|
+
# pvectl reset vm 100 -o json
|
|
14
|
+
#
|
|
15
|
+
class Reset
|
|
16
|
+
include VmLifecycleCommand
|
|
17
|
+
|
|
18
|
+
# Registers the reset command with the CLI.
|
|
19
|
+
#
|
|
20
|
+
# @param cli [GLI::App] the CLI application object
|
|
21
|
+
# @return [void]
|
|
22
|
+
def self.register(cli)
|
|
23
|
+
cli.desc "Reset virtual machines (hard reset)"
|
|
24
|
+
cli.long_desc <<~HELP
|
|
25
|
+
Hard reset one or more virtual machines. Equivalent to pressing the
|
|
26
|
+
physical reset button — the VM is immediately restarted without
|
|
27
|
+
graceful OS shutdown.
|
|
28
|
+
|
|
29
|
+
Only available for VMs. Containers do not support hard reset.
|
|
30
|
+
|
|
31
|
+
EXAMPLES
|
|
32
|
+
Hard reset a VM:
|
|
33
|
+
$ pvectl reset vm 100
|
|
34
|
+
|
|
35
|
+
Reset multiple VMs:
|
|
36
|
+
$ pvectl reset vm 100 101 102
|
|
37
|
+
|
|
38
|
+
NOTES
|
|
39
|
+
May cause data loss or filesystem corruption. Use 'pvectl restart'
|
|
40
|
+
for a graceful reboot instead.
|
|
41
|
+
|
|
42
|
+
Not available for containers — use 'pvectl restart ct' instead.
|
|
43
|
+
|
|
44
|
+
SEE ALSO
|
|
45
|
+
pvectl help restart Graceful reboot (VMs and containers)
|
|
46
|
+
pvectl help stop Hard stop without restart
|
|
47
|
+
HELP
|
|
48
|
+
cli.arg_name "RESOURCE_TYPE [ID...]"
|
|
49
|
+
cli.command :reset do |c|
|
|
50
|
+
SharedFlags.lifecycle(c)
|
|
51
|
+
|
|
52
|
+
c.action do |global_options, options, args|
|
|
53
|
+
resource_type = args.shift
|
|
54
|
+
resource_ids = args
|
|
55
|
+
exit_code = execute(resource_type, resource_ids, options, global_options)
|
|
56
|
+
exit exit_code if exit_code != 0
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
OPERATION = :reset
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Pvectl
|
|
4
|
+
module Commands
|
|
5
|
+
# Shared functionality for lifecycle commands across resource types.
|
|
6
|
+
#
|
|
7
|
+
# Template method pattern: provides common flow (validate, resolve,
|
|
8
|
+
# confirm, execute, output) while specialization modules define
|
|
9
|
+
# resource-specific hooks.
|
|
10
|
+
#
|
|
11
|
+
# @abstract Include a specialization module (VmLifecycleCommand,
|
|
12
|
+
# ContainerLifecycleCommand) instead of this one directly.
|
|
13
|
+
#
|
|
14
|
+
# @example Specialization module pattern
|
|
15
|
+
# module VmLifecycleCommand
|
|
16
|
+
# def self.included(base)
|
|
17
|
+
# base.include(ResourceLifecycleCommand)
|
|
18
|
+
# end
|
|
19
|
+
#
|
|
20
|
+
# private
|
|
21
|
+
# def supported_resources = %w[vm]
|
|
22
|
+
# def resource_label = "VM"
|
|
23
|
+
# # ...
|
|
24
|
+
# end
|
|
25
|
+
#
|
|
26
|
+
module ResourceLifecycleCommand
|
|
27
|
+
# Class methods added when the module is included.
|
|
28
|
+
module ClassMethods
|
|
29
|
+
# Executes the lifecycle command.
|
|
30
|
+
#
|
|
31
|
+
# @param resource_type [String, nil] resource type (vm, ct)
|
|
32
|
+
# @param resource_ids [Array<String>, String, nil] resource identifiers
|
|
33
|
+
# @param options [Hash] command options
|
|
34
|
+
# @param global_options [Hash] global CLI options
|
|
35
|
+
# @return [Integer] exit code
|
|
36
|
+
def execute(resource_type, resource_ids, options, global_options)
|
|
37
|
+
new(resource_type, resource_ids, options, global_options).execute
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Hook called when module is included.
|
|
42
|
+
#
|
|
43
|
+
# @param base [Class] the class including this module
|
|
44
|
+
def self.included(base)
|
|
45
|
+
base.extend(ClassMethods)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Initializes a lifecycle command.
|
|
49
|
+
#
|
|
50
|
+
# @param resource_type [String, nil] resource type
|
|
51
|
+
# @param resource_ids [Array<String>, String, nil] resource identifiers
|
|
52
|
+
# @param options [Hash] command options
|
|
53
|
+
# @param global_options [Hash] global CLI options
|
|
54
|
+
def initialize(resource_type, resource_ids, options, global_options)
|
|
55
|
+
@resource_type = resource_type
|
|
56
|
+
@resource_ids = Array(resource_ids).compact
|
|
57
|
+
@options = options
|
|
58
|
+
@global_options = global_options
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Executes the lifecycle command.
|
|
62
|
+
#
|
|
63
|
+
# @return [Integer] exit code
|
|
64
|
+
def execute
|
|
65
|
+
return usage_error("Resource type required (#{supported_resources.join(', ')})") unless @resource_type
|
|
66
|
+
return usage_error("Unsupported resource: #{@resource_type}") unless supported_resources.include?(@resource_type)
|
|
67
|
+
|
|
68
|
+
if @resource_ids.empty? && !@options[:all] && selector_strings.empty?
|
|
69
|
+
return usage_error("#{resource_id_label}, --all, or -l selector required")
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
perform_operation
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
private
|
|
76
|
+
|
|
77
|
+
# --- Template methods (override in specialization) ---
|
|
78
|
+
|
|
79
|
+
# @return [Array<String>] supported resource type strings
|
|
80
|
+
def supported_resources
|
|
81
|
+
raise NotImplementedError, "#{self.class} must implement #supported_resources"
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# @return [String] human label for resource ("VM", "container")
|
|
85
|
+
def resource_label
|
|
86
|
+
raise NotImplementedError, "#{self.class} must implement #resource_label"
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# @return [String] human label for resource ID ("VMID", "CTID")
|
|
90
|
+
def resource_id_label
|
|
91
|
+
raise NotImplementedError, "#{self.class} must implement #resource_id_label"
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# @return [Object] repository for this resource type
|
|
95
|
+
def build_repository(connection)
|
|
96
|
+
raise NotImplementedError, "#{self.class} must implement #build_repository"
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# @return [Object] lifecycle service
|
|
100
|
+
def build_service(repo, task_repo, options)
|
|
101
|
+
raise NotImplementedError, "#{self.class} must implement #build_service"
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# @return [Object] presenter for results
|
|
105
|
+
def build_presenter
|
|
106
|
+
raise NotImplementedError, "#{self.class} must implement #build_presenter"
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# @return [Object] selector for filtering
|
|
110
|
+
def build_selector(strings)
|
|
111
|
+
raise NotImplementedError, "#{self.class} must implement #build_selector"
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# --- Shared implementation ---
|
|
115
|
+
|
|
116
|
+
# Performs the lifecycle operation.
|
|
117
|
+
#
|
|
118
|
+
# @return [Integer] exit code
|
|
119
|
+
def perform_operation
|
|
120
|
+
load_config
|
|
121
|
+
connection = Pvectl::Connection.new(@config)
|
|
122
|
+
|
|
123
|
+
repo = build_repository(connection)
|
|
124
|
+
task_repo = Pvectl::Repositories::Task.new(connection)
|
|
125
|
+
|
|
126
|
+
resources = resolve_resources(repo)
|
|
127
|
+
return no_resources_found if resources.empty?
|
|
128
|
+
return Pvectl::ExitCodes::SUCCESS unless confirm_operation(resources)
|
|
129
|
+
|
|
130
|
+
service = build_service(repo, task_repo, service_options)
|
|
131
|
+
results = service.execute(operation_name, resources)
|
|
132
|
+
|
|
133
|
+
output_results(results)
|
|
134
|
+
determine_exit_code(results)
|
|
135
|
+
rescue Pvectl::Config::ConfigNotFoundError,
|
|
136
|
+
Pvectl::Config::InvalidConfigError,
|
|
137
|
+
Pvectl::Config::ContextNotFoundError,
|
|
138
|
+
Pvectl::Config::ClusterNotFoundError,
|
|
139
|
+
Pvectl::Config::UserNotFoundError
|
|
140
|
+
raise
|
|
141
|
+
rescue StandardError => e
|
|
142
|
+
$stderr.puts "Error: #{e.message}"
|
|
143
|
+
Pvectl::ExitCodes::GENERAL_ERROR
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
# Resolves resources based on resource_ids, --all flag, or selectors.
|
|
147
|
+
#
|
|
148
|
+
# @param repo [Object] resource repository
|
|
149
|
+
# @return [Array<Object>] resolved resources
|
|
150
|
+
def resolve_resources(repo)
|
|
151
|
+
resources = if @options[:all]
|
|
152
|
+
repo.list(node: @options[:node])
|
|
153
|
+
elsif @resource_ids.any?
|
|
154
|
+
resolved = @resource_ids.map { |id| repo.get(id.to_i) }.compact
|
|
155
|
+
resolved = resolved.select { |r| r.node == @options[:node] } if @options[:node]
|
|
156
|
+
resolved
|
|
157
|
+
else
|
|
158
|
+
return [] if selector_strings.empty?
|
|
159
|
+
|
|
160
|
+
repo.list(node: @options[:node])
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
apply_selectors(resources)
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
# Returns selector strings from options.
|
|
167
|
+
#
|
|
168
|
+
# @return [Array<String>] selector strings
|
|
169
|
+
def selector_strings
|
|
170
|
+
Array(@options[:selector] || @options[:l])
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
# Applies selectors to resource collection.
|
|
174
|
+
#
|
|
175
|
+
# @param resources [Array<Object>] resources to filter
|
|
176
|
+
# @return [Array<Object>] filtered resources
|
|
177
|
+
def apply_selectors(resources)
|
|
178
|
+
return resources if selector_strings.empty?
|
|
179
|
+
|
|
180
|
+
selector = build_selector(selector_strings)
|
|
181
|
+
selector.apply(resources)
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
# Confirms multi-resource operation with user.
|
|
185
|
+
#
|
|
186
|
+
# @param resources [Array<Object>] resources to operate on
|
|
187
|
+
# @return [Boolean] true if operation should proceed
|
|
188
|
+
def confirm_operation(resources)
|
|
189
|
+
return true if resources.size == 1
|
|
190
|
+
return true if @options[:yes]
|
|
191
|
+
|
|
192
|
+
$stdout.puts "You are about to #{operation_name} #{resources.size} #{resource_label}s:"
|
|
193
|
+
resources.each { |r| $stdout.puts " - #{r.vmid} (#{r.name || 'unnamed'}) on #{r.node}" }
|
|
194
|
+
$stdout.puts ""
|
|
195
|
+
$stdout.print "Proceed? [y/N]: "
|
|
196
|
+
|
|
197
|
+
response = $stdin.gets&.strip&.downcase
|
|
198
|
+
%w[y yes].include?(response)
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
# Returns the operation name for this command.
|
|
202
|
+
#
|
|
203
|
+
# @return [Symbol] operation name
|
|
204
|
+
def operation_name
|
|
205
|
+
self.class::OPERATION
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
# Loads configuration from file or environment.
|
|
209
|
+
#
|
|
210
|
+
# @return [void]
|
|
211
|
+
def load_config
|
|
212
|
+
service = Pvectl::Config::Service.new
|
|
213
|
+
service.load(config: @global_options[:config])
|
|
214
|
+
@config = service.current_config
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
# Builds service options from command options.
|
|
218
|
+
#
|
|
219
|
+
# @return [Hash] service options
|
|
220
|
+
def service_options
|
|
221
|
+
opts = {}
|
|
222
|
+
opts[:timeout] = @options[:timeout] if @options[:timeout]
|
|
223
|
+
opts[:async] = true if @options[:async]
|
|
224
|
+
opts[:wait] = true if @options[:wait]
|
|
225
|
+
opts[:fail_fast] = true if @options[:"fail-fast"]
|
|
226
|
+
opts
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
# Outputs operation results using the configured formatter.
|
|
230
|
+
#
|
|
231
|
+
# @param results [Array<Object>] operation results
|
|
232
|
+
# @return [void]
|
|
233
|
+
def output_results(results)
|
|
234
|
+
presenter = build_presenter
|
|
235
|
+
format = @global_options[:output] || "table"
|
|
236
|
+
color_flag = @global_options[:color]
|
|
237
|
+
|
|
238
|
+
formatter = Pvectl::Formatters::Registry.for(format)
|
|
239
|
+
output = formatter.format(results, presenter, color: color_flag)
|
|
240
|
+
puts output
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
# Determines exit code based on results.
|
|
244
|
+
#
|
|
245
|
+
# @param results [Array<Object>] operation results
|
|
246
|
+
# @return [Integer] exit code
|
|
247
|
+
def determine_exit_code(results)
|
|
248
|
+
return Pvectl::ExitCodes::SUCCESS if results.all?(&:successful?)
|
|
249
|
+
return Pvectl::ExitCodes::SUCCESS if results.all?(&:pending?)
|
|
250
|
+
|
|
251
|
+
Pvectl::ExitCodes::GENERAL_ERROR
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
# Outputs usage error and returns exit code.
|
|
255
|
+
#
|
|
256
|
+
# @param message [String] error message
|
|
257
|
+
# @return [Integer] exit code
|
|
258
|
+
def usage_error(message)
|
|
259
|
+
$stderr.puts "Error: #{message}"
|
|
260
|
+
Pvectl::ExitCodes::USAGE_ERROR
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
# Outputs no resources found error and returns exit code.
|
|
264
|
+
#
|
|
265
|
+
# @return [Integer] exit code
|
|
266
|
+
def no_resources_found
|
|
267
|
+
msg = if @options[:all] || selector_strings.any?
|
|
268
|
+
@options[:node] ? "No #{resource_label}s found on node #{@options[:node]}" : "No #{resource_label}s found matching criteria"
|
|
269
|
+
else
|
|
270
|
+
"No #{resource_label}s found for given #{resource_id_label}s"
|
|
271
|
+
end
|
|
272
|
+
$stderr.puts "Error: #{msg}"
|
|
273
|
+
Pvectl::ExitCodes::NOT_FOUND
|
|
274
|
+
end
|
|
275
|
+
end
|
|
276
|
+
end
|
|
277
|
+
end
|