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,249 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Pvectl
|
|
4
|
+
module Commands
|
|
5
|
+
# Handler for the `pvectl ping` command.
|
|
6
|
+
#
|
|
7
|
+
# Verifies connectivity to the Proxmox cluster by calling the version
|
|
8
|
+
# endpoint and measuring response time. Provides a quick health check
|
|
9
|
+
# without detailed resource information.
|
|
10
|
+
#
|
|
11
|
+
# @example Basic usage
|
|
12
|
+
# pvectl ping
|
|
13
|
+
# # Output: OK - Connected to pve1.example.com
|
|
14
|
+
#
|
|
15
|
+
# @example Wide output with latency
|
|
16
|
+
# pvectl ping -o wide
|
|
17
|
+
# # Output: OK - Connected to pve1.example.com | Latency: 45ms
|
|
18
|
+
#
|
|
19
|
+
# @example JSON output for scripts
|
|
20
|
+
# pvectl ping -o json
|
|
21
|
+
# # Output: {"status":"ok","server":"pve1.example.com","latency_ms":45}
|
|
22
|
+
#
|
|
23
|
+
class Ping
|
|
24
|
+
# Registers the ping command with the CLI.
|
|
25
|
+
#
|
|
26
|
+
# @param cli [GLI::App] the CLI application object
|
|
27
|
+
# @return [void]
|
|
28
|
+
def self.register(cli)
|
|
29
|
+
cli.desc "Check connectivity to Proxmox cluster"
|
|
30
|
+
cli.long_desc <<~HELP
|
|
31
|
+
Verify connectivity to the Proxmox cluster by calling the version
|
|
32
|
+
API endpoint and measuring response time.
|
|
33
|
+
|
|
34
|
+
EXAMPLES
|
|
35
|
+
Basic connectivity check:
|
|
36
|
+
$ pvectl ping
|
|
37
|
+
|
|
38
|
+
Wide output with latency:
|
|
39
|
+
$ pvectl ping -o wide
|
|
40
|
+
|
|
41
|
+
JSON output for scripting:
|
|
42
|
+
$ pvectl ping -o json
|
|
43
|
+
|
|
44
|
+
NOTES
|
|
45
|
+
Uses the configured context (or PROXMOX_HOST environment variable)
|
|
46
|
+
to determine which server to ping.
|
|
47
|
+
|
|
48
|
+
Exit code 0 = connected, exit code 4 = connection error.
|
|
49
|
+
|
|
50
|
+
SEE ALSO
|
|
51
|
+
pvectl help config Manage cluster configuration
|
|
52
|
+
pvectl help get nodes List cluster nodes
|
|
53
|
+
HELP
|
|
54
|
+
cli.command :ping do |c|
|
|
55
|
+
c.action do |global_options, _options, _args|
|
|
56
|
+
exit_code = execute(global_options)
|
|
57
|
+
exit exit_code if exit_code != 0
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Executes the ping command.
|
|
63
|
+
#
|
|
64
|
+
# @param global_options [Hash] global CLI options
|
|
65
|
+
# - :config [String, nil] path to config file
|
|
66
|
+
# - :output [String] output format (table, wide, json, yaml)
|
|
67
|
+
# - :color [Boolean, nil] explicit color flag
|
|
68
|
+
# @return [Integer] exit code (0 for success, 4 for connection error)
|
|
69
|
+
def self.execute(global_options)
|
|
70
|
+
new(global_options).execute
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Creates a new Ping command instance.
|
|
74
|
+
#
|
|
75
|
+
# @param global_options [Hash] global CLI options
|
|
76
|
+
def initialize(global_options)
|
|
77
|
+
@global_options = global_options
|
|
78
|
+
@output_format = global_options[:output] || "table"
|
|
79
|
+
@color_flag = global_options[:color]
|
|
80
|
+
@config = nil
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Executes the ping operation.
|
|
84
|
+
#
|
|
85
|
+
# @return [Integer] exit code
|
|
86
|
+
def execute
|
|
87
|
+
load_config
|
|
88
|
+
connection = Pvectl::Connection.new(@config)
|
|
89
|
+
|
|
90
|
+
result = measure_ping(connection)
|
|
91
|
+
output_result(result, @config.server)
|
|
92
|
+
|
|
93
|
+
ExitCodes::SUCCESS
|
|
94
|
+
rescue Pvectl::Config::ConfigNotFoundError,
|
|
95
|
+
Pvectl::Config::InvalidConfigError,
|
|
96
|
+
Pvectl::Config::ContextNotFoundError,
|
|
97
|
+
Pvectl::Config::ClusterNotFoundError,
|
|
98
|
+
Pvectl::Config::UserNotFoundError => e
|
|
99
|
+
# Re-raise config errors to be handled by CLI error handler
|
|
100
|
+
raise
|
|
101
|
+
rescue Timeout::Error
|
|
102
|
+
output_error("Connection timed out", server_url)
|
|
103
|
+
ExitCodes::CONNECTION_ERROR
|
|
104
|
+
rescue Errno::ECONNREFUSED
|
|
105
|
+
output_error("Connection refused", server_url)
|
|
106
|
+
ExitCodes::CONNECTION_ERROR
|
|
107
|
+
rescue SocketError => e
|
|
108
|
+
output_error(e.message, server_url)
|
|
109
|
+
ExitCodes::CONNECTION_ERROR
|
|
110
|
+
rescue StandardError => e
|
|
111
|
+
output_error(e.message, server_url)
|
|
112
|
+
ExitCodes::CONNECTION_ERROR
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
private
|
|
116
|
+
|
|
117
|
+
attr_reader :global_options, :output_format, :color_flag
|
|
118
|
+
|
|
119
|
+
# Loads configuration from service.
|
|
120
|
+
#
|
|
121
|
+
# @return [Config::Models::ResolvedConfig] the resolved configuration
|
|
122
|
+
# @raise [Config::ConfigNotFoundError] if config not found
|
|
123
|
+
def load_config
|
|
124
|
+
service = Pvectl::Config::Service.new
|
|
125
|
+
service.load(config: global_options[:config])
|
|
126
|
+
@config = service.current_config
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# Returns the server URL, or "unknown" if config not loaded.
|
|
130
|
+
#
|
|
131
|
+
# @return [String] server URL or "unknown"
|
|
132
|
+
def server_url
|
|
133
|
+
@config&.server || "unknown"
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# Measures ping latency by timing the version API call.
|
|
137
|
+
#
|
|
138
|
+
# @param connection [Connection] the connection to use
|
|
139
|
+
# @return [Hash] result with :latency_ms
|
|
140
|
+
def measure_ping(connection)
|
|
141
|
+
start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
142
|
+
connection.version
|
|
143
|
+
end_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
144
|
+
|
|
145
|
+
{
|
|
146
|
+
latency_ms: ((end_time - start_time) * 1000).round
|
|
147
|
+
}
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
# Outputs the result based on the selected format.
|
|
151
|
+
#
|
|
152
|
+
# @param result [Hash] the ping result
|
|
153
|
+
# @param server [String] the server URL
|
|
154
|
+
# @return [void]
|
|
155
|
+
def output_result(result, server)
|
|
156
|
+
case output_format
|
|
157
|
+
when "json"
|
|
158
|
+
output_json(result, server)
|
|
159
|
+
when "yaml"
|
|
160
|
+
output_yaml(result, server)
|
|
161
|
+
when "wide"
|
|
162
|
+
output_wide(result, server)
|
|
163
|
+
else
|
|
164
|
+
output_simple(server)
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
# Outputs simple text format.
|
|
169
|
+
#
|
|
170
|
+
# @param server [String] the server URL
|
|
171
|
+
# @return [void]
|
|
172
|
+
def output_simple(server)
|
|
173
|
+
pastel = Formatters::ColorSupport.pastel(explicit_flag: color_flag)
|
|
174
|
+
puts "#{pastel.green('OK')} - Connected to #{extract_host(server)}"
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
# Outputs wide text format with latency.
|
|
178
|
+
#
|
|
179
|
+
# @param result [Hash] the ping result
|
|
180
|
+
# @param server [String] the server URL
|
|
181
|
+
# @return [void]
|
|
182
|
+
def output_wide(result, server)
|
|
183
|
+
pastel = Formatters::ColorSupport.pastel(explicit_flag: color_flag)
|
|
184
|
+
puts "#{pastel.green('OK')} - Connected to #{extract_host(server)} | " \
|
|
185
|
+
"Latency: #{result[:latency_ms]}ms"
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
# Outputs JSON format.
|
|
189
|
+
#
|
|
190
|
+
# @param result [Hash] the ping result
|
|
191
|
+
# @param server [String] the server URL
|
|
192
|
+
# @return [void]
|
|
193
|
+
def output_json(result, server)
|
|
194
|
+
require "json"
|
|
195
|
+
puts JSON.pretty_generate({
|
|
196
|
+
status: "ok",
|
|
197
|
+
server: extract_host(server),
|
|
198
|
+
latency_ms: result[:latency_ms]
|
|
199
|
+
})
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
# Outputs YAML format.
|
|
203
|
+
#
|
|
204
|
+
# @param result [Hash] the ping result
|
|
205
|
+
# @param server [String] the server URL
|
|
206
|
+
# @return [void]
|
|
207
|
+
def output_yaml(result, server)
|
|
208
|
+
require "yaml"
|
|
209
|
+
puts YAML.dump({
|
|
210
|
+
"status" => "ok",
|
|
211
|
+
"server" => extract_host(server),
|
|
212
|
+
"latency_ms" => result[:latency_ms]
|
|
213
|
+
})
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
# Outputs error message.
|
|
217
|
+
#
|
|
218
|
+
# @param message [String] the error message
|
|
219
|
+
# @param server [String] the server URL
|
|
220
|
+
# @return [void]
|
|
221
|
+
def output_error(message, server)
|
|
222
|
+
pastel = Formatters::ColorSupport.pastel(explicit_flag: color_flag)
|
|
223
|
+
host = extract_host(server)
|
|
224
|
+
|
|
225
|
+
case output_format
|
|
226
|
+
when "json"
|
|
227
|
+
require "json"
|
|
228
|
+
puts JSON.pretty_generate({ status: "error", server: host, error: message })
|
|
229
|
+
when "yaml"
|
|
230
|
+
require "yaml"
|
|
231
|
+
puts YAML.dump({ "status" => "error", "server" => host, "error" => message })
|
|
232
|
+
else
|
|
233
|
+
$stderr.puts "#{pastel.red('ERROR')} - Cannot connect to #{host}: #{message}"
|
|
234
|
+
end
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
# Extracts hostname from server URL.
|
|
238
|
+
#
|
|
239
|
+
# @param server_url [String] the full server URL
|
|
240
|
+
# @return [String] the hostname
|
|
241
|
+
def extract_host(server_url)
|
|
242
|
+
uri = URI.parse(server_url)
|
|
243
|
+
uri.host
|
|
244
|
+
rescue StandardError
|
|
245
|
+
server_url
|
|
246
|
+
end
|
|
247
|
+
end
|
|
248
|
+
end
|
|
249
|
+
end
|
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "fileutils"
|
|
4
|
+
|
|
5
|
+
module Pvectl
|
|
6
|
+
module Commands
|
|
7
|
+
# Pull command -- exports resource configuration from the Proxmox cluster
|
|
8
|
+
# as kubectl-like YAML manifest files.
|
|
9
|
+
#
|
|
10
|
+
# @example Register with CLI
|
|
11
|
+
# Pull.register(cli)
|
|
12
|
+
class Pull
|
|
13
|
+
RESOURCE_TYPES = {
|
|
14
|
+
"vm" => :vm,
|
|
15
|
+
"vms" => :vm,
|
|
16
|
+
"container" => :container,
|
|
17
|
+
"containers" => :container,
|
|
18
|
+
"ct" => :container
|
|
19
|
+
}.freeze
|
|
20
|
+
|
|
21
|
+
FILE_PREFIXES = {
|
|
22
|
+
vm: "vm",
|
|
23
|
+
container: "ct"
|
|
24
|
+
}.freeze
|
|
25
|
+
|
|
26
|
+
# Registers the pull command with the CLI.
|
|
27
|
+
#
|
|
28
|
+
# @param cli [GLI::App] the CLI application object
|
|
29
|
+
# @return [void]
|
|
30
|
+
def self.register(cli)
|
|
31
|
+
cli.desc "Pull resource configuration to YAML manifest"
|
|
32
|
+
cli.long_desc <<~HELP
|
|
33
|
+
DESCRIPTION
|
|
34
|
+
Exports resource configuration from the Proxmox cluster as kubectl-like
|
|
35
|
+
YAML manifest files. Supports single resources, multiple IDs, selectors,
|
|
36
|
+
and bulk export with --all.
|
|
37
|
+
|
|
38
|
+
When writing to files (-f), shows a diff of changes and asks for
|
|
39
|
+
confirmation before overwriting existing files. Use --yes to skip
|
|
40
|
+
confirmation or --dry-run to preview changes without writing.
|
|
41
|
+
|
|
42
|
+
EXAMPLES
|
|
43
|
+
$ pvectl pull vm 100
|
|
44
|
+
$ pvectl pull vm 100 -f vm-100.yaml
|
|
45
|
+
$ pvectl pull vm 100 -f vm-100.yaml --dry-run
|
|
46
|
+
$ pvectl pull vm 100 101 102 -f ./manifests/
|
|
47
|
+
$ pvectl pull vm --all -f ./manifests/ --yes
|
|
48
|
+
$ pvectl pull vm -f ./manifests/
|
|
49
|
+
$ pvectl pull vm -l tags=prod -f ./manifests/
|
|
50
|
+
$ pvectl pull container 200
|
|
51
|
+
|
|
52
|
+
NOTES
|
|
53
|
+
Without -f, YAML is printed to stdout (pipe-friendly).
|
|
54
|
+
With -f, shows diff against existing files and asks to confirm.
|
|
55
|
+
With --all or -l, -f must point to a directory.
|
|
56
|
+
When -f points to a directory with existing manifests and no IDs
|
|
57
|
+
are given, IDs are inferred from file names (vm-{vmid}.yaml).
|
|
58
|
+
File naming convention: vm-{vmid}.yaml or ct-{vmid}.yaml.
|
|
59
|
+
|
|
60
|
+
SEE ALSO
|
|
61
|
+
push, get, describe, edit
|
|
62
|
+
HELP
|
|
63
|
+
|
|
64
|
+
cli.command :pull do |c|
|
|
65
|
+
c.flag [:f, :file], desc: "Output file or directory"
|
|
66
|
+
c.flag [:l, :selector], desc: "Filter by selector (e.g. tags=prod,status=running)", multiple: true
|
|
67
|
+
c.switch [:all], desc: "Pull all resources of given type", negatable: false
|
|
68
|
+
c.switch [:y, :yes], desc: "Auto-confirm without prompting", negatable: false
|
|
69
|
+
c.switch [:"dry-run"], desc: "Show diff without writing files", negatable: false
|
|
70
|
+
c.flag [:node], desc: "Limit to specific node"
|
|
71
|
+
|
|
72
|
+
c.action do |global_options, options, args|
|
|
73
|
+
Pull.new(args, options, global_options).execute
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# @param args [Array<String>] command arguments
|
|
79
|
+
# @param options [Hash] command options
|
|
80
|
+
# @param global_options [Hash] global CLI options
|
|
81
|
+
def initialize(args, options, global_options)
|
|
82
|
+
@args = args
|
|
83
|
+
@options = options
|
|
84
|
+
@global_options = global_options
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Executes the pull command.
|
|
88
|
+
#
|
|
89
|
+
# @return [Integer] exit code
|
|
90
|
+
def execute
|
|
91
|
+
resource_type_str = @args.shift
|
|
92
|
+
return usage_error("Resource type is required (vm, container)") unless resource_type_str
|
|
93
|
+
|
|
94
|
+
type = RESOURCE_TYPES[resource_type_str.downcase]
|
|
95
|
+
return usage_error("Unknown resource type '#{resource_type_str}'. Valid: vm, container") unless type
|
|
96
|
+
|
|
97
|
+
ids = @args.map(&:to_i)
|
|
98
|
+
all = @options[:all]
|
|
99
|
+
node = @options[:node]
|
|
100
|
+
output = @options[:file]
|
|
101
|
+
|
|
102
|
+
# Auto-infer IDs from existing manifest files in directory
|
|
103
|
+
if ids.empty? && !all && @options[:selector].nil? && output && directory_output?(output)
|
|
104
|
+
ids = infer_ids_from_directory(output, type)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
if ids.empty? && !all && @options[:selector].nil?
|
|
108
|
+
return usage_error("Provide resource IDs, --all, or -l selector")
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
if (all || @options[:selector]) && output && !directory_output?(output)
|
|
112
|
+
return usage_error("--all and -l require -f to be a directory (end with /)")
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
selector = build_selector(type)
|
|
116
|
+
|
|
117
|
+
load_config
|
|
118
|
+
connection = Pvectl::Connection.new(@config)
|
|
119
|
+
service = build_service(connection)
|
|
120
|
+
|
|
121
|
+
result = service.execute(type: type, ids: ids, all: all, node: node, selector: selector)
|
|
122
|
+
|
|
123
|
+
result[:errors].each { |e| $stderr.puts "Error: #{e}" }
|
|
124
|
+
|
|
125
|
+
write_output(result[:manifests], type, output)
|
|
126
|
+
|
|
127
|
+
result[:errors].empty? ? ExitCodes::SUCCESS : ExitCodes::GENERAL_ERROR
|
|
128
|
+
rescue Pvectl::Config::ConfigNotFoundError,
|
|
129
|
+
Pvectl::Config::InvalidConfigError,
|
|
130
|
+
Pvectl::Config::ContextNotFoundError,
|
|
131
|
+
Pvectl::Config::ClusterNotFoundError,
|
|
132
|
+
Pvectl::Config::UserNotFoundError
|
|
133
|
+
raise
|
|
134
|
+
rescue StandardError => e
|
|
135
|
+
$stderr.puts "Error: #{e.message}"
|
|
136
|
+
ExitCodes::GENERAL_ERROR
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
private
|
|
140
|
+
|
|
141
|
+
def build_service(connection)
|
|
142
|
+
vm_repo = Pvectl::Repositories::Vm.new(connection)
|
|
143
|
+
ct_repo = Pvectl::Repositories::Container.new(connection)
|
|
144
|
+
Pvectl::Services::PullConfig.new(
|
|
145
|
+
vm_repository: vm_repo,
|
|
146
|
+
container_repository: ct_repo
|
|
147
|
+
)
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def build_selector(type)
|
|
151
|
+
expressions = @options[:selector]
|
|
152
|
+
return nil if expressions.nil? || (expressions.is_a?(Array) && expressions.empty?)
|
|
153
|
+
|
|
154
|
+
selector_class = type == :container ? Selectors::Container : Selectors::Vm
|
|
155
|
+
selector_class.new(Array(expressions))
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
# Writes pull results to stdout or files.
|
|
159
|
+
# For file output, shows diff against existing files and asks for confirmation.
|
|
160
|
+
#
|
|
161
|
+
# @param manifests [Array<Hash>] pulled manifests with :yaml and :vmid
|
|
162
|
+
# @param type [Symbol] :vm or :container
|
|
163
|
+
# @param output [String, nil] output file/directory or nil for stdout
|
|
164
|
+
# @return [void]
|
|
165
|
+
def write_output(manifests, type, output)
|
|
166
|
+
return if manifests.empty?
|
|
167
|
+
|
|
168
|
+
if output.nil?
|
|
169
|
+
# stdout mode -- no diff, just print
|
|
170
|
+
manifests.each { |m| $stdout.puts m[:yaml] }
|
|
171
|
+
return
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
# File output mode -- build operations, show diff, confirm, write
|
|
175
|
+
operations = build_file_operations(manifests, type, output)
|
|
176
|
+
actionable = operations.reject { |op| op[:action] == :unchanged }
|
|
177
|
+
|
|
178
|
+
if actionable.empty?
|
|
179
|
+
$stdout.puts "No changes."
|
|
180
|
+
return
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
display_pull_plan(operations, type)
|
|
184
|
+
|
|
185
|
+
if @options[:"dry-run"]
|
|
186
|
+
$stdout.puts "\n(dry-run mode -- no files written)"
|
|
187
|
+
return
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
unless @options[:yes]
|
|
191
|
+
$stdout.print "\nWrite #{actionable.length} file(s)? [y/N] "
|
|
192
|
+
answer = $stdin.gets&.strip&.downcase
|
|
193
|
+
unless answer == "y" || answer == "yes"
|
|
194
|
+
$stdout.puts "Cancelled."
|
|
195
|
+
return
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
apply_file_operations(operations)
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
# Builds a list of file operations (create/update/unchanged) for each manifest.
|
|
203
|
+
#
|
|
204
|
+
# @param manifests [Array<Hash>] pulled manifests
|
|
205
|
+
# @param type [Symbol] :vm or :container
|
|
206
|
+
# @param output [String] output file or directory path
|
|
207
|
+
# @return [Array<Hash>] operation hashes with :action, :path, :vmid, :yaml, :diff
|
|
208
|
+
def build_file_operations(manifests, type, output)
|
|
209
|
+
if manifests.length == 1 && !directory_output?(output)
|
|
210
|
+
[build_operation(manifests.first, output, type)]
|
|
211
|
+
else
|
|
212
|
+
prefix = FILE_PREFIXES[type]
|
|
213
|
+
manifests.map do |m|
|
|
214
|
+
filename = "#{prefix}-#{m[:vmid]}.yaml"
|
|
215
|
+
path = File.join(output, filename)
|
|
216
|
+
build_operation(m, path, type)
|
|
217
|
+
end
|
|
218
|
+
end
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
# Builds a single file operation by comparing new YAML with existing file.
|
|
222
|
+
#
|
|
223
|
+
# @param manifest [Hash] manifest with :yaml and :vmid
|
|
224
|
+
# @param path [String] target file path
|
|
225
|
+
# @param type [Symbol] :vm or :container
|
|
226
|
+
# @return [Hash] operation hash
|
|
227
|
+
def build_operation(manifest, path, type)
|
|
228
|
+
new_yaml = manifest[:yaml]
|
|
229
|
+
|
|
230
|
+
unless File.file?(path)
|
|
231
|
+
return { action: :create, path: path, vmid: manifest[:vmid], yaml: new_yaml }
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
old_yaml = File.read(path)
|
|
235
|
+
return { action: :unchanged, path: path, vmid: manifest[:vmid] } if old_yaml == new_yaml
|
|
236
|
+
|
|
237
|
+
diff = compute_manifest_diff(old_yaml, new_yaml, type)
|
|
238
|
+
{ action: :update, path: path, vmid: manifest[:vmid], yaml: new_yaml, diff: diff }
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
# Computes a flat config diff between old and new manifest YAML strings.
|
|
242
|
+
#
|
|
243
|
+
# @param old_yaml [String] existing file content
|
|
244
|
+
# @param new_yaml [String] new content from server
|
|
245
|
+
# @param type [Symbol] :vm or :container
|
|
246
|
+
# @return [Hash, nil] diff hash or nil on parse error
|
|
247
|
+
def compute_manifest_diff(old_yaml, new_yaml, type)
|
|
248
|
+
old_manifest = ManifestSerializer.from_yaml(old_yaml)
|
|
249
|
+
new_manifest = ManifestSerializer.from_yaml(new_yaml)
|
|
250
|
+
old_flat = ConfigSerializer.from_nested(old_manifest[:spec], type: type)
|
|
251
|
+
new_flat = ConfigSerializer.from_nested(new_manifest[:spec], type: type)
|
|
252
|
+
ConfigSerializer.diff(old_flat, new_flat)
|
|
253
|
+
rescue StandardError
|
|
254
|
+
nil
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
# Displays the pull plan with diffs for each file operation.
|
|
258
|
+
#
|
|
259
|
+
# @param operations [Array<Hash>] file operations
|
|
260
|
+
# @param type [Symbol] :vm or :container
|
|
261
|
+
# @return [void]
|
|
262
|
+
def display_pull_plan(operations, type)
|
|
263
|
+
label = type == :container ? "Container" : "VM"
|
|
264
|
+
operations.each do |op|
|
|
265
|
+
case op[:action]
|
|
266
|
+
when :create
|
|
267
|
+
$stdout.puts "\n#{label} #{op[:vmid]} -- NEW (#{File.basename(op[:path])})"
|
|
268
|
+
when :update
|
|
269
|
+
$stdout.puts "\n#{label} #{op[:vmid]} -- UPDATE (#{File.basename(op[:path])}):"
|
|
270
|
+
if op[:diff] && diff_has_changes?(op[:diff])
|
|
271
|
+
$stdout.puts ConfigSerializer.format_diff(op[:diff])
|
|
272
|
+
else
|
|
273
|
+
$stdout.puts " (metadata changed)"
|
|
274
|
+
end
|
|
275
|
+
when :unchanged
|
|
276
|
+
$stderr.puts "Info: #{File.basename(op[:path])}: no changes"
|
|
277
|
+
end
|
|
278
|
+
end
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
# Checks if a diff hash has any actual changes.
|
|
282
|
+
#
|
|
283
|
+
# @param diff [Hash] diff hash from ConfigSerializer.diff
|
|
284
|
+
# @return [Boolean]
|
|
285
|
+
def diff_has_changes?(diff)
|
|
286
|
+
diff[:changed].any? || diff[:added].any? || diff[:removed].any?
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
# Writes files for all actionable operations.
|
|
290
|
+
#
|
|
291
|
+
# @param operations [Array<Hash>] file operations
|
|
292
|
+
# @return [void]
|
|
293
|
+
def apply_file_operations(operations)
|
|
294
|
+
written = 0
|
|
295
|
+
operations.each do |op|
|
|
296
|
+
next if op[:action] == :unchanged
|
|
297
|
+
|
|
298
|
+
dir = File.dirname(op[:path])
|
|
299
|
+
FileUtils.mkdir_p(dir) unless File.directory?(dir)
|
|
300
|
+
File.write(op[:path], op[:yaml])
|
|
301
|
+
written += 1
|
|
302
|
+
end
|
|
303
|
+
$stderr.puts "Written #{written} manifest(s)"
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
# Infers resource IDs from existing manifest files in a directory.
|
|
307
|
+
# Scans for files matching {prefix}-{vmid}.yaml pattern.
|
|
308
|
+
#
|
|
309
|
+
# @param directory [String] path to directory
|
|
310
|
+
# @param type [Symbol] :vm or :container
|
|
311
|
+
# @return [Array<Integer>] extracted VMIDs
|
|
312
|
+
def infer_ids_from_directory(directory, type)
|
|
313
|
+
return [] unless File.directory?(directory)
|
|
314
|
+
|
|
315
|
+
prefix = FILE_PREFIXES[type]
|
|
316
|
+
pattern = File.join(directory, "#{prefix}-*.yaml")
|
|
317
|
+
Dir.glob(pattern).filter_map do |path|
|
|
318
|
+
basename = File.basename(path, ".yaml")
|
|
319
|
+
match = basename.match(/\A#{Regexp.escape(prefix)}-(\d+)\z/)
|
|
320
|
+
match[1].to_i if match
|
|
321
|
+
end.sort
|
|
322
|
+
end
|
|
323
|
+
|
|
324
|
+
def directory_output?(path)
|
|
325
|
+
return false if path.nil?
|
|
326
|
+
|
|
327
|
+
path.end_with?("/") || File.directory?(path)
|
|
328
|
+
end
|
|
329
|
+
|
|
330
|
+
def load_config
|
|
331
|
+
service = Pvectl::Config::Service.new
|
|
332
|
+
service.load(config: @global_options[:config])
|
|
333
|
+
@config = service.current_config
|
|
334
|
+
end
|
|
335
|
+
|
|
336
|
+
def usage_error(message)
|
|
337
|
+
$stderr.puts "Error: #{message}"
|
|
338
|
+
ExitCodes::USAGE_ERROR
|
|
339
|
+
end
|
|
340
|
+
end
|
|
341
|
+
end
|
|
342
|
+
end
|