vagrant-vcloudair 0.5.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.
Files changed (53) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +34 -0
  3. data/.rubocop.yml +34 -0
  4. data/Gemfile +7 -0
  5. data/LICENSE +21 -0
  6. data/README.md +109 -0
  7. data/lib/vagrant-vcloudair.rb +63 -0
  8. data/lib/vagrant-vcloudair/action.rb +298 -0
  9. data/lib/vagrant-vcloudair/action/announce_ssh_exec.rb +22 -0
  10. data/lib/vagrant-vcloudair/action/build_vapp.rb +235 -0
  11. data/lib/vagrant-vcloudair/action/connect_vcloud.rb +54 -0
  12. data/lib/vagrant-vcloudair/action/destroy_vapp.rb +54 -0
  13. data/lib/vagrant-vcloudair/action/destroy_vm.rb +37 -0
  14. data/lib/vagrant-vcloudair/action/disconnect_vcloud.rb +31 -0
  15. data/lib/vagrant-vcloudair/action/forward_ports.rb +132 -0
  16. data/lib/vagrant-vcloudair/action/handle_nat_port_collisions.rb +153 -0
  17. data/lib/vagrant-vcloudair/action/inventory_check.rb +210 -0
  18. data/lib/vagrant-vcloudair/action/is_bridged.rb +29 -0
  19. data/lib/vagrant-vcloudair/action/is_created.rb +35 -0
  20. data/lib/vagrant-vcloudair/action/is_last_vm.rb +31 -0
  21. data/lib/vagrant-vcloudair/action/is_paused.rb +20 -0
  22. data/lib/vagrant-vcloudair/action/is_running.rb +20 -0
  23. data/lib/vagrant-vcloudair/action/message_already_running.rb +16 -0
  24. data/lib/vagrant-vcloudair/action/message_cannot_suspend.rb +16 -0
  25. data/lib/vagrant-vcloudair/action/message_not_created.rb +16 -0
  26. data/lib/vagrant-vcloudair/action/message_not_running.rb +16 -0
  27. data/lib/vagrant-vcloudair/action/message_will_not_destroy.rb +21 -0
  28. data/lib/vagrant-vcloudair/action/power_off.rb +33 -0
  29. data/lib/vagrant-vcloudair/action/power_off_vapp.rb +40 -0
  30. data/lib/vagrant-vcloudair/action/power_on.rb +39 -0
  31. data/lib/vagrant-vcloudair/action/read_ssh_info.rb +153 -0
  32. data/lib/vagrant-vcloudair/action/read_state.rb +51 -0
  33. data/lib/vagrant-vcloudair/action/resume.rb +25 -0
  34. data/lib/vagrant-vcloudair/action/suspend.rb +25 -0
  35. data/lib/vagrant-vcloudair/action/unmap_port_forwardings.rb +74 -0
  36. data/lib/vagrant-vcloudair/cap/forwarded_ports.rb +38 -0
  37. data/lib/vagrant-vcloudair/cap/public_address.rb +18 -0
  38. data/lib/vagrant-vcloudair/cap/rdp_info.rb +18 -0
  39. data/lib/vagrant-vcloudair/cap/winrm_info.rb +15 -0
  40. data/lib/vagrant-vcloudair/command.rb +285 -0
  41. data/lib/vagrant-vcloudair/config.rb +205 -0
  42. data/lib/vagrant-vcloudair/driver/base.rb +643 -0
  43. data/lib/vagrant-vcloudair/driver/meta.rb +202 -0
  44. data/lib/vagrant-vcloudair/driver/version_5_1.rb +2019 -0
  45. data/lib/vagrant-vcloudair/errors.rb +77 -0
  46. data/lib/vagrant-vcloudair/model/forwarded_port.rb +66 -0
  47. data/lib/vagrant-vcloudair/plugin.rb +111 -0
  48. data/lib/vagrant-vcloudair/provider.rb +41 -0
  49. data/lib/vagrant-vcloudair/util/compile_forwarded_ports.rb +34 -0
  50. data/lib/vagrant-vcloudair/version.rb +5 -0
  51. data/locales/en.yml +169 -0
  52. data/vagrant-vcloudair.gemspec +33 -0
  53. metadata +266 -0
