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,89 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Pvectl
|
|
4
|
+
module Services
|
|
5
|
+
# Orchestrates systemd service lifecycle operations (start/stop/restart/reload)
|
|
6
|
+
# on a Proxmox node.
|
|
7
|
+
#
|
|
8
|
+
# Wraps Repositories::Service to provide a uniform return shape suitable
|
|
9
|
+
# for table/JSON/YAML formatting (Models::NodeOperationResult) and to
|
|
10
|
+
# convert API errors into structured results instead of raising.
|
|
11
|
+
#
|
|
12
|
+
# @example Restarting a service
|
|
13
|
+
# service = ServiceLifecycle.new(service_repository: repo)
|
|
14
|
+
# result = service.execute(operation: :restart, node: "pve1", service: "pveproxy")
|
|
15
|
+
# result.successful? # => true
|
|
16
|
+
#
|
|
17
|
+
class ServiceLifecycle
|
|
18
|
+
# Operations supported by the Proxmox services API.
|
|
19
|
+
OPERATIONS = %i[start stop restart reload].freeze
|
|
20
|
+
|
|
21
|
+
# Creates a new ServiceLifecycle orchestrator.
|
|
22
|
+
#
|
|
23
|
+
# @param service_repository [Repositories::Service] service repository
|
|
24
|
+
def initialize(service_repository:)
|
|
25
|
+
@service_repository = service_repository
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Executes a lifecycle operation on a single service.
|
|
29
|
+
#
|
|
30
|
+
# @param operation [Symbol] one of :start, :stop, :restart, :reload
|
|
31
|
+
# @param node [String] node name
|
|
32
|
+
# @param service [String] service identifier (e.g., "pveproxy")
|
|
33
|
+
# @return [Models::NodeOperationResult] result with task UPID or error
|
|
34
|
+
# @raise [ArgumentError] when operation is unknown
|
|
35
|
+
def execute(operation:, node:, service:)
|
|
36
|
+
validate_operation!(operation)
|
|
37
|
+
|
|
38
|
+
begin
|
|
39
|
+
upid = @service_repository.public_send(operation, node, service)
|
|
40
|
+
build_result(
|
|
41
|
+
operation: operation,
|
|
42
|
+
node: node,
|
|
43
|
+
service: service,
|
|
44
|
+
task_upid: upid,
|
|
45
|
+
success: :pending
|
|
46
|
+
)
|
|
47
|
+
rescue StandardError => e
|
|
48
|
+
build_result(
|
|
49
|
+
operation: operation,
|
|
50
|
+
node: node,
|
|
51
|
+
service: service,
|
|
52
|
+
success: false,
|
|
53
|
+
error: e.message
|
|
54
|
+
)
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
private
|
|
59
|
+
|
|
60
|
+
# Validates operation is one of the supported actions.
|
|
61
|
+
#
|
|
62
|
+
# @param operation [Symbol] operation
|
|
63
|
+
# @raise [ArgumentError] when not in OPERATIONS
|
|
64
|
+
def validate_operation!(operation)
|
|
65
|
+
return if OPERATIONS.include?(operation)
|
|
66
|
+
|
|
67
|
+
raise ArgumentError,
|
|
68
|
+
"Unknown service operation: #{operation}. Valid: #{OPERATIONS.join(', ')}"
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Builds a NodeOperationResult for the operation.
|
|
72
|
+
#
|
|
73
|
+
# @param operation [Symbol] operation
|
|
74
|
+
# @param node [String] node name
|
|
75
|
+
# @param service [String] service identifier
|
|
76
|
+
# @param attrs [Hash] additional result attributes (task_upid, success, error)
|
|
77
|
+
# @return [Models::NodeOperationResult]
|
|
78
|
+
def build_result(operation:, node:, service:, **attrs)
|
|
79
|
+
node_model = Models::Node.new(name: node)
|
|
80
|
+
Models::NodeOperationResult.new(
|
|
81
|
+
operation: operation,
|
|
82
|
+
node_model: node_model,
|
|
83
|
+
resource: { service: service, node: node },
|
|
84
|
+
**attrs
|
|
85
|
+
)
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Pvectl
|
|
4
|
+
module Services
|
|
5
|
+
# Orchestrates non-interactive container configuration updates.
|
|
6
|
+
#
|
|
7
|
+
# Takes key-value pairs directly (no editor), computes diff against
|
|
8
|
+
# current config, and applies changes via the API.
|
|
9
|
+
# Supports dry-run mode and optimistic locking via digest.
|
|
10
|
+
#
|
|
11
|
+
# @example Basic usage
|
|
12
|
+
# service = SetContainer.new(container_repository: repo)
|
|
13
|
+
# result = service.execute(ctid: 200, params: { memory: "8192", cores: "4" })
|
|
14
|
+
#
|
|
15
|
+
# @example Dry run
|
|
16
|
+
# service = SetContainer.new(container_repository: repo, options: { dry_run: true })
|
|
17
|
+
# result = service.execute(ctid: 200, params: { memory: "8192" })
|
|
18
|
+
#
|
|
19
|
+
class SetContainer
|
|
20
|
+
# Creates a new SetContainer service.
|
|
21
|
+
#
|
|
22
|
+
# @param container_repository [Repositories::Container] Container repository
|
|
23
|
+
# @param options [Hash] options (dry_run)
|
|
24
|
+
def initialize(container_repository:, options: {})
|
|
25
|
+
@container_repository = container_repository
|
|
26
|
+
@options = options
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Executes the non-interactive container config update.
|
|
30
|
+
#
|
|
31
|
+
# Fetches current config, computes diff against requested params,
|
|
32
|
+
# and applies changes via the API (unless dry-run).
|
|
33
|
+
#
|
|
34
|
+
# @param ctid [Integer] Container identifier
|
|
35
|
+
# @param params [Hash] key-value pairs to set
|
|
36
|
+
# @return [Models::ContainerOperationResult, nil] result, or nil if no changes
|
|
37
|
+
def execute(ctid:, params:)
|
|
38
|
+
container = @container_repository.get(ctid)
|
|
39
|
+
return not_found_result(ctid) unless container
|
|
40
|
+
|
|
41
|
+
config = @container_repository.fetch_config(container.node, ctid)
|
|
42
|
+
resource_info = { vmid: ctid, node: container.node, status: container.status }
|
|
43
|
+
|
|
44
|
+
changes = compute_diff(config, params)
|
|
45
|
+
|
|
46
|
+
if changes[:changed].empty? && changes[:added].empty? && changes[:removed].empty?
|
|
47
|
+
return nil
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
resource_info[:diff] = changes
|
|
51
|
+
|
|
52
|
+
if @options[:dry_run]
|
|
53
|
+
return build_result(resource_info, success: true)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
update_params = build_update_params(changes, config)
|
|
57
|
+
@container_repository.update(ctid, container.node, update_params)
|
|
58
|
+
build_result(resource_info, success: true)
|
|
59
|
+
rescue StandardError => e
|
|
60
|
+
build_result({ vmid: ctid }, success: false, error: e.message)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
private
|
|
64
|
+
|
|
65
|
+
# Computes diff between current config and requested params.
|
|
66
|
+
#
|
|
67
|
+
# Categorizes each requested param as :changed (value differs),
|
|
68
|
+
# :added (key not in current config), or unchanged (skipped).
|
|
69
|
+
#
|
|
70
|
+
# @param config [Hash] current configuration
|
|
71
|
+
# @param params [Hash] requested key-value changes
|
|
72
|
+
# @return [Hash] diff with :changed, :added, :removed keys
|
|
73
|
+
def compute_diff(config, params)
|
|
74
|
+
changed = {}
|
|
75
|
+
added = {}
|
|
76
|
+
|
|
77
|
+
params.each do |key, value|
|
|
78
|
+
sym_key = key.to_sym
|
|
79
|
+
current = config[sym_key]
|
|
80
|
+
|
|
81
|
+
if current.nil?
|
|
82
|
+
added[sym_key] = value
|
|
83
|
+
elsif current.to_s != value.to_s
|
|
84
|
+
changed[sym_key] = [current.to_s, value.to_s]
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
{ changed: changed, added: added, removed: [] }
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# Builds API update parameters from diff.
|
|
92
|
+
#
|
|
93
|
+
# Maps changed/added keys to their new values and includes
|
|
94
|
+
# digest from original config for optimistic locking.
|
|
95
|
+
#
|
|
96
|
+
# @param changes [Hash] diff hash with :changed, :added
|
|
97
|
+
# @param original_config [Hash] original config (for digest)
|
|
98
|
+
# @return [Hash] API parameters
|
|
99
|
+
def build_update_params(changes, original_config)
|
|
100
|
+
params = {}
|
|
101
|
+
changes[:changed].each { |key, (_old, new_val)| params[key] = new_val }
|
|
102
|
+
changes[:added].each { |key, val| params[key] = val }
|
|
103
|
+
params[:digest] = original_config[:digest] if original_config[:digest]
|
|
104
|
+
params
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# Builds a ContainerOperationResult with the :set operation.
|
|
108
|
+
#
|
|
109
|
+
# @param resource_info [Hash] resource info (vmid, node, status)
|
|
110
|
+
# @param attrs [Hash] additional result attributes
|
|
111
|
+
# @return [Models::ContainerOperationResult]
|
|
112
|
+
def build_result(resource_info, **attrs)
|
|
113
|
+
container = Models::Container.new(vmid: resource_info[:vmid], node: resource_info[:node])
|
|
114
|
+
Models::ContainerOperationResult.new(
|
|
115
|
+
operation: :set, container: container, resource: resource_info, **attrs
|
|
116
|
+
)
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# Builds a not-found error result.
|
|
120
|
+
#
|
|
121
|
+
# @param ctid [Integer] Container identifier
|
|
122
|
+
# @return [Models::ContainerOperationResult]
|
|
123
|
+
def not_found_result(ctid)
|
|
124
|
+
build_result({ vmid: ctid }, success: false, error: "Container #{ctid} not found")
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
end
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Pvectl
|
|
4
|
+
module Services
|
|
5
|
+
# Orchestrates non-interactive node configuration updates.
|
|
6
|
+
#
|
|
7
|
+
# Takes key-value pairs directly (no editor), computes diff against
|
|
8
|
+
# current config, and applies changes via the API.
|
|
9
|
+
# Supports dry-run mode and optimistic locking via digest.
|
|
10
|
+
#
|
|
11
|
+
# The `timezone` key is special: it lives at `PUT /nodes/{node}/time`
|
|
12
|
+
# rather than `PUT /nodes/{node}/config`, so the service splits it off
|
|
13
|
+
# and routes it to a separate `time_repository`.
|
|
14
|
+
#
|
|
15
|
+
# @example Basic usage
|
|
16
|
+
# service = SetNode.new(node_repository: repo)
|
|
17
|
+
# result = service.execute(node_name: "pve1", params: { description: "updated" })
|
|
18
|
+
#
|
|
19
|
+
# @example Setting timezone
|
|
20
|
+
# service = SetNode.new(node_repository: node_repo, time_repository: time_repo)
|
|
21
|
+
# result = service.execute(node_name: "pve1", params: { timezone: "Europe/Warsaw" })
|
|
22
|
+
#
|
|
23
|
+
# @example Dry run
|
|
24
|
+
# service = SetNode.new(node_repository: repo, options: { dry_run: true })
|
|
25
|
+
# result = service.execute(node_name: "pve1", params: { description: "updated" })
|
|
26
|
+
#
|
|
27
|
+
class SetNode
|
|
28
|
+
# Key recognized by this service as the node timezone (handled by the
|
|
29
|
+
# `/time` endpoint rather than `/config`). Accepted as Symbol or String.
|
|
30
|
+
TIMEZONE_KEY = :timezone
|
|
31
|
+
|
|
32
|
+
# Creates a new SetNode service.
|
|
33
|
+
#
|
|
34
|
+
# @param node_repository [Repositories::Node] Node repository
|
|
35
|
+
# @param time_repository [Repositories::TimeConfig, nil] Time repository (optional;
|
|
36
|
+
# required only when `params` includes `:timezone`)
|
|
37
|
+
# @param options [Hash] options (dry_run)
|
|
38
|
+
def initialize(node_repository:, time_repository: nil, options: {})
|
|
39
|
+
@node_repository = node_repository
|
|
40
|
+
@time_repository = time_repository
|
|
41
|
+
@options = options
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Executes the non-interactive node config update.
|
|
45
|
+
#
|
|
46
|
+
# Fetches current config and (if timezone is requested) current timezone,
|
|
47
|
+
# computes diffs against the requested params, and applies changes via
|
|
48
|
+
# the appropriate API endpoint (unless dry-run).
|
|
49
|
+
#
|
|
50
|
+
# @param node_name [String] Node name
|
|
51
|
+
# @param params [Hash] key-value pairs to set
|
|
52
|
+
# @return [Models::NodeOperationResult, nil] result, or nil if no changes
|
|
53
|
+
def execute(node_name:, params:)
|
|
54
|
+
node = @node_repository.get(node_name)
|
|
55
|
+
return not_found_result(node_name) unless node
|
|
56
|
+
|
|
57
|
+
config = @node_repository.fetch_config(node_name)
|
|
58
|
+
config_params, requested_tz = split_timezone(params)
|
|
59
|
+
current_tz = requested_tz ? current_timezone(node_name) : nil
|
|
60
|
+
|
|
61
|
+
changes = compute_diff(config, config_params)
|
|
62
|
+
tz_change = compute_timezone_change(current_tz, requested_tz)
|
|
63
|
+
merge_timezone_into_diff(changes, tz_change) if tz_change
|
|
64
|
+
|
|
65
|
+
return nil if no_changes?(changes)
|
|
66
|
+
|
|
67
|
+
resource_info = { node_name: node_name, status: node.status, diff: changes }
|
|
68
|
+
return build_result(resource_info, success: true) if @options[:dry_run]
|
|
69
|
+
|
|
70
|
+
apply_config_changes(node_name, changes, config) unless config_changes_empty?(changes, tz_change)
|
|
71
|
+
@time_repository.set_timezone(node_name, tz_change[:to]) if tz_change
|
|
72
|
+
|
|
73
|
+
build_result(resource_info, success: true)
|
|
74
|
+
rescue StandardError => e
|
|
75
|
+
build_result({ node_name: node_name }, success: false, error: e.message)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
private
|
|
79
|
+
|
|
80
|
+
# Separates the `:timezone` key (accepted as Symbol or String) from
|
|
81
|
+
# other config params.
|
|
82
|
+
#
|
|
83
|
+
# @param params [Hash] requested params
|
|
84
|
+
# @return [Array(Hash, String?)] pair of [remaining_params, requested_timezone_or_nil]
|
|
85
|
+
def split_timezone(params)
|
|
86
|
+
remaining = {}
|
|
87
|
+
requested_tz = nil
|
|
88
|
+
|
|
89
|
+
params.each do |key, value|
|
|
90
|
+
if key.to_sym == TIMEZONE_KEY
|
|
91
|
+
requested_tz = value.to_s
|
|
92
|
+
else
|
|
93
|
+
remaining[key] = value
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
[remaining, requested_tz]
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# Fetches the current timezone for the node.
|
|
101
|
+
#
|
|
102
|
+
# @param node_name [String]
|
|
103
|
+
# @return [String, nil]
|
|
104
|
+
def current_timezone(node_name)
|
|
105
|
+
return nil unless @time_repository
|
|
106
|
+
|
|
107
|
+
@time_repository.fetch(node_name).timezone
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# Builds a timezone-change descriptor when requested != current.
|
|
111
|
+
#
|
|
112
|
+
# @param current [String, nil]
|
|
113
|
+
# @param requested [String, nil]
|
|
114
|
+
# @return [Hash{from: String, to: String}, nil] descriptor or nil when unchanged/missing
|
|
115
|
+
def compute_timezone_change(current, requested)
|
|
116
|
+
return nil if requested.nil?
|
|
117
|
+
return nil if current.to_s == requested.to_s
|
|
118
|
+
|
|
119
|
+
{ from: current.to_s, to: requested.to_s }
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# Folds a timezone change into the diff structure so it shows up in
|
|
123
|
+
# the operation result alongside config-key changes.
|
|
124
|
+
#
|
|
125
|
+
# @param changes [Hash] diff hash (mutated in place)
|
|
126
|
+
# @param tz_change [Hash] `{from:, to:}`
|
|
127
|
+
# @return [void]
|
|
128
|
+
def merge_timezone_into_diff(changes, tz_change)
|
|
129
|
+
if tz_change[:from].nil? || tz_change[:from].empty?
|
|
130
|
+
changes[:added][TIMEZONE_KEY] = tz_change[:to]
|
|
131
|
+
else
|
|
132
|
+
changes[:changed][TIMEZONE_KEY] = [tz_change[:from], tz_change[:to]]
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# Computes diff between current config and requested params.
|
|
137
|
+
#
|
|
138
|
+
# @param config [Hash] current configuration
|
|
139
|
+
# @param params [Hash] requested key-value changes
|
|
140
|
+
# @return [Hash] diff with :changed, :added, :removed keys
|
|
141
|
+
def compute_diff(config, params)
|
|
142
|
+
changed = {}
|
|
143
|
+
added = {}
|
|
144
|
+
|
|
145
|
+
params.each do |key, value|
|
|
146
|
+
sym_key = key.to_sym
|
|
147
|
+
current = config[sym_key]
|
|
148
|
+
|
|
149
|
+
if current.nil?
|
|
150
|
+
added[sym_key] = value
|
|
151
|
+
elsif current.to_s != value.to_s
|
|
152
|
+
changed[sym_key] = [current.to_s, value.to_s]
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
{ changed: changed, added: added, removed: [] }
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
# Returns true when the diff contains no actionable change.
|
|
160
|
+
#
|
|
161
|
+
# @param changes [Hash] diff hash
|
|
162
|
+
# @return [Boolean]
|
|
163
|
+
def no_changes?(changes)
|
|
164
|
+
changes[:changed].empty? && changes[:added].empty? && changes[:removed].empty?
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
# Applies non-timezone diff entries via the node config endpoint.
|
|
168
|
+
#
|
|
169
|
+
# @param node_name [String]
|
|
170
|
+
# @param changes [Hash] diff hash
|
|
171
|
+
# @param original_config [Hash] for digest
|
|
172
|
+
# @return [void]
|
|
173
|
+
def apply_config_changes(node_name, changes, original_config)
|
|
174
|
+
update_params = build_update_params(changes, original_config)
|
|
175
|
+
return if update_params.empty? || update_params.keys == [:digest]
|
|
176
|
+
|
|
177
|
+
@node_repository.update(node_name, update_params)
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
# True when the only changes in the diff are timezone-related.
|
|
181
|
+
#
|
|
182
|
+
# @param changes [Hash] diff hash
|
|
183
|
+
# @param tz_change [Hash, nil]
|
|
184
|
+
# @return [Boolean]
|
|
185
|
+
def config_changes_empty?(changes, tz_change)
|
|
186
|
+
return false if tz_change.nil?
|
|
187
|
+
|
|
188
|
+
non_tz_changed = changes[:changed].keys - [TIMEZONE_KEY]
|
|
189
|
+
non_tz_added = changes[:added].keys - [TIMEZONE_KEY]
|
|
190
|
+
non_tz_changed.empty? && non_tz_added.empty?
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
# Builds API update parameters from diff (excluding timezone, which is
|
|
194
|
+
# routed elsewhere).
|
|
195
|
+
#
|
|
196
|
+
# @param changes [Hash] diff hash with :changed, :added
|
|
197
|
+
# @param original_config [Hash] original config (for digest)
|
|
198
|
+
# @return [Hash] API parameters
|
|
199
|
+
def build_update_params(changes, original_config)
|
|
200
|
+
params = {}
|
|
201
|
+
changes[:changed].each do |key, (_old, new_val)|
|
|
202
|
+
next if key == TIMEZONE_KEY
|
|
203
|
+
|
|
204
|
+
params[key] = new_val
|
|
205
|
+
end
|
|
206
|
+
changes[:added].each do |key, val|
|
|
207
|
+
next if key == TIMEZONE_KEY
|
|
208
|
+
|
|
209
|
+
params[key] = val
|
|
210
|
+
end
|
|
211
|
+
params[:digest] = original_config[:digest] if original_config[:digest] && !params.empty?
|
|
212
|
+
params
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
# Builds a NodeOperationResult with the :set operation.
|
|
216
|
+
#
|
|
217
|
+
# @param resource_info [Hash] resource info (node_name, status)
|
|
218
|
+
# @param attrs [Hash] additional result attributes
|
|
219
|
+
# @return [Models::NodeOperationResult]
|
|
220
|
+
def build_result(resource_info, **attrs)
|
|
221
|
+
node_model = Models::Node.new(name: resource_info[:node_name])
|
|
222
|
+
Models::NodeOperationResult.new(
|
|
223
|
+
operation: :set, node_model: node_model, resource: resource_info, **attrs
|
|
224
|
+
)
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
# Builds a not-found error result.
|
|
228
|
+
#
|
|
229
|
+
# @param node_name [String] Node name
|
|
230
|
+
# @return [Models::NodeOperationResult]
|
|
231
|
+
def not_found_result(node_name)
|
|
232
|
+
build_result({ node_name: node_name }, success: false, error: "Node #{node_name} not found")
|
|
233
|
+
end
|
|
234
|
+
end
|
|
235
|
+
end
|
|
236
|
+
end
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Pvectl
|
|
4
|
+
module Services
|
|
5
|
+
# Orchestrates non-interactive VM configuration updates.
|
|
6
|
+
#
|
|
7
|
+
# Takes key-value pairs directly (no editor), computes diff against
|
|
8
|
+
# current config, and applies changes via the API.
|
|
9
|
+
# Supports dry-run mode and optimistic locking via digest.
|
|
10
|
+
#
|
|
11
|
+
# @example Basic usage
|
|
12
|
+
# service = SetVm.new(vm_repository: repo)
|
|
13
|
+
# result = service.execute(vmid: 100, params: { memory: "8192", cores: "4" })
|
|
14
|
+
#
|
|
15
|
+
# @example Dry run
|
|
16
|
+
# service = SetVm.new(vm_repository: repo, options: { dry_run: true })
|
|
17
|
+
# result = service.execute(vmid: 100, params: { memory: "8192" })
|
|
18
|
+
#
|
|
19
|
+
class SetVm
|
|
20
|
+
# Creates a new SetVm service.
|
|
21
|
+
#
|
|
22
|
+
# @param vm_repository [Repositories::Vm] VM repository
|
|
23
|
+
# @param options [Hash] options (dry_run)
|
|
24
|
+
def initialize(vm_repository:, options: {})
|
|
25
|
+
@vm_repository = vm_repository
|
|
26
|
+
@options = options
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Executes the non-interactive VM config update.
|
|
30
|
+
#
|
|
31
|
+
# Fetches current config, computes diff against requested params,
|
|
32
|
+
# and applies changes via the API (unless dry-run).
|
|
33
|
+
#
|
|
34
|
+
# @param vmid [Integer] VM identifier
|
|
35
|
+
# @param params [Hash] key-value pairs to set
|
|
36
|
+
# @return [Models::VmOperationResult, nil] result, or nil if no changes
|
|
37
|
+
def execute(vmid:, params:)
|
|
38
|
+
vm = @vm_repository.get(vmid)
|
|
39
|
+
return not_found_result(vmid) unless vm
|
|
40
|
+
|
|
41
|
+
config = @vm_repository.fetch_config(vm.node, vmid)
|
|
42
|
+
resource_info = { vmid: vmid, node: vm.node, status: vm.status }
|
|
43
|
+
|
|
44
|
+
changes = compute_diff(config, params)
|
|
45
|
+
|
|
46
|
+
if changes[:changed].empty? && changes[:added].empty? && changes[:removed].empty?
|
|
47
|
+
return nil
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
resource_info[:diff] = changes
|
|
51
|
+
|
|
52
|
+
if @options[:dry_run]
|
|
53
|
+
return build_result(resource_info, success: true)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
update_params = build_update_params(changes, config)
|
|
57
|
+
@vm_repository.update(vmid, vm.node, update_params)
|
|
58
|
+
build_result(resource_info, success: true)
|
|
59
|
+
rescue StandardError => e
|
|
60
|
+
build_result({ vmid: vmid }, success: false, error: e.message)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
private
|
|
64
|
+
|
|
65
|
+
# Computes diff between current config and requested params.
|
|
66
|
+
#
|
|
67
|
+
# Categorizes each requested param as :changed (value differs),
|
|
68
|
+
# :added (key not in current config), or unchanged (skipped).
|
|
69
|
+
#
|
|
70
|
+
# @param config [Hash] current configuration
|
|
71
|
+
# @param params [Hash] requested key-value changes
|
|
72
|
+
# @return [Hash] diff with :changed, :added, :removed keys
|
|
73
|
+
def compute_diff(config, params)
|
|
74
|
+
changed = {}
|
|
75
|
+
added = {}
|
|
76
|
+
|
|
77
|
+
params.each do |key, value|
|
|
78
|
+
sym_key = key.to_sym
|
|
79
|
+
current = config[sym_key]
|
|
80
|
+
|
|
81
|
+
if current.nil?
|
|
82
|
+
added[sym_key] = value
|
|
83
|
+
elsif current.to_s != value.to_s
|
|
84
|
+
changed[sym_key] = [current.to_s, value.to_s]
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
{ changed: changed, added: added, removed: [] }
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# Builds API update parameters from diff.
|
|
92
|
+
#
|
|
93
|
+
# Maps changed/added keys to their new values and includes
|
|
94
|
+
# digest from original config for optimistic locking.
|
|
95
|
+
#
|
|
96
|
+
# @param changes [Hash] diff hash with :changed, :added
|
|
97
|
+
# @param original_config [Hash] original config (for digest)
|
|
98
|
+
# @return [Hash] API parameters
|
|
99
|
+
def build_update_params(changes, original_config)
|
|
100
|
+
params = {}
|
|
101
|
+
changes[:changed].each { |key, (_old, new_val)| params[key] = new_val }
|
|
102
|
+
changes[:added].each { |key, val| params[key] = val }
|
|
103
|
+
params[:digest] = original_config[:digest] if original_config[:digest]
|
|
104
|
+
params
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# Builds a VmOperationResult with the :set operation.
|
|
108
|
+
#
|
|
109
|
+
# @param resource_info [Hash] resource info (vmid, node, status)
|
|
110
|
+
# @param attrs [Hash] additional result attributes
|
|
111
|
+
# @return [Models::VmOperationResult]
|
|
112
|
+
def build_result(resource_info, **attrs)
|
|
113
|
+
vm = Models::Vm.new(vmid: resource_info[:vmid], node: resource_info[:node])
|
|
114
|
+
Models::VmOperationResult.new(
|
|
115
|
+
operation: :set, vm: vm, resource: resource_info, **attrs
|
|
116
|
+
)
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# Builds a not-found error result.
|
|
120
|
+
#
|
|
121
|
+
# @param vmid [Integer] VM identifier
|
|
122
|
+
# @return [Models::VmOperationResult]
|
|
123
|
+
def not_found_result(vmid)
|
|
124
|
+
build_result({ vmid: vmid }, success: false, error: "VM #{vmid} not found")
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
end
|