Module: Pvectl::Commands::MoveDiskCommand

Included in:
MoveDiskContainer, MoveDiskVm
Defined in:
lib/pvectl/commands/move_disk_command.rb,
sig/pvectl/commands/move_disk_command.rbs

Overview

Shared functionality for the move disk commands (move disk vm, move disk ct).

Mirrors the structure of MigrateCommand but operates on a single resource (VM or container) and a single disk/volume rather than a batch.

Examples:

Including in a command class

class MoveDiskVm
  include MoveDiskCommand
  RESOURCE_TYPE = :vm
  SUPPORTED_RESOURCES = %w[vm].freeze
end

Defined Under Namespace

Modules: ClassMethods

Constant Summary collapse

VALID_FORMATS =

Allowed target formats for VM disks (per Proxmox API).

%w[raw qcow2 vmdk].freeze

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.included(base) ⇒ void

This method returns an undefined value.

Hook called when the module is included.



37
38
39
# File 'lib/pvectl/commands/move_disk_command.rb', line 37

def self.included(base)
  base.extend(ClassMethods)
end

Instance Method Details

#build_service(connection) ⇒ Services::MoveDisk

Builds the MoveDisk service with required dependencies.



147
148
149
150
151
152
153
154
# File 'lib/pvectl/commands/move_disk_command.rb', line 147

def build_service(connection)
  Pvectl::Services::MoveDisk.new(
    vm_repository: Pvectl::Repositories::Vm.new(connection),
    container_repository: Pvectl::Repositories::Container.new(connection),
    task_repository: Pvectl::Repositories::Task.new(connection),
    options: service_options
  )
end

#confirm_operation(resource, disk, target) ⇒ Boolean

Confirms the move operation with the user.



175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
# File 'lib/pvectl/commands/move_disk_command.rb', line 175

def confirm_operation(resource, disk, target)
  return true if @options[:yes]

  type_name = resource_type_symbol == :vm ? "VM" : "container"
  $stdout.puts "You are about to move #{disk} of #{type_name} #{resource.vmid} " \
               "(#{resource.name || 'unnamed'}) on #{resource.node} to storage #{target}."
  $stdout.puts ""
  if @options[:"delete-source"]
    $stdout.puts "Source disk WILL be deleted after a successful copy."
  else
    $stdout.puts "Source disk will be kept as an unused entry."
  end
  $stdout.print "Proceed? [y/N]: "

  response = $stdin.gets&.strip&.downcase
  %w[y yes].include?(response)
end

#disk_labelString

Returns the human label for the disk/volume.



94
95
96
# File 'lib/pvectl/commands/move_disk_command.rb', line 94

def disk_label
  resource_type_symbol == :vm ? "DISK" : "VOLUME"
end

#executeInteger

Executes the move disk command.



55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
# File 'lib/pvectl/commands/move_disk_command.rb', line 55

def execute
  target = @options[:target]
  return usage_error("--target is required") if target.nil? || target.to_s.empty?

  return usage_error("#{id_label} and #{disk_label} are required") if @args.size < 2

  id_str, disk = @args[0], @args[1]

  unless id_str.to_s.match?(/\A\d+\z/)
    return usage_error("Invalid #{id_label}: #{id_str}")
  end

  if @options[:format]
    return usage_error("--format is not supported for containers") if resource_type_symbol == :container
    return usage_error("Invalid format: #{@options[:format]} (allowed: #{VALID_FORMATS.join(', ')})") unless VALID_FORMATS.include?(@options[:format])
  end

  perform_operation(id_str.to_i, disk, target)
end

#id_labelString

Returns the human label for the resource ID ("VMID" or "CTID").



87
88
89
# File 'lib/pvectl/commands/move_disk_command.rb', line 87

def id_label
  resource_type_symbol == :vm ? "VMID" : "CTID"
end

#initialize(args, options, global_options) ⇒ MoveDiskCommand

Initializes a move disk command.



46
47
48
49
50
# File 'lib/pvectl/commands/move_disk_command.rb', line 46

def initialize(args, options, global_options)
  @args = Array(args).compact
  @options = options
  @global_options = global_options
end

#load_configvoid

This method returns an undefined value.

Loads configuration from file or environment.



196
197
198
199
200
# File 'lib/pvectl/commands/move_disk_command.rb', line 196

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 the operation result using the configured formatter.



206
207
208
209
210
211
212
213
214
215
216
217
218
# File 'lib/pvectl/commands/move_disk_command.rb', line 206

def output_result(result)
  presenter = if resource_type_symbol == :vm
                Pvectl::Presenters::VmOperationResult.new
              else
                Pvectl::Presenters::ContainerOperationResult.new
              end
  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_operation(id, disk, target) ⇒ Integer

Performs the move operation: loads config, resolves resource, prompts, invokes service, prints result.



105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
# File 'lib/pvectl/commands/move_disk_command.rb', line 105

def perform_operation(id, disk, target)
  load_config
  connection = Pvectl::Connection.new(@config)

  resource = resolve_resource(connection, id)
  return resource_not_found(id) if resource.nil?

  return ExitCodes::SUCCESS unless confirm_operation(resource, disk, target)

  service = build_service(connection)
  result = service.execute(resource_type_symbol, resource,
                           disk: disk, target_storage: target)

  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.message}"
  ExitCodes::GENERAL_ERROR
end

#resolve_resource(connection, id) ⇒ Models::Vm, ...

Resolves the single resource by ID using the appropriate repository.



136
137
138
139
140
141
# File 'lib/pvectl/commands/move_disk_command.rb', line 136

def resolve_resource(connection, id)
  repo = resource_type_symbol == :vm ?
    Pvectl::Repositories::Vm.new(connection) :
    Pvectl::Repositories::Container.new(connection)
  repo.get(id)
end

#resource_not_found(id) ⇒ Integer

Outputs "not found" error and returns the not-found exit code.



233
234
235
236
# File 'lib/pvectl/commands/move_disk_command.rb', line 233

def resource_not_found(id)
  $stderr.puts "Error: No #{resource_type_symbol == :vm ? 'VM' : 'container'} found with ID #{id}"
  ExitCodes::NOT_FOUND
end

#resource_type_symbolSymbol

Returns the resource type symbol (:vm or :container).



80
81
82
# File 'lib/pvectl/commands/move_disk_command.rb', line 80

def resource_type_symbol
  self.class::RESOURCE_TYPE
end

#service_optionsHash

Builds service options from command options.



159
160
161
162
163
164
165
166
167
# File 'lib/pvectl/commands/move_disk_command.rb', line 159

def service_options
  opts = {}
  opts[:format] = @options[:format] if @options[:format]
  opts[:delete_source] = true if @options[:"delete-source"]
  opts[:bandwidth] = @options[:bandwidth].to_i if @options[:bandwidth]
  opts[:wait] = true if @options[:wait]
  opts[:timeout] = @options[:timeout] if @options[:timeout]
  opts
end

#usage_error(message) ⇒ Integer

Outputs usage error and returns the usage exit code.



224
225
226
227
# File 'lib/pvectl/commands/move_disk_command.rb', line 224

def usage_error(message)
  $stderr.puts "Error: #{message}"
  ExitCodes::USAGE_ERROR
end