vagrant-cosmic 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/CHANGELOG.md +8 -0
- data/Docker/.dockerignore +2 -0
- data/Docker/Dockerfile +48 -0
- data/Docker/Dockerfile.chefdk_0_17 +49 -0
- data/Docker/Dockerfile.latest_dependencies +49 -0
- data/Docker/README.md +95 -0
- data/Docker/vac.ps1 +29 -0
- data/Docker/vac.sh +30 -0
- data/Gemfile +21 -0
- data/Gemfile.lock +187 -0
- data/LICENSE +8 -0
- data/README.md +409 -0
- data/Rakefile +106 -0
- data/build_rpm.sh +7 -0
- data/functional-tests/basic/Vagrantfile.basic_networking +34 -0
- data/functional-tests/basic/basic_spec.rb +15 -0
- data/functional-tests/networking/Vagrantfile.advanced_networking +106 -0
- data/functional-tests/networking/networking_spec.rb +14 -0
- data/functional-tests/rsync/Vagrantfile.advanced_networking +40 -0
- data/functional-tests/rsync/rsync_spec.rb +9 -0
- data/functional-tests/vmlifecycle/Vagrantfile.advanced_networking +64 -0
- data/functional-tests/vmlifecycle/vmlifecycle_spec.rb +25 -0
- data/lib/vagrant-cosmic/action/connect_cosmic.rb +47 -0
- data/lib/vagrant-cosmic/action/is_created.rb +18 -0
- data/lib/vagrant-cosmic/action/is_stopped.rb +18 -0
- data/lib/vagrant-cosmic/action/message_already_created.rb +16 -0
- data/lib/vagrant-cosmic/action/message_not_created.rb +16 -0
- data/lib/vagrant-cosmic/action/message_will_not_destroy.rb +16 -0
- data/lib/vagrant-cosmic/action/read_rdp_info.rb +42 -0
- data/lib/vagrant-cosmic/action/read_ssh_info.rb +70 -0
- data/lib/vagrant-cosmic/action/read_state.rb +38 -0
- data/lib/vagrant-cosmic/action/read_transport_info.rb +59 -0
- data/lib/vagrant-cosmic/action/read_winrm_info.rb +69 -0
- data/lib/vagrant-cosmic/action/run_instance.rb +819 -0
- data/lib/vagrant-cosmic/action/start_instance.rb +81 -0
- data/lib/vagrant-cosmic/action/stop_instance.rb +28 -0
- data/lib/vagrant-cosmic/action/terminate_instance.rb +208 -0
- data/lib/vagrant-cosmic/action/timed_provision.rb +21 -0
- data/lib/vagrant-cosmic/action/wait_for_state.rb +41 -0
- data/lib/vagrant-cosmic/action/warn_networks.rb +19 -0
- data/lib/vagrant-cosmic/action.rb +210 -0
- data/lib/vagrant-cosmic/capabilities/rdp.rb +12 -0
- data/lib/vagrant-cosmic/capabilities/winrm.rb +12 -0
- data/lib/vagrant-cosmic/config.rb +422 -0
- data/lib/vagrant-cosmic/errors.rb +27 -0
- data/lib/vagrant-cosmic/exceptions/exceptions.rb +15 -0
- data/lib/vagrant-cosmic/model/cosmic_resource.rb +51 -0
- data/lib/vagrant-cosmic/plugin.rb +82 -0
- data/lib/vagrant-cosmic/provider.rb +58 -0
- data/lib/vagrant-cosmic/service/cosmic_resource_service.rb +76 -0
- data/lib/vagrant-cosmic/util/timer.rb +17 -0
- data/lib/vagrant-cosmic/version.rb +5 -0
- data/lib/vagrant-cosmic.rb +17 -0
- data/locales/en.yml +131 -0
- data/spec/spec_helper.rb +53 -0
- data/spec/vagrant-cosmic/action/read_ssh_info_spec.rb +80 -0
- data/spec/vagrant-cosmic/action/retrieve_public_ip_port_spec.rb +94 -0
- data/spec/vagrant-cosmic/action/run_instance_spec.rb +573 -0
- data/spec/vagrant-cosmic/action/terminate_instance_spec.rb +207 -0
- data/spec/vagrant-cosmic/config_spec.rb +340 -0
- data/spec/vagrant-cosmic/model/cosmic_resource_spec.rb +95 -0
- data/spec/vagrant-cosmic/service/cosmic_resource_service_spec.rb +43 -0
- data/spec/vagrant-cosmic/support/be_a_resource.rb +6 -0
- data/vagrant-cosmic.gemspec +59 -0
- data/vagrant-cosmic.spec +42 -0
- metadata +218 -0
@@ -0,0 +1,819 @@
|
|
1
|
+
require 'log4r'
|
2
|
+
require 'vagrant/util/retryable'
|
3
|
+
require 'vagrant-cosmic/exceptions/exceptions'
|
4
|
+
require 'vagrant-cosmic/util/timer'
|
5
|
+
require 'vagrant-cosmic/model/cosmic_resource'
|
6
|
+
require 'vagrant-cosmic/service/cosmic_resource_service'
|
7
|
+
|
8
|
+
module VagrantPlugins
|
9
|
+
module Cosmic
|
10
|
+
module Action
|
11
|
+
# This runs the configured instance.
|
12
|
+
class RunInstance
|
13
|
+
include Vagrant::Util::Retryable
|
14
|
+
include VagrantPlugins::Cosmic::Model
|
15
|
+
include VagrantPlugins::Cosmic::Service
|
16
|
+
include VagrantPlugins::Cosmic::Exceptions
|
17
|
+
|
18
|
+
def initialize(app, env)
|
19
|
+
@app = app
|
20
|
+
@logger = Log4r::Logger.new('vagrant_cosmic::action::run_instance')
|
21
|
+
@resource_service = CosmicResourceService.new(env[:cosmic_compute], env[:ui])
|
22
|
+
end
|
23
|
+
|
24
|
+
def call(env)
|
25
|
+
# Initialize metrics if they haven't been
|
26
|
+
env[:metrics] ||= {}
|
27
|
+
@env = env
|
28
|
+
|
29
|
+
sanatize_creation_parameters
|
30
|
+
|
31
|
+
show_creation_summary
|
32
|
+
|
33
|
+
create_vm
|
34
|
+
|
35
|
+
wait_for_instance_ready
|
36
|
+
|
37
|
+
store_volumes
|
38
|
+
|
39
|
+
store_password
|
40
|
+
|
41
|
+
configure_networking
|
42
|
+
|
43
|
+
unless @env[:interrupted]
|
44
|
+
wait_for_communicator_ready
|
45
|
+
@env[:ui].info(I18n.t('vagrant_cosmic.ready'))
|
46
|
+
end
|
47
|
+
|
48
|
+
# Terminate the instance if we were interrupted
|
49
|
+
terminate if @env[:interrupted]
|
50
|
+
|
51
|
+
@app.call(@env)
|
52
|
+
end
|
53
|
+
|
54
|
+
def sanatize_creation_parameters
|
55
|
+
# Get the domain we're going to booting up in
|
56
|
+
@domain = @env[:machine].provider_config.domain_id
|
57
|
+
# Get the configs
|
58
|
+
@domain_config = @env[:machine].provider_config.get_domain_config(@domain)
|
59
|
+
|
60
|
+
sanitize_domain_config
|
61
|
+
|
62
|
+
initialize_parameters
|
63
|
+
|
64
|
+
if @zone.is_undefined?
|
65
|
+
@env[:ui].error("No Zone specified!")
|
66
|
+
exit(false)
|
67
|
+
end
|
68
|
+
|
69
|
+
resolve_parameters
|
70
|
+
|
71
|
+
cs_zone = @env[:cosmic_compute].zones.find {|f| f.id == @zone.id}
|
72
|
+
resolve_network(cs_zone)
|
73
|
+
|
74
|
+
@domain_config.display_name = generate_display_name if @domain_config.display_name.nil?
|
75
|
+
|
76
|
+
if @domain_config.keypair.nil? && @domain_config.ssh_key.nil?
|
77
|
+
@env[:ui].warn(I18n.t('vagrant_cosmic.launch_no_keypair_no_sshkey'))
|
78
|
+
generate_ssh_keypair("vagacs_#{@domain_config.display_name}_#{sprintf('%04d', rand(9999))}",
|
79
|
+
nil, @domain, @domain_config.project_id)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def sanitize_domain_config
|
84
|
+
# Accept a single entry as input, convert it to array
|
85
|
+
@domain_config.pf_trusted_networks = [@domain_config.pf_trusted_networks] if @domain_config.pf_trusted_networks
|
86
|
+
|
87
|
+
if @domain_config.network_id.nil?
|
88
|
+
# Use names if ids are not present
|
89
|
+
@domain_config.network_id = []
|
90
|
+
|
91
|
+
if @domain_config.network_name.nil?
|
92
|
+
@domain_config.network_name = []
|
93
|
+
else
|
94
|
+
@domain_config.network_name = Array(@domain_config.network_name)
|
95
|
+
end
|
96
|
+
else
|
97
|
+
# Use ids if present
|
98
|
+
@domain_config.network_id = Array(@domain_config.network_id)
|
99
|
+
@domain_config.network_name = []
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def initialize_parameters
|
104
|
+
@zone = CosmicResource.new(@domain_config.zone_id, @domain_config.zone_name, 'zone')
|
105
|
+
@networks = CosmicResource.create_list(@domain_config.network_id, @domain_config.network_name, 'network')
|
106
|
+
@service_offering = CosmicResource.new(@domain_config.service_offering_id, @domain_config.service_offering_name, 'service_offering')
|
107
|
+
@disk_offering = CosmicResource.new(@domain_config.disk_offering_id, @domain_config.disk_offering_name, 'disk_offering')
|
108
|
+
@template = CosmicResource.new(@domain_config.template_id, @domain_config.template_name || @env[:machine].config.vm.box, 'template')
|
109
|
+
@pf_ip_address = CosmicResource.new(@domain_config.pf_ip_address_id, @domain_config.pf_ip_address, 'public_ip_address')
|
110
|
+
end
|
111
|
+
|
112
|
+
def resolve_parameters
|
113
|
+
begin
|
114
|
+
@resource_service.sync_resource(@zone, {available: true, name: @zone.name})
|
115
|
+
@resource_service.sync_resource(@service_offering, {listall: true, name: @service_offering.name})
|
116
|
+
@resource_service.sync_resource(@disk_offering, {listall: true, name: @disk_offering.name})
|
117
|
+
@resource_service.sync_resource(@template, {zoneid: @zone.id, templatefilter: 'executable', listall: true, name: @template.name})
|
118
|
+
@resource_service.sync_resource(@pf_ip_address)
|
119
|
+
rescue CosmicResourceNotFound => e
|
120
|
+
@env[:ui].error(e.message)
|
121
|
+
exit(false)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
def resolve_network(cs_zone)
|
126
|
+
if cs_zone.network_type.downcase == 'basic'
|
127
|
+
# No network specification in basic zone
|
128
|
+
@env[:ui].warn(I18n.t('vagrant_cosmic.basic_network', :zone_name => @zone.name)) if !@networks.empty? && (@networks[0].id || @networks[0].name)
|
129
|
+
@networks = [CosmicResource.new(nil, nil, 'network')]
|
130
|
+
|
131
|
+
# No portforwarding in basic zone, so none of the below
|
132
|
+
@domain_config.pf_ip_address = nil
|
133
|
+
@domain_config.pf_ip_address_id = nil
|
134
|
+
@domain_config.pf_public_port = nil
|
135
|
+
@domain_config.pf_public_rdp_port = nil
|
136
|
+
@domain_config.pf_public_port_randomrange = nil
|
137
|
+
else
|
138
|
+
@networks.each do |network|
|
139
|
+
@resource_service.sync_resource(network)
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
def generate_display_name
|
145
|
+
local_user = ENV['USER'] ? ENV['USER'].dup : 'VACS'
|
146
|
+
local_user.gsub!(/[^-a-z0-9_]/i, '')
|
147
|
+
prefix = @env[:root_path].basename.to_s
|
148
|
+
prefix.gsub!(/[^-a-z0-9_]/i, '')
|
149
|
+
|
150
|
+
local_user + '_' + prefix + "_#{Time.now.to_i}"
|
151
|
+
end
|
152
|
+
|
153
|
+
def show_creation_summary
|
154
|
+
# Launch!
|
155
|
+
@env[:ui].info(I18n.t('vagrant_cosmic.launching_instance'))
|
156
|
+
@env[:ui].info(" -- Display Name: #{@domain_config.display_name}")
|
157
|
+
@env[:ui].info(" -- Group: #{@domain_config.group}") if @domain_config.group
|
158
|
+
@env[:ui].info(" -- Service offering: #{@service_offering.name} (#{@service_offering.id})")
|
159
|
+
@env[:ui].info(" -- Disk offering: #{@disk_offering.name} (#{@disk_offering.id})") unless @disk_offering.id.nil?
|
160
|
+
@env[:ui].info(" -- Template: #{@template.name} (#{@template.id})")
|
161
|
+
@env[:ui].info(" -- Project UUID: #{@domain_config.project_id}") unless @domain_config.project_id.nil?
|
162
|
+
@env[:ui].info(" -- Zone: #{@zone.name} (#{@zone.id})")
|
163
|
+
@networks.each do |network|
|
164
|
+
@env[:ui].info(" -- Network: #{network.name} (#{network.id})")
|
165
|
+
end
|
166
|
+
@env[:ui].info(" -- Keypair: #{@domain_config.keypair}") if @domain_config.keypair
|
167
|
+
@env[:ui].info(' -- User Data: Yes') if @domain_config.user_data
|
168
|
+
end
|
169
|
+
|
170
|
+
def create_vm
|
171
|
+
@server = nil
|
172
|
+
begin
|
173
|
+
options = compose_server_creation_options
|
174
|
+
|
175
|
+
@server = @env[:cosmic_compute].servers.create(options)
|
176
|
+
@server_job_id = @server.job_id
|
177
|
+
rescue Fog::Cosmic::Compute::NotFound => e
|
178
|
+
# Invalid subnet doesn't have its own error so we catch and
|
179
|
+
# check the error message here.
|
180
|
+
# XXX FIXME vpc?
|
181
|
+
if e.message =~ /subnet ID/
|
182
|
+
raise Fog::Cosmic::Compute::Error,
|
183
|
+
:message => "Subnet ID not found: #{@networks.map(&:id).compact.join(",")}"
|
184
|
+
end
|
185
|
+
|
186
|
+
raise
|
187
|
+
rescue Fog::Cosmic::Compute::Error => e
|
188
|
+
raise Fog::Cosmic::Compute::Error, :message => e.message
|
189
|
+
end
|
190
|
+
|
191
|
+
@env[:machine].id = @server.id
|
192
|
+
end
|
193
|
+
|
194
|
+
def compose_server_creation_options
|
195
|
+
options = {
|
196
|
+
:display_name => @domain_config.display_name,
|
197
|
+
:group => @domain_config.group,
|
198
|
+
:zone_id => @zone.id,
|
199
|
+
:flavor_id => @service_offering.id,
|
200
|
+
:image_id => @template.id
|
201
|
+
}
|
202
|
+
|
203
|
+
unless @networks.empty?
|
204
|
+
nets = @networks.map(&:id).compact.join(",")
|
205
|
+
options['network_ids'] = nets unless nets.empty?
|
206
|
+
end
|
207
|
+
options['project_id'] = @domain_config.project_id unless @domain_config.project_id.nil?
|
208
|
+
options['key_name'] = @domain_config.keypair unless @domain_config.keypair.nil?
|
209
|
+
options['name'] = @domain_config.name unless @domain_config.name.nil?
|
210
|
+
options['ip_address'] = @domain_config.private_ip_address unless @domain_config.private_ip_address.nil?
|
211
|
+
options['disk_offering_id'] = @disk_offering.id unless @disk_offering.id.nil?
|
212
|
+
|
213
|
+
if @domain_config.user_data != nil
|
214
|
+
options['user_data'] = Base64.urlsafe_encode64(@domain_config.user_data)
|
215
|
+
if options['user_data'].length > 2048
|
216
|
+
raise Errors::UserdataError,
|
217
|
+
:userdataLength => options['user_data'].length
|
218
|
+
end
|
219
|
+
end
|
220
|
+
options
|
221
|
+
end
|
222
|
+
|
223
|
+
def configure_networking
|
224
|
+
begin
|
225
|
+
enable_static_nat_rules
|
226
|
+
|
227
|
+
configure_port_forwarding
|
228
|
+
|
229
|
+
# First create_port_forwardings,
|
230
|
+
# as it may generate 'pf_public_port' or 'pf_public_rdp_port',
|
231
|
+
# which after this may need a firewall rule
|
232
|
+
configure_firewall
|
233
|
+
|
234
|
+
rescue CosmicResourceNotFound => e
|
235
|
+
@env[:ui].error(e.message)
|
236
|
+
terminate
|
237
|
+
exit(false)
|
238
|
+
end
|
239
|
+
end
|
240
|
+
|
241
|
+
def enable_static_nat_rules
|
242
|
+
unless @domain_config.static_nat.empty?
|
243
|
+
@domain_config.static_nat.each do |rule|
|
244
|
+
enable_static_nat(rule)
|
245
|
+
end
|
246
|
+
end
|
247
|
+
end
|
248
|
+
|
249
|
+
def enable_static_nat(rule)
|
250
|
+
@env[:ui].info(I18n.t('vagrant_cosmic.enabling_static_nat'))
|
251
|
+
|
252
|
+
begin
|
253
|
+
ip_address = sync_ip_address(rule[:ipaddressid], rule[:ipaddress])
|
254
|
+
rescue IpNotFoundException
|
255
|
+
return
|
256
|
+
end
|
257
|
+
|
258
|
+
@env[:ui].info(" -- IP address : #{ip_address.name} (#{ip_address.id})")
|
259
|
+
|
260
|
+
options = {
|
261
|
+
:command => 'enableStaticNat',
|
262
|
+
:ipaddressid => ip_address_id,
|
263
|
+
:virtualmachineid => @env[:machine].id
|
264
|
+
}
|
265
|
+
|
266
|
+
begin
|
267
|
+
resp = @env[:cosmic_compute].request(options)
|
268
|
+
is_success = resp['enablestaticnatresponse']['success']
|
269
|
+
|
270
|
+
if is_success != 'true'
|
271
|
+
@env[:ui].warn(" -- Failed to enable static nat: #{resp['enablestaticnatresponse']['errortext']}")
|
272
|
+
return
|
273
|
+
end
|
274
|
+
rescue Fog::Cosmic::Compute::Error => e
|
275
|
+
raise Fog::Cosmic::Compute::Error, :message => e.message
|
276
|
+
end
|
277
|
+
|
278
|
+
# Save ipaddress id to the data dir so it can be disabled when the instance is destroyed
|
279
|
+
static_nat_file = @env[:machine].data_dir.join('static_nat')
|
280
|
+
static_nat_file.open('a+') do |f|
|
281
|
+
f.write("#{ip_address.id}\n")
|
282
|
+
end
|
283
|
+
end
|
284
|
+
|
285
|
+
def configure_port_forwarding
|
286
|
+
|
287
|
+
unless @pf_ip_address.is_undefined?
|
288
|
+
evaluate_pf_private_port
|
289
|
+
evaluate_pf_private_rdp_port
|
290
|
+
generate_and_apply_port_forwarding_rules_for_communicators
|
291
|
+
end
|
292
|
+
|
293
|
+
apply_port_forwarding_rules
|
294
|
+
end
|
295
|
+
|
296
|
+
def get_communicator_short_name(communicator)
|
297
|
+
communicator_short_names = {
|
298
|
+
'VagrantPlugins::CommunicatorSSH::Communicator' => 'ssh',
|
299
|
+
'VagrantPlugins::CommunicatorWinRM::Communicator' => 'winrm'
|
300
|
+
}
|
301
|
+
communicator_short_names[communicator.class.name]
|
302
|
+
end
|
303
|
+
|
304
|
+
def evaluate_pf_private_port
|
305
|
+
return unless @domain_config.pf_private_port.nil?
|
306
|
+
|
307
|
+
communicator_short_name = get_communicator_short_name(@env[:machine].communicate)
|
308
|
+
communicator_config = @env[:machine].config.send(communicator_short_name)
|
309
|
+
|
310
|
+
@domain_config.pf_private_port = communicator_config.port if communicator_config.respond_to?('port')
|
311
|
+
@domain_config.pf_private_port = communicator_config.guest_port if communicator_config.respond_to?('guest_port')
|
312
|
+
@domain_config.pf_private_port = communicator_config.default.port if (communicator_config.respond_to?('default') && communicator_config.default.respond_to?('port'))
|
313
|
+
end
|
314
|
+
|
315
|
+
def evaluate_pf_private_rdp_port
|
316
|
+
@domain_config.pf_private_rdp_port = @env[:machine].config.vm.rdp.port if
|
317
|
+
@env[:machine].config.vm.respond_to?(:rdp) &&
|
318
|
+
@env[:machine].config.vm.rdp.respond_to?(:port)
|
319
|
+
end
|
320
|
+
|
321
|
+
def generate_and_apply_port_forwarding_rules_for_communicators
|
322
|
+
communicators_port_names = [Hash[:public_port => 'pf_public_port', :private_port => 'pf_private_port']]
|
323
|
+
communicators_port_names << Hash[:public_port => 'pf_public_rdp_port', :private_port => 'pf_private_rdp_port'] if is_windows_guest
|
324
|
+
|
325
|
+
communicators_port_names.each do |communicator_port_names|
|
326
|
+
public_port_name = communicator_port_names[:public_port]
|
327
|
+
private_port_name = communicator_port_names[:private_port]
|
328
|
+
|
329
|
+
next unless @domain_config.send(public_port_name) || @domain_config.pf_public_port_randomrange
|
330
|
+
|
331
|
+
port_forwarding_rule = {
|
332
|
+
:ipaddressid => @domain_config.pf_ip_address_id,
|
333
|
+
:ipaddress => @domain_config.pf_ip_address,
|
334
|
+
:protocol => 'tcp',
|
335
|
+
:publicport => @domain_config.send(public_port_name),
|
336
|
+
:privateport => @domain_config.send(private_port_name),
|
337
|
+
:openfirewall => @domain_config.pf_open_firewall
|
338
|
+
}
|
339
|
+
|
340
|
+
public_port = create_randomport_forwarding_rule(
|
341
|
+
port_forwarding_rule,
|
342
|
+
@domain_config.pf_public_port_randomrange[:start]...@domain_config.pf_public_port_randomrange[:end],
|
343
|
+
public_port_name
|
344
|
+
)
|
345
|
+
@domain_config.send("#{public_port_name}=", public_port)
|
346
|
+
end
|
347
|
+
end
|
348
|
+
|
349
|
+
def create_randomport_forwarding_rule(rule, randomrange, filename)
|
350
|
+
pf_public_port = rule[:publicport]
|
351
|
+
retryable(:on => DuplicatePFRule, :tries => 10) do
|
352
|
+
begin
|
353
|
+
# Only if public port is missing, will generate a random one
|
354
|
+
rule[:publicport] = Kernel.rand(randomrange) if pf_public_port.nil?
|
355
|
+
|
356
|
+
apply_port_forwarding_rule(rule)
|
357
|
+
|
358
|
+
if pf_public_port.nil?
|
359
|
+
pf_port_file = @env[:machine].data_dir.join(filename)
|
360
|
+
pf_port_file.open('a+') do |f|
|
361
|
+
f.write("#{rule[:publicport]}")
|
362
|
+
end
|
363
|
+
end
|
364
|
+
rescue Fog::Cosmic::Compute::Error => e
|
365
|
+
if pf_public_port.nil? && !(e.message =~ /The range specified,.*conflicts with rule.*which has/).nil?
|
366
|
+
raise DuplicatePFRule, :message => e.message
|
367
|
+
else
|
368
|
+
raise Fog::Cosmic::Compute::Error, :message => e.message
|
369
|
+
end
|
370
|
+
end
|
371
|
+
end
|
372
|
+
pf_public_port.nil? ? (rule[:publicport]) : (pf_public_port)
|
373
|
+
end
|
374
|
+
|
375
|
+
def apply_port_forwarding_rules
|
376
|
+
return if @domain_config.port_forwarding_rules.empty?
|
377
|
+
|
378
|
+
@domain_config.port_forwarding_rules.each do |port_forwarding_rule|
|
379
|
+
# Sanatize/defaults pf rule before applying
|
380
|
+
port_forwarding_rule[:ipaddressid] = @domain_config.pf_ip_address_id if port_forwarding_rule[:ipaddressid].nil?
|
381
|
+
port_forwarding_rule[:ipaddress] = @domain_config.pf_ip_address if port_forwarding_rule[:ipaddress].nil?
|
382
|
+
port_forwarding_rule[:protocol] = 'tcp' if port_forwarding_rule[:protocol].nil?
|
383
|
+
port_forwarding_rule[:openfirewall] = @domain_config.pf_open_firewall if port_forwarding_rule[:openfirewall].nil?
|
384
|
+
port_forwarding_rule[:publicport] = port_forwarding_rule[:privateport] if port_forwarding_rule[:publicport].nil?
|
385
|
+
port_forwarding_rule[:privateport] = port_forwarding_rule[:publicport] if port_forwarding_rule[:privateport].nil?
|
386
|
+
|
387
|
+
apply_port_forwarding_rule(port_forwarding_rule)
|
388
|
+
end
|
389
|
+
end
|
390
|
+
|
391
|
+
def apply_port_forwarding_rule(rule)
|
392
|
+
port_forwarding_rule = nil
|
393
|
+
@env[:ui].info(I18n.t('vagrant_cosmic.creating_port_forwarding_rule'))
|
394
|
+
|
395
|
+
return unless (options = compose_port_forwarding_rule_creation_options(rule))
|
396
|
+
|
397
|
+
begin
|
398
|
+
resp = @env[:cosmic_compute].create_port_forwarding_rule(options)
|
399
|
+
job_id = resp['createportforwardingruleresponse']['jobid']
|
400
|
+
|
401
|
+
if job_id.nil?
|
402
|
+
@env[:ui].warn(" -- Failed to create port forwarding rule: #{resp['createportforwardingruleresponse']['errortext']}")
|
403
|
+
return
|
404
|
+
end
|
405
|
+
|
406
|
+
while true
|
407
|
+
response = @env[:cosmic_compute].query_async_job_result({:jobid => job_id})
|
408
|
+
if response['queryasyncjobresultresponse']['jobstatus'] != 0
|
409
|
+
port_forwarding_rule = response['queryasyncjobresultresponse']['jobresult']['portforwardingrule']
|
410
|
+
break
|
411
|
+
else
|
412
|
+
sleep 2
|
413
|
+
end
|
414
|
+
end
|
415
|
+
rescue Fog::Cosmic::Compute::Error => e
|
416
|
+
raise Fog::Cosmic::Compute::Error, :message => e.message
|
417
|
+
end
|
418
|
+
|
419
|
+
store_port_forwarding_rules(port_forwarding_rule)
|
420
|
+
end
|
421
|
+
|
422
|
+
def compose_port_forwarding_rule_creation_options(rule)
|
423
|
+
begin
|
424
|
+
ip_address = sync_ip_address(rule[:ipaddressid], rule[:ipaddress])
|
425
|
+
rescue IpNotFoundException
|
426
|
+
return
|
427
|
+
end
|
428
|
+
|
429
|
+
@env[:ui].info(" -- IP address : #{ip_address.name} (#{ip_address.id})")
|
430
|
+
@env[:ui].info(" -- Protocol : #{rule[:protocol]}")
|
431
|
+
@env[:ui].info(" -- Public port : #{rule[:publicport]}")
|
432
|
+
@env[:ui].info(" -- Private port : #{rule[:privateport]}")
|
433
|
+
@env[:ui].info(" -- Open Firewall : #{rule[:openfirewall]}")
|
434
|
+
|
435
|
+
network = get_network_from_public_ip(ip_address)
|
436
|
+
|
437
|
+
options = {
|
438
|
+
:networkid => network.id,
|
439
|
+
:ipaddressid => ip_address.id,
|
440
|
+
:publicport => rule[:publicport],
|
441
|
+
:privateport => rule[:privateport],
|
442
|
+
:protocol => rule[:protocol],
|
443
|
+
:openfirewall => rule[:openfirewall],
|
444
|
+
:virtualmachineid => @env[:machine].id
|
445
|
+
}
|
446
|
+
|
447
|
+
options.delete(:openfirewall) if network.details.has_key?('vpcid')
|
448
|
+
options
|
449
|
+
end
|
450
|
+
|
451
|
+
def get_network_from_public_ip(ip_address)
|
452
|
+
if ip_address.details.has_key?('associatednetworkid')
|
453
|
+
network = @networks.find {|f| f.id == ip_address.details['associatednetworkid']}
|
454
|
+
elsif ip_address.details.has_key?('vpcid')
|
455
|
+
# In case of VPC and ip has not yet been used, a network MUST be specified
|
456
|
+
network = @networks.find {|f| f.details['vpcid'] == ip_address.details['vpcid']}
|
457
|
+
end
|
458
|
+
network
|
459
|
+
end
|
460
|
+
|
461
|
+
def configure_firewall
|
462
|
+
if @domain_config.pf_trusted_networks
|
463
|
+
generate_firewall_rules_for_communicators unless @pf_ip_address.is_undefined?
|
464
|
+
generate_firewall_rules_for_portforwarding_rules
|
465
|
+
end
|
466
|
+
|
467
|
+
return if @domain_config.firewall_rules.empty?
|
468
|
+
auto_complete_firewall_rules
|
469
|
+
apply_firewall_rules
|
470
|
+
end
|
471
|
+
|
472
|
+
def generate_firewall_rules_for_communicators
|
473
|
+
|
474
|
+
return if @pf_ip_address.is_undefined? ||
|
475
|
+
!@domain_config.pf_trusted_networks ||
|
476
|
+
@domain_config.pf_open_firewall
|
477
|
+
|
478
|
+
ports = [Hash[publicport: 'pf_public_port', privateport: 'pf_private_port']]
|
479
|
+
ports << Hash[publicport: 'pf_public_rdp_port', privateport: 'pf_private_rdp_port'] if is_windows_guest
|
480
|
+
|
481
|
+
ports.each do |port_set|
|
482
|
+
forward_portname = @pf_ip_address.details.key?('vpcid') ? port_set[:privateport] : port_set[:publicport]
|
483
|
+
check_portname = port_set[:publicport]
|
484
|
+
|
485
|
+
next unless @domain_config.send(check_portname)
|
486
|
+
|
487
|
+
# Allow access to public port from trusted networks only
|
488
|
+
fw_rule_trusted_networks = {
|
489
|
+
ipaddressid: @pf_ip_address.id,
|
490
|
+
ipaddress: @pf_ip_address.name,
|
491
|
+
protocol: 'tcp',
|
492
|
+
startport: @domain_config.send(forward_portname),
|
493
|
+
endport: @domain_config.send(forward_portname),
|
494
|
+
cidrlist: @domain_config.pf_trusted_networks.join(',')
|
495
|
+
}
|
496
|
+
@domain_config.firewall_rules = [] unless @domain_config.firewall_rules
|
497
|
+
@domain_config.firewall_rules << fw_rule_trusted_networks
|
498
|
+
|
499
|
+
end
|
500
|
+
end
|
501
|
+
|
502
|
+
def generate_firewall_rules_for_portforwarding_rules
|
503
|
+
@pf_ip_address.details.has_key?('vpcid') ? port_name = :privateport : port_name = :publicport
|
504
|
+
|
505
|
+
unless @domain_config.port_forwarding_rules.empty?
|
506
|
+
@domain_config.port_forwarding_rules.each do |port_forwarding_rule|
|
507
|
+
if port_forwarding_rule[:generate_firewall] && @domain_config.pf_trusted_networks && !port_forwarding_rule[:openfirewall]
|
508
|
+
# Allow access to public port from trusted networks only
|
509
|
+
fw_rule_trusted_networks = {
|
510
|
+
:ipaddressid => port_forwarding_rule[:ipaddressid],
|
511
|
+
:ipaddress => port_forwarding_rule[:ipaddress],
|
512
|
+
:protocol => port_forwarding_rule[:protocol],
|
513
|
+
:startport => port_forwarding_rule[port_name],
|
514
|
+
:endport => port_forwarding_rule[port_name],
|
515
|
+
:cidrlist => @domain_config.pf_trusted_networks.join(',')
|
516
|
+
}
|
517
|
+
@domain_config.firewall_rules = [] unless @domain_config.firewall_rules
|
518
|
+
@domain_config.firewall_rules << fw_rule_trusted_networks
|
519
|
+
end
|
520
|
+
end
|
521
|
+
end
|
522
|
+
end
|
523
|
+
|
524
|
+
def auto_complete_firewall_rules
|
525
|
+
@domain_config.firewall_rules.each do |firewall_rule|
|
526
|
+
firewall_rule[:ipaddressid] = @domain_config.pf_ip_address_id if firewall_rule[:ipaddressid].nil?
|
527
|
+
firewall_rule[:ipaddress] = @domain_config.pf_ip_address if firewall_rule[:ipaddress].nil?
|
528
|
+
firewall_rule[:cidrlist] = @domain_config.pf_trusted_networks.join(',') if firewall_rule[:cidrlist].nil?
|
529
|
+
firewall_rule[:protocol] = 'tcp' if firewall_rule[:protocol].nil?
|
530
|
+
firewall_rule[:startport] = firewall_rule[:endport] if firewall_rule[:startport].nil?
|
531
|
+
end
|
532
|
+
end
|
533
|
+
|
534
|
+
def apply_firewall_rules
|
535
|
+
@domain_config.firewall_rules.each do |firewall_rule|
|
536
|
+
create_firewall_rule(firewall_rule)
|
537
|
+
end
|
538
|
+
end
|
539
|
+
|
540
|
+
def create_firewall_rule(rule)
|
541
|
+
acl_name = ''
|
542
|
+
firewall_rule = nil
|
543
|
+
@env[:ui].info(I18n.t('vagrant_cosmic.creating_firewall_rule'))
|
544
|
+
|
545
|
+
ip_address = CosmicResource.new(rule[:ipaddressid], rule[:ipaddress], 'public_ip_address')
|
546
|
+
@resource_service.sync_resource(ip_address)
|
547
|
+
|
548
|
+
@env[:ui].info(" -- IP address : #{ip_address.name} (#{ip_address.id})")
|
549
|
+
@env[:ui].info(" -- Protocol : #{rule[:protocol]}")
|
550
|
+
@env[:ui].info(" -- CIDR list : #{rule[:cidrlist]}")
|
551
|
+
@env[:ui].info(" -- Start port : #{rule[:startport]}")
|
552
|
+
@env[:ui].info(" -- End port : #{rule[:endport]}")
|
553
|
+
@env[:ui].info(" -- ICMP code : #{rule[:icmpcode]}")
|
554
|
+
@env[:ui].info(" -- ICMP type : #{rule[:icmptype]}")
|
555
|
+
|
556
|
+
if ip_address.details.has_key?('vpcid')
|
557
|
+
network = @networks.find{ |f| f.id == ip_address.details['associatednetworkid']}
|
558
|
+
acl_id = network.details['aclid']
|
559
|
+
|
560
|
+
raise CosmicResourceNotFound.new("No ACL found associated with VPC tier #{network.details['name']} (id: #{network.details['id']})") unless acl_id
|
561
|
+
|
562
|
+
resp = @env[:cosmic_compute].list_network_acl_lists(
|
563
|
+
id: network.details[acl_id]
|
564
|
+
)
|
565
|
+
acl_name = resp['listnetworkacllistsresponse']['networkacllist'][0]['name']
|
566
|
+
|
567
|
+
options, response_string, type_string = compose_firewall_rule_creation_options_vpc(network, rule)
|
568
|
+
else
|
569
|
+
options, response_string, type_string = compose_firewall_rule_creation_options_non_vpc(ip_address, rule)
|
570
|
+
end
|
571
|
+
|
572
|
+
firewall_rule = apply_firewall_rule(acl_name, options, response_string, type_string)
|
573
|
+
|
574
|
+
store_firewall_rule(firewall_rule, type_string) if firewall_rule
|
575
|
+
end
|
576
|
+
|
577
|
+
def compose_firewall_rule_creation_options_vpc(network, rule)
|
578
|
+
command_string = 'createNetworkACL'
|
579
|
+
response_string = 'createnetworkaclresponse'
|
580
|
+
type_string = 'networkacl'
|
581
|
+
options = {
|
582
|
+
:command => command_string,
|
583
|
+
:aclid => network.details['aclid'],
|
584
|
+
:action => 'Allow',
|
585
|
+
:protocol => rule[:protocol],
|
586
|
+
:cidrlist => rule[:cidrlist],
|
587
|
+
:startport => rule[:startport],
|
588
|
+
:endport => rule[:endport],
|
589
|
+
:icmpcode => rule[:icmpcode],
|
590
|
+
:icmptype => rule[:icmptype],
|
591
|
+
:traffictype => 'Ingress'
|
592
|
+
}
|
593
|
+
return options, response_string, type_string
|
594
|
+
end
|
595
|
+
|
596
|
+
def compose_firewall_rule_creation_options_non_vpc(ip_address, rule)
|
597
|
+
command_string = 'createFirewallRule'
|
598
|
+
response_string = 'createfirewallruleresponse'
|
599
|
+
type_string = 'firewallrule'
|
600
|
+
options = {
|
601
|
+
:command => command_string,
|
602
|
+
:ipaddressid => ip_address.id,
|
603
|
+
:protocol => rule[:protocol],
|
604
|
+
:cidrlist => rule[:cidrlist],
|
605
|
+
:startport => rule[:startport],
|
606
|
+
:endeport => rule[:endport],
|
607
|
+
:icmpcode => rule[:icmpcode],
|
608
|
+
:icmptype => rule[:icmptype]
|
609
|
+
}
|
610
|
+
return options, response_string, type_string
|
611
|
+
end
|
612
|
+
|
613
|
+
def get_next_free_acl_entry(network)
|
614
|
+
resp = @env[:cosmic_compute].list_network_acls(
|
615
|
+
aclid: network.details['aclid']
|
616
|
+
)
|
617
|
+
number = 0
|
618
|
+
if resp["listnetworkaclsresponse"].key?("networkacl")
|
619
|
+
resp["listnetworkaclsresponse"]["networkacl"].each {|ace| number = [number, ace["number"]].max}
|
620
|
+
end
|
621
|
+
number = number+1
|
622
|
+
end
|
623
|
+
|
624
|
+
def apply_firewall_rule(acl_name, options, response_string, type_string)
|
625
|
+
firewall_rule = nil
|
626
|
+
begin
|
627
|
+
resp = @env[:cosmic_compute].request(options)
|
628
|
+
job_id = resp[response_string]['jobid']
|
629
|
+
|
630
|
+
if job_id.nil?
|
631
|
+
@env[:ui].warn(" -- Failed to create firewall rule: #{resp[response_string]['errortext']}")
|
632
|
+
return
|
633
|
+
end
|
634
|
+
|
635
|
+
while true
|
636
|
+
response = @env[:cosmic_compute].query_async_job_result({:jobid => job_id})
|
637
|
+
if response['queryasyncjobresultresponse']['jobstatus'] != 0
|
638
|
+
firewall_rule = response['queryasyncjobresultresponse']['jobresult'][type_string]
|
639
|
+
break
|
640
|
+
else
|
641
|
+
sleep 2
|
642
|
+
end
|
643
|
+
end
|
644
|
+
rescue Fog::Cosmic::Compute::Error => e
|
645
|
+
if e.message =~ /The range specified,.*conflicts with rule/
|
646
|
+
@env[:ui].warn(" -- Failed to create firewall rule: #{e.message}")
|
647
|
+
elsif e.message =~ /Default ACL cannot be modified/
|
648
|
+
@env[:ui].warn(" -- Failed to create network acl: #{e.message}: #{acl_name}")
|
649
|
+
else
|
650
|
+
raise Fog::Cosmic::Compute::Error, :message => e.message
|
651
|
+
end
|
652
|
+
end
|
653
|
+
firewall_rule
|
654
|
+
end
|
655
|
+
|
656
|
+
def is_windows_guest
|
657
|
+
false || @env[:machine].config.vm.guest == :windows || get_communicator_short_name(@env[:machine].communicate) == 'winrm'
|
658
|
+
end
|
659
|
+
|
660
|
+
def generate_ssh_keypair(keyname, account = nil, domainid = nil, projectid = nil)
|
661
|
+
response = @env[:cosmic_compute].create_ssh_key_pair(keyname, account, domainid, projectid)
|
662
|
+
sshkeypair = response['createsshkeypairresponse']['keypair']
|
663
|
+
|
664
|
+
# Save private key to file
|
665
|
+
sshkeyfile_file = @env[:machine].data_dir.join('sshkeyfile')
|
666
|
+
sshkeyfile_file.open('w') do |f|
|
667
|
+
f.write("#{sshkeypair['privatekey']}")
|
668
|
+
end
|
669
|
+
@domain_config.ssh_key = sshkeyfile_file.to_s
|
670
|
+
|
671
|
+
sshkeyname_file = @env[:machine].data_dir.join('sshkeyname')
|
672
|
+
sshkeyname_file.open('w') do |f|
|
673
|
+
f.write("#{sshkeypair['name']}")
|
674
|
+
end
|
675
|
+
|
676
|
+
@domain_config.keypair = sshkeypair['name']
|
677
|
+
end
|
678
|
+
|
679
|
+
def store_password
|
680
|
+
password = nil
|
681
|
+
if @server.password_enabled and @server.respond_to?('job_id')
|
682
|
+
server_job_result = @env[:cosmic_compute].query_async_job_result({:jobid => @server_job_id})
|
683
|
+
if server_job_result.nil?
|
684
|
+
@env[:ui].warn(' -- Failed to retrieve job_result for retrieving the password')
|
685
|
+
return
|
686
|
+
end
|
687
|
+
|
688
|
+
while true
|
689
|
+
server_job_result = @env[:cosmic_compute].query_async_job_result({:jobid => @server_job_id})
|
690
|
+
if server_job_result['queryasyncjobresultresponse']['jobstatus'] != 0
|
691
|
+
password = server_job_result['queryasyncjobresultresponse']['jobresult']['virtualmachine']['password']
|
692
|
+
break
|
693
|
+
else
|
694
|
+
sleep 2
|
695
|
+
end
|
696
|
+
end
|
697
|
+
|
698
|
+
@env[:ui].info("Password of virtualmachine: #{password}")
|
699
|
+
# Set the password on the current communicator
|
700
|
+
@domain_config.vm_password = password
|
701
|
+
|
702
|
+
# Save password to file
|
703
|
+
vmcredentials_file = @env[:machine].data_dir.join('vmcredentials')
|
704
|
+
vmcredentials_file.open('w') do |f|
|
705
|
+
f.write("#{password}")
|
706
|
+
end
|
707
|
+
end
|
708
|
+
end
|
709
|
+
|
710
|
+
def store_volumes
|
711
|
+
volumes = @env[:cosmic_compute].volumes.find_all { |f| f.server_id == @server.id }
|
712
|
+
# volumes refuses to be iterated directly, do it by index
|
713
|
+
(0...volumes.length).each do |idx|
|
714
|
+
unless volumes[idx].type == 'ROOT'
|
715
|
+
volumes_file = @env[:machine].data_dir.join('volumes')
|
716
|
+
volumes_file.open('a+') do |f|
|
717
|
+
f.write("#{volumes[idx].id}\n")
|
718
|
+
end
|
719
|
+
end
|
720
|
+
end
|
721
|
+
end
|
722
|
+
|
723
|
+
def store_firewall_rule(firewall_rule, type_string)
|
724
|
+
unless firewall_rule.nil?
|
725
|
+
# Save firewall rule id to the data dir so it can be released when the instance is destroyed
|
726
|
+
firewall_file = @env[:machine].data_dir.join('firewall')
|
727
|
+
firewall_file.open('a+') do |f|
|
728
|
+
f.write("#{firewall_rule['id']},#{type_string}\n")
|
729
|
+
end
|
730
|
+
end
|
731
|
+
end
|
732
|
+
|
733
|
+
def store_port_forwarding_rules(port_forwarding_rule)
|
734
|
+
port_forwarding_file = @env[:machine].data_dir.join('port_forwarding')
|
735
|
+
port_forwarding_file.open('a+') do |f|
|
736
|
+
f.write("#{port_forwarding_rule['id']}\n")
|
737
|
+
end
|
738
|
+
end
|
739
|
+
|
740
|
+
def wait_for_communicator_ready
|
741
|
+
@env[:metrics]['instance_ssh_time'] = Util::Timer.time do
|
742
|
+
# Wait for communicator to be ready.
|
743
|
+
communicator_short_name = get_communicator_short_name(@env[:machine].communicate)
|
744
|
+
@env[:ui].info(
|
745
|
+
I18n.t('vagrant_cosmic.waiting_for_communicator',
|
746
|
+
communicator: communicator_short_name.to_s.upcase)
|
747
|
+
)
|
748
|
+
while true
|
749
|
+
# If we're interrupted then just back out
|
750
|
+
break if @env[:interrupted]
|
751
|
+
break if @env[:machine].communicate.ready?
|
752
|
+
sleep 2
|
753
|
+
end
|
754
|
+
end
|
755
|
+
@logger.info("Time for SSH ready: #{@env[:metrics]['instance_ssh_time']}")
|
756
|
+
end
|
757
|
+
|
758
|
+
def wait_for_instance_ready
|
759
|
+
@env[:metrics]['instance_ready_time'] = Util::Timer.time do
|
760
|
+
tries = @domain_config.instance_ready_timeout / 2
|
761
|
+
|
762
|
+
@env[:ui].info(I18n.t('vagrant_cosmic.waiting_for_ready'))
|
763
|
+
begin
|
764
|
+
retryable(:on => Fog::Errors::TimeoutError, :tries => tries) do
|
765
|
+
# If we're interrupted don't worry about waiting
|
766
|
+
next if @env[:interrupted]
|
767
|
+
|
768
|
+
# Wait for the server to be ready
|
769
|
+
@server.wait_for(2) { ready? }
|
770
|
+
end
|
771
|
+
rescue Fog::Errors::TimeoutError
|
772
|
+
# Delete the instance
|
773
|
+
terminate
|
774
|
+
|
775
|
+
# Notify the user
|
776
|
+
raise Errors::InstanceReadyTimeout,
|
777
|
+
:timeout => @domain_config.instance_ready_timeout
|
778
|
+
end
|
779
|
+
end
|
780
|
+
@logger.info("Time to instance ready: #{@env[:metrics]['instance_ready_time']}")
|
781
|
+
end
|
782
|
+
|
783
|
+
def recover(env)
|
784
|
+
return if env['vagrant.error'].is_a?(Vagrant::Errors::VagrantError)
|
785
|
+
|
786
|
+
if env[:machine].provider.state.id != :not_created
|
787
|
+
# Undo the import
|
788
|
+
terminate
|
789
|
+
end
|
790
|
+
end
|
791
|
+
|
792
|
+
def terminate
|
793
|
+
destroy_env = @env.dup
|
794
|
+
destroy_env.delete(:interrupted)
|
795
|
+
destroy_env[:config_validate] = false
|
796
|
+
destroy_env[:force_confirm_destroy] = true
|
797
|
+
@env[:action_runner].run(Action.action_destroy, destroy_env)
|
798
|
+
end
|
799
|
+
|
800
|
+
private
|
801
|
+
|
802
|
+
def sync_ip_address(ip_address_id, ip_address_value)
|
803
|
+
ip_address = CosmicResource.new(ip_address_id, ip_address_value, 'public_ip_address')
|
804
|
+
|
805
|
+
if ip_address.is_undefined?
|
806
|
+
message = 'IP address is not specified. Skip creating port forwarding rule.'
|
807
|
+
@logger.info(message)
|
808
|
+
@env[:ui].info(I18n.t(message))
|
809
|
+
raise IpNotFoundException
|
810
|
+
end
|
811
|
+
|
812
|
+
@resource_service.sync_resource(ip_address)
|
813
|
+
|
814
|
+
ip_address
|
815
|
+
end
|
816
|
+
end
|
817
|
+
end
|
818
|
+
end
|
819
|
+
end
|