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.
- checksums.yaml +7 -0
- data/.gitignore +34 -0
- data/.rubocop.yml +34 -0
- data/Gemfile +7 -0
- data/LICENSE +21 -0
- data/README.md +109 -0
- data/lib/vagrant-vcloudair.rb +63 -0
- data/lib/vagrant-vcloudair/action.rb +298 -0
- data/lib/vagrant-vcloudair/action/announce_ssh_exec.rb +22 -0
- data/lib/vagrant-vcloudair/action/build_vapp.rb +235 -0
- data/lib/vagrant-vcloudair/action/connect_vcloud.rb +54 -0
- data/lib/vagrant-vcloudair/action/destroy_vapp.rb +54 -0
- data/lib/vagrant-vcloudair/action/destroy_vm.rb +37 -0
- data/lib/vagrant-vcloudair/action/disconnect_vcloud.rb +31 -0
- data/lib/vagrant-vcloudair/action/forward_ports.rb +132 -0
- data/lib/vagrant-vcloudair/action/handle_nat_port_collisions.rb +153 -0
- data/lib/vagrant-vcloudair/action/inventory_check.rb +210 -0
- data/lib/vagrant-vcloudair/action/is_bridged.rb +29 -0
- data/lib/vagrant-vcloudair/action/is_created.rb +35 -0
- data/lib/vagrant-vcloudair/action/is_last_vm.rb +31 -0
- data/lib/vagrant-vcloudair/action/is_paused.rb +20 -0
- data/lib/vagrant-vcloudair/action/is_running.rb +20 -0
- data/lib/vagrant-vcloudair/action/message_already_running.rb +16 -0
- data/lib/vagrant-vcloudair/action/message_cannot_suspend.rb +16 -0
- data/lib/vagrant-vcloudair/action/message_not_created.rb +16 -0
- data/lib/vagrant-vcloudair/action/message_not_running.rb +16 -0
- data/lib/vagrant-vcloudair/action/message_will_not_destroy.rb +21 -0
- data/lib/vagrant-vcloudair/action/power_off.rb +33 -0
- data/lib/vagrant-vcloudair/action/power_off_vapp.rb +40 -0
- data/lib/vagrant-vcloudair/action/power_on.rb +39 -0
- data/lib/vagrant-vcloudair/action/read_ssh_info.rb +153 -0
- data/lib/vagrant-vcloudair/action/read_state.rb +51 -0
- data/lib/vagrant-vcloudair/action/resume.rb +25 -0
- data/lib/vagrant-vcloudair/action/suspend.rb +25 -0
- data/lib/vagrant-vcloudair/action/unmap_port_forwardings.rb +74 -0
- data/lib/vagrant-vcloudair/cap/forwarded_ports.rb +38 -0
- data/lib/vagrant-vcloudair/cap/public_address.rb +18 -0
- data/lib/vagrant-vcloudair/cap/rdp_info.rb +18 -0
- data/lib/vagrant-vcloudair/cap/winrm_info.rb +15 -0
- data/lib/vagrant-vcloudair/command.rb +285 -0
- data/lib/vagrant-vcloudair/config.rb +205 -0
- data/lib/vagrant-vcloudair/driver/base.rb +643 -0
- data/lib/vagrant-vcloudair/driver/meta.rb +202 -0
- data/lib/vagrant-vcloudair/driver/version_5_1.rb +2019 -0
- data/lib/vagrant-vcloudair/errors.rb +77 -0
- data/lib/vagrant-vcloudair/model/forwarded_port.rb +66 -0
- data/lib/vagrant-vcloudair/plugin.rb +111 -0
- data/lib/vagrant-vcloudair/provider.rb +41 -0
- data/lib/vagrant-vcloudair/util/compile_forwarded_ports.rb +34 -0
- data/lib/vagrant-vcloudair/version.rb +5 -0
- data/locales/en.yml +169 -0
- data/vagrant-vcloudair.gemspec +33 -0
- 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
|