@@ -0,0 +1,205 @@
1
+ require 'vagrant'
2
+ require 'netaddr'
3
+
4
+ module VagrantPlugins
5
+ module VCloudAir
6
+ class Config < Vagrant.plugin('2', :config)
7
+ # login attributes
8
+
9
+ # The Dedicated Cloud to log in to (optional)
10
+ #
11
+ # @return [String]
12
+ attr_accessor :cloud_id
13
+
14
+ # The username used to log in
15
+ #
16
+ # @return [String]
17
+ attr_accessor :username
18
+
19
+ # The password used to log in
20
+ #
21
+ # @return [String]
22
+ attr_accessor :password
23
+
24
+ # Catalog Name where the item resides
25
+ #
26
+ # @return [String]
27
+ attr_accessor :catalog_name
28
+
29
+ # Catalog Item to be used as a template
30
+ #
31
+ # @return [String]
32
+ # attr_accessor :catalog_item_name
33
+
34
+ # Chunksize for upload in bytes (default 1048576 == 1M)
35
+ #
36
+ # @return [Integer]
37
+ attr_accessor :upload_chunksize
38
+
39
+ # Virtual Data Center to be used
40
+ #
41
+ # @return [String]
42
+ attr_accessor :vdc_name
43
+
44
+ # Virtual Data Center Network to be used
45
+ #
46
+ # @return [String]
47
+ attr_accessor :vdc_network_name
48
+
49
+ # Virtual Data Center Network Id to be used
50
+ #
51
+ # @return [String]
52
+ attr_accessor :vdc_network_id
53
+
54
+ # IP allocation type
55
+ #
56
+ # @return [String]
57
+ attr_accessor :ip_allocation_type
58
+
59
+ # IP subnet
60
+ #
61
+ # @return [String]
62
+ attr_accessor :ip_subnet
63
+
64
+ # DNS
65
+ #
66
+ # @return [Array]
67
+ attr_accessor :ip_dns
68
+
69
+ # Bridge Mode
70
+ #
71
+ # @return [Bool]
72
+ attr_accessor :network_bridge
73
+
74
+ # Port forwarding rules
75
+ #
76
+ # @return [Hash]
77
+ # attr_reader :port_forwarding_rules
78
+
79
+ # Name of the edge gateway [optional]
80
+ #
81
+ # @return [String]
82
+ attr_accessor :vdc_edge_gateway
83
+
84
+ # Public IP of the edge gateway [optional, required if :vdc_edge_gateway
85
+ # is specified]
86
+ #
87
+ # @return [String]
88
+ attr_accessor :vdc_edge_gateway_ip
89
+
90
+ # Name of the vApp prefix [optional, defaults to 'Vagrant' ]
91
+ #
92
+ # @return [String]
93
+ attr_accessor :vapp_prefix
94
+
95
+ ##
96
+ ## vCloud Air config runtime values
97
+ ##
98
+
99
+ # connection handle
100
+ attr_accessor :vcloudair_cnx
101
+
102
+ # org object (Hash)
103
+ attr_accessor :org
104
+
105
+ # org id (String)
106
+ attr_accessor :org_id
107
+
108
+ # vdc object (Hash)
109
+ attr_accessor :vdc
110
+
111
+ # vdc id (String)
112
+ attr_accessor :vdc_id
113
+
114
+ # catalog object (Hash)
115
+ attr_accessor :catalog
116
+
117
+ # catalog id (String)
118
+ attr_accessor :catalog_id
119
+
120
+ # catalog item object (Hash)
121
+ attr_accessor :catalog_item
122
+
123
+ # vApp Name (String)
124
+ attr_accessor :vAppName
125
+
126
+ # vApp Id (String)
127
+ attr_accessor :vAppId
128
+
129
+ # VM memory size in MB (Integer)
130
+ attr_accessor :memory
131
+
132
+ # VM number of cpus (Integer)
133
+ attr_accessor :cpus
134
+
135
+ # NestedHypervisor (Bool)
136
+ attr_accessor :nested_hypervisor
137
+
138
+ def validate(machine)
139
+ errors = _detected_errors
140
+
141
+ errors << I18n.t('vagrant_vcloudair.errors.config.username') if username.nil?
142
+ errors << I18n.t('vagrant_vcloudair.errors.config.password') if password.nil?
143
+
144
+ unless ip_dns.nil?
145
+ if ip_dns.kind_of?(Array)
146
+ ip_dns.each do |dns|
147
+ begin
148
+ cidr = NetAddr::CIDR.create(dns)
149
+ rescue NetAddr::ValidationError
150
+ errors << I18n.t('vagrant_vcloudair.errors.config.dns_not_valid')
151
+ end
152
+ if cidr && cidr.bits < 32
153
+ errors << I18n.t('vagrant_vcloudair.errors.config.dns_specified_as_subnet')
154
+ end
155
+ end
156
+ else
157
+ errors << I18n.t('vagrant_vcloudair.errors.config.ip_dns')
158
+ end
159
+ end
160
+
161
+ unless vdc_edge_gateway_ip.nil?
162
+ begin
163
+ cidr = NetAddr::CIDR.create(vdc_edge_gateway_ip)
164
+ rescue NetAddr::ValidationError
165
+ errors << I18n.t('vagrant_vcloudair.errors.config.edge_gateway_ip_not_valid')
166
+ end
167
+ if cidr && cidr.bits < 32
168
+ errors << I18n.t('vagrant_vcloudair.errors.config.edge_gateway_ip_specified_as_subnet')
169
+ end
170
+ end
171
+
172
+ unless ip_subnet.nil?
173
+ begin
174
+ cidr = NetAddr::CIDR.create(ip_subnet)
175
+ rescue NetAddr::ValidationError
176
+ errors << I18n.t('vagrant_vcloudair.errors.config.ip_subnet_not_valid')
177
+ end
178
+ if cidr && cidr.bits > 30
179
+ errors << I18n.t('vagrant_vcloudair.errors.config.ip_subnet_too_small')
180
+ end
181
+ end
182
+
183
+ if catalog_name.nil?
184
+ errors << I18n.t('vagrant_vcloudair.errors.config.catalog_name')
185
+ end
186
+
187
+ errors << I18n.t('vagrant_vcloudair.errors.config.vdc_name') if vdc_name.nil?
188
+
189
+ if vdc_network_name.nil?
190
+ errors << I18n.t('vagrant_vcloudair.errors.config.vdc_network_name')
191
+ end
192
+
193
+ if network_bridge == true && (!vdc_edge_gateway.nil? || !vdc_edge_gateway_ip.nil?)
194
+ errors << I18n.t('vagrant_vcloudair.errors.config.mixed_bridge')
195
+ end
196
+
197
+ if (vdc_edge_gateway.nil? && !vdc_edge_gateway_ip.nil?) || (!vdc_edge_gateway.nil? && vdc_edge_gateway_ip.nil?)
198
+ errors << I18n.t('vagrant_vcloudair.errors.config.wrong_edge_configuration')
199
+ end
200
+
201
+ { 'VCloudAir Provider' => errors }
202
+ end
203
+ end
204
+ end
205
+ end
@@ -0,0 +1,643 @@
1
+ #
2
+ # Copyright 2012 Stefano Tortarolo
3
+ # Copyright 2013 Fabio Rapposelli and Timo Sugliani
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+ #
17
+
18
+ require 'log4r'
19
+ require 'vagrant/util/busy'
20
+ require 'vagrant/util/platform'
21
+ require 'vagrant/util/retryable'
22
+ require 'vagrant/util/subprocess'
23
+ require 'awesome_print'
24
+
25
+ module VagrantPlugins
26
+ module VCloudAir
27
+ module Driver
28
+ class UnauthorizedAccess < StandardError; end
29
+ class WrongAPIVersion < StandardError; end
30
+ class WrongItemIDError < StandardError; end
31
+ class InvalidStateError < StandardError; end
32
+ class InternalServerError < StandardError; end
33
+ class UnhandledError < StandardError; end
34
+
35
+ # Main class to access vCloud Air rest APIs
36
+ class Base
37
+ include Vagrant::Util::Retryable
38
+
39
+ def initialize
40
+ @logger = Log4r::Logger.new('vagrant::provider::vcloudair::base')
41
+ end
42
+
43
+ ##
44
+ # Authenticate against the specified server
45
+ def login
46
+ end
47
+
48
+ ##
49
+ # Destroy the current session
50
+ def logout
51
+ end
52
+
53
+ ##
54
+ # Fetch existing organizations and their IDs
55
+ def get_organizations
56
+ end
57
+
58
+ ##
59
+ # friendly helper method to fetch an Organization Id by name
60
+ # - name (this isn't case sensitive)
61
+ def get_organization_id_by_name(name)
62
+ end
63
+
64
+ ##
65
+ # friendly helper method to fetch an Organization by name
66
+ # - name (this isn't case sensitive)
67
+ def get_organization_by_name(name)
68
+ end
69
+
70
+ ##
71
+ # Fetch details about an organization:
72
+ # - catalogs
73
+ # - vdcs
74
+ # - networks
75
+ def get_organization(org_id)
76
+ end
77
+
78
+ ##
79
+ # Fetch details about a given catalog
80
+ def get_catalog(catalog_id)
81
+ end
82
+
83
+ ##
84
+ # Friendly helper method to fetch an catalog id by name
85
+ # - organization hash (from get_organization/get_organization_by_name)
86
+ # - catalog name
87
+ def get_catalog_id_by_name(organization, catalog_name)
88
+ end
89
+
90
+ ##
91
+ # Friendly helper method to fetch an catalog by name
92
+ # - organization hash (from get_organization/get_organization_by_name)
93
+ # - catalog name
94
+ def get_catalog_by_name(organization, catalog_name)
95
+ end
96
+
97
+ ##
98
+ # Fetch details about a given vdc:
99
+ # - description
100
+ # - vapps
101
+ # - networks
102
+ def get_vdc(vdc_id)
103
+ end
104
+
105
+ ##
106
+ # Friendly helper method to fetch a Organization VDC Id by name
107
+ # - Organization object
108
+ # - Organization VDC Name
109
+ def get_vdc_id_by_name(organization, vdc_name)
110
+ end
111
+
112
+ ##
113
+ # Friendly helper method to fetch a Organization VDC by name
114
+ # - Organization object
115
+ # - Organization VDC Name
116
+ def get_vdc_by_name(organization, vdc_name)
117
+ end
118
+
119
+ ##
120
+ # Fetch details about a given catalog item:
121
+ # - description
122
+ # - vApp templates
123
+ def get_catalog_item(catalog_item_id)
124
+ end
125
+
126
+ ##
127
+ # friendly helper method to fetch an catalogItem by name
128
+ # - catalogId (use get_catalog_name(org, name))
129
+ # - catalagItemName
130
+ def get_catalog_item_by_name(catalog_id, catalog_item_name)
131
+ end
132
+
133
+ ##
134
+ # Fetch details about a given vapp:
135
+ # - name
136
+ # - description
137
+ # - status
138
+ # - IP
139
+ # - Children VMs:
140
+ # -- IP addresses
141
+ # -- status
142
+ # -- ID
143
+ def get_vapp(vapp_id)
144
+ end
145
+
146
+ ##
147
+ # Delete a given vapp
148
+ # NOTE: It doesn't verify that the vapp is shutdown
149
+ def delete_vapp(vapp_id)
150
+ end
151
+
152
+ ##
153
+ # Suspend a given vapp
154
+ def suspend_vapp(vapp_id)
155
+ end
156
+
157
+ ##
158
+ # reboot a given vapp
159
+ # This will basically initial a guest OS reboot, and will only work if
160
+ # VMware-tools are installed on the underlying VMs.
161
+ # vShield Edge devices are not affected
162
+ def reboot_vapp(vapp_id)
163
+ end
164
+
165
+ ##
166
+ # reset a given vapp
167
+ # This will basically reset the VMs within the vApp
168
+ # vShield Edge devices are not affected.
169
+ def reset_vapp(vapp_id)
170
+ end
171
+
172
+ ##
173
+ # Boot a given vapp
174
+ def poweron_vapp(vapp_id)
175
+ end
176
+
177
+ ##
178
+ # Create a vapp starting from a template
179
+ #
180
+ # Params:
181
+ # - vdc: the associated VDC
182
+ # - vapp_name: name of the target vapp
183
+ # - vapp_description: description of the target vapp
184
+ # - vapp_templateid: ID of the vapp template
185
+ def create_vapp_from_template(vdc, vapp_name, vapp_description,
186
+ vapp_templateid, poweron = false)
187
+ end
188
+
189
+ ##
190
+ # Compose a vapp using existing virtual machines
191
+ #
192
+ # Params:
193
+ # - vdc: the associated VDC
194
+ # - vapp_name: name of the target vapp
195
+ # - vapp_description: description of the target vapp
196
+ # - vm_list: hash with IDs of the VMs used in the composing process
197
+ # - network_config: hash of the network configuration for the vapp
198
+ def compose_vapp_from_vm(vdc, vapp_name, vapp_description,
199
+ vm_list = {}, network_config = {})
200
+ end
201
+
202
+ # Fetch details about a given vapp template:
203
+ # - name
204
+ # - description
205
+ # - Children VMs:
206
+ # -- ID
207
+ def get_vapp_template(vapp_id)
208
+ end
209
+
210
+ ##
211
+ # Set vApp port forwarding rules
212
+ #
213
+ # - vappid: id of the vapp to be modified
214
+ # - network_name: name of the vapp network to be modified
215
+ # - config: hash with network configuration specifications, must contain
216
+ # an array inside :nat_rules with the nat rules to be applied.
217
+ def set_vapp_port_forwarding_rules(vapp_id, network_name, config = {})
218
+ end
219
+
220
+ ##
221
+ # Get vApp port forwarding rules
222
+ #
223
+ # - vappid: id of the vApp
224
+ def get_vapp_port_forwarding_rules(vapp_id)
225
+ end
226
+
227
+ ##
228
+ # get vApp edge public IP from the vApp ID
229
+ # Only works when:
230
+ # - vApp needs to be poweredOn
231
+ # - FenceMode is set to "natRouted"
232
+ # - NatType" is set to "portForwarding
233
+ # This will be required to know how to connect to VMs behind the Edge.
234
+ def get_vapp_edge_public_ip(vapp_id)
235
+ end
236
+
237
+ ##
238
+ # Upload an OVF package
239
+ # - vdcId
240
+ # - vappName
241
+ # - vappDescription
242
+ # - ovfFile
243
+ # - catalogId
244
+ # - uploadOptions {}
245
+ def upload_ovf(vdc_id, vapp_name, vapp_description, ovf_file,
246
+ catalog_id, upload_options = {})
247
+ end
248
+
249
+ def set_vm_hardware(vm_id, cfg)
250
+ end
251
+
252
+ ##
253
+ # Fetch information for a given task
254
+ def get_task(task_id)
255
+ end
256
+
257
+ ##
258
+ # Poll a given task until completion
259
+ def wait_task_completion(task_id)
260
+ end
261
+
262
+ ##
263
+ # Set vApp Network Config
264
+ def set_vapp_network_config(vapp_id, network_name, config = {})
265
+ end
266
+
267
+ ##
268
+ # Set VM Network Config
269
+ def set_vm_network_config(vm_id, network_name, config = {})
270
+ end
271
+
272
+ ##
273
+ # Set VM Guest Customization Config
274
+ def set_vm_guest_customization(vm_id, computer_name, config = {})
275
+ end
276
+
277
+ ##
278
+ # Fetch details about a given VM
279
+ def get_vm(vm_Id)
280
+ end
281
+
282
+ private
283
+
284
+ ##
285
+ # Sends a synchronous request to the vCloud Air API and returns the
286
+ # response as parsed XML + headers using HTTPClient.
287
+ def send_vcloudair_request(params, payload = nil, content_type = nil)
288
+ # Create a new HTTP client
289
+ clnt = HTTPClient.new
290
+
291
+ # Disable SSL cert verification
292
+ # clnt.ssl_config.verify_mode = (OpenSSL::SSL::VERIFY_NONE)
293
+
294
+ # Set SSL proto to TLSv1
295
+ clnt.ssl_config.ssl_version = :TLSv1
296
+
297
+ # Suppress SSL depth message
298
+ clnt.ssl_config.verify_callback = proc { |ok, ctx|; true }
299
+
300
+ extheader = {}
301
+ extheader['accept'] = 'application/xml;version=5.6'
302
+
303
+ unless content_type.nil?
304
+ extheader['Content-Type'] = content_type
305
+ end
306
+
307
+ if @vcloudair_auth_key
308
+ @logger.debug("vCloud Air authorization key: #{@vcloudair_auth_key}")
309
+ extheader['x-vchs-authorization'] = @vcloudair_auth_key
310
+ else
311
+ @logger.debug('vCloud Air authorization not set')
312
+ @logger.debug("Sending username: #{@username} and password: #{@password}")
313
+ extheader['Authorization'] = "Basic " + Base64.strict_encode64("#{@username}:#{@password}")
314
+ # clnt.set_auth(nil, @username, @password)
315
+ end
316
+
317
+ url = "https://vchs.vmware.com/api#{params['command']}"
318
+
319
+ # Massive debug when LOG=DEBUG
320
+ # Using awesome_print to get nice XML output for better readability
321
+ if @logger.level == 1
322
+ ap "[#{Time.now.ctime}] -> SEND #{params['method'].upcase} #{url}"
323
+ ap 'SEND HEADERS'
324
+ ap extheader
325
+ if payload
326
+ payload_xml = Nokogiri.XML(payload)
327
+ ap 'SEND BODY'
328
+ ap payload_xml
329
+ end
330
+ end
331
+
332
+ begin
333
+ response = clnt.request(
334
+ params['method'],
335
+ url,
336
+ nil,
337
+ payload,
338
+ extheader
339
+ )
340
+
341
+ unless response.ok?
342
+ case response.code
343
+ when 400
344
+ error_message = Nokogiri.parse(response.body)
345
+ error = error_message.css('Error')
346
+ fail Errors::InvalidRequestError,
347
+ :message => error.first['message'].to_s
348
+ when 401
349
+ fail Errors::UnauthorizedAccess,
350
+ :message => response.status
351
+ else
352
+ fail Errors::UnattendedCodeError,
353
+ :message => response.status
354
+ end
355
+ end
356
+
357
+ nicexml = Nokogiri.XML(response.body)
358
+
359
+ # Massive debug when LOG=DEBUG
360
+ # Using awesome_print to get nice XML output for readability
361
+ if @logger.level == 1
362
+ ap "[#{Time.now.ctime}] <- RECV #{response.status}"
363
+ # Just avoid the task spam.
364
+ unless url.index('/task/')
365
+ ap 'RECV HEADERS'
366
+ ap response.headers
367
+ ap 'RECV BODY'
368
+ ap nicexml
369
+ end
370
+ end
371
+
372
+ [Nokogiri.parse(response.body), response.headers]
373
+ rescue SocketError, Errno::EADDRNOTAVAIL
374
+ raise Errors::EndpointUnavailable
375
+ end
376
+ end
377
+
378
+ def get_api_version(host_url)
379
+ # Create a new HTTP client
380
+ clnt = HTTPClient.new
381
+
382
+ # Disable SSL cert verification
383
+ # clnt.ssl_config.verify_mode = (OpenSSL::SSL::VERIFY_NONE)
384
+
385
+ # Suppress SSL depth message
386
+ clnt.ssl_config.verify_callback = proc { |ok, ctx|; true }
387
+
388
+ uri = URI(host_url)
389
+ url = "#{uri.scheme}://#{uri.host}:#{uri.port}/api/versions"
390
+
391
+ begin
392
+ response = clnt.request('GET', url, nil, nil, nil)
393
+ unless response.ok?
394
+ fail Errors::UnattendedCodeError,
395
+ :message => response.status + ' ' + response.reason
396
+ end
397
+
398
+ version_info = Nokogiri.parse(response.body)
399
+
400
+ api_version = version_info.css('VersionInfo Version')
401
+
402
+ api_version_supported = 0.0
403
+
404
+ # Go through each available Version and return the latest supported
405
+ # version
406
+ api_version.each do |api_available_version|
407
+ if api_version_supported.to_f < api_available_version.text.to_f
408
+ api_version_supported = api_available_version.text
409
+ end
410
+ end
411
+
412
+ api_version_supported
413
+
414
+ rescue SocketError, Errno::EADDRNOTAVAIL
415
+ raise Errors::EndpointUnavailable
416
+ end
417
+ end
418
+
419
+ ##
420
+ # Sends a synchronous request to the vCloud Air API and returns the
421
+ # response as parsed XML + headers using HTTPClient.
422
+ def send_request(params, payload = nil, content_type = nil)
423
+ # Create a new HTTP client
424
+ clnt = HTTPClient.new
425
+
426
+ # Suppress SSL depth message
427
+ clnt.ssl_config.verify_callback = proc { |ok, ctx|; true }
428
+
429
+ extheader = {}
430
+ extheader['accept'] = "application/*+xml;version=#{@api_version}"
431
+ extheader['Content-Type'] = content_type unless content_type.nil?
432
+
433
+ if @auth_key
434
+ extheader['x-vcloud-authorization'] = @auth_key
435
+ else
436
+ clnt.set_auth(nil, "#{@username}@#{@org_name}", @password)
437
+ end
438
+
439
+ url = "#{@api_url}#{params['command']}"
440
+
441
+ # Massive debug when LOG=DEBUG
442
+ # Using awesome_print to get nice XML output for better readability
443
+ if @logger.level == 1
444
+ ap "[#{Time.now.ctime}] -> SEND #{params['method'].upcase} #{url}"
445
+ if payload
446
+ payload_xml = Nokogiri.XML(payload)
447
+ ap 'SEND HEADERS'
448
+ ap extheader
449
+ ap 'SEND BODY'
450
+ ap payload_xml
451
+ end
452
+ end
453
+
454
+ begin
455
+ response = clnt.request(
456
+ params['method'],
457
+ url,
458
+ nil,
459
+ payload,
460
+ extheader
461
+ )
462
+
463
+ unless response.ok?
464
+ if response.code == 400
465
+ error_message = Nokogiri.parse(response.body)
466
+ error = error_message.css('Error')
467
+ fail Errors::InvalidRequestError,
468
+ :message => error.first['message'].to_s
469
+ else
470
+ fail Errors::UnattendedCodeError,
471
+ :message => response.status
472
+ end
473
+ end
474
+
475
+ nicexml = Nokogiri.XML(response.body)
476
+
477
+ # Massive debug when LOG=DEBUG
478
+ # Using awesome_print to get nice XML output for readability
479
+ if @logger.level == 1
480
+ ap "[#{Time.now.ctime}] <- RECV #{response.status}"
481
+ # Just avoid the task spam.
482
+ unless url.index('/task/')
483
+ ap 'RECV HEADERS'
484
+ ap response.headers
485
+ ap 'RECV BODY'
486
+ ap nicexml
487
+ end
488
+ end
489
+
490
+ [Nokogiri.parse(response.body), response.headers]
491
+ rescue SocketError, Errno::EADDRNOTAVAIL, Errno::ETIMEDOUT
492
+ raise Errors::EndpointUnavailable
493
+ end
494
+ end
495
+
496
+ ##
497
+ # Upload a large file in configurable chunks, output an optional
498
+ # progressbar
499
+ def upload_file(upload_url, upload_file, vapp_template, config = {})
500
+ # Set chunksize to 5M if not specified otherwise
501
+ chunk_size = (config[:chunksize] || 5_242_880)
502
+ @logger.debug("Set chunksize to #{chunk_size} bytes")
503
+
504
+ # Set progressbar to default format if not specified otherwise
505
+ progressbar_format = (
506
+ config[:progressbar_format] || '%t Progress: %p%% %e'
507
+ )
508
+
509
+ # Open our file for upload
510
+ upload_file_handle = File.new(upload_file, 'rb')
511
+ file_name = File.basename(upload_file_handle)
512
+
513
+ # FIXME: I removed the filename below because I recall a weird issue
514
+ # of upload failing because if a too long filename
515
+ # (tsugliani)
516
+ # => Added the filename back, needs more testing (frapposelli)
517
+ progressbar_title = "Uploading #{file_name}"
518
+
519
+ # Create a progressbar object if progress bar is enabled
520
+ if config[:progressbar_enable] == true &&
521
+ upload_file_handle.size.to_i > chunk_size
522
+ progressbar = ProgressBar.create(
523
+ :title => progressbar_title,
524
+ :starting_at => 0,
525
+ :total => upload_file_handle.size.to_i,
526
+ :format => progressbar_format
527
+ )
528
+ else
529
+ puts progressbar_title
530
+ end
531
+ # Create a new HTTP client
532
+ clnt = HTTPClient.new
533
+
534
+ # Suppress SSL depth message
535
+ clnt.ssl_config.verify_callback = proc { |ok, ctx|; true }
536
+
537
+ # Perform ranged upload until the file reaches its end
538
+ until upload_file_handle.eof?
539
+
540
+ # Create ranges for this chunk upload
541
+ range_start = upload_file_handle.pos
542
+ range_stop = upload_file_handle.pos.to_i + chunk_size
543
+
544
+ # Read current chunk
545
+ file_content = upload_file_handle.read(chunk_size)
546
+
547
+ # If statement to handle last chunk transfer if is > than filesize
548
+ if range_stop.to_i > upload_file_handle.size.to_i
549
+ content_range = "bytes #{range_start.to_s}-" +
550
+ "#{upload_file_handle.size.to_s}/" +
551
+ "#{upload_file_handle.size.to_s}"
552
+ range_len = upload_file_handle.size.to_i - range_start.to_i
553
+ else
554
+ content_range = "bytes #{range_start.to_s}-" +
555
+ "#{range_stop.to_s}/" +
556
+ "#{upload_file_handle.size.to_s}"
557
+ range_len = range_stop.to_i - range_start.to_i
558
+ end
559
+
560
+ # Build headers
561
+ extheader = {
562
+ 'x-vcloud-authorization' => @auth_key,
563
+ 'Content-Range' => content_range,
564
+ 'Content-Length' => range_len.to_s
565
+ }
566
+
567
+ upload_request = "#{@host_url}#{upload_url}"
568
+
569
+ # Massive debug when LOG=DEBUG
570
+ # Using awesome_print to get nice XML output for better readability
571
+ if @logger.level == 1
572
+ ap "[#{Time.now.ctime}] -> SEND PUT #{upload_request}"
573
+ ap 'SEND HEADERS'
574
+ ap extheader
575
+ ap 'SEND BODY'
576
+ ap '<data omitted>'
577
+ end
578
+
579
+ begin
580
+
581
+ response = clnt.request(
582
+ 'PUT',
583
+ upload_request,
584
+ nil,
585
+ file_content,
586
+ extheader
587
+ )
588
+
589
+ unless response.ok?
590
+ fail Errors::UnattendedCodeError, :message => response.status
591
+ end
592
+
593
+ if config[:progressbar_enable] == true &&
594
+ upload_file_handle.size.to_i > chunk_size
595
+ params = {
596
+ 'method' => :get,
597
+ 'command' => "/vAppTemplate/vappTemplate-#{vapp_template}"
598
+ }
599
+ response, _headers = send_request(params)
600
+
601
+ response.css(
602
+ "Files File [name='#{file_name}']"
603
+ ).each do |file|
604
+ progressbar.progress = file[:bytesTransferred].to_i
605
+ end
606
+ end
607
+
608
+ rescue
609
+ # FIXME: HUGE FIXME!!!!
610
+ # DO SOMETHING WITH THIS, IT'S JUST STUPID AS IT IS NOW!!!
611
+ retry_time = (config[:retry_time] || 5)
612
+ puts "Range #{content_range} failed to upload, " +
613
+ "retrying the chunk in #{retry_time.to_s} seconds, " +
614
+ 'to stop this task press CTRL+C.'
615
+ sleep retry_time.to_i
616
+ retry
617
+ end
618
+ end
619
+ upload_file_handle.close
620
+ end
621
+
622
+ ##
623
+ # Convert vApp status codes into human readable description
624
+ def convert_vapp_status(status_code)
625
+ case status_code.to_i
626
+ when 0
627
+ 'suspended'
628
+ when 3
629
+ 'paused'
630
+ when 4
631
+ 'running'
632
+ when 8
633
+ 'stopped'
634
+ when 10
635
+ 'mixed'
636
+ else
637
+ "Unknown #{status_code}"
638
+ end
639
+ end
640
+ end # class
641
+ end
642
+ end
643
+ end