durable-proxmox-ruby 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.md +30 -0
- data/LICENSE +21 -0
- data/README.md +572 -0
- data/durable-proxmox-ruby.gemspec +47 -0
- data/exe/proxmox +218 -0
- data/lib/durable_proxmox/client.rb +262 -0
- data/lib/durable_proxmox/dsl.rb +374 -0
- data/lib/durable_proxmox/errors.rb +44 -0
- data/lib/durable_proxmox/ip_address_group.rb +99 -0
- data/lib/durable_proxmox/node.rb +126 -0
- data/lib/durable_proxmox/version.rb +5 -0
- data/lib/durable_proxmox/vm.rb +200 -0
- data/lib/durable_proxmox.rb +31 -0
- metadata +201 -0
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module DurableProxmox
|
|
4
|
+
# Represents a Proxmox virtual machine.
|
|
5
|
+
#
|
|
6
|
+
# Provides access to VM properties, lifecycle operations, and network configuration.
|
|
7
|
+
class VM
|
|
8
|
+
# @return [String] VM ID
|
|
9
|
+
attr_reader :vm_id
|
|
10
|
+
|
|
11
|
+
# @return [String] VM name/hostname
|
|
12
|
+
attr_reader :name
|
|
13
|
+
|
|
14
|
+
# @return [String] VM status
|
|
15
|
+
attr_reader :status
|
|
16
|
+
|
|
17
|
+
# @return [Float] CPU usage
|
|
18
|
+
attr_reader :cpu
|
|
19
|
+
|
|
20
|
+
# @return [Float] Memory usage
|
|
21
|
+
attr_reader :memory
|
|
22
|
+
|
|
23
|
+
# @return [DurableProxmox::Client] The client instance
|
|
24
|
+
attr_reader :client
|
|
25
|
+
|
|
26
|
+
# @return [String] Node ID where the VM resides
|
|
27
|
+
attr_reader :node_id
|
|
28
|
+
|
|
29
|
+
# @return [Hash] Raw VM data from API
|
|
30
|
+
attr_reader :data
|
|
31
|
+
|
|
32
|
+
# Creates a new VM instance.
|
|
33
|
+
#
|
|
34
|
+
# @param data [Hash] VM data from the API
|
|
35
|
+
# @param node [DurableProxmox::Node] The node this VM belongs to
|
|
36
|
+
# @param client [DurableProxmox::Client] The client instance
|
|
37
|
+
def initialize(data, node, client)
|
|
38
|
+
@client = client
|
|
39
|
+
@data = data
|
|
40
|
+
@vm_id = data["vmid"]
|
|
41
|
+
@name = data["name"]
|
|
42
|
+
@status = data["status"]
|
|
43
|
+
@cpu = data["cpu"]
|
|
44
|
+
@memory = data["memory"]
|
|
45
|
+
@node = node
|
|
46
|
+
@cache = {}
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Clears the internal cache.
|
|
50
|
+
#
|
|
51
|
+
# Useful when the VM state has changed externally.
|
|
52
|
+
def clear_cache
|
|
53
|
+
@cache.clear
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Starts the VM.
|
|
57
|
+
#
|
|
58
|
+
# @return [Faraday::Response] The API response
|
|
59
|
+
def start
|
|
60
|
+
puts "Starting VM #{vm_id} (#{name})..."
|
|
61
|
+
response = @client.post("nodes/#{node_id}/qemu/#{vm_id}/status/start")
|
|
62
|
+
upid = response.body.dig("data")
|
|
63
|
+
if upid
|
|
64
|
+
@client.wait_for_upid(node_id, upid)
|
|
65
|
+
puts "VM #{vm_id} (#{name}) started successfully"
|
|
66
|
+
end
|
|
67
|
+
response
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Stops the VM.
|
|
71
|
+
#
|
|
72
|
+
# @return [Faraday::Response] The API response
|
|
73
|
+
def stop
|
|
74
|
+
puts "Stopping VM #{vm_id} (#{name})..."
|
|
75
|
+
response = @client.post("nodes/#{node_id}/qemu/#{vm_id}/status/stop")
|
|
76
|
+
upid = response.body.dig("data")
|
|
77
|
+
if upid
|
|
78
|
+
@client.wait_for_upid(node_id, upid)
|
|
79
|
+
puts "VM #{vm_id} (#{name}) stopped successfully"
|
|
80
|
+
end
|
|
81
|
+
response
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Deletes the VM.
|
|
85
|
+
#
|
|
86
|
+
# @return [Faraday::Response] The API response
|
|
87
|
+
def delete
|
|
88
|
+
puts "Deleting VM #{vm_id} (#{name})..."
|
|
89
|
+
response = @client.delete("nodes/#{node_id}/qemu/#{vm_id}")
|
|
90
|
+
upid = response.body.dig("data")
|
|
91
|
+
if upid
|
|
92
|
+
@client.wait_for_upid(node_id, upid)
|
|
93
|
+
puts "VM #{vm_id} (#{name}) deleted successfully"
|
|
94
|
+
end
|
|
95
|
+
response
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# Retrieves the VM configuration.
|
|
99
|
+
#
|
|
100
|
+
# @return [Hash] VM configuration data
|
|
101
|
+
def config
|
|
102
|
+
@cache["config"] ||= @client.get("nodes/#{node_id}/qemu/#{vm_id}/config").body.dig("data")
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# Retrieves MAC addresses for the VM's network interfaces.
|
|
106
|
+
#
|
|
107
|
+
# @return [Array<String>] Array of MAC addresses
|
|
108
|
+
def mac_addresses
|
|
109
|
+
net_keys = config.keys.select { |key| key =~ /^net\d+$/ }
|
|
110
|
+
net_keys.map do |key|
|
|
111
|
+
net = config[key]
|
|
112
|
+
net.split(",").map do |pair|
|
|
113
|
+
k, v = pair.split("=")
|
|
114
|
+
k == "virtio" ? v : nil
|
|
115
|
+
end.compact.first
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# Returns the node ID for this VM.
|
|
120
|
+
#
|
|
121
|
+
# @return [String] Node ID
|
|
122
|
+
def node_id
|
|
123
|
+
@node.node_id
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# Retrieves network interface information from the QEMU guest agent.
|
|
127
|
+
#
|
|
128
|
+
# @return [Array<Hash>] Array of network interface data
|
|
129
|
+
def network_interfaces
|
|
130
|
+
@cache["network_interfaces"] ||= begin
|
|
131
|
+
url = "nodes/#{node_id}/qemu/#{vm_id}/agent/network-get-interfaces"
|
|
132
|
+
response = @client.get(url)
|
|
133
|
+
response.body.dig("data", "result") || []
|
|
134
|
+
rescue Faraday::ConnectionFailed, Faraday::SSLError, EOFError => e
|
|
135
|
+
# QEMU guest agent may not be running or connection issues
|
|
136
|
+
[]
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# Retrieves IP addresses for the VM from the cloud-init config.
|
|
141
|
+
#
|
|
142
|
+
# @return [DurableProxmox::IPAddressGroup] IP address group
|
|
143
|
+
def ip_addresses
|
|
144
|
+
ip_addresses = []
|
|
145
|
+
ipconfig_keys = config.keys.select { |key| key =~ /^ipconfig\d+$/ }
|
|
146
|
+
ipconfig_keys.each do |key|
|
|
147
|
+
ipconfig = config[key]
|
|
148
|
+
# Parse cloud-init ipconfig string like "ip=192.168.1.100/24,gw=192.168.1.1"
|
|
149
|
+
ipconfig.split(",").each do |pair|
|
|
150
|
+
k, v = pair.split("=", 2)
|
|
151
|
+
if k == "ip" && v != "dhcp"
|
|
152
|
+
# Extract IP without CIDR notation
|
|
153
|
+
ip_addresses << { "ip-address" => v.split("/").first }
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
IPAddressGroup.new(ip_addresses)
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
# Returns the first MAC address.
|
|
161
|
+
#
|
|
162
|
+
# @return [String, nil] The primary MAC address or nil
|
|
163
|
+
def mac
|
|
164
|
+
mac_addresses.first
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
# Returns the first IPv4 address for the VM.
|
|
168
|
+
#
|
|
169
|
+
# @return [String, nil] The primary IPv4 address or nil
|
|
170
|
+
def ip
|
|
171
|
+
ip_addresses.ipv4.octets.first
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
# Exports this VM as DSL code.
|
|
175
|
+
#
|
|
176
|
+
# @return [String] DSL code representing this VM
|
|
177
|
+
def to_dsl
|
|
178
|
+
lines = []
|
|
179
|
+
lines << "vm \"#{name}\" do"
|
|
180
|
+
lines << " vmid #{vm_id}" if vm_id
|
|
181
|
+
|
|
182
|
+
cfg = config
|
|
183
|
+
lines << " cores #{cfg['cores']}" if cfg['cores']
|
|
184
|
+
lines << " memory #{cfg['memory']}" if cfg['memory']
|
|
185
|
+
|
|
186
|
+
# Extract IP from ipconfig
|
|
187
|
+
primary_ip = ip
|
|
188
|
+
lines << " ip \"#{primary_ip}\"" if primary_ip
|
|
189
|
+
|
|
190
|
+
# Extract DNS servers from nameserver
|
|
191
|
+
if cfg['nameserver'] && !cfg['nameserver'].empty?
|
|
192
|
+
dns_list = cfg['nameserver'].split.map { |dns| "\"#{dns}\"" }.join(", ")
|
|
193
|
+
lines << " dns_servers #{dns_list}"
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
lines << "end"
|
|
197
|
+
lines.join("\n")
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "faraday"
|
|
4
|
+
require "json"
|
|
5
|
+
require "dotenv"
|
|
6
|
+
require "ipaddr"
|
|
7
|
+
|
|
8
|
+
require_relative "durable_proxmox/version"
|
|
9
|
+
require_relative "durable_proxmox/errors"
|
|
10
|
+
|
|
11
|
+
module DurableProxmox
|
|
12
|
+
autoload :Client, "durable_proxmox/client"
|
|
13
|
+
autoload :Node, "durable_proxmox/node"
|
|
14
|
+
autoload :VM, "durable_proxmox/vm"
|
|
15
|
+
autoload :IPAddressGroup, "durable_proxmox/ip_address_group"
|
|
16
|
+
autoload :DSL, "durable_proxmox/dsl"
|
|
17
|
+
autoload :VMDefinition, "durable_proxmox/dsl"
|
|
18
|
+
|
|
19
|
+
# Convenience method to create a new client
|
|
20
|
+
#
|
|
21
|
+
# @param url_base [String, nil] The base URL for the Proxmox API
|
|
22
|
+
# @param insecure [Boolean, nil] Skip SSL certificate validation (defaults to DP_INSECURE env var)
|
|
23
|
+
# @return [DurableProxmox::Client] A new client instance
|
|
24
|
+
def self.client(url_base = nil, insecure: nil)
|
|
25
|
+
insecure = ENV['DP_INSECURE'] == 'true' if insecure.nil?
|
|
26
|
+
Client.new(url_base, insecure: insecure)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Load environment variables from .env file if present
|
|
30
|
+
Dotenv.load if defined?(Dotenv)
|
|
31
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: durable-proxmox-ruby
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Durable Programming LLC
|
|
8
|
+
bindir: exe
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: faraday
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - "~>"
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '2.0'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - "~>"
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '2.0'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: json
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - "~>"
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '2.0'
|
|
33
|
+
type: :runtime
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - "~>"
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '2.0'
|
|
40
|
+
- !ruby/object:Gem::Dependency
|
|
41
|
+
name: dotenv
|
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - "~>"
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '2.0'
|
|
47
|
+
type: :runtime
|
|
48
|
+
prerelease: false
|
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - "~>"
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '2.0'
|
|
54
|
+
- !ruby/object:Gem::Dependency
|
|
55
|
+
name: ipaddr
|
|
56
|
+
requirement: !ruby/object:Gem::Requirement
|
|
57
|
+
requirements:
|
|
58
|
+
- - "~>"
|
|
59
|
+
- !ruby/object:Gem::Version
|
|
60
|
+
version: '1.2'
|
|
61
|
+
type: :runtime
|
|
62
|
+
prerelease: false
|
|
63
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
64
|
+
requirements:
|
|
65
|
+
- - "~>"
|
|
66
|
+
- !ruby/object:Gem::Version
|
|
67
|
+
version: '1.2'
|
|
68
|
+
- !ruby/object:Gem::Dependency
|
|
69
|
+
name: minitest
|
|
70
|
+
requirement: !ruby/object:Gem::Requirement
|
|
71
|
+
requirements:
|
|
72
|
+
- - "~>"
|
|
73
|
+
- !ruby/object:Gem::Version
|
|
74
|
+
version: '5.0'
|
|
75
|
+
type: :development
|
|
76
|
+
prerelease: false
|
|
77
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
78
|
+
requirements:
|
|
79
|
+
- - "~>"
|
|
80
|
+
- !ruby/object:Gem::Version
|
|
81
|
+
version: '5.0'
|
|
82
|
+
- !ruby/object:Gem::Dependency
|
|
83
|
+
name: rake
|
|
84
|
+
requirement: !ruby/object:Gem::Requirement
|
|
85
|
+
requirements:
|
|
86
|
+
- - "~>"
|
|
87
|
+
- !ruby/object:Gem::Version
|
|
88
|
+
version: '13.0'
|
|
89
|
+
type: :development
|
|
90
|
+
prerelease: false
|
|
91
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
92
|
+
requirements:
|
|
93
|
+
- - "~>"
|
|
94
|
+
- !ruby/object:Gem::Version
|
|
95
|
+
version: '13.0'
|
|
96
|
+
- !ruby/object:Gem::Dependency
|
|
97
|
+
name: rubocop
|
|
98
|
+
requirement: !ruby/object:Gem::Requirement
|
|
99
|
+
requirements:
|
|
100
|
+
- - "~>"
|
|
101
|
+
- !ruby/object:Gem::Version
|
|
102
|
+
version: '1.0'
|
|
103
|
+
type: :development
|
|
104
|
+
prerelease: false
|
|
105
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
106
|
+
requirements:
|
|
107
|
+
- - "~>"
|
|
108
|
+
- !ruby/object:Gem::Version
|
|
109
|
+
version: '1.0'
|
|
110
|
+
- !ruby/object:Gem::Dependency
|
|
111
|
+
name: yard
|
|
112
|
+
requirement: !ruby/object:Gem::Requirement
|
|
113
|
+
requirements:
|
|
114
|
+
- - "~>"
|
|
115
|
+
- !ruby/object:Gem::Version
|
|
116
|
+
version: '0.9'
|
|
117
|
+
type: :development
|
|
118
|
+
prerelease: false
|
|
119
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
120
|
+
requirements:
|
|
121
|
+
- - "~>"
|
|
122
|
+
- !ruby/object:Gem::Version
|
|
123
|
+
version: '0.9'
|
|
124
|
+
- !ruby/object:Gem::Dependency
|
|
125
|
+
name: webmock
|
|
126
|
+
requirement: !ruby/object:Gem::Requirement
|
|
127
|
+
requirements:
|
|
128
|
+
- - "~>"
|
|
129
|
+
- !ruby/object:Gem::Version
|
|
130
|
+
version: '3.18'
|
|
131
|
+
type: :development
|
|
132
|
+
prerelease: false
|
|
133
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
134
|
+
requirements:
|
|
135
|
+
- - "~>"
|
|
136
|
+
- !ruby/object:Gem::Version
|
|
137
|
+
version: '3.18'
|
|
138
|
+
- !ruby/object:Gem::Dependency
|
|
139
|
+
name: simplecov
|
|
140
|
+
requirement: !ruby/object:Gem::Requirement
|
|
141
|
+
requirements:
|
|
142
|
+
- - "~>"
|
|
143
|
+
- !ruby/object:Gem::Version
|
|
144
|
+
version: '0.22'
|
|
145
|
+
type: :development
|
|
146
|
+
prerelease: false
|
|
147
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
148
|
+
requirements:
|
|
149
|
+
- - "~>"
|
|
150
|
+
- !ruby/object:Gem::Version
|
|
151
|
+
version: '0.22'
|
|
152
|
+
description: This library solves the problem of programmatically managing Proxmox
|
|
153
|
+
virtual environments without the complexity of shell scripts or manual API calls.
|
|
154
|
+
It's designed for system administrators and developers who need to automate VM provisioning,
|
|
155
|
+
configuration, and infrastructure-as-code workflows.
|
|
156
|
+
email:
|
|
157
|
+
- commercial@durableprogramming.com
|
|
158
|
+
executables:
|
|
159
|
+
- proxmox
|
|
160
|
+
extensions: []
|
|
161
|
+
extra_rdoc_files: []
|
|
162
|
+
files:
|
|
163
|
+
- CHANGELOG.md
|
|
164
|
+
- LICENSE
|
|
165
|
+
- README.md
|
|
166
|
+
- durable-proxmox-ruby.gemspec
|
|
167
|
+
- exe/proxmox
|
|
168
|
+
- lib/durable_proxmox.rb
|
|
169
|
+
- lib/durable_proxmox/client.rb
|
|
170
|
+
- lib/durable_proxmox/dsl.rb
|
|
171
|
+
- lib/durable_proxmox/errors.rb
|
|
172
|
+
- lib/durable_proxmox/ip_address_group.rb
|
|
173
|
+
- lib/durable_proxmox/node.rb
|
|
174
|
+
- lib/durable_proxmox/version.rb
|
|
175
|
+
- lib/durable_proxmox/vm.rb
|
|
176
|
+
homepage: https://github.com/durableprogramming/durable-proxmox-ruby
|
|
177
|
+
licenses:
|
|
178
|
+
- MIT
|
|
179
|
+
metadata:
|
|
180
|
+
homepage_uri: https://github.com/durableprogramming/durable-proxmox-ruby
|
|
181
|
+
source_code_uri: https://github.com/durableprogramming/durable-proxmox-ruby
|
|
182
|
+
changelog_uri: https://github.com/durableprogramming/durable-proxmox-ruby/blob/main/CHANGELOG.md
|
|
183
|
+
rdoc_options: []
|
|
184
|
+
require_paths:
|
|
185
|
+
- lib
|
|
186
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
187
|
+
requirements:
|
|
188
|
+
- - ">="
|
|
189
|
+
- !ruby/object:Gem::Version
|
|
190
|
+
version: 2.7.0
|
|
191
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
192
|
+
requirements:
|
|
193
|
+
- - ">="
|
|
194
|
+
- !ruby/object:Gem::Version
|
|
195
|
+
version: '0'
|
|
196
|
+
requirements: []
|
|
197
|
+
rubygems_version: 3.7.1
|
|
198
|
+
specification_version: 4
|
|
199
|
+
summary: A pragmatic Ruby library for interacting with Proxmox VE through a clean
|
|
200
|
+
API
|
|
201
|
+
test_files: []
|