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.
@@ -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: []