Module: Pvectl::Commands::ResourceLifecycleCommand Abstract

Included in:
ContainerLifecycleCommand, VmLifecycleCommand
Defined in:
lib/pvectl/commands/resource_lifecycle_command.rb,
sig/pvectl/commands/resource_lifecycle_command.rbs

Overview

This module is abstract.

Include a specialization module (VmLifecycleCommand, ContainerLifecycleCommand) instead of this one directly.

Shared functionality for lifecycle commands across resource types.

Template method pattern: provides common flow (validate, resolve, confirm, execute, output) while specialization modules define resource-specific hooks.

Examples:

Specialization module pattern

module VmLifecycleCommand
  def self.included(base)
    base.include(ResourceLifecycleCommand)
  end

  private
  def supported_resources = %w[vm]
  def resource_label = "VM"
  # ...
end

Defined Under Namespace

Modules: ClassMethods

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.included(base) ⇒ void

This method returns an undefined value.

Hook called when module is included.



44
45
46
# File 'lib/pvectl/commands/resource_lifecycle_command.rb', line 44

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

Instance Method Details

#apply_selectors(resources) ⇒ Array<Object>

Applies selectors to resource collection.



177
178
179
180
181
182
# File 'lib/pvectl/commands/resource_lifecycle_command.rb', line 177

def apply_selectors(resources)
  return resources if selector_strings.empty?

  selector = build_selector(selector_strings)
  selector.apply(resources)
end

#build_presenterObject

Returns presenter for results.

Raises:

  • (NotImplementedError)


105
106
107
# File 'lib/pvectl/commands/resource_lifecycle_command.rb', line 105

def build_presenter
  raise NotImplementedError, "#{self.class} must implement #build_presenter"
end

#build_repository(connection) ⇒ Object

Returns repository for this resource type.

Raises:

  • (NotImplementedError)


95
96
97
# File 'lib/pvectl/commands/resource_lifecycle_command.rb', line 95

def build_repository(connection)
  raise NotImplementedError, "#{self.class} must implement #build_repository"
end

#build_selector(strings) ⇒ Object

Returns selector for filtering.

Raises:

  • (NotImplementedError)


110
111
112
# File 'lib/pvectl/commands/resource_lifecycle_command.rb', line 110

def build_selector(strings)
  raise NotImplementedError, "#{self.class} must implement #build_selector"
end

#build_service(repo, task_repo, options) ⇒ Object

Returns lifecycle service.

Raises:

  • (NotImplementedError)


100
101
102
# File 'lib/pvectl/commands/resource_lifecycle_command.rb', line 100

def build_service(repo, task_repo, options)
  raise NotImplementedError, "#{self.class} must implement #build_service"
end

#confirm_operation(resources) ⇒ Boolean

Confirms multi-resource operation with user.



188
189
190
191
192
193
194
195
196
197
198
199
# File 'lib/pvectl/commands/resource_lifecycle_command.rb', line 188

def confirm_operation(resources)
  return true if resources.size == 1
  return true if @options[:yes]

  $stdout.puts "You are about to #{operation_name} #{resources.size} #{resource_label}s:"
  resources.each { |r| $stdout.puts "  - #{r.vmid} (#{r.name || 'unnamed'}) on #{r.node}" }
  $stdout.puts ""
  $stdout.print "Proceed? [y/N]: "

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

#determine_exit_code(results) ⇒ Integer

Determines exit code based on results.



247
248
249
250
251
252
# File 'lib/pvectl/commands/resource_lifecycle_command.rb', line 247

def determine_exit_code(results)
  return Pvectl::ExitCodes::SUCCESS if results.all?(&:successful?)
  return Pvectl::ExitCodes::SUCCESS if results.all?(&:pending?)

  Pvectl::ExitCodes::GENERAL_ERROR
end

#executeInteger

Executes the lifecycle command.



64
65
66
67
68
69
70
71
72
73
# File 'lib/pvectl/commands/resource_lifecycle_command.rb', line 64

def execute
  return usage_error("Resource type required (#{supported_resources.join(', ')})") unless @resource_type
  return usage_error("Unsupported resource: #{@resource_type}") unless supported_resources.include?(@resource_type)

  if @resource_ids.empty? && !@options[:all] && selector_strings.empty?
    return usage_error("#{resource_id_label}, --all, or -l selector required")
  end

  perform_operation
end

#initialize(resource_type, resource_ids, options, global_options) ⇒ ResourceLifecycleCommand

Initializes a lifecycle command.



