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.
Files changed (68) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +17 -0
  3. data/CHANGELOG.md +8 -0
  4. data/Docker/.dockerignore +2 -0
  5. data/Docker/Dockerfile +48 -0
  6. data/Docker/Dockerfile.chefdk_0_17 +49 -0
  7. data/Docker/Dockerfile.latest_dependencies +49 -0
  8. data/Docker/README.md +95 -0
  9. data/Docker/vac.ps1 +29 -0
  10. data/Docker/vac.sh +30 -0
  11. data/Gemfile +21 -0
  12. data/Gemfile.lock +187 -0
  13. data/LICENSE +8 -0
  14. data/README.md +409 -0
  15. data/Rakefile +106 -0
  16. data/build_rpm.sh +7 -0
  17. data/functional-tests/basic/Vagrantfile.basic_networking +34 -0
  18. data/functional-tests/basic/basic_spec.rb +15 -0
  19. data/functional-tests/networking/Vagrantfile.advanced_networking +106 -0
  20. data/functional-tests/networking/networking_spec.rb +14 -0
  21. data/functional-tests/rsync/Vagrantfile.advanced_networking +40 -0
  22. data/functional-tests/rsync/rsync_spec.rb +9 -0
  23. data/functional-tests/vmlifecycle/Vagrantfile.advanced_networking +64 -0
  24. data/functional-tests/vmlifecycle/vmlifecycle_spec.rb +25 -0
  25. data/lib/vagrant-cosmic/action/connect_cosmic.rb +47 -0
  26. data/lib/vagrant-cosmic/action/is_created.rb +18 -0
  27. data/lib/vagrant-cosmic/action/is_stopped.rb +18 -0
  28. data/lib/vagrant-cosmic/action/message_already_created.rb +16 -0
  29. data/lib/vagrant-cosmic/action/message_not_created.rb +16 -0
  30. data/lib/vagrant-cosmic/action/message_will_not_destroy.rb +16 -0
  31. data/lib/vagrant-cosmic/action/read_rdp_info.rb +42 -0
  32. data/lib/vagrant-cosmic/action/read_ssh_info.rb +70 -0
  33. data/lib/vagrant-cosmic/action/read_state.rb +38 -0
  34. data/lib/vagrant-cosmic/action/read_transport_info.rb +59 -0
  35. data/lib/vagrant-cosmic/action/read_winrm_info.rb +69 -0
  36. data/lib/vagrant-cosmic/action/run_instance.rb +819 -0
  37. data/lib/vagrant-cosmic/action/start_instance.rb +81 -0
  38. data/lib/vagrant-cosmic/action/stop_instance.rb +28 -0
  39. data/lib/vagrant-cosmic/action/terminate_instance.rb +208 -0
  40. data/lib/vagrant-cosmic/action/timed_provision.rb +21 -0
  41. data/lib/vagrant-cosmic/action/wait_for_state.rb +41 -0
  42. data/lib/vagrant-cosmic/action/warn_networks.rb +19 -0
  43. data/lib/vagrant-cosmic/action.rb +210 -0
  44. data/lib/vagrant-cosmic/capabilities/rdp.rb +12 -0
  45. data/lib/vagrant-cosmic/capabilities/winrm.rb +12 -0
  46. data/lib/vagrant-cosmic/config.rb +422 -0
  47. data/lib/vagrant-cosmic/errors.rb +27 -0
  48. data/lib/vagrant-cosmic/exceptions/exceptions.rb +15 -0
  49. data/lib/vagrant-cosmic/model/cosmic_resource.rb +51 -0
  50. data/lib/vagrant-cosmic/plugin.rb +82 -0
  51. data/lib/vagrant-cosmic/provider.rb +58 -0
  52. data/lib/vagrant-cosmic/service/cosmic_resource_service.rb +76 -0
  53. data/lib/vagrant-cosmic/util/timer.rb +17 -0
  54. data/lib/vagrant-cosmic/version.rb +5 -0
  55. data/lib/vagrant-cosmic.rb +17 -0
  56. data/locales/en.yml +131 -0
  57. data/spec/spec_helper.rb +53 -0
  58. data/spec/vagrant-cosmic/action/read_ssh_info_spec.rb +80 -0
  59. data/spec/vagrant-cosmic/action/retrieve_public_ip_port_spec.rb +94 -0
  60. data/spec/vagrant-cosmic/action/run_instance_spec.rb +573 -0
  61. data/spec/vagrant-cosmic/action/terminate_instance_spec.rb +207 -0
  62. data/spec/vagrant-cosmic/config_spec.rb +340 -0
  63. data/spec/vagrant-cosmic/model/cosmic_resource_spec.rb +95 -0
  64. data/spec/vagrant-cosmic/service/cosmic_resource_service_spec.rb +43 -0
  65. data/spec/vagrant-cosmic/support/be_a_resource.rb +6 -0
  66. data/vagrant-cosmic.gemspec +59 -0
  67. data/vagrant-cosmic.spec +42 -0
  68. 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