Class: Pvectl::Services::EditContainer

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

Overview

Orchestrates the interactive container configuration editing flow.

Fetches current config, opens it in an editor as structured YAML, validates changes, computes a diff, and applies updates via the API. Supports dry-run mode and optimistic locking via digest.

Examples:

Basic usage

service = EditContainer.new(container_repository: repo)
result = service.execute(ctid: 200)

Dry run with injected editor session

service = EditContainer.new(container_repository: repo, editor_session: session,
                            options: { dry_run: true })
result = service.execute(ctid: 200)

Instance Method Summary collapse

Constructor Details

#initialize(container_repository:, editor_session: nil, options: {}) ⇒ EditContainer

Creates a new EditContainer service.

Parameters:

  • container_repository (Repositories::Container)

    Container repository

  • editor_session (EditorSession, nil) (defaults to: nil)

    optional injected editor session

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

    options (dry_run)

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


26
27
28
29
30
# File 'lib/pvectl/services/edit_container.rb', line 26

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

Instance Method Details

#build_result(resource_info, **attrs) ⇒ Models::ContainerOperationResult

Builds a ContainerOperationResult with the :edit operation.

Parameters:

  • resource_info (Hash)

    resource info (ctid, node, status)

  • attrs (Hash)

    additional result attributes

Returns:



106
107
108
109
110
111
112
113
114
# File 'lib/pvectl/services/edit_container.rb', line 106

def build_result(resource_info, **attrs)
  container = Models::Container.new(
    vmid: resource_info[:ctid],
    node: resource_info[:node]
  )
  Models::ContainerOperationResult.new(
    operation: :edit, container: container, resource: resource_info, **attrs
  )
end

#build_update_params(changes, original_config) ⇒ Hash

Builds API update parameters from a diff hash.

Maps changed/added keys to their new values, removed keys to the Proxmox delete parameter, and includes digest for optimistic locking.

Parameters:

  • changes (Hash)

    diff hash with :changed, :added, :removed

  • original_config (Hash)

    original flat config (for digest)

Returns:

  • (Hash)

    Proxmox API parameters



90
91
92
93
94
95
96
97
98
99
# File 'lib/pvectl/services/edit_container.rb', line 90

def build_update_params(changes, original_config)
  params = {}
  changes[:changed].each { |key, (_old, new_val)| params[key] = new_val }
  changes[:added].each { |key, val| params[key] = val }
  unless changes[:removed].empty?
    params[:delete] = changes[:removed].map(&:to_s).join(",")
  end
  params[:digest] = original_config[:digest] if original_config[:digest]
  params
end

#execute(ctid:) ⇒ Models::ContainerOperationResult?

Executes the interactive container edit flow.

Parameters:

  • ctid (Integer)

    Container identifier

  • ctid: (Integer)

Returns:



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
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
# File 'lib/pvectl/services/edit_container.rb', line 36

def execute(ctid:)
  container = @container_repository.get(ctid)
  return not_found_result(ctid) unless container

  config = @container_repository.fetch_config(container.node, ctid)
  resource_info = { ctid: ctid, node: container.node, status: container.status }

  yaml_content = ConfigSerializer.to_yaml(config, type: :container, resource: resource_info)

  validator = ->(content) { ConfigSerializer.validate(content, type: :container) }
  session = @editor_session || EditorSession.new(validator: validator)
  edited = session.edit(yaml_content)

  return nil unless edited

  original_roundtrip = ConfigSerializer.from_yaml(yaml_content, type: :container)
  edited_flat = ConfigSerializer.from_yaml(edited, type: :container)

  violations = ConfigSerializer.readonly_violations(original_roundtrip, edited_flat, type: :container)
  unless violations.empty?
    return build_result(resource_info, success: false,
                        error: "Read-only fields cannot be changed: #{violations.join(', ')}")
  end

  changes = ConfigSerializer.diff(original_roundtrip, edited_flat)

  if changes[:changed].empty? && changes[:added].empty? && changes[:removed].empty?
    return nil
  end

  params = build_update_params(changes, config)

  resource_info[:diff] = changes

  if @options[:dry_run]
    return build_result(resource_info, success: true)
  end

  @container_repository.update(ctid, container.node, params)
  build_result(resource_info, success: true)
rescue StandardError => e
  build_result({ ctid: ctid }, success: false, error: e.message)
end

#not_found_result(ctid) ⇒ Models::ContainerOperationResult

Builds a not-found error result.

Parameters:

  • ctid (Integer)

    Container identifier

Returns:



120
121
122
# File 'lib/pvectl/services/edit_container.rb', line 120

def not_found_result(ctid)
  build_result({ ctid: ctid }, success: false, error: "Container #{ctid} not found")
end