Class: Knife::Proxmox::Api
- Inherits:
-
Object
- Object
- Knife::Proxmox::Api
- Defined in:
- lib/knife-proxmox-ve/api.rb
Overview
Stateless map of the verified Proxmox VE endpoints onto a Knife::Proxmox::Client. Every method is a single round-trip with no cross-call sequencing (that lives in the Provisioner). It knows the shape of each endpoint’s payload, applies the qemu filter where the cluster API mixes VM types, and keeps path segments injection-safe by routing node names and UPIDs through Client.encode_segment.
Constant Summary collapse
- DEFAULT_TIMEOUT =
Polling defaults for #wait_task. A monotonic deadline (not wall clock) guards the timeout so a clock step cannot stretch or cut short the wait.
300- DEFAULT_INTERVAL =
2
Instance Method Summary collapse
-
#agent_ip_addresses(node:, vmid:) ⇒ Object
Routable IPs reported by the guest agent, IPv4 first.
-
#clone(node:, vmid:, newid:, **params) ⇒ Object
Clone a template into a new VM.
-
#find_template(name_or_vmid) ⇒ Object
Resolve a template by name or numeric vmid.
-
#initialize(client) ⇒ Api
constructor
A new instance of Api.
-
#list_resources(template: nil) ⇒ Object
List qemu resources, optionally narrowed to templates (template: true) or plain VMs (template: false); nil keeps both.
-
#next_id ⇒ Object
The cluster’s next free VMID.
-
#start(node:, vmid:) ⇒ Object
Start a VM.
-
#update_config(node:, vmid:, **params) ⇒ Object
Apply VM config (cores, memory, net0, cloud-init keys, …).
-
#vm_config(node:, vmid:) ⇒ Object
Current VM config Hash (string keys).
-
#wait_task(upid:, timeout: DEFAULT_TIMEOUT, interval: DEFAULT_INTERVAL, sleeper: nil) ⇒ Object
Poll a task to completion.
Constructor Details
#initialize(client) ⇒ Api
Returns a new instance of Api.
19 20 21 |
# File 'lib/knife-proxmox-ve/api.rb', line 19 def initialize(client) @client = client end |
Instance Method Details
#agent_ip_addresses(node:, vmid:) ⇒ Object
Routable IPs reported by the guest agent, IPv4 first. Skips loopback (lo / 127.*) and IPv6 link-local (fe80::). Raises (NotFoundError/ApiError) when the agent is not available rather than returning an empty result that the caller would misread.
93 94 95 96 97 98 99 100 101 102 103 104 |
# File 'lib/knife-proxmox-ve/api.rb', line 93 def agent_ip_addresses(node:, vmid:) result = @client.get( "/nodes/#{enc(node)}/qemu/#{enc(vmid)}/agent/network-get-interfaces" ) interfaces = (result && result["result"]) || [] addresses = interfaces.reject { |iface| iface["name"] == "lo" }.flat_map do |iface| (iface["ip-addresses"] || []).map { |a| a["ip-address"] } end addresses.compact.reject { |ip| routable_skip?(ip) }.sort_by { |ip| ipv4?(ip) ? 0 : 1 } end |
#clone(node:, vmid:, newid:, **params) ⇒ Object
Clone a template into a new VM. Returns the UPID of the async clone task.
66 67 68 69 70 71 |
# File 'lib/knife-proxmox-ve/api.rb', line 66 def clone(node:, vmid:, newid:, **params) @client.post( "/nodes/#{enc(node)}/qemu/#{enc(vmid)}/clone", { newid: }.merge(params) ) end |
#find_template(name_or_vmid) ⇒ Object
Resolve a template by name or numeric vmid. Filters /cluster/resources down to qemu templates and matches either the name or the vmid. Raises NotFoundError if no template matches, so the caller never proceeds with a phantom source.
26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
# File 'lib/knife-proxmox-ve/api.rb', line 26 def find_template(name_or_vmid) wanted = name_or_vmid.to_s entry = qemu_resources.find do |r| r["template"].to_i == 1 && (r["name"].to_s == wanted || r["vmid"].to_s == wanted) end unless entry raise NotFoundError.new( "no qemu template matches #{name_or_vmid.inspect}", status: 404 ) end { vmid: entry["vmid"], node: entry["node"], name: entry["name"] } end |
#list_resources(template: nil) ⇒ Object
List qemu resources, optionally narrowed to templates (template: true) or plain VMs (template: false); nil keeps both. Returns a uniform Hash per entry with missing fields normalized to nil.
45 46 47 48 49 50 51 52 53 54 55 56 57 58 |
# File 'lib/knife-proxmox-ve/api.rb', line 45 def list_resources(template: nil) qemu_resources.select { |r| matches_template?(r, template) }.map do |r| { vmid: r["vmid"], node: r["node"], name: r["name"], status: r["status"], template: r["template"], maxmem: r["maxmem"], maxdisk: r["maxdisk"], uptime: r["uptime"], } end end |
#next_id ⇒ Object
The cluster’s next free VMID. Proxmox returns it as a string; coerce to Integer.
61 62 63 |
# File 'lib/knife-proxmox-ve/api.rb', line 61 def next_id @client.get("/cluster/nextid").to_i end |
#start(node:, vmid:) ⇒ Object
Start a VM. Returns the UPID of the async start task.
80 81 82 |
# File 'lib/knife-proxmox-ve/api.rb', line 80 def start(node:, vmid:) @client.post("/nodes/#{enc(node)}/qemu/#{enc(vmid)}/status/start") end |
#update_config(node:, vmid:, **params) ⇒ Object
Apply VM config (cores, memory, net0, cloud-init keys, …). Only the supplied keys are sent, so a partial update never clears unrelated config.
75 76 77 |
# File 'lib/knife-proxmox-ve/api.rb', line 75 def update_config(node:, vmid:, **params) @client.post("/nodes/#{enc(node)}/qemu/#{enc(vmid)}/config", params) end |
#vm_config(node:, vmid:) ⇒ Object
Current VM config Hash (string keys). Used to detect the cloud-init drive and to read back a static IP after configuration.
86 87 88 |
# File 'lib/knife-proxmox-ve/api.rb', line 86 def vm_config(node:, vmid:) @client.get("/nodes/#{enc(node)}/qemu/#{enc(vmid)}/config") end |
#wait_task(upid:, timeout: DEFAULT_TIMEOUT, interval: DEFAULT_INTERVAL, sleeper: nil) ⇒ Object
Poll a task to completion. The node is parsed from the UPID itself (UPID:<node>:…) so the caller cannot pass a mismatched node. Returns true on exitstatus “OK”; raises TaskError (with a scrubbed log) on any other exitstatus; raises TimeoutError once the monotonic deadline passes.
110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 |
# File 'lib/knife-proxmox-ve/api.rb', line 110 def wait_task(upid:, timeout: DEFAULT_TIMEOUT, interval: DEFAULT_INTERVAL, sleeper: nil) node = node_from_upid(upid) sleeper ||= ->(seconds) { sleep(seconds) } deadline = monotonic_now + timeout loop do status = task_status(node, upid) if status["status"] == "stopped" return true if status["exitstatus"] == "OK" raise_task_error(node, upid, status["exitstatus"]) end raise TimeoutError, "task #{upid} did not finish within #{timeout}s" if monotonic_now >= deadline sleeper.call(interval) end end |