Class: Pvectl::Commands::CloneContainer

Inherits:
Object
  • Object
show all
Includes:
SharedConfigParsers
Defined in:
lib/pvectl/commands/clone_container.rb,
sig/pvectl/commands/clone_container.rbs

Overview

Handler for the pvectl clone container command.

Clones a container by CTID, supporting full and linked clones, custom hostname, target node, storage, pool, and description. No batch operations - clones exactly one container at a time.

Examples:

Full clone with auto-generated CTID

pvectl clone container 100

Clone with custom hostname and target CTID

pvectl clone container 100 --vmid 200 --name web-clone

Linked clone to different node

pvectl clone container 100 --linked --target pve2

Class Method Summary collapse

Instance Method Summary collapse

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) ⇒ CloneContainer

Initializes a clone container command.



38
39
40
41
42
# File 'lib/pvectl/commands/clone_container.rb', line 38

def initialize(args, options, global_options)
  @args = args
  @options = options
  @global_options = global_options
end

Class Method Details

.execute(args, options, global_options) ⇒ Integer

Executes the clone container command.



29
30
31
# File 'lib/pvectl/commands/clone_container.rb', line 29

def self.execute(args, options, global_options)
  new(args, options, global_options).execute
end

Instance Method Details

#display_clone_summary(ctid, 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.



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

def display_clone_summary(ctid, config_params)
  $stdout.puts ""
  $stdout.puts "  Clone Container - Summary"
  $stdout.puts "  #{'─' * 40}"
  $stdout.puts "  Source:    #{ctid}"
  $stdout.puts "  New ID:    #{@options[:newid] || '(auto)'}"
  $stdout.puts "  Hostname:  #{@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 container? [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.



198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
# File 'lib/pvectl/commands/clone_container.rb', line 198

def display_config_changes(params)
  $stdout.puts "  ── Config changes #{'─' * 23}"
  $stdout.puts "  CPU:       #{params[:cores]} cores" if params[:cores]
  $stdout.puts "  Memory:    #{params[:memory]} MB" if params[:memory]
  $stdout.puts "  Swap:      #{params[:swap]} MB" if params[:swap]
  if params[:rootfs]
    $stdout.puts "  RootFS:    #{params[:rootfs][:storage]}, #{params[:rootfs][:size]}"
  end
  if params[:mountpoints]
    params[:mountpoints].each_with_index do |mp, i|
      $stdout.puts "  MP#{i}:       #{mp[:mp]}, #{mp[:storage]}"
    end
  end
  if params[:nets]
    params[:nets].each_with_index do |n, i|
      $stdout.puts "  Net#{i}:      #{n[:bridge]}"
    end
  end
  $stdout.puts "  Privileged: yes" if params[:privileged]
  $stdout.puts "  Tags:      #{params[:tags]}" if params[:tags]
end

#executeInteger

Executes the clone container command.

Builds config params from shared flags, validates async+config compatibility, and delegates to the clone operation.



50
51
52
53
54
55
56
57
58
59
60
61
# File 'lib/pvectl/commands/clone_container.rb', line 50

def execute
  ctid = @args.first
  return usage_error("Source CTID required") unless ctid

  config_params = build_ct_config_params

  if @options[:async] && !config_params.empty?
    return usage_error("Config flags require sync mode (remove --async)")
  end

  perform_clone(ctid.to_i, config_params)
end

#load_configvoid

This method returns an undefined value.

Loads configuration from file or environment.



133
134
135
136
137
# File 'lib/pvectl/commands/clone_container.rb', line 133

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.



154
155
156
157
158
159
160
161
162
# File 'lib/pvectl/commands/clone_container.rb', line 154

def output_result(result)
  presenter = Pvectl::Presenters::ContainerOperationResult.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(ctid, 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.



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

def perform_clone(ctid, config_params)
  unless config_params.empty?
    return ExitCodes::SUCCESS if display_clone_summary(ctid, config_params) == :cancelled
  end

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

  ct_repo = Pvectl::Repositories::Container.new(connection)
  task_repo = Pvectl::Repositories::Task.new(connection)

  service = Pvectl::Services::CloneContainer.new(
    container_repository: ct_repo,
    task_repository: task_repo,
    options: service_options
  )

  result = service.execute(
    ctid: ctid,
    new_ctid: @options[:newid]&.to_i,
    hostname: @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.container

  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

This method returns an undefined value.

Prints progress message for sync mode.



122
123
124
125
126
127
128
# File 'lib/pvectl/commands/clone_container.rb', line 122

def print_progress(result)
  source = result.container
  new_hostname = result.resource&.dig(:hostname) || "clone"
  new_id = result.resource&.dig(:new_ctid)
  $stderr.puts "Cloning container #{source.vmid} (#{source.name || 'unnamed'}) to #{new_id} (#{new_hostname})..."
  $stderr.puts ""
end

#service_optionsHash

Builds service options from command options.



142
143
144
145
146
147
148
# File 'lib/pvectl/commands/clone_container.rb', line 142

def service_options
  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.



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

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