vagrant-cosmic 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|