chef-metal-fog 0.4 → 0.5.beta

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,622 @@
1
+ require 'chef_metal/driver'
2
+ require 'chef_metal/machine/windows_machine'
3
+ require 'chef_metal/machine/unix_machine'
4
+ require 'chef_metal/machine_spec'
5
+ require 'chef_metal/convergence_strategy/install_msi'
6
+ require 'chef_metal/convergence_strategy/install_cached'
7
+ require 'chef_metal/convergence_strategy/no_converge'
8
+ require 'chef_metal/transport/ssh'
9
+ require 'chef_metal_fog/version'
10
+ require 'chef_metal_fog/fog_driver_aws'
11
+ require 'fog'
12
+ require 'fog/core'
13
+ require 'fog/compute'
14
+ require 'fog/aws'
15
+ require 'socket'
16
+ require 'etc'
17
+ require 'time'
18
+ require 'cheffish/merged_config'
19
+ require 'chef_metal_fog/recipe_dsl'
20
+
21
+ module ChefMetalFog
22
+ # Provisions cloud machines with the Fog driver.
23
+ #
24
+ # ## Fog Driver URLs
25
+ #
26
+ # All Metal drivers use URLs to uniquely identify a driver's "bucket" of machines.
27
+ # Fog URLs are of the form fog:<provider>:<identifier>:
28
+ #
29
+ # fog:AWS:<account_id>
30
+ # fog:OpenStack:https://identityHost:portNumber/v2.0
31
+ # fog:DigitalOcean:<client id>
32
+ #
33
+ # Identifier is generally something uniquely identifying the account. If multiple
34
+ # users can access the account, the identifier should be the same for all of
35
+ # them (do not use the username in these cases).
36
+ #
37
+ # ## Supporting a new Fog provider
38
+ #
39
+ # The Fog driver does not immediately support all Fog providers out of the box.
40
+ # Some minor work needs to be done to plug them into metal.
41
+ #
42
+ # To add a new supported Fog provider, pick an appropriate identifier, go to
43
+ # from_provider and compute_options_for, and add the new provider in the case
44
+ # statements so that URLs for your fog provider can be generated. If your
45
+ # cloud provider has environment variables or standard config files (like
46
+ # ~/.aws/config), you can read those and merge that information in the
47
+ # compute_options_for function.
48
+ #
49
+ # ## Location format
50
+ #
51
+ # All machines have a location hash to find them. These are the keys used by
52
+ # the fog provisioner:
53
+ #
54
+ # - driver_url: fog:<driver>:<unique_account_info>
55
+ # - server_id: the ID of the server so it can be found again
56
+ # - created_at: timestamp server was created
57
+ # - started_at: timestamp server was last started
58
+ # - is_windows, ssh_username, sudo, use_private_ip_for_ssh: copied from machine_options
59
+ #
60
+ # ## Machine options
61
+ #
62
+ # Machine options (for allocation and readying the machine) include:
63
+ #
64
+ # - bootstrap_options: hash of options to pass to compute.servers.create
65
+ # - is_windows: true if windows. TODO detect this from ami?
66
+ # - create_timeout: the time to wait for the instance to boot to ssh (defaults to 600)
67
+ # - start_timeout: the time to wait for the instance to start (defaults to 600)
68
+ # - ssh_timeout: the time to wait for ssh to be available if the instance is detected as up (defaults to 20)
69
+ # - ssh_username: username to use for ssh
70
+ # - sudo: true to prefix all commands with "sudo"
71
+ # - use_private_ip_for_ssh: hint to use private ip when available
72
+ # - convergence_options: hash of options for the convergence strategy
73
+ # - chef_client_timeout: the time to wait for chef-client to finish
74
+ # - chef_server - the chef server to point convergence at
75
+ #
76
+ # Example bootstrap_options for ec2:
77
+ #
78
+ # :bootstrap_options => {
79
+ # :image_id =>'ami-311f2b45',
80
+ # :flavor_id =>'t1.micro',
81
+ # :key_name => 'key-pair-name'
82
+ # }
83
+ #
84
+ class FogDriver < ChefMetal::Driver
85
+
86
+ include Chef::Mixin::ShellOut
87
+
88
+ DEFAULT_OPTIONS = {
89
+ :create_timeout => 180,
90
+ :start_timeout => 180,
91
+ :ssh_timeout => 20
92
+ }
93
+
94
+ # Passed in a driver_url, and a config in the format of Driver.config.
95
+ def self.from_url(driver_url, config)
96
+ scheme, provider, id = driver_url.split(':', 3)
97
+ config, id = compute_options_for(provider, id, config)
98
+ FogDriver.new("fog:#{provider}:#{id}", config)
99
+ end
100
+
101
+ # Passed in a config which is *not* merged with driver_url (because we don't
102
+ # know what it is yet) but which has the same keys
103
+ def self.from_provider(provider, config)
104
+ # Figure out the options and merge them into the config
105
+ config, id = compute_options_for(provider, nil, config)
106
+ driver_options = config[:driver_options] || {}
107
+ compute_options = driver_options[:compute_options] || {}
108
+
109
+ driver_url = "fog:#{provider}:#{id}"
110
+
111
+ config = ChefMetal.config_for_url(driver_url, config)
112
+ FogDriver.new(driver_url, config)
113
+ end
114
+
115
+ # Create a new fog driver.
116
+ #
117
+ # ## Parameters
118
+ # driver_url - URL of driver. "fog:<provider>:<provider_id>"
119
+ # config - configuration. :driver_options, :keys, :key_paths and :log_level are used.
120
+ # driver_options is a hash with these possible options:
121
+ # - compute_options: the hash of options to Fog::Compute.new.
122
+ # - aws_config_file: aws config file (default: ~/.aws/config)
123
+ # - aws_csv_file: aws csv credentials file downloaded from EC2 interface
124
+ # - aws_profile: profile name to use for credentials
125
+ # - aws_credentials: AWSCredentials object. (will be created for you by default)
126
+ # - log_level: :debug, :info, :warn, :error
127
+ def initialize(driver_url, config)
128
+ super(driver_url, config)
129
+ end
130
+
131
+ def compute_options
132
+ driver_options[:compute_options] || {}
133
+ end
134
+
135
+ def provider
136
+ compute_options[:provider]
137
+ end
138
+
139
+ # Acquire a machine, generally by provisioning it. Returns a Machine
140
+ # object pointing at the machine, allowing useful actions like setup,
141
+ # converge, execute, file and directory.
142
+ def allocate_machine(action_handler, machine_spec, machine_options)
143
+ # If the server does not exist, create it
144
+ create_server(action_handler, machine_spec, machine_options)
145
+ end
146
+
147
+ def ready_machine(action_handler, machine_spec, machine_options)
148
+ server = server_for(machine_spec)
149
+ if server.nil?
150
+ raise "Machine #{machine_spec.name} does not have a server associated with it, or server does not exist."
151
+ end
152
+
153
+ # Attach floating IPs if necessary
154
+ attach_floating_ips(action_handler, machine_spec, machine_options, server)
155
+
156
+ # Start the server if needed, and wait for it to start
157
+ start_server(action_handler, machine_spec, server)
158
+ wait_until_ready(action_handler, machine_spec, machine_options, server)
159
+ begin
160
+ wait_for_transport(action_handler, machine_spec, machine_options, server)
161
+ rescue Fog::Errors::TimeoutError
162
+ # Only ever reboot once, and only if it's been less than 10 minutes since we stopped waiting
163
+ if machine_spec.location['started_at'] || remaining_wait_time(machine_spec, machine_options) < -(10*60)
164
+ raise
165
+ else
166
+ # Sometimes (on EC2) the machine comes up but gets stuck or has
167
+ # some other problem. If this is the case, we restart the server
168
+ # to unstick it. Reboot covers a multitude of sins.
169
+ 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 ..."
170
+ restart_server(action_handler, machine_spec, server)
171
+ wait_until_ready(action_handler, machine_spec, machine_options, server)
172
+ wait_for_transport(action_handler, machine_spec, machine_options, server)
173
+ end
174
+ end
175
+
176
+ machine_for(machine_spec, machine_options, server)
177
+ end
178
+
179
+ # Connect to machine without acquiring it
180
+ def connect_to_machine(machine_spec, machine_options)
181
+ machine_for(machine_spec, machine_options)
182
+ end
183
+
184
+ def destroy_machine(action_handler, machine_spec, machine_options)
185
+ server = server_for(machine_spec)
186
+ if server
187
+ action_handler.perform_action "destroy machine #{machine_spec.name} (#{machine_spec.location['server_id']} at #{driver_url})" do
188
+ server.destroy
189
+ machine_spec.location = nil
190
+ end
191
+ end
192
+ strategy = convergence_strategy_for(machine_spec, machine_options)
193
+ strategy.cleanup_convergence(action_handler, machine_spec)
194
+ end
195
+
196
+ def stop_machine(action_handler, machine_spec, machine_options)
197
+ server = server_for(machine_spec)
198
+ if server
199
+ action_handler.perform_action "stop machine #{machine_spec.name} (#{server.id} at #{driver_url})" do
200
+ server.stop
201
+ end
202
+ end
203
+ end
204
+
205
+ def compute
206
+ @compute ||= Fog::Compute.new(compute_options)
207
+ end
208
+
209
+ # Not meant to be part of public interface
210
+ def transport_for(machine_spec, server)
211
+ # TODO winrm
212
+ create_ssh_transport(machine_spec, server)
213
+ end
214
+
215
+ protected
216
+
217
+ def option_for(machine_options, key)
218
+ machine_options[key] || DEFAULT_OPTIONS[key]
219
+ end
220
+
221
+ def create_server(action_handler, machine_spec, machine_options)
222
+ if machine_spec.location
223
+ if machine_spec.location['driver_url'] != driver_url
224
+ raise "Switching a machine's driver from #{machine_spec.location['driver_url']} to #{driver_url} for is not currently supported! Use machine :destroy and then re-create the machine on the new driver."
225
+ end
226
+
227
+ server = server_for(machine_spec)
228
+ if server
229
+ if %w(terminated archive).include?(server.state) # Can't come back from that
230
+ Chef::Log.warn "Machine #{machine_spec.name} (#{server.id} on #{driver_url}) is terminated. Recreating ..."
231
+ else
232
+ return server
233
+ end
234
+ else
235
+ Chef::Log.warn "Machine #{machine_spec.name} (#{machine_spec.location['server_id']} on #{driver_url}) no longer exists. Recreating ..."
236
+ end
237
+ end
238
+
239
+ bootstrap_options = bootstrap_options_for(action_handler, machine_spec, machine_options)
240
+
241
+ description = [ "creating machine #{machine_spec.name} on #{driver_url}" ]
242
+ bootstrap_options.each_pair { |key,value| description << " #{key}: #{value.inspect}" }
243
+ server = nil
244
+ action_handler.report_progress description
245
+ if action_handler.should_perform_actions
246
+ creator = case provider
247
+ when 'AWS'
248
+ driver_options[:aws_account_info][:aws_username]
249
+ when 'OpenStack'
250
+ compute_options[:openstack_username]
251
+ end
252
+ server = compute.servers.create(bootstrap_options)
253
+ machine_spec.location = {
254
+ 'driver_url' => driver_url,
255
+ 'driver_version' => ChefMetalFog::VERSION,
256
+ 'server_id' => server.id,
257
+ 'creator' => creator,
258
+ 'allocated_at' => Time.now.utc.to_s
259
+ }
260
+ machine_spec.location['key_name'] = bootstrap_options[:key_name] if bootstrap_options[:key_name]
261
+ %w(is_windows ssh_username sudo use_private_ip_for_ssh ssh_gateway).each do |key|
262
+ machine_spec.location[key] = machine_options[key.to_sym] if machine_options[key.to_sym]
263
+ end
264
+ end
265
+ action_handler.performed_action "machine #{machine_spec.name} created as #{server.id} on #{driver_url}"
266
+ server
267
+ end
268
+
269
+ def start_server(action_handler, machine_spec, server)
270
+ # If it is stopping, wait for it to get out of "stopping" transition state before starting
271
+ if server.state == 'stopping'
272
+ action_handler.report_progress "wait for #{machine_spec.name} (#{server.id} on #{driver_url}) to finish stopping ..."
273
+ server.wait_for { server.state != 'stopping' }
274
+ action_handler.report_progress "#{machine_spec.name} is now stopped"
275
+ end
276
+
277
+ if server.state == 'stopped'
278
+ action_handler.perform_action "start machine #{machine_spec.name} (#{server.id} on #{driver_url})" do
279
+ server.start
280
+ machine_spec.location['started_at'] = Time.now.utc.to_s
281
+ end
282
+ end
283
+ end
284
+
285
+ def restart_server(action_handler, machine_spec, server)
286
+ action_handler.perform_action "restart machine #{machine_spec.name} (#{server.id} on #{driver_url})" do
287
+ server.reboot
288
+ machine_spec.location['started_at'] = Time.now.utc.to_s
289
+ end
290
+ end
291
+
292
+ def remaining_wait_time(machine_spec, machine_options)
293
+ if machine_spec.location['started_at']
294
+ timeout = option_for(machine_options, :start_timeout) - (Time.now.utc - Time.parse(machine_spec.location['started_at']))
295
+ else
296
+ timeout = option_for(machine_options, :create_timeout) - (Time.now.utc - Time.parse(machine_spec.location['allocated_at']))
297
+ end
298
+ end
299
+
300
+ def wait_until_ready(action_handler, machine_spec, machine_options, server)
301
+ if !server.ready?
302
+ if action_handler.should_perform_actions
303
+ action_handler.report_progress "waiting for #{machine_spec.name} (#{server.id} on #{driver_url}) to be ready ..."
304
+ server.wait_for(remaining_wait_time(machine_spec, machine_options)) { ready? }
305
+ action_handler.report_progress "#{machine_spec.name} is now ready"
306
+ end
307
+ end
308
+ end
309
+
310
+ def wait_for_transport(action_handler, machine_spec, machine_options, server)
311
+ transport = transport_for(machine_spec, server)
312
+ if !transport.available?
313
+ if action_handler.should_perform_actions
314
+ action_handler.report_progress "waiting for #{machine_spec.name} (#{server.id} on #{driver_url}) to be connectable (transport up and running) ..."
315
+
316
+ _self = self
317
+
318
+ server.wait_for(remaining_wait_time(machine_spec, machine_options)) do
319
+ transport.available?
320
+ end
321
+ action_handler.report_progress "#{machine_spec.name} is now connectable"
322
+ end
323
+ end
324
+ end
325
+
326
+ def attach_floating_ips(action_handler, machine_spec, machine_options, server)
327
+ # TODO this is not particularly idempotent. OK, it is not idempotent AT ALL. Fix.
328
+ if option_for(machine_options, :floating_ip_pool)
329
+ Chef::Log.info 'Attaching IP from pool'
330
+ action_handler.perform_action "attach floating IP from #{option_for(machine_options, :floating_ip_pool)} pool" do
331
+ attach_ip_from_pool(server, option_for(machine_options, :floating_ip_pool))
332
+ end
333
+ elsif option_for(machine_options, :floating_ip)
334
+ Chef::Log.info 'Attaching given IP'
335
+ action_handler.perform_action "attach floating IP #{option_for(machine_options, :floating_ip)}" do
336
+ attach_ip(server, option_for(machine_options, :allocation_id), option_for(machine_options, :floating_ip))
337
+ end
338
+ end
339
+ end
340
+
341
+ # Attach IP to machine from IP pool
342
+ # Code taken from kitchen-openstack driver
343
+ # https://github.com/test-kitchen/kitchen-openstack/blob/master/lib/kitchen/driver/openstack.rb#L196-L207
344
+ def attach_ip_from_pool(server, pool)
345
+ @ip_pool_lock ||= Mutex.new
346
+ @ip_pool_lock.synchronize do
347
+ Chef::Log.info "Attaching floating IP from <#{pool}> pool"
348
+ free_addrs = compute.addresses.collect do |i|
349
+ i.ip if i.fixed_ip.nil? and i.instance_id.nil? and i.pool == pool
350
+ end.compact
351
+ if free_addrs.empty?
352
+ raise ActionFailed, "No available IPs in pool <#{pool}>"
353
+ end
354
+ attach_ip(server, free_addrs[0])
355
+ end
356
+ end
357
+
358
+ # Attach given IP to machine
359
+ # Code taken from kitchen-openstack driver
360
+ # https://github.com/test-kitchen/kitchen-openstack/blob/master/lib/kitchen/driver/openstack.rb#L209-L213
361
+ def attach_ip(server, allocation_id, ip)
362
+ Chef::Log.info "Attaching floating IP <#{ip}>"
363
+ compute.associate_address(:instance_id => server.id,
364
+ :allocation_id => allocation_id,
365
+ :public_ip => ip)
366
+ end
367
+
368
+ def symbolize_keys(options)
369
+ options.inject({}) { |result,(key,value)| result[key.to_sym] = value; result }
370
+ end
371
+
372
+ def server_for(machine_spec)
373
+ if machine_spec.location
374
+ compute.servers.get(machine_spec.location['server_id'])
375
+ else
376
+ nil
377
+ end
378
+ end
379
+
380
+ @@metal_default_lock = Mutex.new
381
+
382
+ def overwrite_default_key_willy_nilly(action_handler)
383
+ driver = self
384
+ updated = @@metal_default_lock.synchronize do
385
+ ChefMetal.inline_resource(action_handler) do
386
+ fog_key_pair 'metal_default' do
387
+ driver driver
388
+ allow_overwrite true
389
+ end
390
+ end
391
+ end
392
+ if updated
393
+ # Only warn the first time
394
+ Chef::Log.warn("Using metal_default key, which is not shared between machines! It is recommended to create an AWS key pair with the fog_key_pair resource, and set :bootstrap_options => { :key_name => <key name> }")
395
+ end
396
+ 'metal_default'
397
+ end
398
+
399
+ def bootstrap_options_for(action_handler, machine_spec, machine_options)
400
+ bootstrap_options = symbolize_keys(machine_options[:bootstrap_options] || {})
401
+ if !bootstrap_options[:key_name]
402
+ bootstrap_options[:key_name] = overwrite_default_key_willy_nilly(action_handler)
403
+ end
404
+ tags = {
405
+ 'Name' => machine_spec.name,
406
+ 'BootstrapId' => machine_spec.id,
407
+ 'BootstrapHost' => Socket.gethostname,
408
+ 'BootstrapUser' => Etc.getlogin
409
+ }
410
+ # User-defined tags override the ones we set
411
+ tags.merge!(bootstrap_options[:tags]) if bootstrap_options[:tags]
412
+ bootstrap_options.merge!({ :tags => tags })
413
+
414
+ # Provide reasonable defaults for DigitalOcean
415
+ if provider == 'DigitalOcean'
416
+ if !bootstrap_options[:image_id]
417
+ bootstrap_options[:image_name] ||= 'CentOS 6.4 x32'
418
+ bootstrap_options[:image_id] = compute.images.select { |image| image.name == bootstrap_options[:image_name] }.first.id
419
+ end
420
+ if !bootstrap_options[:flavor_id]
421
+ bootstrap_options[:flavor_name] ||= '512MB'
422
+ bootstrap_options[:flavor_id] = compute.flavors.select { |flavor| flavor.name == bootstrap_options[:flavor_name] }.first.id
423
+ end
424
+ if !bootstrap_options[:region_id]
425
+ bootstrap_options[:region_name] ||= 'San Francisco 1'
426
+ bootstrap_options[:region_id] = compute.regions.select { |region| region.name == bootstrap_options[:region_name] }.first.id
427
+ end
428
+ bootstrap_options[:ssh_key_ids] ||= [ compute.ssh_keys.select { |k| k.name == bootstrap_options[:key_name] }.first.id ]
429
+
430
+ # You don't get to specify name yourself
431
+ bootstrap_options[:name] = machine_spec.name
432
+ end
433
+
434
+ bootstrap_options[:name] ||= machine_spec.name
435
+
436
+ bootstrap_options
437
+ end
438
+
439
+ def machine_for(machine_spec, machine_options, server = nil)
440
+ server ||= server_for(machine_spec)
441
+ if !server
442
+ raise "Server for node #{machine_spec.name} has not been created!"
443
+ end
444
+
445
+ if machine_spec.location['is_windows']
446
+ ChefMetal::Machine::WindowsMachine.new(machine_spec, transport_for(machine_spec, server), convergence_strategy_for(machine_spec, machine_options))
447
+ else
448
+ ChefMetal::Machine::UnixMachine.new(machine_spec, transport_for(machine_spec, server), convergence_strategy_for(machine_spec, machine_options))
449
+ end
450
+ end
451
+
452
+ def convergence_strategy_for(machine_spec, machine_options)
453
+ # Defaults
454
+ if !machine_spec.location
455
+ return ChefMetal::ConvergenceStrategy::NoConverge.new(machine_options[:convergence_options], config)
456
+ end
457
+
458
+ if machine_spec.location['is_windows']
459
+ ChefMetal::ConvergenceStrategy::InstallMsi.new(machine_options[:convergence_options], config)
460
+ else
461
+ ChefMetal::ConvergenceStrategy::InstallCached.new(machine_options[:convergence_options], config)
462
+ end
463
+ end
464
+
465
+ def ssh_options_for(machine_spec, server)
466
+ result = {
467
+ # TODO create a user known hosts file
468
+ # :user_known_hosts_file => vagrant_ssh_config['UserKnownHostsFile'],
469
+ # :paranoid => true,
470
+ :auth_methods => [ 'publickey' ],
471
+ :keys_only => true,
472
+ :host_key_alias => "#{server.id}.#{provider}"
473
+ }
474
+ if server.respond_to?(:private_key) && server.private_key
475
+ result[:key_data] = [ server.private_key ]
476
+ elsif server.respond_to?(:key_name)
477
+ result[:key_data] = [ get_private_key(server.key_name) ]
478
+ elsif machine_spec.location['key_name']
479
+ result[:key_data] = [ get_private_key(machine_spec.location['key_name']) ]
480
+ else
481
+ # TODO make a way to suggest other keys to try ...
482
+ raise "No key found to connect to #{machine_spec.name}!"
483
+ end
484
+ result
485
+ end
486
+
487
+ def create_ssh_transport(machine_spec, server)
488
+ ssh_options = ssh_options_for(machine_spec, server)
489
+ # If we're on AWS, the default is to use ubuntu, not root
490
+ if provider == 'AWS'
491
+ username = machine_spec.location[:ssh_username] || 'ubuntu'
492
+ else
493
+ username = machine_spec.location[:ssh_username] || 'root'
494
+ end
495
+ options = {}
496
+ if machine_spec.location[:sudo] || (!machine_spec.location.has_key?(:sudo) && username != 'root')
497
+ options[:prefix] = 'sudo '
498
+ end
499
+
500
+ remote_host = nil
501
+ if machine_spec.location[:use_private_ip_for_ssh]
502
+ remote_host = server.private_ip_address
503
+ elsif !server.public_ip_address
504
+ Chef::Log.warn("Server has no public ip address. Using private ip '#{server.private_ip_address}'. Set driver option 'use_private_ip_for_ssh' => true if this will always be the case ...")
505
+ remote_host = server.private_ip_address
506
+ elsif server.public_ip_address
507
+ remote_host = server.public_ip_address
508
+ else
509
+ raise "Server #{server.id} has no private or public IP address!"
510
+ end
511
+
512
+ #Enable pty by default
513
+ options[:ssh_pty_enable] = true
514
+ options[:ssh_gateway] = machine_spec.location[:ssh_gateway] if machine_spec.location.has_key?(:ssh_gateway)
515
+
516
+ ChefMetal::Transport::SSH.new(remote_host, username, ssh_options, options, config)
517
+ end
518
+
519
+ def self.compute_options_for(provider, id, config)
520
+ driver_options = config[:driver_options] || {}
521
+ compute_options = driver_options[:compute_options] || {}
522
+ new_compute_options = {}
523
+ new_compute_options[:provider] = provider
524
+ new_config = { :driver_options => { :compute_options => new_compute_options }}
525
+
526
+ # Set the identifier from the URL
527
+ if id && id != ''
528
+ case provider
529
+ when 'AWS'
530
+ if id !~ /^\d{12}$/
531
+ # Assume it is a profile name, and set that.
532
+ driver_options[:aws_profile] = id
533
+ id = nil
534
+ end
535
+ when 'DigitalOcean'
536
+ new_compute_options[:digitalocean_client_id] = id
537
+ when 'OpenStack'
538
+ new_compute_options[:openstack_auth_url] = id
539
+ else
540
+ raise "unsupported fog provider #{provider}"
541
+ end
542
+ elsif provider == 'AWS'
543
+ id = 'default'
544
+ end
545
+
546
+ # Set auth info from environment
547
+ case provider
548
+ when 'AWS'
549
+ # Grab the profile
550
+ aws_profile = FogDriverAWS.get_aws_profile(driver_options, compute_options, id)
551
+ [ :aws_access_key_id, :aws_secret_access_key, :aws_session_token ].each do |key|
552
+ new_compute_options[key] = aws_profile[key] if aws_profile[key]
553
+ end
554
+ when 'OpenStack'
555
+ # TODO it is supposed to be unnecessary to load credentials from fog this way;
556
+ # why are we doing it?
557
+ # TODO support http://docs.openstack.org/cli-reference/content/cli_openrc.html
558
+ credential = Fog.credential
559
+
560
+ new_compute_options[:openstack_username] ||= credential[:openstack_username]
561
+ new_compute_options[:openstack_api_key] ||= credential[:openstack_api_key]
562
+ new_compute_options[:openstack_auth_url] ||= credential[:openstack_auth_url]
563
+ new_compute_options[:openstack_tenant] ||= credential[:openstack_tenant]
564
+ end
565
+
566
+ config = Cheffish::MergedConfig.new(new_config, config)
567
+
568
+ id = case provider
569
+ when 'AWS'
570
+ account_info = FogDriverAWS.aws_account_info_for(config[:driver_options][:compute_options])
571
+ new_config[:driver_options][:aws_account_info] = account_info
572
+ account_info[:aws_account_id]
573
+ when 'DigitalOcean'
574
+ compute_options[:digitalocean_client_id]
575
+ when 'OpenStack'
576
+ compute_options[:openstack_auth_url]
577
+ end
578
+
579
+ [ config, id ]
580
+ end
581
+
582
+ def get_default_private_key
583
+ if config[:private_keys] && config[:private_keys].size > 0
584
+ get_private_key(config[:private_keys].keys.first)
585
+ else
586
+ config[:private_key_paths].each do |private_key_path|
587
+ Dir.entries(private_key_path).each do |key|
588
+ ext = File.extname(key)
589
+ if ext == '' || ext == '.pem'
590
+ key_name = key[0..-(ext.length+1)]
591
+ if key_name == name
592
+ return IO.read("#{private_key_path}/#{key}")
593
+ end
594
+ end
595
+ end
596
+ end
597
+ end
598
+ end
599
+
600
+ def get_private_key(name)
601
+ if config[:private_keys] && config[:private_keys][name]
602
+ if config[:private_keys][name].is_a?(String)
603
+ IO.read(config[:private_keys][name])
604
+ else
605
+ config[:private_keys][name].to_pem
606
+ end
607
+ elsif config[:private_key_paths]
608
+ config[:private_key_paths].each do |private_key_path|
609
+ Dir.entries(private_key_path).each do |key|
610
+ ext = File.extname(key)
611
+ if ext == '' || ext == '.pem'
612
+ key_name = key[0..-(ext.length+1)]
613
+ if key_name == name
614
+ return IO.read("#{private_key_path}/#{key}")
615
+ end
616
+ end
617
+ end
618
+ end
619
+ end
620
+ end
621
+ end
622
+ end