chef-provisioning-fog 0.15.0 → 0.15.1

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 (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