Class: Pvectl::Commands::CloneVm
- Inherits:
-
Object
- Object
- Pvectl::Commands::CloneVm
- Includes:
- SharedConfigParsers
- Defined in:
- lib/pvectl/commands/clone_vm.rb,
sig/pvectl/commands/clone_vm.rbs
Overview
Handler for the pvectl clone vm command.
Clones a VM by VMID, supporting full and linked clones, custom name, target node, storage, pool, and description. No batch operations - clones exactly one VM at a time.
Class Method Summary collapse
-
.execute(args, options, global_options) ⇒ Integer
Executes the clone VM command.
-
.register(cli) ⇒ void
Registers the clone command with the CLI.
Instance Method Summary collapse
-
#display_clone_summary(vmid, config_params) ⇒ Symbol?
Displays clone summary with config changes and prompts for confirmation.
-
#display_config_changes(params) ⇒ void
Displays the config changes section of the clone summary.
-
#execute ⇒ Integer
Executes the clone VM command.
-
#initialize(args, options, global_options) ⇒ CloneVm
constructor
Initializes a clone VM command.
-
#load_config ⇒ void
Loads configuration from file or environment.
-
#output_result(result) ⇒ void
Outputs operation result using the configured formatter.
-
#perform_clone(vmid, config_params) ⇒ Integer
Performs the clone operation.
-
#print_progress(result) ⇒ void
Prints progress message for sync mode.
-
#service_options ⇒ Hash
Builds service options from command options.
-
#usage_error(message) ⇒ Integer
Outputs usage error and returns exit code.
Methods included from SharedConfigParsers
#build_ct_config_params, #build_vm_config_params, #parse_ct_mountpoints, #parse_ct_nets, #parse_vm_cloud_init, #parse_vm_disks, #parse_vm_nets
Constructor Details
#initialize(args, options, global_options) ⇒ CloneVm
Initializes a clone VM command.
141 142 143 144 145 |
# File 'lib/pvectl/commands/clone_vm.rb', line 141 def initialize(args, , ) @args = args @options = @global_options = end |
Class Method Details
.execute(args, options, global_options) ⇒ Integer
Executes the clone VM command.
132 133 134 |
# File 'lib/pvectl/commands/clone_vm.rb', line 132 def self.execute(args, , ) new(args, , ).execute end |
.register(cli) ⇒ void
This method returns an undefined value.
Registers the clone command with the CLI.
27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 |
# File 'lib/pvectl/commands/clone_vm.rb', line 27 def self.register(cli) cli.desc "Clone a resource" cli.long_desc <<~HELP Clone a virtual machine or container, optionally modifying the configuration of the clone (CPU, memory, disks, network). Supports full clones (independent copy) and linked clones (shares base image with source — requires source to be a template). EXAMPLES Clone a VM to the same node: $ pvectl clone vm 100 --name web-clone Clone to a different node: $ pvectl clone vm 100 --name web-prod --target pve2 Clone with modified configuration: $ pvectl clone vm 100 --name web-prod --cores 4 --memory 8192 Linked clone (thin provisioning, requires template): $ pvectl clone vm 100 --linked --name thin-clone Clone a container with new network config: $ pvectl clone ct 200 --name db-clone --memory 4096 --net bridge=vmbr1 Clone with explicit new ID: $ pvectl clone vm 100 --newid 150 --name web-test NOTES Config modification is a two-step process: clone first, then update configuration via the Proxmox API. If the config update fails, the clone still exists but with the original configuration. Linked clones share the base disk with the source. They are faster to create and use less storage, but the source cannot be deleted. If --name is not specified, Proxmox auto-generates a name. SEE ALSO pvectl help create Create new VMs/containers from scratch pvectl help migrate Move resources between nodes pvectl help template Convert to template for linked clones HELP cli.arg_name "RESOURCE_TYPE ID" cli.command :clone do |c| c.desc "Name/hostname for the new resource" c.flag [:name, :n], arg_name: "NAME" c.desc "ID for the new resource (auto-selected if not specified)" c.flag [:newid], type: Integer, arg_name: "ID" c.desc "Target node for the clone" c.flag [:target, :t], arg_name: "NODE" c.desc "Target storage for the clone" c.flag [:storage, :s], arg_name: "STORAGE" c.desc "Create a linked clone (requires source to be a template)" c.switch [:linked], negatable: false c.desc "Resource pool for the new resource" c.flag [:pool, :p], arg_name: "POOL" c.desc "Description for the new resource" c.flag [:description, :d], arg_name: "DESCRIPTION" c.desc "Timeout in seconds for sync operations (default: 300)" c.flag [:timeout], type: Integer, arg_name: "SECONDS" c.desc "Async mode (return task ID immediately)" c.switch [:async], negatable: false c.desc "Skip confirmation prompt" c.switch [:yes, :y], negatable: false # Shared config flags for VM/container modification after clone SharedFlags.common_config(c) SharedFlags.vm_config(c) SharedFlags.container_config(c) c.action do |, , args| resource_type = args.shift exit_code = case resource_type when "vm" Commands::CloneVm.execute(args, , ) when "container", "ct" Commands::CloneContainer.execute(args, , ) else $stderr.puts "Error: Unknown resource type: #{resource_type}" $stderr.puts "Valid types: vm, container, ct" ExitCodes::USAGE_ERROR end exit exit_code if exit_code != 0 end end end |
Instance Method Details
#display_clone_summary(vmid, config_params) ⇒ Symbol?
Displays clone summary with config changes and prompts for confirmation.
Only called when config params are present. Shows source/target info and the config changes that will be applied after cloning.
275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 |
# File 'lib/pvectl/commands/clone_vm.rb', line 275 def display_clone_summary(vmid, config_params) $stdout.puts "" $stdout.puts " Clone VM - Summary" $stdout.puts " #{'─' * 40}" $stdout.puts " Source: #{vmid}" $stdout.puts " New ID: #{@options[:newid] || '(auto)'}" $stdout.puts " Name: #{@options[:name] || '(auto)'}" target_display = @options[:target] ? "→ #{@options[:target]}" : "(same)" $stdout.puts " Node: #{target_display}" $stdout.puts " Storage: #{@options[:storage]}" if @options[:storage] display_config_changes(config_params) $stdout.puts " #{'─' * 40}" $stdout.puts "" return nil if @options[:yes] $stdout.print "Clone and configure this VM? [y/N] " $stdout.flush answer = $stdin.gets&.strip&.downcase answer == "y" ? nil : :cancelled end |
#display_config_changes(params) ⇒ void
This method returns an undefined value.
Displays the config changes section of the clone summary.
301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 |
# File 'lib/pvectl/commands/clone_vm.rb', line 301 def display_config_changes(params) $stdout.puts " ── Config changes #{'─' * 23}" $stdout.puts " CPU: #{params[:cores]} cores" if params[:cores] $stdout.puts " Sockets: #{params[:sockets]}" if params[:sockets] $stdout.puts " Memory: #{params[:memory]} MB" if params[:memory] if params[:disks] params[:disks].each_with_index do |d, i| $stdout.puts " Disk#{i}: #{d[:storage]}, #{d[:size]}" end end if params[:nets] params[:nets].each_with_index do |n, i| $stdout.puts " Net#{i}: #{n[:bridge]}" end end $stdout.puts " OS Type: #{params[:ostype]}" if params[:ostype] $stdout.puts " Agent: enabled" if params[:agent] $stdout.puts " Tags: #{params[:tags]}" if params[:tags] end |
#execute ⇒ Integer
Executes the clone VM command.
Builds config params from shared flags, validates async+config compatibility, and delegates to the clone operation.
153 154 155 156 157 158 159 160 161 162 163 164 |
# File 'lib/pvectl/commands/clone_vm.rb', line 153 def execute vmid = @args.first return usage_error("Source VMID required") unless vmid config_params = build_vm_config_params if @options[:async] && !config_params.empty? return usage_error("Config flags require sync mode (remove --async)") end perform_clone(vmid.to_i, config_params) end |
#load_config ⇒ void
This method returns an undefined value.
Loads configuration from file or environment.
236 237 238 239 240 |
# File 'lib/pvectl/commands/clone_vm.rb', line 236 def load_config service = Pvectl::Config::Service.new service.load(config: @global_options[:config]) @config = service.current_config end |
#output_result(result) ⇒ void
This method returns an undefined value.
Outputs operation result using the configured formatter.
257 258 259 260 261 262 263 264 265 |
# File 'lib/pvectl/commands/clone_vm.rb', line 257 def output_result(result) presenter = Pvectl::Presenters::VmOperationResult.new format = @global_options[:output] || "table" color_flag = @global_options[:color] formatter = Pvectl::Formatters::Registry.for(format) output = formatter.format([result], presenter, color: color_flag) puts output end |
#perform_clone(vmid, config_params) ⇒ Integer
Performs the clone operation.
When config params are present, displays a summary and prompts for confirmation before proceeding. Passes config_params to the service for the two-step clone+configure flow.
177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 |
# File 'lib/pvectl/commands/clone_vm.rb', line 177 def perform_clone(vmid, config_params) unless config_params.empty? return ExitCodes::SUCCESS if display_clone_summary(vmid, config_params) == :cancelled end load_config connection = Pvectl::Connection.new(@config) vm_repo = Pvectl::Repositories::Vm.new(connection) task_repo = Pvectl::Repositories::Task.new(connection) service = Pvectl::Services::CloneVm.new( vm_repository: vm_repo, task_repository: task_repo, options: ) result = service.execute( vmid: vmid, new_vmid: @options[:newid]&.to_i, name: @options[:name], target_node: @options[:target], storage: @options[:storage], linked: @options[:linked], pool: @options[:pool], description: @options[:description], config_params: config_params ) print_progress(result) if !@options[:async] && result.vm output_result(result) result.failed? ? ExitCodes::GENERAL_ERROR : ExitCodes::SUCCESS rescue Pvectl::Config::ConfigNotFoundError, Pvectl::Config::InvalidConfigError, Pvectl::Config::ContextNotFoundError, Pvectl::Config::ClusterNotFoundError, Pvectl::Config::UserNotFoundError raise rescue StandardError => e $stderr.puts "Error: #{e.}" ExitCodes::GENERAL_ERROR end |
#print_progress(result) ⇒ void
This method returns an undefined value.
Prints progress message for sync mode.
225 226 227 228 229 230 231 |
# File 'lib/pvectl/commands/clone_vm.rb', line 225 def print_progress(result) source = result.vm new_name = result.resource&.dig(:name) || "clone" new_id = result.resource&.dig(:new_vmid) $stderr.puts "Cloning VM #{source.vmid} (#{source.name || 'unnamed'}) to #{new_id} (#{new_name})..." $stderr.puts "" end |
#service_options ⇒ Hash
Builds service options from command options.
245 246 247 248 249 250 251 |
# File 'lib/pvectl/commands/clone_vm.rb', line 245 def opts = {} opts[:timeout] = @options[:timeout] if @options[:timeout] opts[:async] = true if @options[:async] opts[:start] = true if @options[:start] opts end |
#usage_error(message) ⇒ Integer
Outputs usage error and returns exit code.
325 326 327 328 |
# File 'lib/pvectl/commands/clone_vm.rb', line 325 def usage_error() $stderr.puts "Error: #{}" ExitCodes::USAGE_ERROR end |