chef-provisioning-fog 0.15.0 → 0.15.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (32) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +11 -0
  3. data/LICENSE +201 -201
  4. data/README.md +208 -3
  5. data/Rakefile +6 -6
  6. data/chef-provisioning-fog.gemspec +28 -0
  7. data/lib/chef/provider/fog_key_pair.rb +266 -266
  8. data/lib/chef/provisioning/driver_init/fog.rb +3 -3
  9. data/lib/chef/provisioning/fog_driver/driver.rb +736 -736
  10. data/lib/chef/provisioning/fog_driver/providers/aws.rb +492 -492
  11. data/lib/chef/provisioning/fog_driver/providers/aws/credentials.rb +115 -115
  12. data/lib/chef/provisioning/fog_driver/providers/cloudstack.rb +44 -44
  13. data/lib/chef/provisioning/fog_driver/providers/digitalocean.rb +136 -136
  14. data/lib/chef/provisioning/fog_driver/providers/google.rb +85 -85
  15. data/lib/chef/provisioning/fog_driver/providers/joyent.rb +63 -63
  16. data/lib/chef/provisioning/fog_driver/providers/openstack.rb +117 -117
  17. data/lib/chef/provisioning/fog_driver/providers/rackspace.rb +42 -42
  18. data/lib/chef/provisioning/fog_driver/providers/softlayer.rb +36 -36
  19. data/lib/chef/provisioning/fog_driver/providers/vcair.rb +409 -409
  20. data/lib/chef/provisioning/fog_driver/providers/xenserver.rb +210 -210
  21. data/lib/chef/provisioning/fog_driver/recipe_dsl.rb +32 -32
  22. data/lib/chef/provisioning/fog_driver/version.rb +7 -7
  23. data/lib/chef/resource/fog_key_pair.rb +34 -34
  24. data/spec/spec_helper.rb +18 -18
  25. data/spec/support/aws/config-file.csv +2 -2
  26. data/spec/support/aws/ini-file.ini +10 -10
  27. data/spec/support/chef_metal_fog/providers/testdriver.rb +16 -16
  28. data/spec/unit/chef/provisioning/fog_driver/driver_spec.rb +71 -71
  29. data/spec/unit/fog_driver_spec.rb +32 -32
  30. data/spec/unit/providers/aws/credentials_spec.rb +45 -45
  31. data/spec/unit/providers/rackspace_spec.rb +16 -16
  32. metadata +5 -3
