Class: Pvectl::Services::CloneContainer

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

Overview

Orchestrates container clone operations.

Handles validation, auto-generation of CTID/hostname, and sync/async modes. Supports both full clones and linked clones (templates only).

Examples:

Full clone with auto-generated CTID

service = CloneContainer.new(container_repository: ct_repo, task_repository: task_repo)
result = service.execute(ctid: 100)

Linked clone to specific node

service = CloneContainer.new(container_repository: ct_repo, task_repository: task_repo)
result = service.execute(ctid: 100, linked: true, target_node: "pve2")

Async clone with custom timeout

service = CloneContainer.new(container_repository: ct_repo, task_repository: task_repo, options: { async: true })
result = service.execute(ctid: 100, new_ctid: 200, hostname: "web-clone")

Constant Summary collapse

DEFAULT_TIMEOUT =

Returns:

  • (Integer)
300
START_TIMEOUT =

Returns Default timeout for start operations (seconds).

Returns:

  • (Integer)

    Default timeout for start operations (seconds)

60

Instance Method Summary collapse

Constructor Details

#initialize(container_repository:, task_repository:, options: {}) ⇒ CloneContainer

Creates a new CloneContainer service.

Parameters:

  • container_repository (Repositories::Container)

    Container repository

  • task_repository (Repositories::Task)

    Task repository

  • options (Hash) (defaults to: {})

    Options (timeout, async)

  • container_repository: (Object)
  • task_repository: (Object)
  • options: (Hash[Symbol, untyped]) (defaults to: {})


33
34
35
36
37
# File 'lib/pvectl/services/clone_container.rb', line 33

def initialize(container_repository:, task_repository:, options: {})
  @container_repository = container_repository
  @task_repository = task_repository
  @options = options
end

Instance Method Details

#add_mountpoint_params(params, mountpoints) ⇒ void

This method returns an undefined value.

Adds mountpoint parameters mapped to mp0, mp1, etc.

Parameters:

  • params (Hash)

    Parameters hash to modify

  • mountpoints (Array<Hash>)

    Mountpoint configurations



201
202
203
204
205
# File 'lib/pvectl/services/clone_container.rb', line 201

def add_mountpoint_params(params, mountpoints)
  mountpoints.each_with_index do |mp, index|
    params[:"mp#{index}"] = Parsers::LxcMountConfig.to_proxmox(mp)
  end
end

#add_net_params(params, nets) ⇒ void

This method returns an undefined value.

Adds network parameters mapped to net0, net1, etc.

Parameters:

  • params (Hash)

    Parameters hash to modify

  • nets (Array<Hash>)

    Network configurations



212
213
214
215
216
# File 'lib/pvectl/services/clone_container.rb', line 212

def add_net_params(params, nets)
  nets.each_with_index do |net, index|
    params[:"net#{index}"] = Parsers::LxcNetConfig.to_proxmox(net)
  end
end

#apply_config_update(source_ct, new_ctid, node, config_params, resource_info) ⇒ Models::ContainerOperationResult

Applies config update to the cloned container.

Converts user-friendly params to Proxmox API format and calls the repository update method. Returns partial result on failure.

Parameters:

  • source_ct (Models::Container)

    Source container

  • new_ctid (Integer)

    Cloned container identifier

  • node (String)

    Target node for the cloned container

  • config_params (Hash)

    Config parameters to apply

  • resource_info (Hash)

    Resource info for result

Returns:



155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
# File 'lib/pvectl/services/clone_container.rb', line 155

def apply_config_update(source_ct, new_ctid, node, config_params, resource_info)
  api_params = build_ct_config_api_params(config_params)
  @container_repository.update(new_ctid, node, api_params)
  start_container(new_ctid, node) if @options[:start]
  Models::ContainerOperationResult.new(
    container: source_ct, operation: :clone,
    success: true, resource: resource_info
  )
rescue StandardError => e
  Models::ContainerOperationResult.new(
    container: source_ct, operation: :clone,
    success: :partial, resource: resource_info,
    error: "Cloned successfully, but config update failed: #{e.message}"
  )
end