54
55
56
57
58
59
# File 'lib/pvectl/commands/resource_lifecycle_command.rb', line 54

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

#load_configvoid

This method returns an undefined value.

Loads configuration from file or environment.



211
212
213
214
215
# File 'lib/pvectl/commands/resource_lifecycle_command.rb', line 211

def load_config
  service = Pvectl::Config::Service.new
  service.load(config: @global_options[:config])
  @config = service.current_config
end

#no_resources_foundInteger

Outputs no resources found error and returns exit code.



266
267
268
269
270
271
272
273
274
# File 'lib/pvectl/commands/resource_lifecycle_command.rb', line 266

def no_resources_found
  msg = if @options[:all] || selector_strings.any?
          @options[:node] ? "No #{resource_label}s found on node #{@options[:node]}" : "No #{resource_label}s found matching criteria"
        else
          "No #{resource_label}s found for given #{resource_id_label}s"
        end
  $stderr.puts "Error: #{msg}"
  Pvectl::ExitCodes::NOT_FOUND
end

#operation_nameSymbol

Returns the operation name for this command.



204
205
206
# File 'lib/pvectl/commands/resource_lifecycle_command.rb', line 204

def operation_name
  self.class::OPERATION
end

#output_results(results) ⇒ void

This method returns an undefined value.

Outputs operation results using the configured formatter.



233
234
235
236
237
238
239
240
241
# File 'lib/pvectl/commands/resource_lifecycle_command.rb', line 233

def output_results(results)
  presenter = build_presenter
  format = @global_options[:output] || "table"
  color_flag = @global_options[:color]

  formatter = Pvectl::Formatters::Registry.for(format)
  output = formatter.format(results, presenter, color: color_flag)
  puts output
end

#perform_operationInteger

Performs the lifecycle operation.



119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
# File 'lib/pvectl/commands/resource_lifecycle_command.rb', line 119

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

  repo = build_repository(connection)
  task_repo = Pvectl::Repositories::Task.new(connection)

  resources = resolve_resources(repo)
  return no_resources_found if resources.empty?
  return Pvectl::ExitCodes::SUCCESS unless confirm_operation(resources)

  service = build_service(repo, task_repo, service_options)
  results = service.execute(operation_name, resources)

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

#resolve_resources(repo) ⇒ Array<Object>

Resolves resources based on resource_ids, --all flag, or selectors.



150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
# File 'lib/pvectl/commands/resource_lifecycle_command.rb', line 150

def resolve_resources(repo)
  resources = if @options[:all]
                repo.list(node: @options[:node])
              elsif @resource_ids.any?
                resolved = @resource_ids.map { |id| repo.get(id.to_i) }.compact
                resolved = resolved.select { |r| r.node == @options[:node] } if @options[:node]
                resolved
              else
                return [] if selector_strings.empty?

                repo.list(node: @options[:node])
              end

  apply_selectors(resources)
end

#resource_id_labelString

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

Raises:

  • (NotImplementedError)


90
91
92
# File 'lib/pvectl/commands/resource_lifecycle_command.rb', line 90

def resource_id_label
  raise NotImplementedError, "#{self.class} must implement #resource_id_label"
end

#resource_labelString

Returns human label for resource ("VM", "container").

Raises:

  • (NotImplementedError)


85
86
87
# File 'lib/pvectl/commands/resource_lifecycle_command.rb', line 85

def resource_label
  raise NotImplementedError, "#{self.class} must implement #resource_label"
end

#selector_stringsArray<String>

Returns selector strings from options.



169
170
171
# File 'lib/pvectl/commands/resource_lifecycle_command.rb', line 169

def selector_strings
  Array(@options[:selector] || @options[:l])
end

#service_optionsHash

Builds service options from command options.



220
221
222
223
224
225
226
227
# File 'lib/pvectl/commands/resource_lifecycle_command.rb', line 220

def service_options
  opts = {}
  opts[:timeout] = @options[:timeout] if @options[:timeout]
  opts[:async] = true if @options[:async]
  opts[:wait] = true if @options[:wait]
  opts[:fail_fast] = true if @options[:"fail-fast"]
  opts
end

#supported_resourcesArray<String>

Returns supported resource type strings.

Raises:

  • (NotImplementedError)


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

def supported_resources
  raise NotImplementedError, "#{self.class} must implement #supported_resources"
end

#usage_error(message) ⇒ Integer

Outputs usage error and returns exit code.



258
259
260
261
# File 'lib/pvectl/commands/resource_lifecycle_command.rb', line 258

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