vcloud-rest 1.2.0 → 1.3.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 +4 -4
- data/CHANGELOG.md +21 -0
- data/README.md +50 -7
- data/lib/vcloud-rest/connection.rb +129 -9
- data/lib/vcloud-rest/vcloud/catalog.rb +32 -22
- data/lib/vcloud-rest/vcloud/disk.rb +113 -0
- data/lib/vcloud-rest/vcloud/media.rb +100 -0
- data/lib/vcloud-rest/vcloud/network.rb +1 -1
- data/lib/vcloud-rest/vcloud/ovf.rb +5 -97
- data/lib/vcloud-rest/vcloud/vapp.rb +61 -0
- data/lib/vcloud-rest/vcloud/vdc.rb +15 -1
- data/lib/vcloud-rest/vcloud/vm.rb +56 -4
- data/lib/vcloud-rest/version.rb +1 -1
- metadata +126 -17
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 470e97ca321d742340571fff5cbbaa6e5a8fbe2b
|
4
|
+
data.tar.gz: 0460be22738dca2e1b05668a861777d6380dd3d4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: acbace49f0444b3bff99e21ef01c9947597cba62adedf0b4385967627d4898cee59463cfbe1216f9abfdc75312344cd43146dd5608b48cc33eb894313f1e749c
|
7
|
+
data.tar.gz: 813bdcc2737e13da061b0b76d659fe0147275e8d61c80220106dca631dfa5688ae48bbc35e6510cc5016ae3c686b87afd9cccec543f0d62d7def955c88523596
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,26 @@
|
|
1
1
|
Changes
|
2
2
|
==
|
3
|
+
2014-06-03 (1.3.0)
|
4
|
+
|
5
|
+
Note that starting from this release Ruby 1.8.7+ and 1.9.2 are not explicitly tested against anymore.
|
6
|
+
|
7
|
+
FEATURES:
|
8
|
+
|
9
|
+
* Add "media" item type management to `get_catalog_item`
|
10
|
+
* Add `discard_suspend_state_[vapp|vm]` to discard suspended state of a vApp or VM
|
11
|
+
* Add independent disk capabilities
|
12
|
+
* `get_vapp` returns also network IDs
|
13
|
+
* Add `discard_[vm|vapp]_snapshot` to discard snapshots
|
14
|
+
* Add `acquire_ticket_vm` that retrieves a screen ticket (VMRC) for a given VM
|
15
|
+
|
16
|
+
FIXES:
|
17
|
+
|
18
|
+
* Fix add network to VM for API v.5.5
|
19
|
+
* Fix VM IP address retrieval with different interfaces on same network
|
20
|
+
* Don't use hardcoded rasd:Parent in `add_disk`
|
21
|
+
|
22
|
+
Note that now `get_vm` appends NetworkConnectionIndex to network name to generate a unique hash key.
|
23
|
+
|
3
24
|
2014-02-06 (1.2.0)
|
4
25
|
|
5
26
|
FEATURES:
|
data/README.md
CHANGED
@@ -17,7 +17,7 @@ This plugin is distributed as a Ruby Gem. To install it, run:
|
|
17
17
|
|
18
18
|
Depending on your system's configuration, you may need to run this command with root privileges.
|
19
19
|
|
20
|
-
vcloud-rest is tested against ruby 2.1.
|
20
|
+
vcloud-rest is tested against ruby 2.1.2, 2.0.0, 1.9.3 and ruby-head.
|
21
21
|
|
22
22
|
FEATURES
|
23
23
|
--
|
@@ -49,10 +49,12 @@ TODO
|
|
49
49
|
|
50
50
|
PREREQUISITES
|
51
51
|
--
|
52
|
-
- nokogiri
|
53
|
-
- rest-client
|
54
|
-
- httpclient
|
55
|
-
- ruby-progressbar
|
52
|
+
- nokogiri
|
53
|
+
- rest-client
|
54
|
+
- httpclient
|
55
|
+
- ruby-progressbar
|
56
|
+
|
57
|
+
(see *vcloud_rest.gemspec* for details)
|
56
58
|
|
57
59
|
For testing purpose:
|
58
60
|
- minitest (included in ruby 1.9)
|
@@ -88,14 +90,55 @@ Or:
|
|
88
90
|
|
89
91
|
ruby spec/connection_spec.rb
|
90
92
|
|
91
|
-
Note: in order to run tests with ruby 1.8.
|
93
|
+
Note: in order to run tests with ruby 1.8.x you need to export RUBYOPT="rubygems"
|
94
|
+
|
95
|
+
### Write new tests
|
96
|
+
|
97
|
+
Tests are now managed using VCR and thus real interactions are recorded and replayed.
|
98
|
+
|
99
|
+
In order to write new tests the following steps are required:
|
100
|
+
|
101
|
+
1. create a file *test_credentials.yml*
|
102
|
+
1. create a new test entry specifying a new VCR cassette *my_test_case.yml*
|
103
|
+
1. review and anonymize data if necessary under *spec/fixtures/vcr_cassettes/my_test_case.yml*
|
104
|
+
|
105
|
+
**Note:** values in *test_credentials.yml* are automatically anonymized.
|
106
|
+
|
107
|
+
Examples:
|
108
|
+
|
109
|
+
=> test_credentials.yml
|
110
|
+
:host: https://vcloud_instance_url
|
111
|
+
:username: test_username
|
112
|
+
:password: test_password
|
113
|
+
:org: test_organization
|
114
|
+
|
115
|
+
|
116
|
+
=> Test entry in connection_spec.rb
|
117
|
+
it "should power off a given vapp" do
|
118
|
+
VCR.use_cassette('vapps/poweroff_vapp') do
|
119
|
+
connection.login
|
120
|
+
task_id = connection.poweroff_vapp("65b4dbc9-b0b1-46e4-a420-8f8147369f8b")
|
121
|
+
expect(task_id).to eq "ae791b59-4c9f-4fe2-9916-703f1fc3cbd5"
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
=> Recorded fixture (credentials auto-anonymized)
|
126
|
+
---
|
127
|
+
http_interactions:
|
128
|
+
- request:
|
129
|
+
method: post
|
130
|
+
uri: https://testuser%40testorg:testpass@testurl.local/api/sessions
|
131
|
+
body:
|
132
|
+
encoding: UTF-8
|
133
|
+
...
|
134
|
+
|
92
135
|
|
93
136
|
LICENSE
|
94
137
|
--
|
95
138
|
|
96
139
|
Author:: Stefano Tortarolo <stefano.tortarolo@gmail.com>
|
97
140
|
|
98
|
-
Copyright:: Copyright (c) 2012-
|
141
|
+
Copyright:: Copyright (c) 2012-2014
|
99
142
|
License:: Apache License, Version 2.0
|
100
143
|
|
101
144
|
Licensed under the Apache License, Version 2.0 (the "License");
|
@@ -21,6 +21,7 @@ require 'nokogiri'
|
|
21
21
|
require 'httpclient'
|
22
22
|
require 'ruby-progressbar'
|
23
23
|
require 'logger'
|
24
|
+
require 'uri'
|
24
25
|
|
25
26
|
require 'vcloud-rest/vcloud/vapp'
|
26
27
|
require 'vcloud-rest/vcloud/org'
|
@@ -28,7 +29,9 @@ require 'vcloud-rest/vcloud/catalog'
|
|
28
29
|
require 'vcloud-rest/vcloud/vdc'
|
29
30
|
require 'vcloud-rest/vcloud/vm'
|
30
31
|
require 'vcloud-rest/vcloud/ovf'
|
32
|
+
require 'vcloud-rest/vcloud/media'
|
31
33
|
require 'vcloud-rest/vcloud/network'
|
34
|
+
require 'vcloud-rest/vcloud/disk'
|
32
35
|
|
33
36
|
module VCloudClient
|
34
37
|
class UnauthorizedAccess < StandardError; end
|
@@ -82,6 +85,7 @@ module VCloudClient
|
|
82
85
|
}
|
83
86
|
|
84
87
|
response, headers = send_request(params)
|
88
|
+
ensure
|
85
89
|
# reset auth key to nil
|
86
90
|
@auth_key = nil
|
87
91
|
end
|
@@ -130,20 +134,21 @@ module VCloudClient
|
|
130
134
|
# Sends a synchronous request to the vCloud API and returns the response as parsed XML + headers.
|
131
135
|
def send_request(params, payload=nil, content_type=nil)
|
132
136
|
headers = {:accept => "application/*+xml;version=#{@api_version}"}
|
137
|
+
invocation_params = {:method => params['method'],
|
138
|
+
:headers => headers,
|
139
|
+
:url => "#{@api_url}#{params['command']}",
|
140
|
+
:payload => payload}
|
141
|
+
|
133
142
|
if @auth_key
|
134
143
|
headers.merge!({:x_vcloud_authorization => @auth_key})
|
144
|
+
else
|
145
|
+
invocation_params.merge!({:user => "#{@username}@#{@org_name}",
|
146
|
+
:password => @password })
|
135
147
|
end
|
136
148
|
|
137
|
-
if content_type
|
138
|
-
headers.merge!({:content_type => content_type})
|
139
|
-
end
|
149
|
+
headers.merge!({:content_type => content_type}) if content_type
|
140
150
|
|
141
|
-
request = RestClient::Request.new(
|
142
|
-
:user => "#{@username}@#{@org_name}",
|
143
|
-
:password => @password,
|
144
|
-
:headers => headers,
|
145
|
-
:url => "#{@api_url}#{params['command']}",
|
146
|
-
:payload => payload)
|
151
|
+
request = RestClient::Request.new(invocation_params)
|
147
152
|
|
148
153
|
begin
|
149
154
|
response = request.execute
|
@@ -236,6 +241,18 @@ module VCloudClient
|
|
236
241
|
task_id
|
237
242
|
end
|
238
243
|
|
244
|
+
##
|
245
|
+
# Discard suspended state of a vApp/VM
|
246
|
+
def discard_suspended_state_action(id, type=:vapp)
|
247
|
+
params = {
|
248
|
+
"method" => :post,
|
249
|
+
"command" => "/vApp/#{type}-#{id}/action/discardSuspendedState"
|
250
|
+
}
|
251
|
+
response, headers = send_request(params)
|
252
|
+
task_id = headers[:location].gsub(/.*\/task\//, "")
|
253
|
+
task_id
|
254
|
+
end
|
255
|
+
|
239
256
|
##
|
240
257
|
# Create a new vapp/vm snapshot (overwrites any existing)
|
241
258
|
def create_snapshot_action(id, description="New Snapshot", type=:vapp)
|
@@ -266,6 +283,109 @@ module VCloudClient
|
|
266
283
|
task_id
|
267
284
|
end
|
268
285
|
|
286
|
+
##
|
287
|
+
# Discard all existing snapshots (vapp/vm)
|
288
|
+
def discard_snapshot_action(id, type=:vapp)
|
289
|
+
params = {
|
290
|
+
"method" => :post,
|
291
|
+
"command" => "/vApp/#{type}-#{id}/action/removeAllSnapshots"
|
292
|
+
}
|
293
|
+
response, headers = send_request(params)
|
294
|
+
task_id = headers[:location].gsub(/.*\/task\//, "")
|
295
|
+
task_id
|
296
|
+
end
|
297
|
+
|
298
|
+
##
|
299
|
+
# Upload a large file in configurable chunks, output an optional progressbar
|
300
|
+
def upload_file(uploadURL, uploadFile, progressUrl, config={})
|
301
|
+
raise ::IOError, "#{uploadFile} not found." unless File.exists?(uploadFile)
|
302
|
+
|
303
|
+
# Set chunksize to 10M if not specified otherwise
|
304
|
+
chunkSize = (config[:chunksize] || 10485760)
|
305
|
+
|
306
|
+
# Set progress bar to default format if not specified otherwise
|
307
|
+
progressBarFormat = (config[:progressbar_format] || "%e <%B> %p%% %t")
|
308
|
+
|
309
|
+
# Set progress bar length to 120 if not specified otherwise
|
310
|
+
progressBarLength = (config[:progressbar_length] || 120)
|
311
|
+
|
312
|
+
# Open our file for upload
|
313
|
+
uploadFileHandle = File.new(uploadFile, "rb" )
|
314
|
+
fileName = File.basename(uploadFileHandle)
|
315
|
+
|
316
|
+
progressBarTitle = "Uploading: " + uploadFile.to_s
|
317
|
+
|
318
|
+
# Create a progressbar object if progress bar is enabled
|
319
|
+
if config[:progressbar_enable] == true && uploadFileHandle.size.to_i > chunkSize
|
320
|
+
progressbar = ProgressBar.create(
|
321
|
+
:title => progressBarTitle,
|
322
|
+
:starting_at => 0,
|
323
|
+
:total => uploadFileHandle.size.to_i,
|
324
|
+
:length => progressBarLength,
|
325
|
+
:format => progressBarFormat
|
326
|
+
)
|
327
|
+
else
|
328
|
+
@logger.info progressBarTitle
|
329
|
+
end
|
330
|
+
# Create a new HTTP client
|
331
|
+
clnt = HTTPClient.new
|
332
|
+
|
333
|
+
# Disable SSL cert verification
|
334
|
+
clnt.ssl_config.verify_mode=(OpenSSL::SSL::VERIFY_NONE)
|
335
|
+
|
336
|
+
# Suppress SSL depth message
|
337
|
+
clnt.ssl_config.verify_callback=proc{ |ok, ctx|; true };
|
338
|
+
|
339
|
+
# Perform ranged upload until the file reaches its end
|
340
|
+
until uploadFileHandle.eof?
|
341
|
+
|
342
|
+
# Create ranges for this chunk upload
|
343
|
+
rangeStart = uploadFileHandle.pos
|
344
|
+
rangeStop = uploadFileHandle.pos.to_i + chunkSize
|
345
|
+
|
346
|
+
# Read current chunk
|
347
|
+
fileContent = uploadFileHandle.read(chunkSize)
|
348
|
+
|
349
|
+
# If statement to handle last chunk transfer if is > than filesize
|
350
|
+
if rangeStop.to_i > uploadFileHandle.size.to_i
|
351
|
+
contentRange = "bytes #{rangeStart.to_s}-#{uploadFileHandle.size.to_s}/#{uploadFileHandle.size.to_s}"
|
352
|
+
rangeLen = uploadFileHandle.size.to_i - rangeStart.to_i
|
353
|
+
else
|
354
|
+
contentRange = "bytes #{rangeStart.to_s}-#{rangeStop.to_s}/#{uploadFileHandle.size.to_s}"
|
355
|
+
rangeLen = rangeStop.to_i - rangeStart.to_i
|
356
|
+
end
|
357
|
+
|
358
|
+
# Build headers
|
359
|
+
extheader = {
|
360
|
+
'x-vcloud-authorization' => @auth_key,
|
361
|
+
'Content-Range' => contentRange,
|
362
|
+
'Content-Length' => rangeLen.to_s
|
363
|
+
}
|
364
|
+
|
365
|
+
begin
|
366
|
+
uploadRequest = "#{@host_url}#{uploadURL}"
|
367
|
+
connection = clnt.request('PUT', uploadRequest, nil, fileContent, extheader)
|
368
|
+
|
369
|
+
if config[:progressbar_enable] == true && uploadFileHandle.size.to_i > chunkSize
|
370
|
+
params = {
|
371
|
+
'method' => :get,
|
372
|
+
'command' => progressUrl
|
373
|
+
}
|
374
|
+
response, headers = send_request(params)
|
375
|
+
|
376
|
+
response.css("Files File [name='#{fileName}']").each do |file|
|
377
|
+
progressbar.progress=file[:bytesTransferred].to_i
|
378
|
+
end
|
379
|
+
end
|
380
|
+
rescue
|
381
|
+
retryTime = (config[:retry_time] || 5)
|
382
|
+
@logger.warn "Range #{contentRange} failed to upload, retrying the chunk in #{retryTime.to_s} seconds, to stop the action press CTRL+C."
|
383
|
+
sleep retryTime.to_i
|
384
|
+
retry
|
385
|
+
end
|
386
|
+
end
|
387
|
+
uploadFileHandle.close
|
388
|
+
end
|
269
389
|
|
270
390
|
end # class
|
271
391
|
end
|
@@ -66,31 +66,41 @@ module VCloudClient
|
|
66
66
|
description = description.text unless description.nil?
|
67
67
|
|
68
68
|
items = []
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
69
|
+
# manage two different types of catalog items: vAppTemplate and media
|
70
|
+
if response.css("Entity[type='application/vnd.vmware.vcloud.vAppTemplate+xml']").size > 0
|
71
|
+
response.css("Entity[type='application/vnd.vmware.vcloud.vAppTemplate+xml']").each do |item|
|
72
|
+
itemId = item['href'].gsub(/.*\/vAppTemplate\/vappTemplate\-/, "")
|
73
|
+
|
74
|
+
# Fetch the catalogItemId information
|
75
|
+
params = {
|
76
|
+
'method' => :get,
|
77
|
+
'command' => "/vAppTemplate/vappTemplate-#{itemId}"
|
78
|
+
}
|
79
|
+
response, headers = send_request(params)
|
80
|
+
|
81
|
+
# VMs Hash for all the vApp VM entities
|
82
|
+
vms_hash = {}
|
83
|
+
response.css("/VAppTemplate/Children/Vm").each do |vmElem|
|
84
|
+
vmName = vmElem["name"]
|
85
|
+
vmId = vmElem["href"].gsub(/.*\/vAppTemplate\/vm\-/, "")
|
86
|
+
|
87
|
+
# Add the VM name/id to the VMs Hash
|
88
|
+
vms_hash[vmName] = { :id => vmId }
|
89
|
+
end
|
90
|
+
|
91
|
+
items << { :id => itemId,
|
92
|
+
:name => item['name'],
|
93
|
+
:vms_hash => vms_hash }
|
87
94
|
end
|
88
95
|
|
89
|
-
items
|
90
|
-
|
91
|
-
|
96
|
+
{ :id => catalogItemId, :description => description, :items => items, :type => 'vAppTemplate' }
|
97
|
+
elsif response.css("Entity[type='application/vnd.vmware.vcloud.media+xml']").size > 0
|
98
|
+
name = response.css("Entity[type='application/vnd.vmware.vcloud.media+xml']").first['name']
|
99
|
+
{ :id => catalogItemId, :description => description, :name => name, :type => 'media' }
|
100
|
+
else
|
101
|
+
@logger.warn 'WARNING: either this catalog item is empty or contains something not managed by vcloud-rest'
|
102
|
+
{ :id => catalogItemId, :description => description, :type => 'unknown' }
|
92
103
|
end
|
93
|
-
{ :id => catalogItemId, :description => description, :items => items }
|
94
104
|
end
|
95
105
|
|
96
106
|
##
|
@@ -0,0 +1,113 @@
|
|
1
|
+
##
|
2
|
+
# Manage independent disks
|
3
|
+
# Independent disks are stand-alone virtual disks created in organization vDCs.
|
4
|
+
# Every disk is associated with an organization vDC but not with a VM and
|
5
|
+
# can be later attached to any VM deployed in that vDC.
|
6
|
+
#
|
7
|
+
# VERY IMPORTANT NOTE: independent disks size, unlike set_vm_disk_info, is in Kb
|
8
|
+
##
|
9
|
+
module VCloudClient
|
10
|
+
class Connection
|
11
|
+
def create_disk(name, size, vdc_id, description="")
|
12
|
+
builder = Nokogiri::XML::Builder.new do |xml|
|
13
|
+
xml.DiskCreateParams(
|
14
|
+
"xmlns" => "http://www.vmware.com/vcloud/v1.5") {
|
15
|
+
xml.Disk("name" => name, "size" => size) {
|
16
|
+
xml.Description description
|
17
|
+
}
|
18
|
+
}
|
19
|
+
end
|
20
|
+
|
21
|
+
params = {
|
22
|
+
'method' => :post,
|
23
|
+
'command' => "/vdc/#{vdc_id}/disk"
|
24
|
+
}
|
25
|
+
|
26
|
+
@logger.debug "Creating independent disk #{name} in VDC #{vdc_id}"
|
27
|
+
response, headers = send_request(params, builder.to_xml, "application/vnd.vmware.vcloud.diskCreateParams+xml")
|
28
|
+
|
29
|
+
# Get the id of the new disk
|
30
|
+
disk_url = response.css("Disk").first[:href]
|
31
|
+
disk_id = disk_url.gsub(/.*\/disk\//, "")
|
32
|
+
@logger.debug "Independent disk created = #{disk_id}"
|
33
|
+
|
34
|
+
task = response.css("Task[operationName='vdcCreateDisk']").first
|
35
|
+
task_id = task["href"].gsub(/.*\/task\//, "")
|
36
|
+
|
37
|
+
{ :disk_id => disk_id, :task_id => task_id }
|
38
|
+
end
|
39
|
+
|
40
|
+
def get_disk(disk_id)
|
41
|
+
params = {
|
42
|
+
'method' => :get,
|
43
|
+
'command' => "/disk/#{disk_id}"
|
44
|
+
}
|
45
|
+
|
46
|
+
@logger.debug "Fetching independent disk #{disk_id}"
|
47
|
+
response, headers = send_request(params)
|
48
|
+
|
49
|
+
name = response.css("Disk").attribute("name").text
|
50
|
+
size = response.css("Disk").attribute("size").text
|
51
|
+
description = response.css("Description").first
|
52
|
+
description = description.text unless description.nil?
|
53
|
+
storage_profile = response.css("StorageProfile").first[:name]
|
54
|
+
owner = response.css("User").first[:name]
|
55
|
+
{ :id => disk_id, :name => name, :size => size, :description => description, :storage_profile => storage_profile, :owner => owner }
|
56
|
+
end
|
57
|
+
|
58
|
+
def get_disk_by_name(organization, vdcName, diskName)
|
59
|
+
result = nil
|
60
|
+
|
61
|
+
get_vdc_by_name(organization, vdcName)[:disks].each do |disk|
|
62
|
+
if disk[0].downcase == diskName.downcase
|
63
|
+
result = get_disk(disk[1])
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
result
|
68
|
+
end
|
69
|
+
|
70
|
+
def attach_disk_to_vm(disk_id, vm_id)
|
71
|
+
disk_attach_action(disk_id, vm_id, 'attach')
|
72
|
+
end
|
73
|
+
|
74
|
+
def detach_disk_from_vm(disk_id, vm_id)
|
75
|
+
disk_attach_action(disk_id, vm_id, 'detach')
|
76
|
+
end
|
77
|
+
|
78
|
+
def delete_disk(disk_id)
|
79
|
+
params = {
|
80
|
+
'method' => :delete,
|
81
|
+
'command' => "/disk/#{disk_id}"
|
82
|
+
}
|
83
|
+
|
84
|
+
@logger.debug "Deleting independent disk #{disk_id}"
|
85
|
+
response, headers = send_request(params)
|
86
|
+
|
87
|
+
task = response.css("Task").first
|
88
|
+
task_id = task["href"].gsub(/.*\/task\//, "")
|
89
|
+
end
|
90
|
+
|
91
|
+
private
|
92
|
+
|
93
|
+
def disk_attach_action(disk_id, vm_id, action)
|
94
|
+
builder = Nokogiri::XML::Builder.new do |xml|
|
95
|
+
xml.DiskAttachOrDetachParams("xmlns" => "http://www.vmware.com/vcloud/v1.5") {
|
96
|
+
xml.Disk(
|
97
|
+
"type" => "application/vnd.vmware.vcloud.disk+xml",
|
98
|
+
"href" => "#{@api_url}/disk/#{disk_id}")
|
99
|
+
}
|
100
|
+
end
|
101
|
+
|
102
|
+
params = {
|
103
|
+
'method' => :post,
|
104
|
+
'command' => "/vApp/vm-#{vm_id}/disk/action/#{action}"
|
105
|
+
}
|
106
|
+
|
107
|
+
response, headers = send_request(params, builder.to_xml, "application/vnd.vmware.vcloud.diskAttachOrDetachParams+xml")
|
108
|
+
|
109
|
+
task = response.css("Task").first
|
110
|
+
task_id = task["href"].gsub(/.*\/task\//, "")
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
module VCloudClient
|
2
|
+
class Connection
|
3
|
+
|
4
|
+
def upload_media(vdcId, mediaName, mediaDescription, mediaFile, type, catalogId, uploadOptions={})
|
5
|
+
raise ::IOError, "File #{mediaFile} is missing." unless File.exists?(mediaFile)
|
6
|
+
|
7
|
+
fileName = File.basename(mediaFile)
|
8
|
+
mediaName = File.basename(fileName, ".*") if mediaName.nil? || mediaName.empty?
|
9
|
+
type = File.extname(fileName).delete(".") if type.nil? || type.empty?
|
10
|
+
size = File.size(mediaFile)
|
11
|
+
|
12
|
+
builder = Nokogiri::XML::Builder.new do |xml|
|
13
|
+
xml.Media(
|
14
|
+
"xmlns" => "http://www.vmware.com/vcloud/v1.5",
|
15
|
+
"xmlns:ovf" => "http://schemas.dmtf.org/ovf/envelope/1",
|
16
|
+
"size" => size,
|
17
|
+
"imageType" => type,
|
18
|
+
"name" => mediaName) {
|
19
|
+
xml.Description mediaDescription
|
20
|
+
}
|
21
|
+
end
|
22
|
+
|
23
|
+
params = {
|
24
|
+
'method' => :post,
|
25
|
+
'command' => "/vdc/#{vdcId}/media"
|
26
|
+
}
|
27
|
+
|
28
|
+
@logger.debug "Creating Media item"
|
29
|
+
response, headers = send_request(params, builder.to_xml, "application/vnd.vmware.vcloud.media+xml")
|
30
|
+
|
31
|
+
# Get the new media id from response
|
32
|
+
mediaUrl = response.css("Media").first[:href]
|
33
|
+
mediaId = mediaUrl.gsub(/.*\/media\//, "")
|
34
|
+
@logger.debug "Media item created - #{mediaId}"
|
35
|
+
|
36
|
+
# Get File upload:default link from response
|
37
|
+
uploadHref = response.css("Files Link [rel='upload:default']").first[:href]
|
38
|
+
fileUpload = uploadHref.gsub(/(.*)(\/transfer\/.*)/, "\\2")
|
39
|
+
|
40
|
+
begin
|
41
|
+
@logger.debug "Uploading #{mediaFile}"
|
42
|
+
upload_file(fileUpload, mediaFile, "/media/#{mediaId}", uploadOptions)
|
43
|
+
|
44
|
+
# Add item to the catalog catalogId
|
45
|
+
builder = Nokogiri::XML::Builder.new do |xml|
|
46
|
+
xml.CatalogItem(
|
47
|
+
"xmlns" => "http://www.vmware.com/vcloud/v1.5",
|
48
|
+
"type" => "application/vnd.vmware.vcloud.catalogItem+xml",
|
49
|
+
"name" => mediaName) {
|
50
|
+
xml.Description mediaDescription
|
51
|
+
xml.Entity("href" => "#{@api_url}/media/#{mediaId}")
|
52
|
+
}
|
53
|
+
end
|
54
|
+
|
55
|
+
params = {
|
56
|
+
'method' => :post,
|
57
|
+
'command' => "/catalog/#{catalogId}/catalogItems"
|
58
|
+
}
|
59
|
+
|
60
|
+
@logger.debug "Adding media item #{mediaName} to catalog."
|
61
|
+
response, headers = send_request(params, builder.to_xml, "application/vnd.vmware.vcloud.catalogItem+xml")
|
62
|
+
|
63
|
+
# TODO: the best thing would detect the real importing status.
|
64
|
+
entity = response.css("Entity").first
|
65
|
+
result = {}
|
66
|
+
if entity
|
67
|
+
result[:id] = entity['href'].gsub(/.*\/media\//, "")
|
68
|
+
result[:name] = entity['name']
|
69
|
+
end
|
70
|
+
result
|
71
|
+
rescue Exception => e
|
72
|
+
@logger.error "Exception detected: #{e.message}."
|
73
|
+
|
74
|
+
# Get Media Task
|
75
|
+
params = {
|
76
|
+
'method' => :get,
|
77
|
+
'command' => "/media/#{mediaId}"
|
78
|
+
}
|
79
|
+
response, headers = send_request(params)
|
80
|
+
|
81
|
+
# Cancel Task
|
82
|
+
# Note that it might not exist (i.e., error for existing vdc entity)
|
83
|
+
tasks = response.css("Tasks")
|
84
|
+
unless tasks.empty?
|
85
|
+
tasks.css("Task").each do |task|
|
86
|
+
if task['status'] == 'error'
|
87
|
+
@logger.error task.css('Error').first['message']
|
88
|
+
else
|
89
|
+
id = task['href'].gsub(/.*\/task\//, "")
|
90
|
+
@logger.error "Aborting task #{id}..."
|
91
|
+
cancel_task(id)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
raise e
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
@@ -10,6 +10,7 @@ module VCloudClient
|
|
10
10
|
# - uploadOptions {}
|
11
11
|
def upload_ovf(vdcId, vappName, vappDescription, ovfFile, catalogId, uploadOptions={})
|
12
12
|
raise ::IOError, "OVF #{ovfFile} is missing." unless File.exists?(ovfFile)
|
13
|
+
raise ::IOError, "Only .ovf files are supported" unless File.extname(ovfFile) =~ /\.ovf/
|
13
14
|
|
14
15
|
# if send_manifest is not set, setting it true
|
15
16
|
if uploadOptions[:send_manifest].nil? || uploadOptions[:send_manifest]
|
@@ -52,7 +53,7 @@ module VCloudClient
|
|
52
53
|
# Send OVF Descriptor
|
53
54
|
uploadURL = "/transfer/#{descriptorUpload}"
|
54
55
|
uploadFile = "#{ovfDir}/#{ovfFileBasename}.ovf"
|
55
|
-
upload_file(uploadURL, uploadFile, vAppTemplate, uploadOptions)
|
56
|
+
upload_file(uploadURL, uploadFile, "/vAppTemplate/vappTemplate-#{vAppTemplate}", uploadOptions)
|
56
57
|
|
57
58
|
@logger.debug "OVF Descriptor uploaded."
|
58
59
|
|
@@ -80,7 +81,7 @@ module VCloudClient
|
|
80
81
|
if uploadManifest == "true"
|
81
82
|
uploadURL = "/transfer/#{transferGUID}/descriptor.mf"
|
82
83
|
uploadFile = "#{ovfDir}/#{ovfFileBasename}.mf"
|
83
|
-
upload_file(uploadURL, uploadFile, vAppTemplate, uploadOptions)
|
84
|
+
upload_file(uploadURL, uploadFile, "/vAppTemplate/vappTemplate-#{vAppTemplate}", uploadOptions)
|
84
85
|
@logger.debug "OVF Manifest uploaded."
|
85
86
|
end
|
86
87
|
|
@@ -94,7 +95,7 @@ module VCloudClient
|
|
94
95
|
fileName = file[:href].gsub(/.*\/transfer\/#{transferGUID}\//, "")
|
95
96
|
uploadFile = "#{ovfDir}/#{fileName}"
|
96
97
|
uploadURL = "/transfer/#{transferGUID}/#{fileName}"
|
97
|
-
upload_file(uploadURL, uploadFile, vAppTemplate, uploadOptions)
|
98
|
+
upload_file(uploadURL, uploadFile, "/vAppTemplate/vappTemplate-#{vAppTemplate}", uploadOptions)
|
98
99
|
end
|
99
100
|
|
100
101
|
# Add item to the catalog catalogId
|
@@ -156,98 +157,5 @@ module VCloudClient
|
|
156
157
|
raise e
|
157
158
|
end
|
158
159
|
end
|
159
|
-
|
160
|
-
private
|
161
|
-
##
|
162
|
-
# Upload a large file in configurable chunks, output an optional progressbar
|
163
|
-
def upload_file(uploadURL, uploadFile, vAppTemplate, config={})
|
164
|
-
raise ::IOError, "#{uploadFile} not found." unless File.exists?(uploadFile)
|
165
|
-
|
166
|
-
# Set chunksize to 10M if not specified otherwise
|
167
|
-
chunkSize = (config[:chunksize] || 10485760)
|
168
|
-
|
169
|
-
# Set progress bar to default format if not specified otherwise
|
170
|
-
progressBarFormat = (config[:progressbar_format] || "%e <%B> %p%% %t")
|
171
|
-
|
172
|
-
# Set progress bar length to 120 if not specified otherwise
|
173
|
-
progressBarLength = (config[:progressbar_length] || 120)
|
174
|
-
|
175
|
-
# Open our file for upload
|
176
|
-
uploadFileHandle = File.new(uploadFile, "rb" )
|
177
|
-
fileName = File.basename(uploadFileHandle)
|
178
|
-
|
179
|
-
progressBarTitle = "Uploading: " + uploadFile.to_s
|
180
|
-
|
181
|
-
# Create a progressbar object if progress bar is enabled
|
182
|
-
if config[:progressbar_enable] == true && uploadFileHandle.size.to_i > chunkSize
|
183
|
-
progressbar = ProgressBar.create(
|
184
|
-
:title => progressBarTitle,
|
185
|
-
:starting_at => 0,
|
186
|
-
:total => uploadFileHandle.size.to_i,
|
187
|
-
:length => progressBarLength,
|
188
|
-
:format => progressBarFormat
|
189
|
-
)
|
190
|
-
else
|
191
|
-
@logger.info progressBarTitle
|
192
|
-
end
|
193
|
-
# Create a new HTTP client
|
194
|
-
clnt = HTTPClient.new
|
195
|
-
|
196
|
-
# Disable SSL cert verification
|
197
|
-
clnt.ssl_config.verify_mode=(OpenSSL::SSL::VERIFY_NONE)
|
198
|
-
|
199
|
-
# Suppress SSL depth message
|
200
|
-
clnt.ssl_config.verify_callback=proc{ |ok, ctx|; true };
|
201
|
-
|
202
|
-
# Perform ranged upload until the file reaches its end
|
203
|
-
until uploadFileHandle.eof?
|
204
|
-
|
205
|
-
# Create ranges for this chunk upload
|
206
|
-
rangeStart = uploadFileHandle.pos
|
207
|
-
rangeStop = uploadFileHandle.pos.to_i + chunkSize
|
208
|
-
|
209
|
-
# Read current chunk
|
210
|
-
fileContent = uploadFileHandle.read(chunkSize)
|
211
|
-
|
212
|
-
# If statement to handle last chunk transfer if is > than filesize
|
213
|
-
if rangeStop.to_i > uploadFileHandle.size.to_i
|
214
|
-
contentRange = "bytes #{rangeStart.to_s}-#{uploadFileHandle.size.to_s}/#{uploadFileHandle.size.to_s}"
|
215
|
-
rangeLen = uploadFileHandle.size.to_i - rangeStart.to_i
|
216
|
-
else
|
217
|
-
contentRange = "bytes #{rangeStart.to_s}-#{rangeStop.to_s}/#{uploadFileHandle.size.to_s}"
|
218
|
-
rangeLen = rangeStop.to_i - rangeStart.to_i
|
219
|
-
end
|
220
|
-
|
221
|
-
# Build headers
|
222
|
-
extheader = {
|
223
|
-
'x-vcloud-authorization' => @auth_key,
|
224
|
-
'Content-Range' => contentRange,
|
225
|
-
'Content-Length' => rangeLen.to_s
|
226
|
-
}
|
227
|
-
|
228
|
-
begin
|
229
|
-
uploadRequest = "#{@host_url}#{uploadURL}"
|
230
|
-
connection = clnt.request('PUT', uploadRequest, nil, fileContent, extheader)
|
231
|
-
|
232
|
-
if config[:progressbar_enable] == true && uploadFileHandle.size.to_i > chunkSize
|
233
|
-
params = {
|
234
|
-
'method' => :get,
|
235
|
-
'command' => "/vAppTemplate/vappTemplate-#{vAppTemplate}"
|
236
|
-
}
|
237
|
-
response, headers = send_request(params)
|
238
|
-
|
239
|
-
response.css("Files File [name='#{fileName}']").each do |file|
|
240
|
-
progressbar.progress=file[:bytesTransferred].to_i
|
241
|
-
end
|
242
|
-
end
|
243
|
-
rescue
|
244
|
-
retryTime = (config[:retry_time] || 5)
|
245
|
-
@logger.warn "Range #{contentRange} failed to upload, retrying the chunk in #{retryTime.to_s} seconds, to stop the action press CTRL+C."
|
246
|
-
sleep retryTime.to_i
|
247
|
-
retry
|
248
|
-
end
|
249
|
-
end
|
250
|
-
uploadFileHandle.close
|
251
|
-
end
|
252
|
-
end
|
160
|
+
end # class
|
253
161
|
end
|
@@ -34,6 +34,9 @@ module VCloudClient
|
|
34
34
|
|
35
35
|
networks = response.css('NetworkConfig').reject{|n| n.attribute('networkName').text == 'none'}.
|
36
36
|
collect do |network|
|
37
|
+
net_id = network.css('Link [rel=repair]')
|
38
|
+
net_id = net_id.attribute('href').text.gsub(/.*\/network\/(.*)\/action.*/, '\1') unless net_id.nil?
|
39
|
+
|
37
40
|
net_name = network.attribute('networkName').text
|
38
41
|
|
39
42
|
gateway = network.css('Gateway')
|
@@ -62,6 +65,7 @@ module VCloudClient
|
|
62
65
|
}
|
63
66
|
|
64
67
|
{
|
68
|
+
:id => net_id,
|
65
69
|
:name => net_name,
|
66
70
|
:scope => ipscope
|
67
71
|
}
|
@@ -163,6 +167,12 @@ module VCloudClient
|
|
163
167
|
power_action(vAppId, 'suspend')
|
164
168
|
end
|
165
169
|
|
170
|
+
##
|
171
|
+
# Discard suspended state of a given vapp
|
172
|
+
def discard_suspend_state_vapp(vAppId)
|
173
|
+
discard_suspended_state_action(vAppId, :vapp)
|
174
|
+
end
|
175
|
+
|
166
176
|
##
|
167
177
|
# reboot a given vapp
|
168
178
|
# This will basically initial a guest OS reboot, and will only work if
|
@@ -310,7 +320,52 @@ module VCloudClient
|
|
310
320
|
{ :vapp_id => vapp_id, :task_id => task_id }
|
311
321
|
end
|
312
322
|
|
323
|
+
##
|
324
|
+
# Create a new virtual machine from a template in an existing vApp.
|
325
|
+
#
|
326
|
+
# Params:
|
327
|
+
# - vapp: the target vapp
|
328
|
+
# - vm: hash with template ID and new VM name
|
329
|
+
# - network_config: hash of the network configuration for the VM
|
330
|
+
def add_vm_to_vapp(vapp, vm, network_config={})
|
331
|
+
builder = Nokogiri::XML::Builder.new do |xml|
|
332
|
+
xml.RecomposeVAppParams(
|
333
|
+
"xmlns" => "http://www.vmware.com/vcloud/v1.5",
|
334
|
+
"xmlns:ovf" => "http://schemas.dmtf.org/ovf/envelope/1",
|
335
|
+
"name" => vapp[:name]) {
|
336
|
+
xml.SourcedItem {
|
337
|
+
xml.Source("href" => "#{@api_url}/vAppTemplate/vm-#{vm[:template_id]}", "name" => vm[:vm_name])
|
338
|
+
xml.InstantiationParams {
|
339
|
+
xml.NetworkConnectionSection(
|
340
|
+
"xmlns:ovf" => "http://schemas.dmtf.org/ovf/envelope/1",
|
341
|
+
"type" => "application/vnd.vmware.vcloud.networkConnectionSection+xml",
|
342
|
+
"href" => "#{@api_url}/vAppTemplate/vm-#{vm[:template_id]}/networkConnectionSection/") {
|
343
|
+
xml['ovf'].Info "Network config for sourced item"
|
344
|
+
xml.PrimaryNetworkConnectionIndex "0"
|
345
|
+
xml.NetworkConnection("network" => network_config[:name]) {
|
346
|
+
xml.NetworkConnectionIndex "0"
|
347
|
+
xml.IsConnected "true"
|
348
|
+
xml.IpAddressAllocationMode(network_config[:ip_allocation_mode] || "POOL")
|
349
|
+
}
|
350
|
+
}
|
351
|
+
}
|
352
|
+
xml.NetworkAssignment("containerNetwork" => network_config[:name], "innerNetwork" => network_config[:name])
|
353
|
+
}
|
354
|
+
xml.AllEULAsAccepted "true"
|
355
|
+
}
|
356
|
+
end
|
357
|
+
|
358
|
+
params = {
|
359
|
+
"method" => :post,
|
360
|
+
"command" => "/vApp/vapp-#{vapp[:id]}/action/recomposeVApp"
|
361
|
+
}
|
313
362
|
|
363
|
+
response, headers = send_request(params, builder.to_xml, "application/vnd.vmware.vcloud.recomposeVAppParams+xml")
|
364
|
+
|
365
|
+
task = response.css("Task[operationName='vdcRecomposeVapp']").first
|
366
|
+
task_id = task["href"].gsub(/.*\/task\//, "")
|
367
|
+
task_id
|
368
|
+
end
|
314
369
|
|
315
370
|
##
|
316
371
|
# Create a new vm snapshot (overwrites any existing)
|
@@ -340,6 +395,12 @@ module VCloudClient
|
|
340
395
|
revert_snapshot_action(vmId, :vapp)
|
341
396
|
end
|
342
397
|
|
398
|
+
##
|
399
|
+
# Discard all existing snapshots
|
400
|
+
def discard_vapp_snapshot(vmId)
|
401
|
+
discard_snapshot_action(vmId, :vapp)
|
402
|
+
end
|
403
|
+
|
343
404
|
##
|
344
405
|
# Clone a vapp in a given VDC to a new Vapp
|
345
406
|
def clone_vapp(vdc_id, source_vapp_id, name, deploy="true", poweron="false", linked="false", delete_source="false")
|
@@ -4,6 +4,8 @@ module VCloudClient
|
|
4
4
|
# Fetch details about a given vdc:
|
5
5
|
# - description
|
6
6
|
# - vapps
|
7
|
+
# - templates
|
8
|
+
# - disks
|
7
9
|
# - networks
|
8
10
|
def get_vdc(vdcId)
|
9
11
|
params = {
|
@@ -24,12 +26,24 @@ module VCloudClient
|
|
24
26
|
vapps[item['name']] = item['href'].gsub(/.*\/vApp\/vapp\-/, "")
|
25
27
|
end
|
26
28
|
|
29
|
+
templates = {}
|
30
|
+
response.css("ResourceEntity[type='application/vnd.vmware.vcloud.vAppTemplate+xml']").each do |item|
|
31
|
+
templates[item['name']] = item['href'].gsub(/.*\/vAppTemplate\/vappTemplate\-/, "")
|
32
|
+
end
|
33
|
+
|
34
|
+
disks = {}
|
35
|
+
response.css("ResourceEntity[type='application/vnd.vmware.vcloud.disk+xml']").each do |item|
|
36
|
+
disks[item['name']] = item['href'].gsub(/.*\/disk\//, "")
|
37
|
+
end
|
38
|
+
|
27
39
|
networks = {}
|
28
40
|
response.css("Network[type='application/vnd.vmware.vcloud.network+xml']").each do |item|
|
29
41
|
networks[item['name']] = item['href'].gsub(/.*\/network\//, "")
|
30
42
|
end
|
43
|
+
|
31
44
|
{ :id => vdcId, :name => name, :description => description,
|
32
|
-
:vapps => vapps, :
|
45
|
+
:vapps => vapps, :templates => templates, :disks => disks,
|
46
|
+
:networks => networks }
|
33
47
|
end
|
34
48
|
|
35
49
|
##
|
@@ -186,8 +186,9 @@ module VCloudClient
|
|
186
186
|
# For some reasons these elements must be removed
|
187
187
|
netconfig_response.css("Link").each {|n| n.remove}
|
188
188
|
|
189
|
-
# Delete placeholder network
|
190
|
-
netconfig_response.css('NetworkConnection').find{|n| n.attribute('network').text == 'none'}
|
189
|
+
# Delete placeholder network if present (since vcloud 5.5 has been removed)
|
190
|
+
none_network = netconfig_response.css('NetworkConnection').find{|n| n.attribute('network').text == 'none'}
|
191
|
+
none_network.remove if none_network
|
191
192
|
|
192
193
|
networks_count = netconfig_response.css('NetworkConnection').count
|
193
194
|
|
@@ -349,7 +350,11 @@ module VCloudClient
|
|
349
350
|
external_ip = network.css('ExternalIpAddress').first
|
350
351
|
external_ip = external_ip.text if external_ip
|
351
352
|
|
352
|
-
|
353
|
+
# Append NetworkConnectionIndex to network name to generate a unique hash key,
|
354
|
+
# otherwise different interfaces on the same network would use the same hash key
|
355
|
+
key = "#{network['network']}_#{network.css('NetworkConnectionIndex').first.text}"
|
356
|
+
|
357
|
+
networks[key] = {
|
353
358
|
:index => network.css('NetworkConnectionIndex').first.text,
|
354
359
|
:ip => ip,
|
355
360
|
:external_ip => external_ip,
|
@@ -422,6 +427,12 @@ module VCloudClient
|
|
422
427
|
power_action(vmId, 'suspend', :vm)
|
423
428
|
end
|
424
429
|
|
430
|
+
##
|
431
|
+
# Discard suspended state of a given vm
|
432
|
+
def discard_suspend_state_vm(vmId)
|
433
|
+
discard_suspended_state_action(vmId, :vm)
|
434
|
+
end
|
435
|
+
|
425
436
|
##
|
426
437
|
# reboot a given vm
|
427
438
|
# This will basically initial a guest OS reboot, and will only work if
|
@@ -455,11 +466,52 @@ module VCloudClient
|
|
455
466
|
revert_snapshot_action(vmId, :vm)
|
456
467
|
end
|
457
468
|
|
469
|
+
##
|
470
|
+
# Discard all existing snapshots
|
471
|
+
def discard_vm_snapshot(vmId)
|
472
|
+
discard_snapshot_action(vmId, :vm)
|
473
|
+
end
|
474
|
+
|
475
|
+
##
|
476
|
+
# Retrieve a screen ticket that you can use with the VMRC browser plug-in
|
477
|
+
# to gain access to the console of a running VM.
|
478
|
+
def acquire_ticket_vm(vmId)
|
479
|
+
|
480
|
+
params = {
|
481
|
+
'method' => :post,
|
482
|
+
'command' => "/vApp/vm-#{vmId}/screen/action/acquireTicket"
|
483
|
+
}
|
484
|
+
|
485
|
+
response, headers = send_request(params)
|
486
|
+
|
487
|
+
screen_ticket = response.css("ScreenTicket").text
|
488
|
+
|
489
|
+
result = {}
|
490
|
+
|
491
|
+
if screen_ticket =~ /mks:\/\/([^\/]*)\/([^\?]*)\?ticket=(.*)/
|
492
|
+
result = { host: $1, moid: $2, token: $3 }
|
493
|
+
result[:token] = URI.unescape result[:token]
|
494
|
+
end
|
495
|
+
result
|
496
|
+
end
|
458
497
|
|
459
498
|
private
|
460
499
|
def add_disk(source_xml, disk_info)
|
500
|
+
|
461
501
|
disks_count = source_xml.css("Item").css("rasd|HostResource").count
|
462
502
|
|
503
|
+
disk_parent_ids = Set.new
|
504
|
+
|
505
|
+
source_xml.css("Item").each do |entry|
|
506
|
+
parent_id = entry.css('rasd|Parent').first.text unless entry.css('rasd|Parent').empty?
|
507
|
+
resource_type = entry.css('rasd|ResourceType').first.text unless entry.css('rasd|ResourceType').empty?
|
508
|
+
disk_parent_ids << parent_id if resource_type == '17'
|
509
|
+
end
|
510
|
+
|
511
|
+
raise InvalidStateError, "Could not handle this request because multiple nodes with ResourceType 17 and different rasd:Parent exist" if disk_parent_ids.size > 1
|
512
|
+
|
513
|
+
raise InvalidStateError, "Could not handle this request because I could not find a node with ResourceType 17" if disk_parent_ids.size == 0
|
514
|
+
|
463
515
|
# FIXME: This is a hack, but dealing with nokogiri APIs can be quite
|
464
516
|
# frustrating sometimes...
|
465
517
|
sibling = source_xml.css("Item").first
|
@@ -478,7 +530,7 @@ module VCloudClient
|
|
478
530
|
ns12:busSubType=\"lsilogic\"
|
479
531
|
ns12:busType=\"6\"/>
|
480
532
|
<rasd:InstanceID>200#{disks_count}</rasd:InstanceID>
|
481
|
-
<rasd:Parent
|
533
|
+
<rasd:Parent>#{disk_parent_ids.first}</rasd:Parent>
|
482
534
|
<rasd:ResourceType>17</rasd:ResourceType>
|
483
535
|
</Item>""")
|
484
536
|
end
|
data/lib/vcloud-rest/version.rb
CHANGED
metadata
CHANGED
@@ -1,71 +1,179 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: vcloud-rest
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Stefano Tortarolo
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-
|
11
|
+
date: 2014-06-03 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: nokogiri
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- -
|
17
|
+
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
19
|
version: 1.5.10
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
|
-
- -
|
24
|
+
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: 1.5.10
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: rest-client
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
|
-
- - ~>
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.6'
|
34
|
+
- - ">="
|
32
35
|
- !ruby/object:Gem::Version
|
33
36
|
version: 1.6.7
|
34
37
|
type: :runtime
|
35
38
|
prerelease: false
|
36
39
|
version_requirements: !ruby/object:Gem::Requirement
|
37
40
|
requirements:
|
38
|
-
- - ~>
|
41
|
+
- - "~>"
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '1.6'
|
44
|
+
- - ">="
|
39
45
|
- !ruby/object:Gem::Version
|
40
46
|
version: 1.6.7
|
41
47
|
- !ruby/object:Gem::Dependency
|
42
48
|
name: httpclient
|
43
49
|
requirement: !ruby/object:Gem::Requirement
|
44
50
|
requirements:
|
45
|
-
- - ~>
|
51
|
+
- - "~>"
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '2.3'
|
54
|
+
- - ">="
|
46
55
|
- !ruby/object:Gem::Version
|
47
56
|
version: 2.3.3
|
48
57
|
type: :runtime
|
49
58
|
prerelease: false
|
50
59
|
version_requirements: !ruby/object:Gem::Requirement
|
51
60
|
requirements:
|
52
|
-
- - ~>
|
61
|
+
- - "~>"
|
62
|
+
- !ruby/object:Gem::Version
|
63
|
+
version: '2.3'
|
64
|
+
- - ">="
|
53
65
|
- !ruby/object:Gem::Version
|
54
66
|
version: 2.3.3
|
55
67
|
- !ruby/object:Gem::Dependency
|
56
68
|
name: ruby-progressbar
|
57
69
|
requirement: !ruby/object:Gem::Requirement
|
58
70
|
requirements:
|
59
|
-
- - ~>
|
71
|
+
- - "~>"
|
60
72
|
- !ruby/object:Gem::Version
|
61
|
-
version: 1.
|
73
|
+
version: '1.5'
|
74
|
+
- - ">="
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: 1.5.1
|
62
77
|
type: :runtime
|
63
78
|
prerelease: false
|
64
79
|
version_requirements: !ruby/object:Gem::Requirement
|
65
80
|
requirements:
|
66
|
-
- - ~>
|
81
|
+
- - "~>"
|
82
|
+
- !ruby/object:Gem::Version
|
83
|
+
version: '1.5'
|
84
|
+
- - ">="
|
85
|
+
- !ruby/object:Gem::Version
|
86
|
+
version: 1.5.1
|
87
|
+
- !ruby/object:Gem::Dependency
|
88
|
+
name: rake
|
89
|
+
requirement: !ruby/object:Gem::Requirement
|
90
|
+
requirements:
|
91
|
+
- - "~>"
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: '10.1'
|
94
|
+
type: :development
|
95
|
+
prerelease: false
|
96
|
+
version_requirements: !ruby/object:Gem::Requirement
|
97
|
+
requirements:
|
98
|
+
- - "~>"
|
99
|
+
- !ruby/object:Gem::Version
|
100
|
+
version: '10.1'
|
101
|
+
- !ruby/object:Gem::Dependency
|
102
|
+
name: rspec
|
103
|
+
requirement: !ruby/object:Gem::Requirement
|
104
|
+
requirements:
|
105
|
+
- - "~>"
|
106
|
+
- !ruby/object:Gem::Version
|
107
|
+
version: '2.14'
|
108
|
+
type: :development
|
109
|
+
prerelease: false
|
110
|
+
version_requirements: !ruby/object:Gem::Requirement
|
111
|
+
requirements:
|
112
|
+
- - "~>"
|
113
|
+
- !ruby/object:Gem::Version
|
114
|
+
version: '2.14'
|
115
|
+
- !ruby/object:Gem::Dependency
|
116
|
+
name: webmock
|
117
|
+
requirement: !ruby/object:Gem::Requirement
|
118
|
+
requirements:
|
119
|
+
- - "~>"
|
120
|
+
- !ruby/object:Gem::Version
|
121
|
+
version: '1.13'
|
122
|
+
type: :development
|
123
|
+
prerelease: false
|
124
|
+
version_requirements: !ruby/object:Gem::Requirement
|
125
|
+
requirements:
|
126
|
+
- - "~>"
|
127
|
+
- !ruby/object:Gem::Version
|
128
|
+
version: '1.13'
|
129
|
+
- !ruby/object:Gem::Dependency
|
130
|
+
name: vcr
|
131
|
+
requirement: !ruby/object:Gem::Requirement
|
132
|
+
requirements:
|
133
|
+
- - "~>"
|
67
134
|
- !ruby/object:Gem::Version
|
68
|
-
version:
|
135
|
+
version: '2.9'
|
136
|
+
type: :development
|
137
|
+
prerelease: false
|
138
|
+
version_requirements: !ruby/object:Gem::Requirement
|
139
|
+
requirements:
|
140
|
+
- - "~>"
|
141
|
+
- !ruby/object:Gem::Version
|
142
|
+
version: '2.9'
|
143
|
+
- !ruby/object:Gem::Dependency
|
144
|
+
name: simplecov
|
145
|
+
requirement: !ruby/object:Gem::Requirement
|
146
|
+
requirements:
|
147
|
+
- - "~>"
|
148
|
+
- !ruby/object:Gem::Version
|
149
|
+
version: '0.8'
|
150
|
+
- - ">="
|
151
|
+
- !ruby/object:Gem::Version
|
152
|
+
version: 0.8.2
|
153
|
+
type: :development
|
154
|
+
prerelease: false
|
155
|
+
version_requirements: !ruby/object:Gem::Requirement
|
156
|
+
requirements:
|
157
|
+
- - "~>"
|
158
|
+
- !ruby/object:Gem::Version
|
159
|
+
version: '0.8'
|
160
|
+
- - ">="
|
161
|
+
- !ruby/object:Gem::Version
|
162
|
+
version: 0.8.2
|
163
|
+
- !ruby/object:Gem::Dependency
|
164
|
+
name: coveralls
|
165
|
+
requirement: !ruby/object:Gem::Requirement
|
166
|
+
requirements:
|
167
|
+
- - ">="
|
168
|
+
- !ruby/object:Gem::Version
|
169
|
+
version: '0'
|
170
|
+
type: :development
|
171
|
+
prerelease: false
|
172
|
+
version_requirements: !ruby/object:Gem::Requirement
|
173
|
+
requirements:
|
174
|
+
- - ">="
|
175
|
+
- !ruby/object:Gem::Version
|
176
|
+
version: '0'
|
69
177
|
description: Ruby bindings to create, list and manage vCloud servers
|
70
178
|
email:
|
71
179
|
- stefano.tortarolo@gmail.com
|
@@ -74,10 +182,12 @@ extensions: []
|
|
74
182
|
extra_rdoc_files: []
|
75
183
|
files:
|
76
184
|
- CHANGELOG.md
|
77
|
-
- README.md
|
78
185
|
- LICENSE
|
186
|
+
- README.md
|
79
187
|
- lib/vcloud-rest/connection.rb
|
80
188
|
- lib/vcloud-rest/vcloud/catalog.rb
|
189
|
+
- lib/vcloud-rest/vcloud/disk.rb
|
190
|
+
- lib/vcloud-rest/vcloud/media.rb
|
81
191
|
- lib/vcloud-rest/vcloud/network.rb
|
82
192
|
- lib/vcloud-rest/vcloud/org.rb
|
83
193
|
- lib/vcloud-rest/vcloud/ovf.rb
|
@@ -96,19 +206,18 @@ require_paths:
|
|
96
206
|
- lib
|
97
207
|
required_ruby_version: !ruby/object:Gem::Requirement
|
98
208
|
requirements:
|
99
|
-
- -
|
209
|
+
- - ">="
|
100
210
|
- !ruby/object:Gem::Version
|
101
211
|
version: '0'
|
102
212
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
103
213
|
requirements:
|
104
|
-
- -
|
214
|
+
- - ">="
|
105
215
|
- !ruby/object:Gem::Version
|
106
216
|
version: '0'
|
107
217
|
requirements: []
|
108
218
|
rubyforge_project:
|
109
|
-
rubygems_version: 2.
|
219
|
+
rubygems_version: 2.2.2
|
110
220
|
signing_key:
|
111
221
|
specification_version: 4
|
112
222
|
summary: Unofficial ruby bindings for VMWare vCloud's API
|
113
223
|
test_files: []
|
114
|
-
has_rdoc:
|