#build_clone_options(hostname:, target_node:, storage:, linked:, pool:, description:) ⇒ Hash

Builds clone options hash for repository call.

Parameters:

  • hostname (String)

    Clone hostname

  • target_node (String, nil)

    Target node

  • storage (String, nil)

    Target storage

  • linked (Boolean)

    Linked clone flag

  • pool (String, nil)

    Resource pool

  • description (String, nil)

    Description

  • hostname: (String)
  • target_node: (String, nil)
  • storage: (String, nil)
  • linked: (Boolean)
  • pool: (String, nil)
  • description: (String, nil)

Returns:

  • (Hash)

    Clone options



135
136
137
138
139
140
141
142
# File 'lib/pvectl/services/clone_container.rb', line 135

def build_clone_options(hostname:, target_node:, storage:, linked:, pool:, description:)
  opts = { hostname: hostname, full: !linked }
  opts[:target] = target_node if target_node
  opts[:storage] = storage if storage
  opts[:pool] = pool if pool
  opts[:description] = description if description
  opts
end

#build_ct_config_api_params(config_params) ⇒ Hash

Builds Proxmox API parameters from user-friendly container config options.

Maps config keys to their Proxmox API equivalents. Does not include hostname, ostemplate, description, or pool (those belong to the clone step).

Parameters:

  • config_params (Hash)

    User-friendly config parameters

Returns:

  • (Hash)

    Proxmox API parameters



178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
# File 'lib/pvectl/services/clone_container.rb', line 178

def build_ct_config_api_params(config_params)
  params = {}
  params[:cores] = config_params[:cores] if config_params[:cores]
  params[:memory] = config_params[:memory] if config_params[:memory]
  params[:swap] = config_params[:swap] if config_params[:swap]
  params[:unprivileged] = config_params[:privileged] ? 0 : 1 unless config_params[:privileged].nil?
  params[:rootfs] = Parsers::LxcMountConfig.to_proxmox(config_params[:rootfs]) if config_params[:rootfs]
  add_mountpoint_params(params, config_params[:mountpoints]) if config_params[:mountpoints]
  add_net_params(params, config_params[:nets]) if config_params[:nets]
  params[:features] = config_params[:features] if config_params[:features]
  params[:password] = config_params[:password] if config_params[:password]
  params[:"ssh-public-keys"] = config_params[:ssh_public_keys] if config_params[:ssh_public_keys]
  params[:onboot] = config_params[:onboot] ? 1 : 0 unless config_params[:onboot].nil?
  params[:startup] = config_params[:startup] if config_params[:startup]
  params[:tags] = config_params[:tags] if config_params[:tags]
  params
end

#container_not_found_error(ctid) ⇒ Models::ContainerOperationResult

Returns error for container not found.

Parameters:

  • ctid (Integer)

    Container identifier

Returns:



239
240
241
242
243
244
245
# File 'lib/pvectl/services/clone_container.rb', line 239

def container_not_found_error(ctid)
  Models::ContainerOperationResult.new(
    operation: :clone,
    success: false,
    error: "Container #{ctid} not found"
  )
end

#execute(ctid:, node: nil, new_ctid: nil, hostname: nil, target_node: nil, storage: nil, linked: false, pool: nil, description: nil, config_params: {}) ⇒ Models::ContainerOperationResult

Executes clone operation.

Performs a two-step flow: clone the container first, then optionally apply config updates via PUT /nodes/node/lxc/ctid/config.

