ruby-proxmox 1.0.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,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: e21fae3fba8bd653738abd93c225830fe7fe3a70d07e8701ae08265479a69873
4
+ data.tar.gz: b7cf6351dade3050ac1e60c63a3428ced63f21689bdca1c460bf348105ef1bc2
5
+ SHA512:
6
+ metadata.gz: c20c5ec02c7afe1e4a0fc5a654cecf045c5e3ed957219e6c9e5ad26698468e5f3ba45173ec7a3131d5dbd066b6055a69f033d940459fbb141c40c1ab1645a0eb
7
+ data.tar.gz: e68edbd101fb3f49bb5525859a6d419d71f3026d8f07efe21923096c507cf1dbe0a01b0257d0ec98b026af82383d66f01d0966d596dfc2b3d6091ef036993089
@@ -0,0 +1,18 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ test.*
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --color
@@ -0,0 +1,41 @@
1
+ # This configuration was generated by
2
+ # `rubocop --auto-gen-config`
3
+ # on 2015-11-14 20:39:56 -0600 using RuboCop version 0.35.1.
4
+ # The point is for the user to remove these configuration records
5
+ # one by one as the offenses are removed from the code base.
6
+ # Note that changes in the inspected code, or installation of new
7
+ # versions of RuboCop, may require this file to be generated again.
8
+
9
+ # Offense count: 1
10
+ Lint/Void:
11
+ Exclude:
12
+ - 'spec/lib/proxmox_spec.rb'
13
+
14
+ # Offense count: 1
15
+ # Configuration parameters: CountComments.
16
+ Metrics/ClassLength:
17
+ Max: 130
18
+
19
+ # Offense count: 45
20
+ # Configuration parameters: AllowURI, URISchemes.
21
+ Metrics/LineLength:
22
+ Max: 1094
23
+
24
+ # Offense count: 1
25
+ # Configuration parameters: CountComments.
26
+ Metrics/MethodLength:
27
+ Max: 11
28
+
29
+ # Offense count: 1
30
+ # Configuration parameters: CountKeywordArgs.
31
+ Metrics/ParameterLists:
32
+ Max: 6
33
+
34
+ # Offense count: 1
35
+ # Configuration parameters: Exclude.
36
+ Style/Documentation:
37
+ Exclude:
38
+ - 'spec/**/*'
39
+ - 'test/**/*'
40
+ - 'lib/proxmox/version.rb'
41
+
@@ -0,0 +1,7 @@
1
+ language: ruby
2
+ cache: bundler
3
+ bundler_args: --without developpement
4
+ rvm:
5
+ - 2.0.0
6
+ - 2.1.0
7
+ script: bundle exec rspec spec
data/Gemfile ADDED
@@ -0,0 +1,27 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in ruby-proxmox.gemspec
4
+ gemspec
5
+
6
+ group :developpement do
7
+ gem 'guard'
8
+ gem 'guard-rspec'
9
+ gem 'guard-bundler'
10
+
11
+ gem 'growl' if RUBY_PLATFORM =~ /darwin/
12
+ gem 'wdm' if RUBY_PLATFORM =~ /mingw/
13
+ gem 'ruby_gntp' if RUBY_PLATFORM =~ /mingw/
14
+
15
+ gem 'guard-spork'
16
+
17
+ gem 'yard'
18
+ gem 'redcarpet'
19
+ gem 'guard-yard'
20
+ end
21
+
22
+ group :test do
23
+ gem 'spork'
24
+ gem 'simplecov'
25
+ gem 'json', '~> 1.8.3'
26
+ gem 'coveralls'
27
+ end
@@ -0,0 +1,19 @@
1
+ # A sample Guardfile
2
+ # More info at https://github.com/guard/guard#readme
3
+
4
+ guard 'bundler' do
5
+ watch('Gemfile')
6
+ watch(/^.+\.gemspec/)
7
+ end
8
+
9
+ guard :rspec, cmd: 'rspec' do
10
+ watch(%r{^spec/.+_spec\.rb$})
11
+ watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
12
+ watch('spec/spec_helper.rb') { 'spec' }
13
+ end
14
+
15
+ group :docs do
16
+ guard 'yard' do
17
+ watch(%r{lib/.+\.rb})
18
+ end
19
+ end
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Nicolas Ledez
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,86 @@
1
+ # Proxmox
2
+
3
+ A ruby gem for managing Proxmox Servers with Ruby. Based on https://github.com/nledez/proxmox
4
+
5
+ ### Usage Information
6
+
7
+ Current build:
8
+ [RubyGem](http://rubygems.org/gems/ruby-proxmox)
9
+
10
+ [Rubydoc](http://rubydoc.info/github/nledez/proxmox/master/frames)
11
+
12
+ Inspirated from:
13
+ https://bitbucket.org/jmoratilla/knife-proxmox/ but I would like to have
14
+ the same without chef.
15
+ https://github.com/maxschulze/uv_proxmox but listing some task does not
16
+ work for me. No tests, use ssh.
17
+
18
+ Documentation from:
19
+ - http://pve.proxmox.com/wiki/Proxmox_VE_API
20
+ - http://pve.proxmox.com/pve2-api-doc/
21
+
22
+ ## Installation
23
+
24
+ Add this line to your application's Gemfile:
25
+
26
+ gem 'proxmox'
27
+
28
+ And then execute:
29
+
30
+ $ bundle
31
+
32
+ Or install it yourself as:
33
+
34
+ $ gem install proxmox
35
+
36
+ ## Usage
37
+
38
+ require 'awesome_print'
39
+ require 'proxmox'
40
+
41
+ def wait_status(server1, task)
42
+ puts task
43
+ while server1.task_status(task) == "running"
44
+ puts '.'
45
+ sleep 1
46
+ end
47
+
48
+ puts server1.task_status(task)
49
+ end
50
+
51
+ server1 =
52
+ Proxmox::Proxmox.new("https://the-proxmox-server:8006/api2/json/",
53
+ "node", "root", "secret", "pam")
54
+ ap server1.templates
55
+
56
+ vm1 = server1.openvz_post("ubuntu-10.04-standard_10.04-4_i386", 200)
57
+ wait_status(server1, vm1)
58
+
59
+ ap server1.openvz_vm_status(200)
60
+ vm1 = server1.openvz_vm_start(200)
61
+ begin
62
+ wait_status(server1, vm1)
63
+ rescue
64
+ end
65
+ sleep 5
66
+ ap server1.openvz_vm_shutdown(200)
67
+ begin
68
+ wait_status(server1, vm1)
69
+ rescue
70
+ end
71
+ sleep 5
72
+ ap server1.openvz_vm_status(200)
73
+
74
+ vm1 = server1.openvz_delete(200)
75
+ wait_status(server1, vm1)
76
+
77
+ ## Contributing
78
+
79
+ 1. Fork it
80
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
81
+ 3. Create spec (in file spec/lib/proxmox_spec.rb)
82
+ 4. Write your code (in lib/proxmox.rb)
83
+ 5. Check spec & coverage (`bundle exec rspec` or `bundle exec guard`)
84
+ 6. Commit your changes (`git commit -am 'Add some feature'`)
85
+ 7. Push to the branch (`git push origin my-new-feature`)
86
+ 8. Create new Pull Request
@@ -0,0 +1,6 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rspec/core/rake_task'
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task default: :spec
@@ -0,0 +1,405 @@
1
+ # encoding: utf-8
2
+
3
+ require 'proxmox/version'
4
+ require 'rest_client'
5
+ require 'json'
6
+
7
+ # This module encapsulates ability to manage Proxmox server
8
+ module Proxmox
9
+ # Object to manage Proxmox server
10
+ class Proxmox
11
+ # Return connection status
12
+ # - connected
13
+ # - error
14
+ attr_reader :connection_status
15
+
16
+ # Create a object to manage a Proxmox server through API
17
+ #
18
+ # :call-seq:
19
+ # new(pve_cluster, node, username, password, realm, ssl_options) -> Proxmox
20
+ #
21
+ # Example:
22
+ #
23
+ # Proxmox::Proxmox.new('https://the-proxmox-server:8006/api2/json/', 'node', 'root', 'secret', 'pam', {verify_ssl: false})
24
+ #
25
+ def initialize(pve_cluster, node, username, password, realm, ssl_options = {})
26
+ @pve_cluster = pve_cluster
27
+ @node = node
28
+ @username = username
29
+ @password = password
30
+ @realm = realm
31
+ @ssl_options = ssl_options
32
+ @connection_status = 'error'
33
+ @site = RestClient::Resource.new(@pve_cluster, @ssl_options)
34
+ @auth_params = create_ticket
35
+ end
36
+
37
+ def get(path, args = {})
38
+ http_action_get(path, args)
39
+ end
40
+
41
+ def post(path, args = {})
42
+ http_action_post(path, args)
43
+ end
44
+
45
+ def put(path, args = {})
46
+ http_action_put(path, args)
47
+ end
48
+
49
+ def delete(path)
50
+ http_action_delete(path)
51
+ end
52
+
53
+ # Get task status
54
+ #
55
+ # :call-seq:
56
+ # task_status(task-id) -> String
57
+ #
58
+ # - taksstatus
59
+ # - taskstatus:exitstatus
60
+ #
61
+ # Example:
62
+ #
63
+ # taskstatus 'UPID:localhost:00051DA0:119EAABC:521CCB19:vzcreate:203:root@pam:'
64
+ #
65
+ # Examples return:
66
+ # - running
67
+ # - stopped:OK
68
+ #
69
+ def task_status(upid)
70
+ data = http_action_get "nodes/#{@node}/tasks/#{URI.encode upid}/status"
71
+ status = data['status']
72
+ exitstatus = data['exitstatus']
73
+ if exitstatus
74
+ "#{status}:#{exitstatus}"
75
+ else
76
+ "#{status}"
77
+ end
78
+ end
79
+
80
+ # Get template list
81
+ #
82
+ # :call-seq:
83
+ # templates -> Hash
84
+ #
85
+ # Return a Hash of all templates
86
+ #
87
+ # Example:
88
+ #
89
+ # templates
90
+ #
91
+ # Example return:
92
+ #
93
+ # {
94
+ # 'ubuntu-10.04-standard_10.04-4_i386' => {
95
+ # 'format' => 'tgz',
96
+ # 'content' => 'vztmpl',
97
+ # 'volid' => 'local:vztmpl/ubuntu-10.04-standard_10.04-4_i386.tar.gz',
98
+ # 'size' => 142126884
99
+ # },
100
+ # 'ubuntu-12.04-standard_12.04-1_i386' => {
101
+ # 'format' => 'tgz',
102
+ # 'content' => 'vztmpl',
103
+ # 'volid' => 'local:vztmpl/ubuntu-12.04-standard_12.04-1_i386.tar.gz',
104
+ # 'size' => 130040792
105
+ # }
106
+ # }
107
+ #
108
+ def templates
109
+ data = http_action_get "nodes/#{@node}/storage/local/content"
110
+ template_list = {}
111
+ data.each do |ve|
112
+ name = ve['volid'].gsub(%r{local:vztmpl\/(.*).tar.gz}, '\1')
113
+ template_list[name] = ve
114
+ end
115
+ template_list
116
+ end
117
+
118
+ # Get CT list
119
+ #
120
+ # :call-seq:
121
+ # openvz_get -> Hash
122
+ #
123
+ # Return a Hash of all openvz container
124
+ #
125
+ # Example:
126
+ #
127
+ # openvz_get
128
+ #
129
+ # Example return:
130
+ # {
131
+ # '101' => {
132
+ # 'maxswap' => 536870912,
133
+ # 'disk' => 405168128,
134
+ # 'ip' => '192.168.1.5',
135
+ # 'status' => 'running',
136
+ # 'netout' => 272,
137
+ # 'maxdisk' => 4294967296,
138
+ # 'maxmem' => 536870912,
139
+ # 'uptime' => 3068073,
140
+ # 'swap' => 0,
141
+ # 'vmid' => '101',
142
+ # 'nproc' => '10',
143
+ # 'diskread' => 0,
144
+ # 'cpu' => 0.00031670581100007,
145
+ # 'netin' => 0,
146
+ # 'name' => 'test2.domain.com',
147
+ # 'failcnt' => 0,
148
+ # 'diskwrite' => 0,
149
+ # 'mem' => 22487040,
150
+ # 'type' => 'openvz',
151
+ # 'cpus' => 1
152
+ # },
153
+ # [...]
154
+ # }
155
+ def openvz_get
156
+ data = http_action_get "nodes/#{@node}/openvz"
157
+ ve_list = {}
158
+ data.each do |ve|
159
+ ve_list[ve['vmid']] = ve
160
+ end
161
+ ve_list
162
+ end
163
+
164
+ # Create CT container
165
+ #
166
+ # :call-seq:
167
+ # openvz_post(ostemplate, vmid) -> String
168
+ # openvz_post(ostemplate, vmid, options) -> String
169
+ #
170
+ # Return a String as task ID
171
+ #
172
+ # Examples:
173
+ #
174
+ # openvz_post('ubuntu-10.04-standard_10.04-4_i386', 200)
175
+ # openvz_post('ubuntu-10.04-standard_10.04-4_i386', 200, {'hostname' => 'test.test.com', 'password' => 'testt' })
176
+ #
177
+ # Example return:
178
+ #
179
+ # UPID:localhost:000BC66A:1279E395:521EFC4E:vzcreate:200:root@pam:
180
+ #
181
+ def openvz_post(ostemplate, vmid, config = {})
182
+ config['vmid'] = vmid
183
+ config['ostemplate'] = "local%3Avztmpl%2F#{ostemplate}.tar.gz"
184
+ vm_definition = config.to_a.map { |v| v.join '=' }.join '&'
185
+
186
+ http_action_post("nodes/#{@node}/openvz", vm_definition)
187
+ end
188
+
189
+ # Delete CT
190
+ #
191
+ # :call-seq:
192
+ # openvz_delete(vmid) -> String
193
+ #
194
+ # Return a string as task ID
195
+ #
196
+ # Example:
197
+ #
198
+ # openvz_delete(200)
199
+ #
200
+ # Example return:
201
+ #
202
+ # UPID:localhost:000BC66A:1279E395:521EFC4E:vzdelete:200:root@pam:
203
+ #
204
+ def openvz_delete(vmid)
205
+ http_action_delete "nodes/#{@node}/openvz/#{vmid}"
206
+ end
207
+
208
+ # Get CT status
209
+ #
210
+ # :call-seq:
211
+ # openvz_delete(vmid) -> String
212
+ #
213
+ # Return a string as task ID
214
+ #
215
+ # Example:
216
+ #
217
+ # openvz_delete(200)
218
+ #
219
+ # Example return:
220
+ #
221
+ # UPID:localhost:000BC66A:1279E395:521EFC4E:vzdelete:200:root@pam:
222
+ #
223
+ def openvz_status(vmid)
224
+ http_action_get "nodes/#{@node}/openvz/#{vmid}/status/current"
225
+ end
226
+
227
+ # Start CT
228
+ #
229
+ # :call-seq:
230
+ # openvz_start(vmid) -> String
231
+ #
232
+ # Return a string as task ID
233
+ #
234
+ # Example:
235
+ #
236
+ # openvz_start(200)
237
+ #
238
+ # Example return:
239
+ #
240
+ # UPID:localhost:000BC66A:1279E395:521EFC4E:vzstart:200:root@pam:
241
+ #
242
+ def openvz_start(vmid)
243
+ http_action_post "nodes/#{@node}/openvz/#{vmid}/status/start"
244
+ end
245
+
246
+ # Stop CT
247
+ #
248
+ # :call-seq:
249
+ # openvz_stop(vmid) -> String
250
+ #
251
+ # Return a string as task ID
252
+ #
253
+ # Example:
254
+ #
255
+ # openvz_stop(200)
256
+ #
257
+ # Example return:
258
+ #
259
+ # UPID:localhost:000BC66A:1279E395:521EFC4E:vzstop:200:root@pam:
260
+ #
261
+ def openvz_stop(vmid)
262
+ http_action_post "nodes/#{@node}/openvz/#{vmid}/status/stop"
263
+ end
264
+
265
+ # Shutdown CT
266
+ #
267
+ # :call-seq:
268
+ # openvz_shutdown(vmid) -> String
269
+ #
270
+ # Return a string as task ID
271
+ #
272
+ # Example:
273
+ #
274
+ # openvz_shutdown(200)
275
+ #
276
+ # Example return:
277
+ #
278
+ # UPID:localhost:000BC66A:1279E395:521EFC4E:vzshutdown:200:root@pam:
279
+ #
280
+ def openvz_shutdown(vmid)
281
+ http_action_post "nodes/#{@node}/openvz/#{vmid}/status/shutdown"
282
+ end
283
+
284
+ # Get CT config
285
+ #
286
+ # :call-seq:
287
+ # openvz_config(vmid) -> String
288
+ #
289
+ # Return a string as task ID
290
+ #
291
+ # Example:
292
+ #
293
+ # openvz_config(200)
294
+ #
295
+ # Example return:
296
+ #
297
+ # {
298
+ # 'quotaugidlimit' => 0,
299
+ # 'disk' => 0,
300
+ # 'ostemplate' => 'ubuntu-10.04-standard_10.04-4_i386.tar.gz',
301
+ # 'hostname' => 'test.test.com',
302
+ # 'nameserver' => '127.0.0.1 192.168.1.1',
303
+ # 'memory' => 256,
304
+ # 'searchdomain' => 'domain.com',
305
+ # 'onboot' => 0,
306
+ # 'cpuunits' => 1000,
307
+ # 'swap' => 256,
308
+ # 'quotatime' => 0,
309
+ # 'digest' => 'e7e6e21a215af6b9da87a8ecb934956b8983f960',
310
+ # 'cpus' => 1,
311
+ # 'storage' => 'local'
312
+ # }
313
+ #
314
+ def openvz_config(vmid)
315
+ http_action_get "nodes/#{@node}/openvz/#{vmid}/config"
316
+ end
317
+
318
+ # Set CT config
319
+ #
320
+ # :call-seq:
321
+ # openvz_config_set(vmid, parameters) -> Nil
322
+ #
323
+ # Return nil
324
+ #
325
+ # Example:
326
+ #
327
+ # openvz_config(200, { 'swap' => 2048 })
328
+ #
329
+ # Example return:
330
+ #
331
+ # nil
332
+ #
333
+ def openvz_config_set(vmid, data)
334
+ http_action_put("nodes/#{@node}/openvz/#{vmid}/config", data)
335
+ end
336
+
337
+ # Reset Qemu
338
+ def qemu_reset(vmid)
339
+ http_action_post("nodes/#{@node}/qemu/#{vmid}/status/reset")
340
+ end
341
+
342
+ private
343
+
344
+ # Methods manages auth
345
+ def create_ticket
346
+ post_param = { username: @username, realm: @realm, password: @password }
347
+ @site['access/ticket'].post post_param do |response, _request, _result, &_block|
348
+ if response.code == 200
349
+ extract_ticket response
350
+ else
351
+ @connection_status = 'error'
352
+ end
353
+ end
354
+ end
355
+
356
+ # Method create ticket
357
+ def extract_ticket(response)
358
+ data = JSON.parse(response.body)
359
+ ticket = data['data']['ticket']
360
+ csrf_prevention_token = data['data']['CSRFPreventionToken']
361
+ unless ticket.nil?
362
+ token = 'PVEAuthCookie=' + ticket.gsub!(/:/, '%3A').gsub!(/=/, '%3D')
363
+ end
364
+ @connection_status = 'connected'
365
+ {
366
+ CSRFPreventionToken: csrf_prevention_token,
367
+ cookie: token
368
+ }
369
+ end
370
+
371
+ # Extract data or return error
372
+ def check_response(response)
373
+ if response.code == 200
374
+ JSON.parse(response.body)['data']
375
+ else
376
+ 'NOK: error code = ' + response.code.to_s
377
+ end
378
+ end
379
+
380
+ # Methods manage http dialogs
381
+ def http_action_post(url, data = {})
382
+ @site[url].post data, @auth_params do |response, _request, _result, &_block|
383
+ check_response response
384
+ end
385
+ end
386
+
387
+ def http_action_put(url, data = {})
388
+ @site[url].put data, @auth_params do |response, _request, _result, &_block|
389
+ check_response response
390
+ end
391
+ end
392
+
393
+ def http_action_get(url, data = {})
394
+ @site[url].get @auth_params.merge(data) do |response, _request, _result, &_block|
395
+ check_response response
396
+ end
397
+ end
398
+
399
+ def http_action_delete(url)
400
+ @site[url].delete @auth_params do |response, _request, _result, &_block|
401
+ check_response response
402
+ end
403
+ end
404
+ end
405
+ end
@@ -0,0 +1,5 @@
1
+ # encoding: utf-8
2
+
3
+ module Proxmox
4
+ VERSION = '1.0.0'
5
+ end
@@ -0,0 +1,26 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'proxmox/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'ruby-proxmox'
8
+ spec.version = Proxmox::VERSION
9
+ spec.authors = ['Nathaniel Suchy', 'Nicolas Ledez']
10
+ spec.email = ['me@lunorian.is']
11
+ spec.description = 'A ruby gem for managing Proxmox Servers with Ruby. Based on https://github.com/nledez/proxmox'
12
+ spec.summary = 'A ruby gem for managing Proxmox Servers with Ruby. Based on https://github.com/nledez/proxmox'
13
+ spec.homepage = 'https://github.com/ulayer/ruby-proxmox'
14
+ spec.license = 'MIT'
15
+
16
+ spec.files = `git ls-files`.split($INPUT_RECORD_SEPARATOR)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.add_dependency 'rest-client', '>=1.6.7'
20
+ spec.require_paths = ['lib']
21
+
22
+ spec.add_development_dependency 'bundler', '~> 1.3'
23
+ spec.add_development_dependency 'rake'
24
+ spec.add_development_dependency 'rspec'
25
+ spec.add_development_dependency 'webmock'
26
+ end
@@ -0,0 +1,294 @@
1
+ require 'spec_helper'
2
+ require 'webmock/rspec'
3
+ require 'proxmox'
4
+
5
+ describe Proxmox do
6
+ before(:each) do
7
+ @common_headers_in = { 'User-Agent' => 'Ruby',
8
+ 'Cookie' => /.*/,
9
+ 'Csrfpreventiontoken' => /.*/ }
10
+ @common_headers_out = { connection: 'close',
11
+ server: 'pve-api-daemon/3.0',
12
+ content_type: 'application/json;charset=UTF-8' }
13
+
14
+ # Common auth
15
+ stub_request(:post, 'http://localhost:8006/api2/json/access/ticket').with(
16
+ headers: {
17
+ 'Content-Type' => 'application/x-www-form-urlencoded',
18
+ 'User-Agent' => 'Ruby'
19
+ },
20
+ body: {
21
+ 'username' => 'root',
22
+ 'password' => 'secret',
23
+ 'realm' => 'pam'
24
+ }
25
+ ).to_return(
26
+ status: 200,
27
+ headers: @common_headers_out,
28
+ body: '{"data":{"cap":{"dc":{"Sys.Audit":1},"access":{"Group.Allocate":1,"User.Modify":1},"nodes":{"Sys.Audit":1,"Sys.Syslog":1,"Sys.Console":1,"Sys.Modify":1,"Sys.PowerMgmt":1},"vms":{"VM.Backup":1,"VM.Allocate":1,"VM.Config.CPU":1,"VM.Config.Network":1,"VM.Migrate":1,"VM.Config.Memory":1,"VM.Config.Options":1,"Permissions.Modify":1,"VM.Monitor":1,"VM.Console":1,"VM.Config.Disk":1,"VM.Config.HWType":1,"VM.Clone":1,"VM.Snapshot":1,"VM.Audit":1,"VM.PowerMgmt":1,"VM.Config.CDROM":1},"storage":{"Datastore.AllocateTemplate":1,"Datastore.Allocate":1,"Datastore.Audit":1,"Permissions.Modify":1,"Datastore.AllocateSpace":1}},"CSRFPreventionToken":"51F00E60:Pnd0AHehuTE++j87nUz0nLuyW+0","ticket":"PVE:root@pam:51F00E60::OS5lBKlaabgnmekdbVY2JYAbd5Z/MPWCeZ9b33UwjsE1yVB1esIwUQoXJ4Xgb/+UVE9mtS2K3dJ65wyPDsGYTDc0TCl0VmdOGz7djXMlMy5ShRjXcX/GLs77LHXlLQOO+ED/jCoz0tHV55igNSBNMG2UrSLlTGvgm8zf1fNqAsVszrAWgeFu+e/1CLIfs//cWyimBuDx+r3m/NOjaoyeb2u63eBCPrWyEiCJZniMZDVnqqQcOm32tE2XQj4D2LS+xaHn2fdZDlcAo0uY4qVspKiMjf9g2AudRblkobCTf7KdhanIm0kCSqkvHJy2EMcAbxcqnGnjPiYSH0WYZMTnlA==","username":"root@pam"}}'
29
+ )
30
+
31
+ @server1 = Proxmox::Proxmox.new('http://localhost:8006/api2/json/', 'localhost', 'root', 'secret', 'pam')
32
+ end
33
+
34
+ describe 'get' do
35
+ it 'calls http_action_get' do
36
+ expect(@server1).to receive(:http_action_get).with("version", {})
37
+ @server1.get("version")
38
+ end
39
+ end
40
+
41
+ describe 'post' do
42
+ it 'calls http_action_post' do
43
+ expect(@server1).to receive(:http_action_post).with("version", {})
44
+ @server1.post("version")
45
+ end
46
+ end
47
+
48
+ describe 'put' do
49
+ it 'calls http_action_put' do
50
+ expect(@server1).to receive(:http_action_put).with("version", {})
51
+ @server1.put("version")
52
+ end
53
+ end
54
+
55
+ describe 'delete' do
56
+ it 'calls http_action_delete' do
57
+ expect(@server1).to receive(:http_action_delete).with("version")
58
+ @server1.delete("version")
59
+ end
60
+ end
61
+
62
+ it 'should connect to Proxmox server' do
63
+ # Bad auth
64
+ stub_request(:post, 'http://localhost:8006/api2/json/access/ticket').with(
65
+ headers: {
66
+ 'Content-Type' => 'application/x-www-form-urlencoded',
67
+ 'User-Agent' => 'Ruby'
68
+ },
69
+ body: {
70
+ 'username' => 'root',
71
+ 'password' => 'bad',
72
+ 'realm' => 'pam'
73
+ }
74
+ ).to_return(
75
+ status: 500,
76
+ headers: @common_headers_out,
77
+ body: '{"data":null}'
78
+ )
79
+ expect(@server1.connection_status).to eq 'connected'
80
+
81
+ server2 = Proxmox::Proxmox.new('http://localhost:8006/api2/json/', 'localhost', 'root', 'bad', 'pam')
82
+ expect(server2.connection_status).to eq 'error'
83
+ end
84
+
85
+ describe 'task_status' do
86
+ it 'should get task status' do
87
+ # Status done
88
+ stub_request(:get, 'http://localhost:8006/api2/json/nodes/localhost/tasks/UPID:localhost:00051DA0:119EAABB:521CCB19:vzcreate:200:root@pam:/status').with(
89
+ headers: @common_headers_in).to_return(
90
+ status: 200,
91
+ headers: {
92
+ connection: 'close', server: 'pve-api-daemon/3.0', content_type: 'application/json;charset=UTF-8' },
93
+ body: '{"data":{"exitstatus":"OK","status":"stopped","upid":"UPID:localhost:00051DA0:119EAABB:521CCB19:vzcreate:200:root@pam:","node":"localhost","pid":335264,"starttime":1377618713,"user":"root@pam","type":"vzcreate","id":"200","pstart":295611067}}'
94
+ )
95
+
96
+ # Status running
97
+ stub_request(:get, 'http://localhost:8006/api2/json/nodes/localhost/tasks/UPID:localhost:00055DDA:11A99D07:521CE71F:vzcreate:200:root@pam:/status').with(
98
+ headers: @common_headers_in).to_return(
99
+ status: 200,
100
+ headers: @common_headers_out,
101
+ body: '{"data":{"status":"running","upid":"UPID:localhost:00055DDA:11A99D07:521CE71F:vzcreate:200:root@pam:","node":"localhost","pid":351706,"starttime":1377625887,"user":"root@pam","type":"vzcreate","id":"200","pstart":296328455}}'
102
+ )
103
+
104
+ # Get status
105
+ expect(@server1.task_status('UPID:localhost:00051DA0:119EAABB:521CCB19:vzcreate:200:root@pam:')).to be_eql 'stopped:OK'
106
+ expect(@server1.task_status('UPID:localhost:00055DDA:11A99D07:521CE71F:vzcreate:200:root@pam:')).to be_eql 'running'
107
+ end
108
+ end
109
+
110
+ describe 'templates' do
111
+ it 'should get template list' do
112
+ # First template list
113
+ stub_request(:get, 'http://localhost:8006/api2/json/nodes/localhost/storage/local/content').with(
114
+ headers: @common_headers_in).to_return(
115
+ status: 200,
116
+ headers: @common_headers_out,
117
+ body: '{"data":[{"format":"tgz","content":"vztmpl","volid":"local:vztmpl/ubuntu-10.04-standard_10.04-4_i386.tar.gz","size":142126884},{"format":"tgz","content":"vztmpl","volid":"local:vztmpl/ubuntu-12.04-standard_12.04-1_i386.tar.gz","size":130040792}]}'
118
+ )
119
+
120
+ expect(@server1.templates).to be_an_instance_of Hash
121
+ expect(@server1.templates.keys.sort).to be_eql ['ubuntu-10.04-standard_10.04-4_i386', 'ubuntu-12.04-standard_12.04-1_i386']
122
+ end
123
+ end
124
+
125
+ describe 'openvz_get' do
126
+ it 'should get openvz vm list' do
127
+ # First VM list
128
+ stub_request(:get, 'http://localhost:8006/api2/json/nodes/localhost/openvz').with(
129
+ headers: @common_headers_in).to_return(
130
+ status: 200,
131
+ headers: @common_headers_out,
132
+ body: '{"data":[{"maxswap":536870912,"disk":404037632,"ip":"192.168.1.5","status":"running","netout":272,"maxdisk":4294967296,"maxmem":536870912,"uptime":3847,"swap":0,"vmid":"101","nproc":"10","diskread":0,"cpu":0.00183354942808597,"netin":0,"name":"test2.dummy.tld","failcnt":0,"diskwrite":0,"mem":21303296,"type":"openvz","cpus":1},{"maxswap":536870912,"disk":387186688,"ip":"192.168.1.1","status":"running","netout":272,"maxdisk":4294967296,"maxmem":536870912,"uptime":17120,"swap":0,"vmid":"100","nproc":"17","diskread":0,"cpu":0.000504170031344927,"netin":0,"name":"test.dummy.tld","failcnt":0,"diskwrite":0,"mem":27987968,"type":"openvz","cpus":1}]}'
133
+ )
134
+
135
+ # Second VM list
136
+ stub_request(:get, 'http://localhost:8006/api2/json/nodes/otherone/openvz').with(
137
+ headers: @common_headers_in).to_return(
138
+ status: 200,
139
+ headers: @common_headers_out,
140
+ body: '{"data":[{"maxswap":536870912,"disk":404041728,"ip":"192.168.1.5","status":"running","netout":272,"maxdisk":4294967296,"maxmem":536870912,"uptime":6176,"swap":0,"vmid":"101","nproc":"10","diskread":0,"cpu":0.00161487378340585,"netin":0,"name":"test2.dummy.tld","failcnt":0,"diskwrite":0,"mem":21299200,"type":"openvz","cpus":1},{"maxswap":2147483648,"disk":0,"ip":"10.0.0.1","status":"stopped","netout":0,"maxdisk":10737418240,"maxmem":1073741824,"uptime":0,"swap":0,"vmid":"102","nproc":0,"diskread":0,"cpu":0,"netin":0,"name":"test3.other.domain","failcnt":0,"diskwrite":0,"mem":0,"type":"openvz","cpus":2},{"maxswap":536870912,"disk":387194880,"ip":"192.168.1.1","status":"running","netout":272,"maxdisk":4294967296,"maxmem":536870912,"uptime":19449,"swap":0,"vmid":"100","nproc":"17","diskread":0,"cpu":0.000589570582552814,"netin":0,"name":"test.dummy.tld","failcnt":0,"diskwrite":0,"mem":28282880,"type":"openvz","cpus":1}]}'
141
+ )
142
+
143
+ expect(@server1.openvz_get).to be_an_instance_of Hash
144
+ expect(@server1.openvz_get.keys.sort).to be_eql %w(100 101)
145
+
146
+ server2 = Proxmox::Proxmox.new('http://localhost:8006/api2/json/', 'otherone', 'root', 'secret', 'pam')
147
+ expect(server2.openvz_get).to be_an_instance_of Hash
148
+ expect(server2.openvz_get.keys.sort).to be_eql %w(100 101 102)
149
+ end
150
+ end
151
+
152
+ describe 'openvz_post' do
153
+ it 'should create a container' do
154
+ # Create VM
155
+ stub_request(:post, 'http://localhost:8006/api2/json/nodes/localhost/openvz').with(
156
+ body: 'vmid=200&ostemplate=local%3Avztmpl%2Fubuntu-10.04-standard_10.04-4_i386.tar.gz',
157
+ headers: @common_headers_in).to_return(
158
+ status: 200,
159
+ headers: @common_headers_out,
160
+ body: '{"data":"UPID:localhost:00051DA0:119EAABB:521CCB19:vzcreate:200:root@pam:"}'
161
+ )
162
+
163
+ stub_request(:post, 'http://localhost:8006/api2/json/nodes/localhost/openvz').with(
164
+ body: 'hostname=vm1.domain.com&password=secret&memory=512&swap=512&disk=4&vmid=203&ostemplate=local%3Avztmpl%2Fubuntu-10.04-standard_10.04-4_i386.tar.gz',
165
+ headers: @common_headers_in).to_return(
166
+ status: 200,
167
+ headers: @common_headers_out,
168
+ body: '{"data":"UPID:localhost:00051DA0:119EAABC:521CCB19:vzcreate:203:root@pam:"}'
169
+ )
170
+
171
+ stub_request(:post, 'http://localhost:8006/api2/json/nodes/localhost/openvz').with(
172
+ body: 'vmid=204&ostemplate=local%3Avztmpl%2Fubuntu-10.04-standard_10.04-4_i386.tar.gz',
173
+ headers: @common_headers_in).to_return(
174
+ status: 500,
175
+ headers: { connection: 'close', server: 'pve-api-daemon/3.0', content_type: 'application/json;charset=UTF-8'
176
+ },
177
+ body: 'NOK: error code = 500'
178
+ )
179
+
180
+ # Create the vm
181
+ expect(@server1.openvz_post('ubuntu-10.04-standard_10.04-4_i386', 200)).to be_eql 'UPID:localhost:00051DA0:119EAABB:521CCB19:vzcreate:200:root@pam:'
182
+ expect(@server1.openvz_post('ubuntu-10.04-standard_10.04-4_i386', 203, 'hostname' => 'vm1.domain.com', 'password' => 'secret', 'memory' => 512, 'swap' => 512, 'disk' => 4)).to be_eql 'UPID:localhost:00051DA0:119EAABC:521CCB19:vzcreate:203:root@pam:'
183
+ expect(@server1.openvz_post('ubuntu-10.04-standard_10.04-4_i386', 204)).to be_eql 'NOK: error code = 500'
184
+ end
185
+ end
186
+
187
+ describe 'openvz_delete' do
188
+ it 'should delete openvz container' do
189
+ # Delete VM
190
+ stub_request(:delete, 'http://localhost:8006/api2/json/nodes/localhost/openvz/200').with(
191
+ headers: @common_headers_in).to_return(
192
+ status: 200,
193
+ headers: @common_headers_out,
194
+ body: '{"data":"UPID:localhost:0005C1EB:11BAA4EB:521D12B8:vzdestroy:200:root@pam:"}'
195
+ )
196
+
197
+ stub_request(:delete, 'http://localhost:8006/api2/json/nodes/localhost/openvz/201').with(
198
+ headers: @common_headers_in).to_return(
199
+ status: 500,
200
+ headers: @common_headers_out,
201
+ body: 'NOK: error code = 500'
202
+ )
203
+
204
+ # Delete the vm
205
+ expect(@server1.openvz_delete(200)).to be_eql 'UPID:localhost:0005C1EB:11BAA4EB:521D12B8:vzdestroy:200:root@pam:'
206
+ expect(@server1.openvz_delete(201)).to be_eql 'NOK: error code = 500'
207
+ end
208
+ end
209
+
210
+ describe 'openvz_status' do
211
+ it 'should get container status' do
212
+ # VM Status
213
+ stub_request(:get, 'http://localhost:8006/api2/json/nodes/localhost/openvz/200/status/current').with(
214
+ headers: @common_headers_in).to_return(
215
+ status: 200,
216
+ headers: @common_headers_out,
217
+ body: '{"data":{"maxswap":268435456,"disk":0,"ip":"-","status":"stopped","ha":0,"netout":0,"maxdisk":9.44473296573929e+21,"maxmem":268435456,"uptime":0,"swap":0,"nproc":0,"diskread":0,"cpu":0,"netin":0,"name":"CT200","failcnt":0,"diskwrite":0,"mem":0,"type":"openvz","cpus":1}}'
218
+ )
219
+
220
+ expect(@server1.openvz_status(200)).to be_an_instance_of Hash
221
+ expect(@server1.openvz_status(200)['status']).to be_eql 'stopped'
222
+ expect(@server1.openvz_status(200)['cpus']).to be_eql 1
223
+ end
224
+ end
225
+
226
+ describe 'openvz_start' do
227
+ it 'should start vm' do
228
+ stub_request(:post, 'http://localhost:8006/api2/json/nodes/localhost/openvz/200/status/start').with(
229
+ headers: @common_headers_in).to_return(
230
+ status: 200,
231
+ headers: @common_headers_out,
232
+ body: '{"data":"UPID:ks311324:0005D91C:11BE5277:521D1C23:vzstart:200:root@pam:"}'
233
+ )
234
+
235
+ expect(@server1.openvz_start(200)).to be_eql 'UPID:ks311324:0005D91C:11BE5277:521D1C23:vzstart:200:root@pam:'
236
+ end
237
+ end
238
+
239
+ describe 'openvz_stop' do
240
+ it 'should stop vm' do
241
+ stub_request(:post, 'http://localhost:8006/api2/json/nodes/localhost/openvz/200/status/stop').with(
242
+ headers: @common_headers_in).to_return(
243
+ status: 200,
244
+ headers: @common_headers_out,
245
+ body: '{"data":"UPID:ks311324:0005D91C:11BE5277:521D1C23:vzstop:200:root@pam:"}'
246
+ )
247
+
248
+ expect(@server1.openvz_stop(200)).to be_eql 'UPID:ks311324:0005D91C:11BE5277:521D1C23:vzstop:200:root@pam:'
249
+ end
250
+ end
251
+
252
+ describe 'openvz_shutdown' do
253
+ it 'should shutdown vm' do
254
+ stub_request(:post, 'http://localhost:8006/api2/json/nodes/localhost/openvz/200/status/shutdown').with(
255
+ headers: @common_headers_in).to_return(
256
+ status: 200,
257
+ headers: @common_headers_out,
258
+ body: '{"data":"UPID:ks311324:0005D91C:11BE5277:521D1C23:vzshutdown:200:root@pam:"}'
259
+ )
260
+
261
+ expect(@server1.openvz_shutdown(200)).to be_eql 'UPID:ks311324:0005D91C:11BE5277:521D1C23:vzshutdown:200:root@pam:'
262
+ end
263
+ end
264
+
265
+ describe 'openvz_config' do
266
+ it 'should get container config' do
267
+ # VM config
268
+ stub_request(:get, 'http://localhost:8006/api2/json/nodes/localhost/openvz/200/config').with(
269
+ headers: @common_headers_in).to_return(
270
+ status: 200,
271
+ headers: @common_headers_out,
272
+ body: '{"data":{"quotaugidlimit":0,"disk":0,"ostemplate":"ubuntu-10.04-standard_10.04-4_i386.tar.gz","nameserver":"127.0.0.1 192.168.1.1","memory":256,"searchdomain":"domain.com","onboot":0,"cpuunits":1000,"swap":256,"quotatime":0,"digest":"5a6f4052d559d3ecc89c849214f482217018a07e","cpus":1,"storage":"local"}}'
273
+ )
274
+
275
+ expect(@server1.openvz_config(200)).to be_an_instance_of Hash
276
+ expect(@server1.openvz_config(200)['searchdomain']).to be_eql 'domain.com'
277
+ expect(@server1.openvz_config(200)['ostemplate']).to be_eql 'ubuntu-10.04-standard_10.04-4_i386.tar.gz'
278
+ end
279
+ end
280
+
281
+ describe 'openvz_config_set' do
282
+ it 'should modify container config' do
283
+ # VM config
284
+ stub_request(:put, 'http://localhost:8006/api2/json/nodes/localhost/openvz/200/config').with(
285
+ headers: @common_headers_in).to_return(
286
+ status: 200,
287
+ headers: @common_headers_out,
288
+ body: '{"data":null}'
289
+ )
290
+
291
+ @server1.openvz_config_set(200, 'searchdomain' => 'other.com')
292
+ end
293
+ end
294
+ end
@@ -0,0 +1,28 @@
1
+ require 'spork'
2
+ require 'rspec'
3
+
4
+ Spork.prefork do
5
+ unless ENV['DRB']
6
+ require 'simplecov'
7
+ SimpleCov.start 'rails'
8
+ end
9
+ end
10
+
11
+ Spork.each_run do
12
+ if ENV['DRB']
13
+ require 'simplecov'
14
+ SimpleCov.start 'rails'
15
+ end
16
+ end
17
+
18
+ if ENV['CI']
19
+ require 'coveralls'
20
+ Coveralls.wear!
21
+ end
22
+
23
+ RSpec.configure do |config|
24
+ config.run_all_when_everything_filtered = true
25
+ config.filter_run :focus
26
+
27
+ config.order = 'random'
28
+ end
metadata ADDED
@@ -0,0 +1,131 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ruby-proxmox
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Nathaniel Suchy
8
+ - Nicolas Ledez
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2019-03-21 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rest-client
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - ">="
19
+ - !ruby/object:Gem::Version
20
+ version: 1.6.7
21
+ type: :runtime
22
+ prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - ">="
26
+ - !ruby/object:Gem::Version
27
+ version: 1.6.7
28
+ - !ruby/object:Gem::Dependency
29
+ name: bundler
30
+ requirement: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - "~>"
33
+ - !ruby/object:Gem::Version
34
+ version: '1.3'
35
+ type: :development
36
+ prerelease: false
37
+ version_requirements: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - "~>"
40
+ - !ruby/object:Gem::Version
41
+ version: '1.3'
42
+ - !ruby/object:Gem::Dependency
43
+ name: rake
44
+ requirement: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - ">="
47
+ - !ruby/object:Gem::Version
48
+ version: '0'
49
+ type: :development
50
+ prerelease: false
51
+ version_requirements: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - ">="
54
+ - !ruby/object:Gem::Version
55
+ version: '0'
56
+ - !ruby/object:Gem::Dependency
57
+ name: rspec
58
+ requirement: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - ">="
61
+ - !ruby/object:Gem::Version
62
+ version: '0'
63
+ type: :development
64
+ prerelease: false
65
+ version_requirements: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - ">="
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ - !ruby/object:Gem::Dependency
71
+ name: webmock
72
+ requirement: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - ">="
75
+ - !ruby/object:Gem::Version
76
+ version: '0'
77
+ type: :development
78
+ prerelease: false
79
+ version_requirements: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - ">="
82
+ - !ruby/object:Gem::Version
83
+ version: '0'
84
+ description: A ruby gem for managing Proxmox Servers with Ruby. Based on https://github.com/nledez/proxmox
85
+ email:
86
+ - me@lunorian.is
87
+ executables: []
88
+ extensions: []
89
+ extra_rdoc_files: []
90
+ files:
91
+ - ".gitignore"
92
+ - ".rspec"
93
+ - ".rubocop.yml"
94
+ - ".travis.yml"
95
+ - Gemfile
96
+ - Guardfile
97
+ - LICENSE.txt
98
+ - README.md
99
+ - Rakefile
100
+ - lib/proxmox.rb
101
+ - lib/proxmox/version.rb
102
+ - ruby-proxmox.gemspec
103
+ - spec/lib/proxmox_spec.rb
104
+ - spec/spec_helper.rb
105
+ homepage: https://github.com/ulayer/ruby-proxmox
106
+ licenses:
107
+ - MIT
108
+ metadata: {}
109
+ post_install_message:
110
+ rdoc_options: []
111
+ require_paths:
112
+ - lib
113
+ required_ruby_version: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ required_rubygems_version: !ruby/object:Gem::Requirement
119
+ requirements:
120
+ - - ">="
121
+ - !ruby/object:Gem::Version
122
+ version: '0'
123
+ requirements: []
124
+ rubyforge_project:
125
+ rubygems_version: 2.7.6
126
+ signing_key:
127
+ specification_version: 4
128
+ summary: A ruby gem for managing Proxmox Servers with Ruby. Based on https://github.com/nledez/proxmox
129
+ test_files:
130
+ - spec/lib/proxmox_spec.rb
131
+ - spec/spec_helper.rb