@@ -1,42 +1,42 @@
1
- # fog:Rackspace:https://identity.api.rackspacecloud.com/v2.0
2
- class Chef
3
- module Provisioning
4
- module FogDriver
5
- module Providers
6
- class Rackspace < FogDriver::Driver
7
-
8
- Driver.register_provider_class('Rackspace', FogDriver::Providers::Rackspace)
9
-
10
- def creator
11
- compute_options[:rackspace_username]
12
- end
13
-
14
- def self.compute_options_for(provider, id, config)
15
- new_compute_options = {}
16
- new_compute_options[:provider] = provider
17
- new_config = { :driver_options => { :compute_options => new_compute_options }}
18
- new_defaults = {
19
- :driver_options => { :compute_options => {} },
20
- :machine_options => { :bootstrap_options => {} }
21
- }
22
- result = Cheffish::MergedConfig.new(new_config, config, new_defaults)
23
-
24
- new_compute_options[:rackspace_auth_url] = id if (id && id != '')
25
- credential = Fog.credentials
26
-
27
- new_compute_options[:rackspace_username] ||= credential[:rackspace_username]
28
- new_compute_options[:rackspace_api_key] ||= credential[:rackspace_api_key]
29
- new_compute_options[:rackspace_auth_url] ||= credential[:rackspace_auth_url]
30
- new_compute_options[:rackspace_region] ||= credential[:rackspace_region]
31
- new_compute_options[:rackspace_endpoint] ||= credential[:rackspace_endpoint]
32
-
33
- id = result[:driver_options][:compute_options][:rackspace_auth_url]
34
-
35
- [result, id]
36
- end
37
-
38
- end
39
- end
40
- end
41
- end
42
- end
1
+ # fog:Rackspace:https://identity.api.rackspacecloud.com/v2.0
2
+ class Chef
3
+ module Provisioning
4
+ module FogDriver
5
+ module Providers
6
+ class Rackspace < FogDriver::Driver
7
+
8
+ Driver.register_provider_class('Rackspace', FogDriver::Providers::Rackspace)
9
+
10
+ def creator
11
+ compute_options[:rackspace_username]
12
+ end
13
+
14
+ def self.compute_options_for(provider, id, config)
15
+ new_compute_options = {}
16
+ new_compute_options[:provider] = provider
17
+ new_config = { :driver_options => { :compute_options => new_compute_options }}
18
+ new_defaults = {
19
+ :driver_options => { :compute_options => {} },
20
+ :machine_options => { :bootstrap_options => {} }
21
+ }
22
+ result = Cheffish::MergedConfig.new(new_config, config, new_defaults)
23
+
24
+ new_compute_options[:rackspace_auth_url] = id if (id && id != '')
25
+ credential = Fog.credentials
26
+
27
+ new_compute_options[:rackspace_username] ||= credential[:rackspace_username]
28
+ new_compute_options[:rackspace_api_key] ||= credential[:rackspace_api_key]
29
+ new_compute_options[:rackspace_auth_url] ||= credential[:rackspace_auth_url]
30
+ new_compute_options[:rackspace_region] ||= credential[:rackspace_region]
31
+ new_compute_options[:rackspace_endpoint] ||= credential[:rackspace_endpoint]
32
+
33
+ id = result[:driver_options][:compute_options][:rackspace_auth_url]
34
+
35
+ [result, id]
36
+ end
37
+
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -1,36 +1,36 @@
1
- class Chef
2
- module Provisioning
3
- module FogDriver
4
- module Providers
5
- class SoftLayer < FogDriver::Driver
6
- Driver.register_provider_class('SoftLayer', FogDriver::Providers::SoftLayer)
7
-
8
- def creator
9
- compute_options[:softlayer_username]
10
- end
11
-
12
- def self.compute_options_for(provider, id, config)
13
- new_compute_options = {}
14
- new_compute_options[:provider] = provider
15
- new_config = { :driver_options => { :compute_options => new_compute_options }}
16
- new_defaults = {
17
- :driver_options => { :compute_options => {} },
18
- :machine_options => { :bootstrap_options => {} }
19
- }
20
- result = Cheffish::MergedConfig.new(new_config, config, new_defaults)
21
-
22
- credential = Fog.credentials
23
-
24
- new_compute_options[:softlayer_username] ||= credential[:softlayer_username]
25
- new_compute_options[:softlayer_api_key] ||= credential[:softlayer_api_key]
26
-
27
- id = result[:driver_options][:compute_options][:softlayer_auth_url]
28
-
29
- [result, id]
30
- end
31
- end
32
- end
33
- end
34
- end
35
- end
36
-
1
+ class Chef
2
+ module Provisioning
3
+ module FogDriver
4
+ module Providers
5
+ class SoftLayer < FogDriver::Driver
6
+ Driver.register_provider_class('SoftLayer', FogDriver::Providers::SoftLayer)
7
+
8
+ def creator
9
+ compute_options[:softlayer_username]
10
+ end
11
+
12
+ def self.compute_options_for(provider, id, config)
13
+ new_compute_options = {}
14
+ new_compute_options[:provider] = provider
15
+ new_config = { :driver_options => { :compute_options => new_compute_options }}
16
+ new_defaults = {
17
+ :driver_options => { :compute_options => {} },
18
+ :machine_options => { :bootstrap_options => {} }
19
+ }
20
+ result = Cheffish::MergedConfig.new(new_config, config, new_defaults)
21
+
22
+ credential = Fog.credentials
23
+
24
+ new_compute_options[:softlayer_username] ||= credential[:softlayer_username]
25
+ new_compute_options[:softlayer_api_key] ||= credential[:softlayer_api_key]
26
+
27
+ id = result[:driver_options][:compute_options][:softlayer_auth_url]
28
+
29
+ [result, id]
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
36
+
@@ -1,409 +1,409 @@
1
- # fog:Vcair:<client id>
2
- class Chef
3
- module Provisioning
4
- module FogDriver
5
- module Providers
6
- class Vcair < FogDriver::Driver
7
- Driver.register_provider_class('Vcair', FogDriver::Providers::Vcair)
8
-
9
- def creator
10
- Chef::Config[:knife][:vcair_username]
11
- end
12
-
13
- def compute
14
- @compute ||= begin
15
- Chef::Log.debug("vcair_username #{Chef::Config[:knife][:vcair_username]}")
16
- Chef::Log.debug("vcair_org #{Chef::Config[:knife][:vcair_org]}")
17
- Chef::Log.debug("vcair_api_host #{Chef::Config[:knife][:vcair_api_host]}")
18
- #Chef::Log.debug("vcair_api_version #{Chef::Config[:knife][:vcair_api_version]}")
19
- Chef::Log.debug("vcair_show_progress #{Chef::Config[:knife][:vcair_show_progress]}")
20
-
21
- username = [
22
- Chef::Config[:knife][:vcair_username],
23
- Chef::Config[:knife][:vcair_org]
24
- ].join('@')
25
-
26
- @auth_params = {
27
- :provider => 'vclouddirector', #TODO: see compute_options_for, and grab else where
28
- :vcloud_director_username => username,
29
- :vcloud_director_password => Chef::Config[:knife][:vcair_password],
30
- :vcloud_director_host => Chef::Config[:knife][:vcair_api_host],
31
- #:vcair_api_host => Chef::Config[:knife][:vcair_api_host],
32
- :vcloud_director_api_version => Chef::Config[:knife][:vcair_api_version],
33
- :vcloud_director_show_progress => false
34
- }
35
-
36
- Fog::Compute.new(@auth_params)
37
- rescue Excon::Errors::Unauthorized => e
38
- error_message = "Connection failure, please check your username and password."
39
- Chef::Log.error(error_message)
40
- raise "#{e.message}. #{error_message}"
41
- rescue Excon::Errors::SocketError => e
42
- error_message = "Connection failure, please check your authentication URL."
43
- Chef::Log.error(error_message)
44
- raise "#{e.message}. #{error_message}"
45
- end
46
- end
47
-
48
-
49
- def create_many_servers(num_servers, bootstrap_options, parallelizer)
50
- parallelizer.parallelize(1.upto(num_servers)) do |i|
51
- clean_bootstrap_options = Marshal.load(Marshal.dump(bootstrap_options)) # Prevent destructive operations on bootstrap_options.
52
- vm=nil
53
- begin
54
- begin
55
- instantiate(clean_bootstrap_options)
56
- rescue Fog::Errors::Error => e
57
- unless e.minor_error_code == "DUPLICATE_NAME"
58
- # if it's already there, just use the current one
59
- raise e
60
- end
61
- end
62
-
63
- vapp = vdc.vapps.get_by_name(bootstrap_options[:name])
64
- vm = vapp.vms.find {|v| v.vapp_name == bootstrap_options[:name]}
65
- update_customization(clean_bootstrap_options, vm)
66
-
67
- if bootstrap_options[:cpus]
68
- vm.cpu = bootstrap_options[:cpus]
69
- end
70
- if bootstrap_options[:memory]
71
- vm.memory = bootstrap_options[:memory]
72
- end
73
- update_network(bootstrap_options, vapp, vm)
74
-
75
- rescue Excon::Errors::BadRequest => e
76
- response = Chef::JSONCompat.from_json(e.response.body)
77
- if response['badRequest']['code'] == 400
78
- message = "Bad request (400): #{response['badRequest']['message']}"
79
- Chef::Log.error(message)
80
- else
81
- message = "Unknown server error (#{response['badRequest']['code']}): #{response['badRequest']['message']}"
82
- Chef::Log.error(message)
83
- end
84
- raise message
85
- rescue Fog::Errors::Error => e
86
- raise e.message
87
- end
88
-
89
- yield vm if block_given?
90
- vm
91
-
92
- end.to_a
93
- end
94
-
95
-
96
- def start_server(action_handler, machine_spec, server)
97
-
98
- # If it is stopping, wait for it to get out of "stopping" transition state before starting
99
- if server.status == 'stopping'
100
- action_handler.report_progress "wait for #{machine_spec.name} (#{server.id} on #{driver_url}) to finish stopping ..."
101
- # vCloud Air
102
- # NOTE: vCloud Air Fog does not get server.status via http every time
103
- server.wait_for { server.reload ; server.status != 'stopping' }
104
- action_handler.report_progress "#{machine_spec.name} is now stopped"
105
- end
106
-
107
- # NOTE: vCloud Air Fog does not get server.status via http every time
108
- server.reload
109
-
110
- if server.status == 'off' or server.status != 'on'
111
- action_handler.perform_action "start machine #{machine_spec.name} (#{server.id} on #{driver_url})" do
112
- server.power_on
113
- machine_spec.location['started_at'] = Time.now.to_i
114
- end
115
- machine_spec.save(action_handler)
116
- end
117
- end
118
-
119
-
120
- def server_for(machine_spec)
121
- if machine_spec.location
122
- vapp = vdc.vapps.get_by_name(machine_spec.name)
123
-
124
- server = unless vapp.nil?
125
- unless vapp.vms.first.nil?
126
- vapp.vms.find{|vm| vm.id == machine_spec.location['server_id'] }
127
- end
128
- end
129
- else
130
- nil
131
- end
132
- end
133
-
134
- def servers_for(machine_specs)
135
- result = {}
136
- machine_specs.each do |machine_spec|
137
- server_for(machine_spec)
138
- end
139
- result
140
- end
141
-
142
- def ssh_options_for(machine_spec, machine_options, server)
143
- { auth_methods: [ 'password' ],
144
- timeout: (machine_options[:ssh_timeout] || 600),
145
- password: machine_options[:ssh_password]
146
- }.merge(machine_options[:ssh_options] || {})
147
- end
148
-
149
- def create_ssh_transport(machine_spec, machine_options, server)
150
- ssh_options = ssh_options_for(machine_spec, machine_options, server)
151
- username = machine_spec.location['ssh_username'] || default_ssh_username
152
- options = {}
153
- if machine_spec.location[:sudo] || (!machine_spec.location.has_key?(:sudo) && username != 'root')
154
- options[:prefix] = 'sudo '
155
- end
156
-
157
- remote_host = nil
158
- # vCloud Air networking is funky
159
- #if machine_options[:use_private_ip_for_ssh] # vCloud Air probably needs private ip for now
160
- if server.ip_address
161
- remote_host = server.ip_address
162
- else
163
- raise "Server #{server.id} has no private or public IP address!"
164
- end
165
-
166
- #Enable pty by default
167
- options[:ssh_pty_enable] = true
168
- options[:ssh_gateway] = machine_spec.location['ssh_gateway'] if machine_spec.location.has_key?('ssh_gateway')
169
-
170
- Transport::SSH.new(remote_host, username, ssh_options, options, config)
171
- end
172
-
173
- def ready_machine(action_handler, machine_spec, machine_options)
174
- server = server_for(machine_spec)
175
- if server.nil?
176
- raise "Machine #{machine_spec.name} does not have a server associated with it, or server does not exist."
177
- end
178
-
179
- # Start the server if needed, and wait for it to start
180
- start_server(action_handler, machine_spec, server)
181
- wait_until_ready(action_handler, machine_spec, machine_options, server)
182
-
183
- # Attach/detach floating IPs if necessary
184
- # vCloud Air is funky for network. VM has to be powered off or you get this error:
185
- # Primary NIC cannot be changed when the VM is not in Powered-off state
186
- # See code in update_network()
187
- #DISABLED: converge_floating_ips(action_handler, machine_spec, machine_options, server)
188
-
189
- begin
190
- wait_for_transport(action_handler, machine_spec, machine_options, server)
191
- rescue Fog::Errors::TimeoutError
192
- # Only ever reboot once, and only if it's been less than 10 minutes since we stopped waiting
193
- if machine_spec.location['started_at'] || remaining_wait_time(machine_spec, machine_options) < -(10*60)
194
- raise
195
- else
196
- # Sometimes (on EC2) the machine comes up but gets stuck or has
197
- # some other problem. If this is the case, we restart the server
198
- # to unstick it. Reboot covers a multitude of sins.
199
- Chef::Log.warn "Machine #{machine_spec.name} (#{server.id} on #{driver_url}) was started but SSH did not come up. Rebooting machine in an attempt to unstick it ..."
200
- restart_server(action_handler, machine_spec, server)
201
- wait_until_ready(action_handler, machine_spec, machine_options, server)
202
- wait_for_transport(action_handler, machine_spec, machine_options, server)
203
- end
204
- end
205
-
206
- machine_for(machine_spec, machine_options, server)
207
- end
208
-
209
- def org
210
- @org ||= compute.organizations.get_by_name(Chef::Config[:knife][:vcair_org])
211
- end
212
-
213
- def vdc
214
- if Chef::Config[:knife][:vcair_vdc]
215
- @vdc ||= org.vdcs.get_by_name(Chef::Config[:knife][:vcair_vdc])
216
- else
217
- @vdc ||= org.vdcs.first
218
- end
219
- end
220
-
221
- def net
222
- if Chef::Config[:knife][:vcair_net]
223
- @net ||= org.networks.get_by_name(Chef::Config[:knife][:vcair_net])
224
- else
225
- # Grab first non-isolated (bridged, natRouted) network
226
- @net ||= org.networks.find { |n| n if !n.fence_mode.match("isolated") }
227
- end
228
- end
229
-
230
- def template(bootstrap_options)
231
- # TODO: find by catalog item ID and/or NAME
232
- # TODO: add option to search just public and/or private catalogs
233
-
234
- #TODO: maybe make a hash for caching
235
- org.catalogs.map do |cat|
236
- #cat.catalog_items.get_by_name(config_value(:image))
237
- cat.catalog_items.get_by_name(bootstrap_options[:image_name])
238
- end.compact.first
239
- end
240
-
241
- def instantiate(bootstrap_options)
242
- begin
243
- #node_name = config_value(:chef_node_name)
244
- node_name = bootstrap_options[:name]
245
- template(bootstrap_options).instantiate(
246
- node_name,
247
- vdc_id: vdc.id,
248
- network_id: net.id,
249
- description: "id:#{node_name}")
250
- #rescue CloudExceptions::ServerCreateError => e
251
- rescue => e
252
- raise e
253
- end
254
- end
255
-
256
- # Create a WinRM transport for a vCloud Air Vapp VM instance
257
- # @param [Hash] machine_spec Machine-spec hash
258
- # @param [Hash] machine_options Machine options (from the recipe)
259
- # @param [Fog::Compute::Server] server A Fog mapping to the AWS instance
260
- # @return [ChefMetal::Transport::WinRM] A WinRM Transport object to talk to the server
261
- def create_winrm_transport(machine_spec, machine_options, server)
262
- port = machine_spec.location['winrm_port'] || 5985
263
- endpoint = "http://#{server.ip_address}:#{port}/wsman"
264
- type = :plaintext
265
-
266
- # Use basic HTTP auth - this is required for the WinRM setup we
267
- # are using
268
- # TODO: Improve that and support different users
269
- options = {
270
- :user => 'Administrator',
271
- :pass => machine_options[:winrm_options][:password],
272
- :disable_sspi => true,
273
- :basic_auth_only => true
274
- }
275
- Chef::Provisioning::Transport::WinRM.new(endpoint, type, options, {})
276
- end
277
-
278
- def update_customization(bootstrap_options, server)
279
- ## Initialization before first power on.
280
- custom=server.customization
281
-
282
- if bootstrap_options[:customization_script]
283
- custom.script = open(bootstrap_options[:customization_script]).read
284
- end
285
-
286
- bootstrap_options[:protocol] ||= case server.operating_system
287
- when /Windows/
288
- 'winrm'
289
- else
290
- 'ssh'
291
- end
292
- password = case bootstrap_options[:protocol]
293
- when 'ssh'
294
- bootstrap_options[:ssh_options][:password]
295
- when 'winrm'
296
- bootstrap_options[:winrm_options][:password]
297
- end
298
-
299
- if password
300
- custom.admin_password = password
301
- custom.admin_password_auto = false
302
- custom.reset_password_required = false
303
- else
304
- # Password will be autogenerated
305
- custom.admin_password_auto=true
306
- # API will force password resets when auto is enabled
307
- custom.reset_password_required = true
308
- end
309
-
310
- # TODO: Add support for admin_auto_logon to Fog
311
- # c.admin_auto_logon_count = 100
312
- # c.admin_auto_logon_enabled = true
313
-
314
- # DNS and Windows want AlphaNumeric and dashes for hostnames
315
- # Windows can only handle 15 character hostnames
316
- # TODO: only change name for Windows!
317
- #c.computer_name = config_value(:chef_node_name).gsub(/\W/,"-").slice(0..14)
318
- custom.computer_name = bootstrap_options[:name].gsub(/\W/,"-").slice(0..14)
319
- custom.enabled = true
320
- custom.save
321
- end
322
-
323
- ## vCloud Air
324
- ## TODO: make work with floating_ip junk currently used
325
- ## NOTE: current vCloud Air networking changes require VM to be powered off
326
- def update_network(bootstrap_options, vapp, vm)
327
- ## TODO: allow user to specify network to connect to (see above net used)
328
- # Define network connection for vm based on existing routed network
329
-
330
- # vCloud Air inlining vapp() and vm()
331
- #vapp = vdc.vapps.get_by_name(bootstrap_options[:name])
332
- #vm = vapp.vms.find {|v| v.vapp_name == bootstrap_options[:name]}
333
- return if vm.ip_address != "" # return if ip address is set, as this isn't a new VM
334
- nc = vapp.network_config.find { |netc| netc if netc[:networkName].match(net.name) }
335
- networks_config = [nc]
336
- section = {PrimaryNetworkConnectionIndex: 0}
337
- section[:NetworkConnection] = networks_config.compact.each_with_index.map do |network, i|
338
- connection = {
339
- network: network[:networkName],
340
- needsCustomization: true,
341
- NetworkConnectionIndex: i,
342
- IsConnected: true
343
- }
344
- ip_address = network[:ip_address]
345
- ## TODO: support config options for allocation mode
346
- #allocation_mode = network[:allocation_mode]
347
- #allocation_mode = 'manual' if ip_address
348
- #allocation_mode = 'dhcp' unless %w{dhcp manual pool}.include?(allocation_mode)
349
- #allocation_mode = 'POOL'
350
- #connection[:Dns1] = dns1 if dns1
351
- allocation_mode = 'pool'
352
- connection[:IpAddressAllocationMode] = allocation_mode.upcase
353
- connection[:IpAddress] = ip_address if ip_address
354
- connection
355
- end
356
-
357
- ## attach the network to the vm
358
- nc_task = compute.put_network_connection_system_section_vapp(
359
- vm.id,section).body
360
- compute.process_task(nc_task)
361
- end
362
-
363
- def bootstrap_options_for(action_handler, machine_spec, machine_options)
364
- bootstrap_options = symbolize_keys(machine_options[:bootstrap_options] || {})
365
-
366
- bootstrap_options[:tags] = default_tags(machine_spec, bootstrap_options[:tags] || {})
367
- bootstrap_options[:name] ||= machine_spec.name
368
-
369
- bootstrap_options = bootstrap_options.merge(machine_options.configs[1])
370
- bootstrap_options
371
- end
372
-
373
- def destroy_machine(action_handler, machine_spec, machine_options)
374
- server = server_for(machine_spec)
375
- if server && server.status != 'archive' # TODO: does vCloud Air do archive?
376
- action_handler.perform_action "destroy machine #{machine_spec.name} (#{machine_spec.location['server_id']} at #{driver_url})" do
377
- #NOTE: currently doing 1 vm for 1 vapp
378
- vapp = vdc.vapps.get_by_name(machine_spec.name)
379
- if vapp
380
- vapp.power_off
381
- vapp.undeploy
382
- vapp.destroy
383
- else
384
- Chef::Log.warn "No VApp named '#{server_name}' was found."
385
- end
386
- end
387
- end
388
- machine_spec.location = nil
389
- strategy = convergence_strategy_for(machine_spec, machine_options)
390
- strategy.cleanup_convergence(action_handler, machine_spec)
391
- end
392
-
393
- def self.compute_options_for(provider, id, config)
394
- new_compute_options = {}
395
- new_compute_options[:provider] = 'vclouddirector'
396
- new_config = { :driver_options => { :compute_options => new_compute_options }}
397
- new_defaults = {
398
- :driver_options => { :compute_options => {} },
399
- :machine_options => { :bootstrap_options => {}, :ssh_options => {} }
400
- }
401
- result = Cheffish::MergedConfig.new(new_config, config, new_defaults)
402
-
403
- [result, id]
404
- end
405
- end
406
- end
407
- end
408
- end
409
- end
1
+ # fog:Vcair:<client id>
2
+ class Chef
3
+ module Provisioning
4
+ module FogDriver
5
+ module Providers
6
+ class Vcair < FogDriver::Driver
7
+ Driver.register_provider_class('Vcair', FogDriver::Providers::Vcair)
8
+
9
+ def creator
10
+ Chef::Config[:knife][:vcair_username]
11
+ end
12
+
13
+ def compute
14
+ @compute ||= begin
15
+ Chef::Log.debug("vcair_username #{Chef::Config[:knife][:vcair_username]}")
16
+ Chef::Log.debug("vcair_org #{Chef::Config[:knife][:vcair_org]}")
17
+ Chef::Log.debug("vcair_api_host #{Chef::Config[:knife][:vcair_api_host]}")
18
+ #Chef::Log.debug("vcair_api_version #{Chef::Config[:knife][:vcair_api_version]}")
19
+ Chef::Log.debug("vcair_show_progress #{Chef::Config[:knife][:vcair_show_progress]}")
20
+
21
+ username = [
22
+ Chef::Config[:knife][:vcair_username],
23
+ Chef::Config[:knife][:vcair_org]
24
+ ].join('@')
25
+
26
+ @auth_params = {
27
+ :provider => 'vclouddirector', #TODO: see compute_options_for, and grab else where
28
+ :vcloud_director_username => username,
29
+ :vcloud_director_password => Chef::Config[:knife][:vcair_password],
30
+ :vcloud_director_host => Chef::Config[:knife][:vcair_api_host],
31
+ #:vcair_api_host => Chef::Config[:knife][:vcair_api_host],
32
+ :vcloud_director_api_version => Chef::Config[:knife][:vcair_api_version],
33
+ :vcloud_director_show_progress => false
34
+ }
35
+
36
+ Fog::Compute.new(@auth_params)
37
+ rescue Excon::Errors::Unauthorized => e
38
+ error_message = "Connection failure, please check your username and password."
39
+ Chef::Log.error(error_message)
40
+ raise "#{e.message}. #{error_message}"
41
+ rescue Excon::Errors::SocketError => e
42
+ error_message = "Connection failure, please check your authentication URL."
43
+ Chef::Log.error(error_message)
44
+ raise "#{e.message}. #{error_message}"
45
+ end
46
+ end
47
+
48
+
49
+ def create_many_servers(num_servers, bootstrap_options, parallelizer)
50
+ parallelizer.parallelize(1.upto(num_servers)) do |i|
51
+ clean_bootstrap_options = Marshal.load(Marshal.dump(bootstrap_options)) # Prevent destructive operations on bootstrap_options.
52
+ vm=nil
53
+ begin
54
+ begin
55
+ instantiate(clean_bootstrap_options)
56
+ rescue Fog::Errors::Error => e
57
+ unless e.minor_error_code == "DUPLICATE_NAME"
58
+ # if it's already there, just use the current one
59
+ raise e
60
+ end
61
+ end
62
+
63
+ vapp = vdc.vapps.get_by_name(bootstrap_options[:name])
64
+ vm = vapp.vms.find {|v| v.vapp_name == bootstrap_options[:name]}
65
+ update_customization(clean_bootstrap_options, vm)
66
+
67
+ if bootstrap_options[:cpus]
68
+ vm.cpu = bootstrap_options[:cpus]
69
+ end
70
+ if bootstrap_options[:memory]
71
+ vm.memory = bootstrap_options[:memory]
72
+ end
73
+ update_network(bootstrap_options, vapp, vm)
74
+
75
+ rescue Excon::Errors::BadRequest => e
76
+ response = Chef::JSONCompat.from_json(e.response.body)
77
+ if response['badRequest']['code'] == 400
78
+ message = "Bad request (400): #{response['badRequest']['message']}"
79
+ Chef::Log.error(message)
80
+ else
81
+ message = "Unknown server error (#{response['badRequest']['code']}): #{response['badRequest']['message']}"
82
+ Chef::Log.error(message)
83
+ end
84
+ raise message
85
+ rescue Fog::Errors::Error => e
86
+ raise e.message
87
+ end
88
+
89
+ yield vm if block_given?
90
+ vm
91
+
92
+ end.to_a
93
+ end
94
+
95
+
96
+ def start_server(action_handler, machine_spec, server)
97
+
98
+ # If it is stopping, wait for it to get out of "stopping" transition state before starting
99
+ if server.status == 'stopping'
100
+ action_handler.report_progress "wait for #{machine_spec.name} (#{server.id} on #{driver_url}) to finish stopping ..."
101
+ # vCloud Air
102
+ # NOTE: vCloud Air Fog does not get server.status via http every time
103
+ server.wait_for { server.reload ; server.status != 'stopping' }
104
+ action_handler.report_progress "#{machine_spec.name} is now stopped"
105
+ end
106
+
107
+ # NOTE: vCloud Air Fog does not get server.status via http every time
108
+ server.reload
109
+
110
+ if server.status == 'off' or server.status != 'on'
111
+ action_handler.perform_action "start machine #{machine_spec.name} (#{server.id} on #{driver_url})" do
112
+ server.power_on
113
+ machine_spec.location['started_at'] = Time.now.to_i
114
+ end
115
+ machine_spec.save(action_handler)
116
+ end
117
+ end
118
+
119
+
120
+ def server_for(machine_spec)
121
+ if machine_spec.location
122
+ vapp = vdc.vapps.get_by_name(machine_spec.name)
123
+
124
+ server = unless vapp.nil?
125
+ unless vapp.vms.first.nil?
126
+ vapp.vms.find{|vm| vm.id == machine_spec.location['server_id'] }
127
+ end
128
+ end
129
+ else
130
+ nil
131
+ end
132
+ end
133
+
134
+ def servers_for(machine_specs)
135
+ result = {}
136
+ machine_specs.each do |machine_spec|
137
+ server_for(machine_spec)
138
+ end
139
+ result
140
+ end
141
+
142
+ def ssh_options_for(machine_spec, machine_options, server)
143
+ { auth_methods: [ 'password' ],
144
+ timeout: (machine_options[:ssh_timeout] || 600),
145
+ password: machine_options[:ssh_password]
146
+ }.merge(machine_options[:ssh_options] || {})
147
+ end
148
+
149
+ def create_ssh_transport(machine_spec, machine_options, server)
150
+ ssh_options = ssh_options_for(machine_spec, machine_options, server)
151
+ username = machine_spec.location['ssh_username'] || default_ssh_username
152
+ options = {}
153
+ if machine_spec.location[:sudo] || (!machine_spec.location.has_key?(:sudo) && username != 'root')
154
+ options[:prefix] = 'sudo '
155
+ end
156
+
157
+ remote_host = nil
158
+ # vCloud Air networking is funky
159
+ #if machine_options[:use_private_ip_for_ssh] # vCloud Air probably needs private ip for now
160
+ if server.ip_address
161
+ remote_host = server.ip_address
162
+ else
163
+ raise "Server #{server.id} has no private or public IP address!"
164
+ end
165
+
166
+ #Enable pty by default
167
+ options[:ssh_pty_enable] = true
168
+ options[:ssh_gateway] = machine_spec.location['ssh_gateway'] if machine_spec.location.has_key?('ssh_gateway')
169
+
170
+ Transport::SSH.new(remote_host, username, ssh_options, options, config)
171
+ end
172
+
173
+ def ready_machine(action_handler, machine_spec, machine_options)
174
+ server = server_for(machine_spec)
175
+ if server.nil?
176
+ raise "Machine #{machine_spec.name} does not have a server associated with it, or server does not exist."
177
+ end
178
+
179
+ # Start the server if needed, and wait for it to start
180
+ start_server(action_handler, machine_spec, server)
181
+ wait_until_ready(action_handler, machine_spec, machine_options, server)
182
+
183
+ # Attach/detach floating IPs if necessary
184
+ # vCloud Air is funky for network. VM has to be powered off or you get this error:
185
+ # Primary NIC cannot be changed when the VM is not in Powered-off state
186
+ # See code in update_network()
187
+ #DISABLED: converge_floating_ips(action_handler, machine_spec, machine_options, server)
188
+
189
+ begin
190
+ wait_for_transport(action_handler, machine_spec, machine_options, server)
191
+ rescue Fog::Errors::TimeoutError
192
+ # Only ever reboot once, and only if it's been less than 10 minutes since we stopped waiting
193
+ if machine_spec.location['started_at'] || remaining_wait_time(machine_spec, machine_options) < -(10*60)
194
+ raise
195
+ else
196
+ # Sometimes (on EC2) the machine comes up but gets stuck or has
197
+ # some other problem. If this is the case, we restart the server
198
+ # to unstick it. Reboot covers a multitude of sins.
199
+ Chef::Log.warn "Machine #{machine_spec.name} (#{server.id} on #{driver_url}) was started but SSH did not come up. Rebooting machine in an attempt to unstick it ..."
200
+ restart_server(action_handler, machine_spec, server)
201
+ wait_until_ready(action_handler, machine_spec, machine_options, server)
202
+ wait_for_transport(action_handler, machine_spec, machine_options, server)
203
+ end
204
+ end
205
+
206
+ machine_for(machine_spec, machine_options, server)
207
+ end
208
+
209
+ def org
210
+ @org ||= compute.organizations.get_by_name(Chef::Config[:knife][:vcair_org])
211
+ end
212
+
213
+ def vdc
214
+ if Chef::Config[:knife][:vcair_vdc]
215
+ @vdc ||= org.vdcs.get_by_name(Chef::Config[:knife][:vcair_vdc])
216
+ else
217
+ @vdc ||= org.vdcs.first
218
+ end
219
+ end
220
+
221
+ def net
222
+ if Chef::Config[:knife][:vcair_net]
223
+ @net ||= org.networks.get_by_name(Chef::Config[:knife][:vcair_net])
224
+ else
225
+ # Grab first non-isolated (bridged, natRouted) network
226
+ @net ||= org.networks.find { |n| n if !n.fence_mode.match("isolated") }
227
+ end
228
+ end
229
+
230
+ def template(bootstrap_options)
231
+ # TODO: find by catalog item ID and/or NAME
232
+ # TODO: add option to search just public and/or private catalogs
233
+
234
+ #TODO: maybe make a hash for caching
235
+ org.catalogs.map do |cat|
236
+ #cat.catalog_items.get_by_name(config_value(:image))
237
+ cat.catalog_items.get_by_name(bootstrap_options[:image_name])
238
+ end.compact.first
239
+ end
240
+
241
+ def instantiate(bootstrap_options)
242
+ begin
243
+ #node_name = config_value(:chef_node_name)
244
+ node_name = bootstrap_options[:name]
245
+ template(bootstrap_options).instantiate(
246
+ node_name,
247
+ vdc_id: vdc.id,
248
+ network_id: net.id,
249
+ description: "id:#{node_name}")
250
+ #rescue CloudExceptions::ServerCreateError => e
251
+ rescue => e
252
+ raise e
253
+ end
254
+ end
255
+
256
+ # Create a WinRM transport for a vCloud Air Vapp VM instance
257
+ # @param [Hash] machine_spec Machine-spec hash
258
+ # @param [Hash] machine_options Machine options (from the recipe)
259
+ # @param [Fog::Compute::Server] server A Fog mapping to the AWS instance
260
+ # @return [ChefMetal::Transport::WinRM] A WinRM Transport object to talk to the server
261
+ def create_winrm_transport(machine_spec, machine_options, server)
262
+ port = machine_spec.location['winrm_port'] || 5985
263
+ endpoint = "http://#{server.ip_address}:#{port}/wsman"
264
+ type = :plaintext
265
+
266
+ # Use basic HTTP auth - this is required for the WinRM setup we
267
+ # are using
268
+ # TODO: Improve that and support different users
269
+ options = {
270
+ :user => 'Administrator',
271
+ :pass => machine_options[:winrm_options][:password],
272
+ :disable_sspi => true,
273
+ :basic_auth_only => true
274
+ }
275
+ Chef::Provisioning::Transport::WinRM.new(endpoint, type, options, {})
276
+ end
277
+
278
+ def update_customization(bootstrap_options, server)
279
+ ## Initialization before first power on.
280
+ custom=server.customization
281
+
282
+ if bootstrap_options[:customization_script]
283
+ custom.script = open(bootstrap_options[:customization_script]).read
284
+ end
285
+
286
+ bootstrap_options[:protocol] ||= case server.operating_system
287
+ when /Windows/
288
+ 'winrm'
289
+ else
290
+ 'ssh'
291
+ end
292
+ password = case bootstrap_options[:protocol]
293
+ when 'ssh'
294
+ bootstrap_options[:ssh_options][:password]
295
+ when 'winrm'
296
+ bootstrap_options[:winrm_options][:password]
297
+ end
298
+
299
+ if password
300
+ custom.admin_password = password
301
+ custom.admin_password_auto = false
302
+ custom.reset_password_required = false
303
+ else
304
+ # Password will be autogenerated
305
+ custom.admin_password_auto=true
306
+ # API will force password resets when auto is enabled
307
+ custom.reset_password_required = true
308
+ end
309
+
310
+ # TODO: Add support for admin_auto_logon to Fog
311
+ # c.admin_auto_logon_count = 100
312
+ # c.admin_auto_logon_enabled = true
313
+
314
+ # DNS and Windows want AlphaNumeric and dashes for hostnames
315
+ # Windows can only handle 15 character hostnames
316
+ # TODO: only change name for Windows!
317
+ #c.computer_name = config_value(:chef_node_name).gsub(/\W/,"-").slice(0..14)
318
+ custom.computer_name = bootstrap_options[:name].gsub(/\W/,"-").slice(0..14)
319
+ custom.enabled = true
320
+ custom.save
321
+ end
322
+
323
+ ## vCloud Air
324
+ ## TODO: make work with floating_ip junk currently used
325
+ ## NOTE: current vCloud Air networking changes require VM to be powered off
326
+ def update_network(bootstrap_options, vapp, vm)
327
+ ## TODO: allow user to specify network to connect to (see above net used)
328
+ # Define network connection for vm based on existing routed network
329
+
330
+ # vCloud Air inlining vapp() and vm()
331
+ #vapp = vdc.vapps.get_by_name(bootstrap_options[:name])
332
+ #vm = vapp.vms.find {|v| v.vapp_name == bootstrap_options[:name]}
333
+ return if vm.ip_address != "" # return if ip address is set, as this isn't a new VM
334
+ nc = vapp.network_config.find { |netc| netc if netc[:networkName].match(net.name) }
335
+ networks_config = [nc]
336
+ section = {PrimaryNetworkConnectionIndex: 0}
337
+ section[:NetworkConnection] = networks_config.compact.each_with_index.map do |network, i|
338
+ connection = {
339
+ network: network[:networkName],
340
+ needsCustomization: true,
341
+ NetworkConnectionIndex: i,
342
+ IsConnected: true
343
+ }
344
+ ip_address = network[:ip_address]
345
+ ## TODO: support config options for allocation mode
346
+ #allocation_mode = network[:allocation_mode]
347
+ #allocation_mode = 'manual' if ip_address
348
+ #allocation_mode = 'dhcp' unless %w{dhcp manual pool}.include?(allocation_mode)
349
+ #allocation_mode = 'POOL'
350
+ #connection[:Dns1] = dns1 if dns1
351
+ allocation_mode = 'pool'
352
+ connection[:IpAddressAllocationMode] = allocation_mode.upcase
353
+ connection[:IpAddress] = ip_address if ip_address
354
+ connection
355
+ end
356
+
357
+ ## attach the network to the vm
358
+ nc_task = compute.put_network_connection_system_section_vapp(
359
+ vm.id,section).body
360
+ compute.process_task(nc_task)
361
+ end
362
+
363
+ def bootstrap_options_for(action_handler, machine_spec, machine_options)
364
+ bootstrap_options = symbolize_keys(machine_options[:bootstrap_options] || {})
365
+
366
+ bootstrap_options[:tags] = default_tags(machine_spec, bootstrap_options[:tags] || {})
367
+ bootstrap_options[:name] ||= machine_spec.name
368
+
369
+ bootstrap_options = bootstrap_options.merge(machine_options.configs[1])
370
+ bootstrap_options
371
+ end
372
+
373
+ def destroy_machine(action_handler, machine_spec, machine_options)
374
+ server = server_for(machine_spec)
375
+ if server && server.status != 'archive' # TODO: does vCloud Air do archive?
376
+ action_handler.perform_action "destroy machine #{machine_spec.name} (#{machine_spec.location['server_id']} at #{driver_url})" do
377
+ #NOTE: currently doing 1 vm for 1 vapp
378
+ vapp = vdc.vapps.get_by_name(machine_spec.name)
379
+ if vapp
380
+ vapp.power_off
381
+ vapp.undeploy
382
+ vapp.destroy
383
+ else
384
+ Chef::Log.warn "No VApp named '#{server_name}' was found."
385
+ end
386
+ end
387
+ end
388
+ machine_spec.location = nil
389
+ strategy = convergence_strategy_for(machine_spec, machine_options)
390
+ strategy.cleanup_convergence(action_handler, machine_spec)
391
+ end
392
+
393
+ def self.compute_options_for(provider, id, config)
394
+ new_compute_options = {}
395
+ new_compute_options[:provider] = 'vclouddirector'
396
+ new_config = { :driver_options => { :compute_options => new_compute_options }}
397
+ new_defaults = {
398
+ :driver_options => { :compute_options => {} },
399
+ :machine_options => { :bootstrap_options => {}, :ssh_options => {} }
400
+ }
401
+ result = Cheffish::MergedConfig.new(new_config, config, new_defaults)
402
+
403
+ [result, id]
404
+ end
405
+ end
406
+ end
407
+ end
408
+ end
409
+ end