Parameters:

  • ctid (Integer)

    Source container identifier

  • node (String, nil) (defaults to: nil)

    Source node (auto-detected from container if nil)

  • new_ctid (Integer, nil) (defaults to: nil)

    New CTID (auto-selected if nil)

  • hostname (String, nil) (defaults to: nil)

    Hostname for clone (auto-generated if nil)

  • target_node (String, nil) (defaults to: nil)

    Target node for clone

  • storage (String, nil) (defaults to: nil)

    Target storage

  • linked (Boolean) (defaults to: false)

    Linked clone (default: false, requires template)

  • pool (String, nil) (defaults to: nil)

    Resource pool

  • description (String, nil) (defaults to: nil)

    Description

  • config_params (Hash) (defaults to: {})

    Container config parameters to apply after clone

  • ctid: (Integer)
  • node: (String, nil) (defaults to: nil)
  • new_ctid: (Integer, nil) (defaults to: nil)
  • hostname: (String, nil) (defaults to: nil)
  • target_node: (String, nil) (defaults to: nil)
  • storage: (String, nil) (defaults to: nil)
  • linked: (Boolean) (defaults to: false)
  • pool: (String, nil) (defaults to: nil)
  • description: (String, nil) (defaults to: nil)
  • config_params: (Hash[Symbol, untyped]) (defaults to: {})

Returns:



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

def execute(ctid:, node: nil, new_ctid: nil, hostname: nil, target_node: nil,
            storage: nil, linked: false, pool: nil, description: nil,
            config_params: {})
  source_ct = @container_repository.get(ctid)
  return container_not_found_error(ctid) unless source_ct

  if linked && !source_ct.template?
    return linked_clone_error(source_ct)
  end

  node ||= source_ct.node
  new_ctid ||= @container_repository.next_available_ctid
  hostname ||= generate_hostname(source_ct)

  clone_options = build_clone_options(
    hostname: hostname, target_node: target_node, storage: storage,
    linked: linked, pool: pool, description: description
  )

  upid = @container_repository.clone(ctid, node, new_ctid, clone_options)
  resource_info = { new_ctid: new_ctid, hostname: hostname, node: target_node || node }

  if @options[:async]
    Models::ContainerOperationResult.new(
      container: source_ct, operation: :clone,
      task_upid: upid, success: :pending,
      resource: resource_info
    )
  else
    task = @task_repository.wait(upid, timeout: timeout)

    unless task.successful?
      return Models::ContainerOperationResult.new(
        container: source_ct, operation: :clone,
        task: task, success: task.successful?,
        resource: resource_info
      )
    end

    if config_params.any?
      apply_config_update(source_ct, new_ctid, resource_info[:node], config_params, resource_info)
    else
      start_container(new_ctid, resource_info[:node]) if @options[:start]
      Models::ContainerOperationResult.new(
        container: source_ct, operation: :clone,
        task: task, success: true,
        resource: resource_info
      )
    end
  end
rescue StandardError => e
  Models::ContainerOperationResult.new(
    container: source_ct, operation: :clone,
    success: false, error: e.message
  )
end

#generate_hostname(source_ct) ⇒ String

Generates clone hostname from source container.

Parameters:

Returns:

  • (String)

    Generated hostname



118
119
120
121
122
123
124
# File 'lib/pvectl/services/clone_container.rb', line 118

def generate_hostname(source_ct)
  if source_ct.name && !source_ct.name.empty?
    "#{source_ct.name}-clone"
  else
    "ct-#{source_ct.vmid}-clone"
  end
end

#linked_clone_error(source_ct) ⇒ Models::ContainerOperationResult

Returns error for linked clone of non-template container.

Parameters:

Returns:



251
252
253
254
255
256
257
# File 'lib/pvectl/services/clone_container.rb', line 251

def linked_clone_error(source_ct)
  Models::ContainerOperationResult.new(
    container: source_ct, operation: :clone,
    success: false,
    error: "Linked clone requires container to be a template. Container #{source_ct.vmid} is not a template"
  )
end

#start_container(ctid, node) ⇒ void

This method returns an undefined value.

Starts a container after successful clone and config update.

Parameters:

  • ctid (Integer)

    Container identifier

  • node (String)

    Node name



223
224
225
226
# File 'lib/pvectl/services/clone_container.rb', line 223

def start_container(ctid, node)
  upid = @container_repository.start(ctid, node)
  @task_repository.wait(upid, timeout: START_TIMEOUT)
end

#timeoutInteger

Returns configured timeout.

Returns:

  • (Integer)

    Timeout in seconds



231
232
233
# File 'lib/pvectl/services/clone_container.rb', line 231

def timeout
  @options[:timeout] || DEFAULT_TIMEOUT
end