Module: Pvectl::Commands::TemplateCommand

Includes:
IrreversibleCommand
Included in:
TemplateContainer, TemplateVm
Defined in:
lib/pvectl/commands/template_command.rb,
sig/pvectl/commands/template_command.rbs

Overview

Template-specific functionality built on IrreversibleCommand.

Converts VMs/containers to Proxmox templates (irreversible operation). Filters out resources that are already templates with a warning. Calls the repository's convert_to_template method for each resource.

Examples:

Including in a command class

class TemplateVm
  include TemplateCommand
  RESOURCE_TYPE = :vm
  SUPPORTED_RESOURCES = %w[vm].freeze
end

Class Method Summary collapse

Instance Method Summary collapse

Methods included from IrreversibleCommand

#apply_selectors, #build_repository, #determine_exit_code, #execute, #initialize, #load_config, #no_resources_found, #output_results, #resolve_resources, #resource_type_symbol, #selector_strings, #service_options, #supported_resource?, #supported_types, #usage_error

Class Method Details

.included(base) ⇒ Object

Hook called when module is included.



24
25
26
# File 'lib/pvectl/commands/template_command.rb', line 24

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

Instance Method Details

#build_error_result(resource, error_message) ⇒ Models::OperationResult

Builds an error operation result.



186
187
188
189
190
191
# File 'lib/pvectl/commands/template_command.rb', line 186

def build_error_result(resource, error_message)
  result_class = resource_type_symbol == :vm ? Models::VmOperationResult : Models::ContainerOperationResult
  attrs = { operation: :template, success: false, error: error_message }
  attrs[resource_type_symbol == :vm ? :vm : :container] = resource
  result_class.new(attrs)
end

#build_presenterPresenters::Base

Builds presenter for template results.



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

def build_presenter
  if resource_type_symbol == :vm
    Pvectl::Presenters::VmOperationResult.new
  else
    Pvectl::Presenters::ContainerOperationResult.new
  end
end

#build_success_result(resource) ⇒ Models::OperationResult

Builds a successful operation result.



174
175
176
177
178
179
# File 'lib/pvectl/commands/template_command.rb', line 174

def build_success_result(resource)
  result_class = resource_type_symbol == :vm ? Models::VmOperationResult : Models::ContainerOperationResult
  attrs = { operation: :template, success: true }
  attrs[resource_type_symbol == :vm ? :vm : :container] = resource
  result_class.new(attrs)
end

#confirm_message(resources) ⇒ String

Returns confirmation message for template conversion.



85
86
87
88
89
90
91
92
93
94
95
96
97
# File 'lib/pvectl/commands/template_command.rb', line 85

def confirm_message(resources)
  type_name = resource_type_symbol == :vm ? "VM" : "container"
  type_plural = resource_type_symbol == :vm ? "VMs" : "containers"

  if resources.size == 1
    r = resources.first
    "You are about to convert #{type_name} #{r.vmid} (#{r.name || 'unnamed'}) on #{r.node} to a template."
  else
    lines = ["You are about to convert #{resources.size} #{type_plural} to templates:"]
    resources.each { |r| lines << "  - #{r.vmid} (#{r.name || 'unnamed'}) on #{r.node}" }
    lines.join("\n")
  end
end

#confirm_operation(resources) ⇒ Boolean

Confirms template operation — uses --yes flag.



69
70
71
72
73
74
75
76
77
78
79
# File 'lib/pvectl/commands/template_command.rb', line 69

def confirm_operation(resources)
  return true if @options[:yes]

  $stdout.puts confirm_message(resources)
  $stdout.puts ""
  $stdout.puts irreversibility_warning
  $stdout.print "Proceed? [y/N]: "

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

#convert_single(repo, resource) ⇒ Models::OperationResult

Converts a single resource to template. Handles running resources: errors without --force, stops with --force.



125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
# File 'lib/pvectl/commands/template_command.rb', line 125

def convert_single(repo, resource)
  if resource.status == "running"
    return running_error(resource) unless @options[:force]

    stop_result = stop_resource(repo, resource)
    return stop_result if stop_result.failed?
  end

  if resource_type_symbol == :vm
    repo.convert_to_template(resource.vmid, resource.node, disk: @options[:disk])
  else
    repo.convert_to_template(resource.vmid, resource.node)
  end

  build_success_result(resource)
rescue StandardError => e
  build_error_result(resource, e.message)
end

#irreversibility_warningString

Returns irreversibility warning for template conversion.



102
103
104
# File 'lib/pvectl/commands/template_command.rb', line 102

def irreversibility_warning
  "This action is IRREVERSIBLE. Templates cannot be converted back and cannot be started."
end

#perform_operationInteger

Overrides perform_operation to filter already-template resources.



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
# File 'lib/pvectl/commands/template_command.rb', line 33

def perform_operation
  load_config
  connection = Pvectl::Connection.new(@config)

  resources = resolve_resources(connection)
  return no_resources_found if resources.empty?

  # Filter out already-template resources with warning
  convertible, already_templates = resources.partition { |r| !r.template? }

  already_templates.each do |r|
    type_name = resource_type_symbol == :vm ? "VM" : "Container"
    $stderr.puts "Warning: #{type_name} #{r.vmid} is already a template, skipping"
  end

  return ExitCodes::SUCCESS if convertible.empty?
  return ExitCodes::SUCCESS unless confirm_operation(convertible)

  results = perform_service_call(convertible, connection)
  output_results(results)
  determine_exit_code(results)
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

#perform_service_call(resources, connection) ⇒ Array<Models::OperationResult>

Performs template conversion for each resource.



111
112
113
114
115
116
117
# File 'lib/pvectl/commands/template_command.rb', line 111

def perform_service_call(resources, connection)
  repo = build_repository(connection)
  @task_repository = Pvectl::Repositories::Task.new(connection) if @options[:force]
  resources.map do |resource|
    convert_single(repo, resource)
  end
end

#running_error(resource) ⇒ Models::OperationResult

Returns error result for a running resource without --force.



148
149
150
151
# File 'lib/pvectl/commands/template_command.rb', line 148

def running_error(resource)
  type_name = resource_type_symbol == :vm ? "VM" : "Container"
  build_error_result(resource, "#{type_name} #{resource.vmid} is running. Stop it first or use --force")
end

#stop_resource(repo, resource) ⇒ Models::OperationResult

Stops a running resource before template conversion.



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

def stop_resource(repo, resource)
  upid = repo.stop(resource.vmid, resource.node)
  timeout = @options[:timeout] || 60
  task = @task_repository.wait(upid, timeout: timeout)

  if task.successful?
    build_success_result(resource)
  else
    build_error_result(resource, "Failed to stop: #{task.exitstatus}")
  end
end