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,297 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "yaml"
|
|
4
|
+
|
|
5
|
+
module Pvectl
|
|
6
|
+
module Config
|
|
7
|
+
# Loads and resolves configuration from multiple sources.
|
|
8
|
+
#
|
|
9
|
+
# Provider handles loading configuration from YAML files and environment
|
|
10
|
+
# variables, then merging them with proper priority to produce a final
|
|
11
|
+
# ResolvedConfig. Priority order (highest to lowest):
|
|
12
|
+
# 1. CLI options
|
|
13
|
+
# 2. Environment variables
|
|
14
|
+
# 3. Configuration file
|
|
15
|
+
#
|
|
16
|
+
# @example Loading configuration
|
|
17
|
+
# provider = Provider.new
|
|
18
|
+
# config = provider.resolve(
|
|
19
|
+
# config_path: "~/.pvectl/config",
|
|
20
|
+
# cli_options: { context: "production" }
|
|
21
|
+
# )
|
|
22
|
+
#
|
|
23
|
+
class Provider
|
|
24
|
+
# Mapping of environment variables to configuration keys
|
|
25
|
+
ENV_VARS = {
|
|
26
|
+
"PROXMOX_HOST" => :server,
|
|
27
|
+
"PROXMOX_TOKEN_ID" => :token_id,
|
|
28
|
+
"PROXMOX_TOKEN_SECRET" => :token_secret,
|
|
29
|
+
"PROXMOX_USER" => :username,
|
|
30
|
+
"PROXMOX_PASSWORD" => :password,
|
|
31
|
+
"PROXMOX_VERIFY_SSL" => :verify_ssl,
|
|
32
|
+
"PVECTL_CONTEXT" => :context,
|
|
33
|
+
"PVECTL_CONFIG" => :config_path,
|
|
34
|
+
# Retry/timeout settings
|
|
35
|
+
"PROXMOX_TIMEOUT" => :timeout,
|
|
36
|
+
"PROXMOX_RETRY_COUNT" => :retry_count,
|
|
37
|
+
"PROXMOX_RETRY_DELAY" => :retry_delay,
|
|
38
|
+
"PROXMOX_MAX_RETRY_DELAY" => :max_retry_delay,
|
|
39
|
+
"PROXMOX_RETRY_WRITES" => :retry_writes
|
|
40
|
+
}.freeze
|
|
41
|
+
|
|
42
|
+
# Keys that should be parsed as integers
|
|
43
|
+
INTEGER_VARS = %i[timeout retry_count retry_delay max_retry_delay].freeze
|
|
44
|
+
|
|
45
|
+
# Keys that should be parsed as booleans
|
|
46
|
+
BOOLEAN_VARS = %i[verify_ssl retry_writes].freeze
|
|
47
|
+
|
|
48
|
+
# Checks if a configuration file exists at the given path.
|
|
49
|
+
#
|
|
50
|
+
# @param path [String] path to configuration file
|
|
51
|
+
# @return [Boolean] true if file exists
|
|
52
|
+
def file_exists?(path)
|
|
53
|
+
File.exist?(path)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Checks if a file has insecure permissions (readable by group/others).
|
|
57
|
+
#
|
|
58
|
+
# @param path [String] path to file
|
|
59
|
+
# @return [Boolean] true if permissions are insecure
|
|
60
|
+
def insecure_permissions?(path)
|
|
61
|
+
return false unless File.exist?(path)
|
|
62
|
+
|
|
63
|
+
mode = File.stat(path).mode & 0o777
|
|
64
|
+
(mode & 0o077) != 0
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Loads configuration from a YAML file.
|
|
68
|
+
#
|
|
69
|
+
# @param path [String] path to configuration file
|
|
70
|
+
# @return [Hash] parsed configuration hash
|
|
71
|
+
# @raise [ConfigNotFoundError] if file does not exist
|
|
72
|
+
# @raise [InvalidConfigError] if YAML is invalid
|
|
73
|
+
def load_file(path)
|
|
74
|
+
raise ConfigNotFoundError, "Configuration file not found: #{path}" unless File.exist?(path)
|
|
75
|
+
|
|
76
|
+
content = File.read(path)
|
|
77
|
+
YAML.safe_load(content, permitted_classes: [Symbol])
|
|
78
|
+
rescue Psych::SyntaxError => e
|
|
79
|
+
raise InvalidConfigError, "Invalid YAML in #{path}: #{e.message}"
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Loads configuration from environment variables.
|
|
83
|
+
#
|
|
84
|
+
# @return [Hash] configuration values from environment
|
|
85
|
+
# @raise [InvalidConfigError] if integer values are invalid
|
|
86
|
+
def load_env
|
|
87
|
+
result = {}
|
|
88
|
+
|
|
89
|
+
ENV_VARS.each do |env_var, config_key|
|
|
90
|
+
value = ENV[env_var]
|
|
91
|
+
next if value.nil? || value.empty?
|
|
92
|
+
|
|
93
|
+
result[config_key] = parse_env_value(config_key, value)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
result
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# Resolves the context name from various sources.
|
|
100
|
+
#
|
|
101
|
+
# Priority: CLI > ENV > file
|
|
102
|
+
#
|
|
103
|
+
# @param cli_options [Hash] CLI options (may contain :context)
|
|
104
|
+
# @param file_config [Hash] configuration from file (may contain "current-context")
|
|
105
|
+
# @return [String, nil] resolved context name
|
|
106
|
+
def resolve_context_name(cli_options:, file_config: {})
|
|
107
|
+
cli_options[:context] || ENV["PVECTL_CONTEXT"] || file_config["current-context"]
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# Resolves full configuration by merging all sources.
|
|
111
|
+
#
|
|
112
|
+
# @param config_path [String] path to configuration file
|
|
113
|
+
# @param cli_options [Hash] CLI options
|
|
114
|
+
# @param cluster_override [String, nil] for testing invalid cluster references
|
|
115
|
+
# @return [Models::ResolvedConfig] resolved configuration
|
|
116
|
+
# @raise [ConfigNotFoundError] if config file not found
|
|
117
|
+
# @raise [ContextNotFoundError] if context not found
|
|
118
|
+
# @raise [ClusterNotFoundError] if cluster not found
|
|
119
|
+
# @raise [UserNotFoundError] if user not found
|
|
120
|
+
def resolve(config_path:, cli_options:, cluster_override: nil)
|
|
121
|
+
file_config = load_file(config_path)
|
|
122
|
+
env_config = load_env
|
|
123
|
+
|
|
124
|
+
context_name = resolve_context_name(cli_options: cli_options, file_config: file_config)
|
|
125
|
+
context = find_context(file_config, context_name)
|
|
126
|
+
|
|
127
|
+
cluster_name = cluster_override || context.cluster_ref
|
|
128
|
+
cluster = find_cluster(file_config, cluster_name)
|
|
129
|
+
user = find_user(file_config, context.user_ref)
|
|
130
|
+
|
|
131
|
+
build_resolved_config(
|
|
132
|
+
context: context,
|
|
133
|
+
cluster: cluster,
|
|
134
|
+
user: user,
|
|
135
|
+
env_config: env_config,
|
|
136
|
+
cli_options: cli_options
|
|
137
|
+
)
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
private
|
|
141
|
+
|
|
142
|
+
# Parses an environment variable value to the appropriate type.
|
|
143
|
+
#
|
|
144
|
+
# @param key [Symbol] configuration key
|
|
145
|
+
# @param value [String] raw environment variable value
|
|
146
|
+
# @return [Object] parsed value (Integer, Boolean, or String)
|
|
147
|
+
# @raise [InvalidConfigError] if value cannot be parsed
|
|
148
|
+
def parse_env_value(key, value)
|
|
149
|
+
if BOOLEAN_VARS.include?(key)
|
|
150
|
+
parse_boolean_env(value)
|
|
151
|
+
elsif INTEGER_VARS.include?(key)
|
|
152
|
+
parse_integer_env(key, value)
|
|
153
|
+
else
|
|
154
|
+
value
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
# Parses a boolean environment variable.
|
|
159
|
+
#
|
|
160
|
+
# @param value [String] raw value
|
|
161
|
+
# @return [Boolean] parsed boolean
|
|
162
|
+
def parse_boolean_env(value)
|
|
163
|
+
%w[true 1 yes].include?(value.to_s.downcase)
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
# Parses an integer environment variable with validation.
|
|
167
|
+
#
|
|
168
|
+
# @param key [Symbol] configuration key (for error messages)
|
|
169
|
+
# @param value [String] raw environment variable value
|
|
170
|
+
# @return [Integer] parsed integer value
|
|
171
|
+
# @raise [InvalidConfigError] if value is not a valid non-negative integer
|
|
172
|
+
def parse_integer_env(key, value)
|
|
173
|
+
unless value.to_s.match?(/\A\d+\z/)
|
|
174
|
+
raise InvalidConfigError,
|
|
175
|
+
"Invalid integer for #{key.to_s.tr('_', '-')}: '#{value}' " \
|
|
176
|
+
"(must be a non-negative integer)"
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
value.to_i
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
# Finds a context by name in the configuration.
|
|
183
|
+
#
|
|
184
|
+
# @param config [Hash] configuration hash
|
|
185
|
+
# @param name [String] context name
|
|
186
|
+
# @return [Models::Context] context model
|
|
187
|
+
# @raise [ContextNotFoundError] if context not found
|
|
188
|
+
def find_context(config, name)
|
|
189
|
+
contexts = config["contexts"] || []
|
|
190
|
+
context_hash = contexts.find { |c| c["name"] == name }
|
|
191
|
+
|
|
192
|
+
if context_hash.nil?
|
|
193
|
+
available = contexts.map { |c| c["name"] }.join(", ")
|
|
194
|
+
raise ContextNotFoundError, "Context '#{name}' not found. Available: #{available}"
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
Models::Context.from_hash(context_hash)
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
# Finds a cluster by name in the configuration.
|
|
201
|
+
#
|
|
202
|
+
# @param config [Hash] configuration hash
|
|
203
|
+
# @param name [String] cluster name
|
|
204
|
+
# @return [Models::Cluster] cluster model
|
|
205
|
+
# @raise [ClusterNotFoundError] if cluster not found
|
|
206
|
+
def find_cluster(config, name)
|
|
207
|
+
clusters = config["clusters"] || []
|
|
208
|
+
cluster_hash = clusters.find { |c| c["name"] == name }
|
|
209
|
+
|
|
210
|
+
raise ClusterNotFoundError, "Cluster '#{name}' not found in configuration" if cluster_hash.nil?
|
|
211
|
+
|
|
212
|
+
Models::Cluster.from_hash(cluster_hash)
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
# Finds a user by name in the configuration.
|
|
216
|
+
#
|
|
217
|
+
# @param config [Hash] configuration hash
|
|
218
|
+
# @param name [String] user name
|
|
219
|
+
# @return [Models::User] user model
|
|
220
|
+
# @raise [UserNotFoundError] if user not found
|
|
221
|
+
def find_user(config, name)
|
|
222
|
+
users = config["users"] || []
|
|
223
|
+
user_hash = users.find { |u| u["name"] == name }
|
|
224
|
+
|
|
225
|
+
raise UserNotFoundError, "User '#{name}' not found in configuration" if user_hash.nil?
|
|
226
|
+
|
|
227
|
+
Models::User.from_hash(user_hash)
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
# Builds a ResolvedConfig from merged sources.
|
|
231
|
+
#
|
|
232
|
+
# @param context [Models::Context] resolved context
|
|
233
|
+
# @param cluster [Models::Cluster] resolved cluster
|
|
234
|
+
# @param user [Models::User] resolved user
|
|
235
|
+
# @param env_config [Hash] environment configuration
|
|
236
|
+
# @param cli_options [Hash] CLI options
|
|
237
|
+
# @return [Models::ResolvedConfig] resolved configuration
|
|
238
|
+
def build_resolved_config(context:, cluster:, user:, env_config:, cli_options:)
|
|
239
|
+
# Merge with priority: CLI > ENV > file
|
|
240
|
+
server = env_config[:server] || cluster.server
|
|
241
|
+
verify_ssl = env_config.key?(:verify_ssl) ? env_config[:verify_ssl] : cluster.verify_ssl
|
|
242
|
+
|
|
243
|
+
# Determine auth type and credentials
|
|
244
|
+
auth_type, token_id, token_secret, username, password = resolve_auth(user, env_config)
|
|
245
|
+
|
|
246
|
+
# Resolve retry/timeout settings with priority: ENV > file
|
|
247
|
+
timeout = env_config[:timeout] || cluster.timeout
|
|
248
|
+
retry_count = env_config[:retry_count] || cluster.retry_count
|
|
249
|
+
retry_delay = env_config[:retry_delay] || cluster.retry_delay
|
|
250
|
+
max_retry_delay = env_config[:max_retry_delay] || cluster.max_retry_delay
|
|
251
|
+
retry_writes = env_config.key?(:retry_writes) ? env_config[:retry_writes] : cluster.retry_writes
|
|
252
|
+
|
|
253
|
+
Models::ResolvedConfig.new(
|
|
254
|
+
context_name: context.name,
|
|
255
|
+
server: server,
|
|
256
|
+
verify_ssl: verify_ssl,
|
|
257
|
+
certificate_authority: cluster.certificate_authority,
|
|
258
|
+
auth_type: auth_type,
|
|
259
|
+
token_id: token_id,
|
|
260
|
+
token_secret: token_secret,
|
|
261
|
+
username: username,
|
|
262
|
+
password: password,
|
|
263
|
+
default_node: context.default_node,
|
|
264
|
+
timeout: timeout,
|
|
265
|
+
retry_count: retry_count,
|
|
266
|
+
retry_delay: retry_delay,
|
|
267
|
+
max_retry_delay: max_retry_delay,
|
|
268
|
+
retry_writes: retry_writes
|
|
269
|
+
)
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
# Resolves authentication type and credentials.
|
|
273
|
+
#
|
|
274
|
+
# @param user [Models::User] user from config
|
|
275
|
+
# @param env_config [Hash] environment configuration
|
|
276
|
+
# @return [Array] [auth_type, token_id, token_secret, username, password]
|
|
277
|
+
def resolve_auth(user, env_config)
|
|
278
|
+
# ENV token auth takes precedence
|
|
279
|
+
if env_config[:token_id] && env_config[:token_secret]
|
|
280
|
+
return [:token, env_config[:token_id], env_config[:token_secret], nil, nil]
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
# ENV password auth
|
|
284
|
+
if env_config[:username] && env_config[:password]
|
|
285
|
+
return [:password, nil, nil, env_config[:username], env_config[:password]]
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
# Fall back to user from config
|
|
289
|
+
if user.token_auth?
|
|
290
|
+
[:token, user.token_id, user.token_secret, nil, nil]
|
|
291
|
+
else
|
|
292
|
+
[:password, nil, nil, user.username, user.password]
|
|
293
|
+
end
|
|
294
|
+
end
|
|
295
|
+
end
|
|
296
|
+
end
|
|
297
|
+
end
|
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Pvectl
|
|
4
|
+
module Config
|
|
5
|
+
# Facade for configuration management operations.
|
|
6
|
+
#
|
|
7
|
+
# Service coordinates Provider, Store, and Wizard to provide a unified
|
|
8
|
+
# interface for loading, modifying, and saving configuration. It handles
|
|
9
|
+
# the complete lifecycle of configuration management.
|
|
10
|
+
#
|
|
11
|
+
# @example Loading configuration
|
|
12
|
+
# service = Service.new
|
|
13
|
+
# service.load(config: "~/.pvectl/config", context: "production")
|
|
14
|
+
# puts service.current_config.server
|
|
15
|
+
#
|
|
16
|
+
# @example Switching contexts
|
|
17
|
+
# service.load(config: path)
|
|
18
|
+
# service.use_context("development")
|
|
19
|
+
#
|
|
20
|
+
# @example Listing contexts
|
|
21
|
+
# service.contexts.each do |ctx|
|
|
22
|
+
# puts ctx.name
|
|
23
|
+
# end
|
|
24
|
+
#
|
|
25
|
+
class Service
|
|
26
|
+
# Default path for configuration file
|
|
27
|
+
DEFAULT_CONFIG_PATH = File.expand_path("~/.pvectl/config").freeze
|
|
28
|
+
|
|
29
|
+
# @return [String] path to loaded configuration file
|
|
30
|
+
attr_reader :config_path
|
|
31
|
+
|
|
32
|
+
# @return [Hash] raw configuration hash from file
|
|
33
|
+
attr_reader :raw_config
|
|
34
|
+
|
|
35
|
+
# @return [String] name of the current context
|
|
36
|
+
attr_reader :current_context_name
|
|
37
|
+
|
|
38
|
+
# Creates a new Service instance.
|
|
39
|
+
#
|
|
40
|
+
# @param provider [Provider, nil] configuration provider (default: new Provider)
|
|
41
|
+
# @param store [Store, nil] configuration store (default: new Store)
|
|
42
|
+
# @param wizard [Wizard, nil] configuration wizard (default: nil, created on demand)
|
|
43
|
+
def initialize(provider: nil, store: nil, wizard: nil)
|
|
44
|
+
@provider = provider || Provider.new
|
|
45
|
+
@store = store || Store.new
|
|
46
|
+
@wizard = wizard
|
|
47
|
+
@loaded = false
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Loads configuration from file and CLI options.
|
|
51
|
+
#
|
|
52
|
+
# Determines the configuration path from CLI options, environment,
|
|
53
|
+
# or default location. Warns if file permissions are insecure.
|
|
54
|
+
#
|
|
55
|
+
# @param cli_options [Hash] CLI options (:config, :context)
|
|
56
|
+
# @return [Service] self for chaining
|
|
57
|
+
# @raise [ConfigNotFoundError] if config file not found and wizard not available
|
|
58
|
+
# @raise [InvalidConfigError] if config file contains invalid YAML
|
|
59
|
+
def load(cli_options = {})
|
|
60
|
+
@config_path = resolve_config_path(cli_options)
|
|
61
|
+
|
|
62
|
+
unless @provider.file_exists?(@config_path)
|
|
63
|
+
raise ConfigNotFoundError, "Configuration file not found: #{@config_path}"
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
warn_insecure_permissions if @provider.insecure_permissions?(@config_path)
|
|
67
|
+
|
|
68
|
+
@raw_config = @provider.load_file(@config_path)
|
|
69
|
+
@current_context_name = @provider.resolve_context_name(
|
|
70
|
+
cli_options: cli_options,
|
|
71
|
+
file_config: @raw_config
|
|
72
|
+
)
|
|
73
|
+
@resolved_config = nil # Reset cached resolved config
|
|
74
|
+
@loaded = true
|
|
75
|
+
|
|
76
|
+
self
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Returns the current resolved configuration.
|
|
80
|
+
#
|
|
81
|
+
# @return [Models::ResolvedConfig] resolved configuration
|
|
82
|
+
# @raise [ConfigError] if configuration not loaded
|
|
83
|
+
def current_config
|
|
84
|
+
raise ConfigError, "Configuration not loaded. Call #load first." unless @loaded
|
|
85
|
+
|
|
86
|
+
@resolved_config ||= @provider.resolve(
|
|
87
|
+
config_path: @config_path,
|
|
88
|
+
cli_options: { context: @current_context_name }
|
|
89
|
+
)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Returns all contexts from the configuration.
|
|
93
|
+
#
|
|
94
|
+
# @return [Array<Models::Context>] list of context models
|
|
95
|
+
def contexts
|
|
96
|
+
(@raw_config["contexts"] || []).map do |ctx_hash|
|
|
97
|
+
Models::Context.from_hash(ctx_hash)
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# Returns a specific context by name.
|
|
102
|
+
#
|
|
103
|
+
# @param name [String] context name
|
|
104
|
+
# @return [Models::Context, nil] context or nil if not found
|
|
105
|
+
def context(name)
|
|
106
|
+
contexts.find { |ctx| ctx.name == name }
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# Switches to a different context.
|
|
110
|
+
#
|
|
111
|
+
# Updates both the in-memory state and the configuration file.
|
|
112
|
+
#
|
|
113
|
+
# @param context_name [String] name of context to switch to
|
|
114
|
+
# @return [void]
|
|
115
|
+
# @raise [ContextNotFoundError] if context not found
|
|
116
|
+
def use_context(context_name)
|
|
117
|
+
unless context(context_name)
|
|
118
|
+
available = contexts.map(&:name).join(", ")
|
|
119
|
+
raise ContextNotFoundError, "Context '#{context_name}' not found. Available: #{available}"
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
@store.update_current_context(@config_path, context_name)
|
|
123
|
+
@current_context_name = context_name
|
|
124
|
+
@resolved_config = nil # Reset cached config
|
|
125
|
+
@raw_config["current-context"] = context_name
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# Creates or updates a context.
|
|
129
|
+
#
|
|
130
|
+
# @param name [String] context name
|
|
131
|
+
# @param cluster [String] cluster reference
|
|
132
|
+
# @param user [String] user reference
|
|
133
|
+
# @param default_node [String, nil] optional default node
|
|
134
|
+
# @return [Models::Context] the new or updated context
|
|
135
|
+
def set_context(name:, cluster:, user:, default_node: nil)
|
|
136
|
+
new_context = Models::Context.new(
|
|
137
|
+
name: name,
|
|
138
|
+
cluster_ref: cluster,
|
|
139
|
+
user_ref: user,
|
|
140
|
+
default_node: default_node
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
@store.upsert_context(@config_path, new_context)
|
|
144
|
+
|
|
145
|
+
# Update in-memory config
|
|
146
|
+
refresh_raw_config
|
|
147
|
+
|
|
148
|
+
new_context
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
# Returns all clusters from the configuration.
|
|
152
|
+
#
|
|
153
|
+
# @return [Array<Models::Cluster>] list of cluster models
|
|
154
|
+
def clusters
|
|
155
|
+
(@raw_config["clusters"] || []).map do |cluster_hash|
|
|
156
|
+
Models::Cluster.from_hash(cluster_hash)
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
# Returns a specific cluster by name.
|
|
161
|
+
#
|
|
162
|
+
# @param name [String] cluster name
|
|
163
|
+
# @return [Models::Cluster, nil] cluster or nil if not found
|
|
164
|
+
def cluster(name)
|
|
165
|
+
clusters.find { |c| c.name == name }
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
# Creates or updates a cluster.
|
|
169
|
+
#
|
|
170
|
+
# @param name [String] cluster name
|
|
171
|
+
# @param server [String] Proxmox server URL
|
|
172
|
+
# @param verify_ssl [Boolean] whether to verify SSL (default: true)
|
|
173
|
+
# @param certificate_authority [String, nil] path to CA certificate
|
|
174
|
+
# @return [Models::Cluster] the new or updated cluster
|
|
175
|
+
def set_cluster(name:, server:, verify_ssl: true, certificate_authority: nil)
|
|
176
|
+
new_cluster = Models::Cluster.new(
|
|
177
|
+
name: name,
|
|
178
|
+
server: server,
|
|
179
|
+
verify_ssl: verify_ssl,
|
|
180
|
+
certificate_authority: certificate_authority
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
@store.upsert_cluster(@config_path, new_cluster)
|
|
184
|
+
|
|
185
|
+
# Update in-memory config
|
|
186
|
+
refresh_raw_config
|
|
187
|
+
|
|
188
|
+
new_cluster
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
# Returns all users from the configuration.
|
|
192
|
+
#
|
|
193
|
+
# @return [Array<Models::User>] list of user models
|
|
194
|
+
def users
|
|
195
|
+
(@raw_config["users"] || []).map do |user_hash|
|
|
196
|
+
Models::User.from_hash(user_hash)
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
# Returns a specific user by name.
|
|
201
|
+
#
|
|
202
|
+
# @param name [String] user name
|
|
203
|
+
# @return [Models::User, nil] user or nil if not found
|
|
204
|
+
def user(name)
|
|
205
|
+
users.find { |u| u.name == name }
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
# Creates or updates user credentials.
|
|
209
|
+
#
|
|
210
|
+
# @param name [String] user name
|
|
211
|
+
# @param token_id [String, nil] API token ID
|
|
212
|
+
# @param token_secret [String, nil] API token secret
|
|
213
|
+
# @param username [String, nil] username for password auth
|
|
214
|
+
# @param password [String, nil] password for password auth
|
|
215
|
+
# @return [Models::User] the new or updated user
|
|
216
|
+
def set_credentials(name:, token_id: nil, token_secret: nil, username: nil, password: nil)
|
|
217
|
+
new_user = Models::User.new(
|
|
218
|
+
name: name,
|
|
219
|
+
token_id: token_id,
|
|
220
|
+
token_secret: token_secret,
|
|
221
|
+
username: username,
|
|
222
|
+
password: password
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
@store.upsert_user(@config_path, new_user)
|
|
226
|
+
|
|
227
|
+
# Update in-memory config
|
|
228
|
+
refresh_raw_config
|
|
229
|
+
|
|
230
|
+
new_user
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
# Returns configuration with secrets masked for display.
|
|
234
|
+
#
|
|
235
|
+
# @return [Hash] configuration with masked secrets
|
|
236
|
+
def masked_config
|
|
237
|
+
config = deep_copy(@raw_config)
|
|
238
|
+
|
|
239
|
+
(config["users"] || []).each do |user|
|
|
240
|
+
user_data = user["user"] || {}
|
|
241
|
+
user_data["token-secret"] = "********" if user_data["token-secret"]
|
|
242
|
+
user_data["password"] = "********" if user_data["password"]
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
config
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
# Saves the current configuration to file.
|
|
249
|
+
#
|
|
250
|
+
# @return [void]
|
|
251
|
+
def save
|
|
252
|
+
@store.save(@config_path, @raw_config)
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
private
|
|
256
|
+
|
|
257
|
+
# Resolves the configuration file path.
|
|
258
|
+
#
|
|
259
|
+
# Priority: CLI option > ENV > default
|
|
260
|
+
#
|
|
261
|
+
# @param cli_options [Hash] CLI options
|
|
262
|
+
# @return [String] resolved path
|
|
263
|
+
def resolve_config_path(cli_options)
|
|
264
|
+
cli_options[:config] ||
|
|
265
|
+
ENV["PVECTL_CONFIG"] ||
|
|
266
|
+
DEFAULT_CONFIG_PATH
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
# Outputs a warning about insecure file permissions.
|
|
270
|
+
#
|
|
271
|
+
# @return [void]
|
|
272
|
+
def warn_insecure_permissions
|
|
273
|
+
$stderr.puts "Warning: Configuration file has insecure permissions. " \
|
|
274
|
+
"Consider running: chmod 600 #{@config_path}"
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
# Refreshes the raw configuration from disk.
|
|
278
|
+
#
|
|
279
|
+
# @return [void]
|
|
280
|
+
def refresh_raw_config
|
|
281
|
+
@raw_config = @provider.load_file(@config_path)
|
|
282
|
+
end
|
|
283
|
+
|
|
284
|
+
# Creates a deep copy of a hash.
|
|
285
|
+
#
|
|
286
|
+
# @param obj [Object] object to copy
|
|
287
|
+
# @return [Object] deep copy
|
|
288
|
+
def deep_copy(obj)
|
|
289
|
+
case obj
|
|
290
|
+
when Hash
|
|
291
|
+
obj.transform_values { |v| deep_copy(v) }
|
|
292
|
+
when Array
|
|
293
|
+
obj.map { |v| deep_copy(v) }
|
|
294
|
+
else
|
|
295
|
+
obj
|
|
296
|
+
end
|
|
297
|
+
end
|
|
298
|
+
end
|
|
299
|
+
end
|
|
300
|
+
end
|