chef-provisioning-fog 0.10

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