chef-provisioning-fog 0.13.2 → 0.14.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 7e8d9aff16a913485687ea30699bc056f40f6550
4
- data.tar.gz: 00049afa78c2598b84dd90a1fd019dd9655f4c5b
3
+ metadata.gz: 52b659938865f82a47d2bd233d5f9e4d62d4b708
4
+ data.tar.gz: ee9dc1faac3c6e1a2527fcd64079fd7b0521ae73
5
5
  SHA512:
6
- metadata.gz: b95693417ce3f1791dd1e08ab1448c8250cb6c660aa2df21fe427dc8f0c1a43c927b59e89ca94bbdffe3c0654594a076fce3e0cf2fc21854c56a4666b8d5dc45
7
- data.tar.gz: cd886bb181cad6dddb995d7bf0abecdf0fadd4df59cf2b9cf9bf5fcf238011a39f74bbb3088c2acbce46fd18f9030e735720d201892586ee4402455b0dadf8af
6
+ metadata.gz: 469dcadcf9ca2ad0e608de694abdc3a85b3e1dd9bb677330191ef0e86c2d3041c1197ad89a678718d17980c1ea983f4987805a3593d190760972d418ab865394
7
+ data.tar.gz: 28fc5d4cce8c2c1b77306d32fc5592bf83eeab2d1b18c82b433c2a272e345c9c7f4b0141296d22052b85dbb82d5cc5e8a3c04c9754777b8fd4f294a28ca37bca
data/README.md CHANGED
@@ -1,3 +1,3 @@
1
1
  # chef-provisioning-fog
2
2
 
3
- This is the Fog driver for Chef Provisioning. It provides EC2, Rackspace, DigitalOcean, SoftLayer, and Openstack functionality.
3
+ This is the Fog driver for Chef Provisioning. It provides Amazon EC2, Rackspace, DigitalOcean, SoftLayer, OpenStack, and vCloud Air functionality.
@@ -19,6 +19,7 @@ require 'fog/compute'
19
19
  require 'socket'
20
20
  require 'etc'
21
21
  require 'time'
22
+ require 'retryable'
22
23
  require 'cheffish/merged_config'
23
24
  require 'chef/provisioning/fog_driver/recipe_dsl'
24
25
 
@@ -51,15 +52,15 @@ module FogDriver
51
52
  #
52
53
  # To add a new supported Fog provider, pick an appropriate identifier, go to
53
54
  # from_provider and compute_options_for, and add the new provider in the case
54
- # statements so that URLs for your fog provider can be generated. If your
55
+ # statements so that URLs for your Fog provider can be generated. If your
55
56
  # cloud provider has environment variables or standard config files (like
56
57
  # ~/.aws/credentials or ~/.aws/config), you can read those and merge that information
57
58
  # in the compute_options_for function.
58
59
  #
59
- # ## Location format
60
+ # ## Reference format
60
61
  #
61
- # All machines have a location hash to find them. These are the keys used by
62
- # the fog provisioner:
62
+ # All machines have a reference hash to find them. These are the keys used by
63
+ # the Fog provisioner:
63
64
  #
64
65
  # - driver_url: fog:<driver>:<unique_account_info>
65
66
  # - server_id: the ID of the server so it can be found again
@@ -102,6 +103,9 @@ module FogDriver
102
103
  :ssh_timeout => 20
103
104
  }
104
105
 
106
+ RETRYABLE_ERRORS = [Fog::Compute::AWS::Error]
107
+ RETRYABLE_OPTIONS = { tries: 12, sleep: 5, on: RETRYABLE_ERRORS }
108
+
105
109
  class << self
106
110
  alias :__new__ :new
107
111
 
@@ -149,7 +153,7 @@ module FogDriver
149
153
  Provisioning.driver_for_url(driver_url, config)
150
154
  end
151
155
 
152
- # Create a new fog driver.
156
+ # Create a new Fog driver.
153
157
  #
154
158
  # ## Parameters
155
159
  # driver_url - URL of driver. "fog:<provider>:<provider_id>"
@@ -211,7 +215,7 @@ module FogDriver
211
215
  wait_for_transport(action_handler, machine_spec, machine_options, server)
212
216
  rescue Fog::Errors::TimeoutError
213
217
  # Only ever reboot once, and only if it's been less than 10 minutes since we stopped waiting
214
- if machine_spec.location['started_at'] || remaining_wait_time(machine_spec, machine_options) < -(10*60)
218
+ if machine_spec.reference['started_at'] || remaining_wait_time(machine_spec, machine_options) < -(10*60)
215
219
  raise
216
220
  else
217
221
  # Sometimes (on EC2) the machine comes up but gets stuck or has
@@ -235,9 +239,9 @@ module FogDriver
235
239
  def destroy_machine(action_handler, machine_spec, machine_options)
236
240
  server = server_for(machine_spec)
237
241
  if server
238
- action_handler.perform_action "destroy machine #{machine_spec.name} (#{machine_spec.location['server_id']} at #{driver_url})" do
242
+ action_handler.perform_action "destroy machine #{machine_spec.name} (#{machine_spec.reference['server_id']} at #{driver_url})" do
239
243
  server.destroy
240
- machine_spec.location = nil
244
+ machine_spec.reference = nil
241
245
  end
242
246
  end
243
247
  strategy = convergence_strategy_for(machine_spec, machine_options)
@@ -254,7 +258,7 @@ module FogDriver
254
258
  end
255
259
 
256
260
  def image_for(image_spec)
257
- compute.images.get(image_spec.location['image_id'])
261
+ compute.images.get(image_spec.reference['image_id'])
258
262
  end
259
263
 
260
264
  def compute
@@ -263,7 +267,7 @@ module FogDriver
263
267
 
264
268
  # Not meant to be part of public interface
265
269
  def transport_for(machine_spec, machine_options, server, action_handler = nil)
266
- if machine_spec.location['is_windows']
270
+ if machine_spec.reference['is_windows']
267
271
  action_handler.report_progress "Waiting for admin password on #{machine_spec.name} to be ready (may take up to 15 minutes)..." if action_handler
268
272
  transport = create_winrm_transport(machine_spec, machine_options, server)
269
273
  action_handler.report_progress 'Admin password available ...' if action_handler
@@ -280,7 +284,7 @@ module FogDriver
280
284
  end
281
285
 
282
286
  def creator
283
- raise "unsupported fog provider #{provider} (please implement #creator)"
287
+ raise "unsupported Fog provider #{provider} (please implement #creator)"
284
288
  end
285
289
 
286
290
  def create_servers(action_handler, specs_and_options, parallelizer, &block)
@@ -299,8 +303,8 @@ module FogDriver
299
303
  yield machine_spec, server if block_given?
300
304
  next
301
305
  end
302
- elsif machine_spec.location
303
- Chef::Log.warn "Machine #{machine_spec.name} (#{machine_spec.location['server_id']} on #{driver_url}) no longer exists. Recreating ..."
306
+ elsif machine_spec.reference
307
+ Chef::Log.warn "Machine #{machine_spec.name} (#{machine_spec.reference['server_id']} on #{driver_url}) no longer exists. Recreating ..."
304
308
  end
305
309
 
306
310
  bootstrap_options = bootstrap_options_for(action_handler, machine_spec, machine_options)
@@ -325,16 +329,16 @@ module FogDriver
325
329
  # Assign each one to a machine spec
326
330
  machine_spec = machine_specs.pop
327
331
  machine_options = specs_and_options[machine_spec]
328
- machine_spec.location = {
332
+ machine_spec.reference = {
329
333
  'driver_url' => driver_url,
330
334
  'driver_version' => FogDriver::VERSION,
331
335
  'server_id' => server.id,
332
336
  'creator' => creator,
333
337
  'allocated_at' => Time.now.to_i
334
338
  }
335
- machine_spec.location['key_name'] = bootstrap_options[:key_name] if bootstrap_options[:key_name]
339
+ machine_spec.reference['key_name'] = bootstrap_options[:key_name] if bootstrap_options[:key_name]
336
340
  %w(is_windows ssh_username sudo use_private_ip_for_ssh ssh_gateway).each do |key|
337
- machine_spec.location[key] = machine_options[key.to_sym] if machine_options[key.to_sym]
341
+ machine_spec.reference[key] = machine_options[key.to_sym] if machine_options[key.to_sym]
338
342
  end
339
343
  action_handler.performed_action "machine #{machine_spec.name} created as #{server.id} on #{driver_url}"
340
344
 
@@ -368,7 +372,7 @@ module FogDriver
368
372
  if server.state == 'stopped'
369
373
  action_handler.perform_action "start machine #{machine_spec.name} (#{server.id} on #{driver_url})" do
370
374
  server.start
371
- machine_spec.location['started_at'] = Time.now.to_i
375
+ machine_spec.reference['started_at'] = Time.now.to_i
372
376
  end
373
377
  machine_spec.save(action_handler)
374
378
  end
@@ -377,16 +381,16 @@ module FogDriver
377
381
  def restart_server(action_handler, machine_spec, server)
378
382
  action_handler.perform_action "restart machine #{machine_spec.name} (#{server.id} on #{driver_url})" do
379
383
  server.reboot
380
- machine_spec.location['started_at'] = Time.now.to_i
384
+ machine_spec.reference['started_at'] = Time.now.to_i
381
385
  end
382
386
  machine_spec.save(action_handler)
383
387
  end
384
388
 
385
389
  def remaining_wait_time(machine_spec, machine_options)
386
- if machine_spec.location['started_at']
387
- timeout = option_for(machine_options, :start_timeout) - (Time.now.utc - parse_time(machine_spec.location['started_at']))
390
+ if machine_spec.reference['started_at']
391
+ timeout = option_for(machine_options, :start_timeout) - (Time.now.utc - parse_time(machine_spec.reference['started_at']))
388
392
  else
389
- timeout = option_for(machine_options, :create_timeout) - (Time.now.utc - parse_time(machine_spec.location['allocated_at']))
393
+ timeout = option_for(machine_options, :create_timeout) - (Time.now.utc - parse_time(machine_spec.reference['allocated_at']))
390
394
  end
391
395
  timeout > 0 ? timeout : 0.01
392
396
  end
@@ -402,8 +406,10 @@ module FogDriver
402
406
  def wait_until_ready(action_handler, machine_spec, machine_options, server)
403
407
  if !server.ready?
404
408
  if action_handler.should_perform_actions
405
- action_handler.report_progress "waiting for #{machine_spec.name} (#{server.id} on #{driver_url}) to be ready ..."
406
- server.wait_for(remaining_wait_time(machine_spec, machine_options)) { ready? }
409
+ Retryable.retryable(RETRYABLE_OPTIONS) do |retries,exception|
410
+ action_handler.report_progress "waiting for #{machine_spec.name} (#{server.id} on #{driver_url}) to be ready, API attempt #{retries+1}/#{RETRYABLE_OPTIONS[:tries]} ..."
411
+ server.wait_for(remaining_wait_time(machine_spec, machine_options)) { ready? }
412
+ end
407
413
  action_handler.report_progress "#{machine_spec.name} is now ready"
408
414
  end
409
415
  end
@@ -414,12 +420,14 @@ module FogDriver
414
420
  transport = transport_for(machine_spec, machine_options, server, action_handler)
415
421
  if !transport.available?
416
422
  if action_handler.should_perform_actions
417
- action_handler.report_progress "waiting for #{machine_spec.name} (#{server.id} on #{driver_url}) to be connectable (transport up and running) ..."
423
+ Retryable.retryable(RETRYABLE_OPTIONS) do |retries,exception|
424
+ action_handler.report_progress "waiting for #{machine_spec.name} (#{server.id} on #{driver_url}) to be connectable (transport up and running), API attempt #{retries+1}/#{RETRYABLE_OPTIONS[:tries]} ..."
418
425
 
419
- _self = self
426
+ _self = self
420
427
 
421
- server.wait_for(remaining_wait_time(machine_spec, machine_options)) do
422
- transport.available?
428
+ server.wait_for(remaining_wait_time(machine_spec, machine_options)) do
429
+ transport.available?
430
+ end
423
431
  end
424
432
  action_handler.report_progress "#{machine_spec.name} is now connectable"
425
433
  end
@@ -429,7 +437,7 @@ module FogDriver
429
437
  def converge_floating_ips(action_handler, machine_spec, machine_options, server)
430
438
  pool = option_for(machine_options, :floating_ip_pool)
431
439
  floating_ip = option_for(machine_options, :floating_ip)
432
- attached_floating_ips = find_floating_ips(server)
440
+ attached_floating_ips = find_floating_ips(server, action_handler)
433
441
  if pool
434
442
 
435
443
  Chef::Log.debug "Attaching IP from pool #{pool}"
@@ -467,12 +475,15 @@ module FogDriver
467
475
  end
468
476
 
469
477
  # Find all attached floating IPs from all networks
470
- def find_floating_ips(server)
478
+ def find_floating_ips(server, action_handler)
471
479
  floating_ips = []
472
- server.addresses.each do |network, addrs|
473
- addrs.each do | full_addr |
474
- if full_addr['OS-EXT-IPS:type'] == 'floating'
475
- floating_ips << full_addr['addr']
480
+ Retryable.retryable(RETRYABLE_OPTIONS) do |retries,exception|
481
+ action_handler.report_progress "Querying for floating IPs attached to server #{server.id}, API attempt #{retries+1}/#{RETRYABLE_OPTIONS[:tries]} ..."
482
+ server.addresses.each do |network, addrs|
483
+ addrs.each do | full_addr |
484
+ if full_addr['OS-EXT-IPS:type'] == 'floating'
485
+ floating_ips << full_addr['addr']
486
+ end
476
487
  end
477
488
  end
478
489
  end
@@ -512,8 +523,8 @@ module FogDriver
512
523
  end
513
524
 
514
525
  def server_for(machine_spec)
515
- if machine_spec.location
516
- compute.servers.get(machine_spec.location['server_id'])
526
+ if machine_spec.reference
527
+ compute.servers.get(machine_spec.reference['server_id'])
517
528
  else
518
529
  nil
519
530
  end
@@ -522,11 +533,11 @@ module FogDriver
522
533
  def servers_for(machine_specs)
523
534
  result = {}
524
535
  machine_specs.each do |machine_spec|
525
- if machine_spec.location
526
- if machine_spec.location['driver_url'] != driver_url
527
- 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."
536
+ if machine_spec.reference
537
+ if machine_spec.reference['driver_url'] != driver_url
538
+ raise "Switching a machine's driver from #{machine_spec.reference['driver_url']} to #{driver_url} for is not currently supported! Use machine :destroy and then re-create the machine on the new driver."
528
539
  end
529
- result[machine_spec] = compute.servers.get(machine_spec.location['server_id'])
540
+ result[machine_spec] = compute.servers.get(machine_spec.reference['server_id'])
530
541
  else
531
542
  result[machine_spec] = nil
532
543
  end
@@ -537,8 +548,8 @@ module FogDriver
537
548
  @@chef_default_lock = Mutex.new
538
549
 
539
550
  def overwrite_default_key_willy_nilly(action_handler, machine_spec)
540
- if machine_spec.location &&
541
- Gem::Version.new(machine_spec.location['driver_version']) < Gem::Version.new('0.10')
551
+ if machine_spec.reference &&
552
+ Gem::Version.new(machine_spec.reference['driver_version']) < Gem::Version.new('0.10')
542
553
  return 'metal_default'
543
554
  end
544
555
 
@@ -585,7 +596,7 @@ module FogDriver
585
596
  raise "Server for node #{machine_spec.name} has not been created!"
586
597
  end
587
598
 
588
- if machine_spec.location['is_windows']
599
+ if machine_spec.reference['is_windows']
589
600
  Machine::WindowsMachine.new(machine_spec, transport_for(machine_spec, machine_options, server), convergence_strategy_for(machine_spec, machine_options))
590
601
  else
591
602
  Machine::UnixMachine.new(machine_spec, transport_for(machine_spec, machine_options, server), convergence_strategy_for(machine_spec, machine_options))
@@ -594,11 +605,11 @@ module FogDriver
594
605
 
595
606
  def convergence_strategy_for(machine_spec, machine_options)
596
607
  # Defaults
597
- if !machine_spec.location
608
+ if !machine_spec.reference
598
609
  return ConvergenceStrategy::NoConverge.new(machine_options[:convergence_options], config)
599
610
  end
600
611
 
601
- if machine_spec.location['is_windows']
612
+ if machine_spec.reference['is_windows']
602
613
  ConvergenceStrategy::InstallMsi.new(machine_options[:convergence_options], config)
603
614
  elsif machine_options[:cached_installer] == true
604
615
  ConvergenceStrategy::InstallCached.new(machine_options[:convergence_options], config)
@@ -622,10 +633,10 @@ module FogDriver
622
633
  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(', ')}"
623
634
  end
624
635
  key
625
- elsif machine_spec.location['key_name']
626
- key = get_private_key(machine_spec.location['key_name'])
636
+ elsif machine_spec.reference['key_name']
637
+ key = get_private_key(machine_spec.reference['key_name'])
627
638
  if !key
628
- 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(', ')}"
639
+ raise "Server was created with key name '#{machine_spec.reference['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(', ')}"
629
640
  end
630
641
  key
631
642
  elsif machine_options[:bootstrap_options][:key_path]
@@ -634,7 +645,7 @@ module FogDriver
634
645
  get_private_key(machine_options[:bootstrap_options][:key_name])
635
646
  else
636
647
  # TODO make a way to suggest other keys to try ...
637
- raise "No key found to connect to #{machine_spec.name} (#{machine_spec.location.inspect})!"
648
+ raise "No key found to connect to #{machine_spec.name} (#{machine_spec.reference.inspect})!"
638
649
  end
639
650
  end
640
651
 
@@ -661,17 +672,17 @@ module FogDriver
661
672
 
662
673
  def create_ssh_transport(machine_spec, machine_options, server)
663
674
  ssh_options = ssh_options_for(machine_spec, machine_options, server)
664
- username = machine_spec.location['ssh_username'] || default_ssh_username
665
- if machine_options.has_key?(:ssh_username) && machine_options[:ssh_username] != machine_spec.location['ssh_username']
666
- 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.")
675
+ username = machine_spec.reference['ssh_username'] || default_ssh_username
676
+ if machine_options.has_key?(:ssh_username) && machine_options[:ssh_username] != machine_spec.reference['ssh_username']
677
+ Chef::Log.warn("Server #{machine_spec.name} was created with SSH username #{machine_spec.reference['ssh_username']} and machine_options specifies username #{machine_options[:ssh_username]}. Using #{machine_spec.reference['ssh_username']}. Please edit the node and change the chef_provisioning.reference.ssh_username attribute if you want to change it.")
667
678
  end
668
679
  options = {}
669
- if machine_spec.location[:sudo] || (!machine_spec.location.has_key?(:sudo) && username != 'root')
680
+ if machine_spec.reference[:sudo] || (!machine_spec.reference.has_key?(:sudo) && username != 'root')
670
681
  options[:prefix] = 'sudo '
671
682
  end
672
683
 
673
684
  remote_host = nil
674
- if machine_spec.location['use_private_ip_for_ssh']
685
+ if machine_spec.reference['use_private_ip_for_ssh']
675
686
  remote_host = server.private_ip_address
676
687
  elsif !server.public_ip_address
677
688
  Chef::Log.warn("Server #{machine_spec.name} has no public floating_ip address. Using private floating_ip '#{server.private_ip_address}'. Set driver option 'use_private_ip_for_ssh' => true if this will always be the case ...")
@@ -684,13 +695,13 @@ module FogDriver
684
695
 
685
696
  #Enable pty by default
686
697
  options[:ssh_pty_enable] = true
687
- options[:ssh_gateway] = machine_spec.location['ssh_gateway'] if machine_spec.location.has_key?('ssh_gateway')
698
+ options[:ssh_gateway] = machine_spec.reference['ssh_gateway'] if machine_spec.reference.has_key?('ssh_gateway')
688
699
 
689
700
  Transport::SSH.new(remote_host, username, ssh_options, options, config)
690
701
  end
691
702
 
692
703
  def self.compute_options_for(provider, id, config)
693
- raise "unsupported fog provider #{provider}"
704
+ raise "unsupported Fog provider #{provider}"
694
705
  end
695
706
  end
696
707
  end
@@ -36,7 +36,7 @@ module FogDriver
36
36
  remote_host = if machine_spec.location['use_private_ip_for_ssh']
37
37
  server.private_ip_address
38
38
  elsif !server.public_ip_address
39
- 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 ...")
39
+ 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 ...")
40
40
  server.private_ip_address
41
41
  elsif server.public_ip_address
42
42
  server.public_ip_address
@@ -349,7 +349,7 @@ module FogDriver
349
349
  id = $1
350
350
  new_compute_options[:region] = $3
351
351
  else
352
- Chef::Log.warn("Old-style AWS URL #{id} from an early beta of chef-metal (before 0.11-final) found. If you have servers in multiple regions on this account, you may see odd behavior like servers being recreated. To fix, edit any nodes with attribute chef_provisioning.location.driver_url to include the region like so: fog:AWS:#{id}:<region> (e.g. us-east-1)")
352
+ Chef::Log.warn("Old-style AWS URL #{id} from an early beta of chef-provisioning (before chef-metal 0.11-final) found. If you have servers in multiple regions on this account, you may see odd behavior like servers being recreated. To fix, edit any nodes with attribute chef_provisioning.location.driver_url to include the region like so: fog:AWS:#{id}:<region> (e.g. us-east-1)")
353
353
  end
354
354
  else
355
355
  # Assume it is a profile name, and set that.
@@ -384,7 +384,7 @@ module FogDriver
384
384
  end
385
385
 
386
386
  def create_many_servers(num_servers, bootstrap_options, parallelizer)
387
- # Create all the servers in one request if we have a version of fog that can do that
387
+ # Create all the servers in one request if we have a version of Fog that can do that
388
388
  if compute.servers.respond_to?(:create_many)
389
389
  servers = compute.servers.create_many(num_servers, num_servers, bootstrap_options)
390
390
  if block_given?
@@ -11,7 +11,7 @@ module FogDriver
11
11
  end
12
12
 
13
13
  def converge_floating_ips(action_handler, machine_spec, machine_options, server)
14
- # Digital ocean does not have floating IPs
14
+ # DigitalOcean does not have floating IPs
15
15
  end
16
16
 
17
17
  def bootstrap_options_for(action_handler, machine_spec, machine_options)
@@ -0,0 +1,84 @@
1
+ class Chef
2
+ module Provisioning
3
+ module FogDriver
4
+ module Providers
5
+ class Google < FogDriver::Driver
6
+ Driver.register_provider_class('Google', FogDriver::Providers::Google)
7
+
8
+ def creator
9
+ ''
10
+ end
11
+
12
+ def converge_floating_ips(action_handler, machine_spec, machine_options, server)
13
+ end
14
+
15
+ def server_for(machine_spec)
16
+ if machine_spec.name
17
+ compute.servers.get(machine_spec.name)
18
+ else
19
+ nil
20
+ end
21
+ end
22
+
23
+ def servers_for(machine_specs)
24
+ result = {}
25
+ machine_specs.each do |machine_spec|
26
+ result[machine_spec] = server_for(machine_spec)
27
+ end
28
+ result
29
+ end
30
+
31
+ def bootstrap_options_for(action_handler, machine_spec, machine_options)
32
+ bootstrap_options = symbolize_keys(machine_options[:bootstrap_options] || {})
33
+ bootstrap_options[:image_name] = 'debian-7-wheezy-v20150325'
34
+ bootstrap_options[:machine_type] = 'n1-standard-1'
35
+ bootstrap_options[:zone_name] = 'europe-west1-b'
36
+ bootstrap_options[:name] = machine_spec.name
37
+
38
+ if bootstrap_options[:disks].nil?
39
+ # create the persistent boot disk
40
+ disk_defaults = {
41
+ :name => machine_spec.name,
42
+ :size_gb => 10,
43
+ :zone_name => bootstrap_options[:zone_name],
44
+ :source_image => bootstrap_options[:image_name],
45
+ }
46
+
47
+ disk = compute.disks.create(disk_defaults)
48
+ disk.wait_for { disk.ready? }
49
+ bootstrap_options[:disks] = [disk]
50
+ end
51
+
52
+ bootstrap_options
53
+ end
54
+
55
+ def destroy_machine(action_handler, machine_spec, machine_options)
56
+ server = server_for(machine_spec)
57
+ if server && server.state != 'archive'
58
+ action_handler.perform_action "destroy machine #{machine_spec.name} (#{machine_spec.location['server_id']} at #{driver_url})" do
59
+ server.destroy
60
+ end
61
+ end
62
+ machine_spec.location = nil
63
+ strategy = convergence_strategy_for(machine_spec, machine_options)
64
+ strategy.cleanup_convergence(action_handler, machine_spec)
65
+ end
66
+
67
+ def self.compute_options_for(provider, id, config)
68
+ new_compute_options = {}
69
+ new_compute_options[:provider] = provider
70
+ new_config = { :driver_options => { :compute_options => new_compute_options }}
71
+ new_defaults = {
72
+ :driver_options => { :compute_options => {} },
73
+ :machine_options => { :bootstrap_options => {}, :ssh_options => {} }
74
+ }
75
+ result = Cheffish::MergedConfig.new(new_config, config, new_defaults)
76
+
77
+ [result, '']
78
+ end
79
+
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,376 @@
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
+ instantiate(clean_bootstrap_options)
55
+
56
+ vapp = vdc.vapps.get_by_name(bootstrap_options[:name])
57
+ vm = vapp.vms.find {|v| v.vapp_name == bootstrap_options[:name]}
58
+
59
+ update_customization(clean_bootstrap_options, vm)
60
+
61
+ if bootstrap_options[:cpus]
62
+ vm.cpu = bootstrap_options[:cpus]
63
+ end
64
+ if bootstrap_options[:memory]
65
+ vm.memory = bootstrap_options[:memory]
66
+ end
67
+ update_network(bootstrap_options, vapp, vm)
68
+
69
+ rescue Excon::Errors::BadRequest => e
70
+ response = Chef::JSONCompat.from_json(e.response.body)
71
+ if response['badRequest']['code'] == 400
72
+ message = "Bad request (400): #{response['badRequest']['message']}"
73
+ Chef::Log.error(message)
74
+ else
75
+ message = "Unknown server error (#{response['badRequest']['code']}): #{response['badRequest']['message']}"
76
+ Chef::Log.error(message)
77
+ end
78
+ raise message
79
+ rescue Fog::Errors::Error => e
80
+ raise e.message
81
+ end
82
+
83
+ yield vm if block_given?
84
+ vm
85
+
86
+ end.to_a
87
+ end
88
+
89
+
90
+ def start_server(action_handler, machine_spec, server)
91
+
92
+ # If it is stopping, wait for it to get out of "stopping" transition state before starting
93
+ if server.status == 'stopping'
94
+ action_handler.report_progress "wait for #{machine_spec.name} (#{server.id} on #{driver_url}) to finish stopping ..."
95
+ # vCloud Air
96
+ # NOTE: vCloud Air Fog does not get server.status via http every time
97
+ server.wait_for { server.reload ; server.status != 'stopping' }
98
+ action_handler.report_progress "#{machine_spec.name} is now stopped"
99
+ end
100
+
101
+ # NOTE: vCloud Air Fog does not get server.status via http every time
102
+ server.reload
103
+
104
+ if server.status == 'off' or server.status != 'on'
105
+ action_handler.perform_action "start machine #{machine_spec.name} (#{server.id} on #{driver_url})" do
106
+ server.power_on
107
+ machine_spec.location['started_at'] = Time.now.to_i
108
+ end
109
+ machine_spec.save(action_handler)
110
+ end
111
+ end
112
+
113
+
114
+ def server_for(machine_spec)
115
+ if machine_spec.location
116
+ vapp = vdc.vapps.get_by_name(machine_spec.name)
117
+
118
+ server = unless vapp.nil?
119
+ unless vapp.vms.first.nil?
120
+ vapp.vms.find{|vm| vm.id == machine_spec.location['server_id'] }
121
+ end
122
+ end
123
+ else
124
+ nil
125
+ end
126
+ end
127
+
128
+ def servers_for(machine_specs)
129
+ result = {}
130
+ machine_specs.each do |machine_spec|
131
+ server_for(machine_spec)
132
+ end
133
+ result
134
+ end
135
+
136
+ def ssh_options_for(machine_spec, machine_options, server)
137
+ { auth_methods: [ 'password' ],
138
+ timeout: (machine_options[:ssh_timeout] || 600),
139
+ password: machine_options[:ssh_password]
140
+ }.merge(machine_options[:ssh_options] || {})
141
+ end
142
+
143
+ def create_ssh_transport(machine_spec, machine_options, server)
144
+ ssh_options = ssh_options_for(machine_spec, machine_options, server)
145
+ username = machine_spec.location['ssh_username'] || default_ssh_username
146
+ options = {}
147
+ if machine_spec.location[:sudo] || (!machine_spec.location.has_key?(:sudo) && username != 'root')
148
+ options[:prefix] = 'sudo '
149
+ end
150
+
151
+ remote_host = nil
152
+ # vCloud Air networking is funky
153
+ #if machine_options[:use_private_ip_for_ssh] # vCloud Air probably needs private ip for now
154
+ if server.ip_address
155
+ remote_host = server.ip_address
156
+ else
157
+ raise "Server #{server.id} has no private or public IP address!"
158
+ end
159
+
160
+ #Enable pty by default
161
+ options[:ssh_pty_enable] = true
162
+ options[:ssh_gateway] = machine_spec.location['ssh_gateway'] if machine_spec.location.has_key?('ssh_gateway')
163
+
164
+ Transport::SSH.new(remote_host, username, ssh_options, options, config)
165
+ end
166
+
167
+ def ready_machine(action_handler, machine_spec, machine_options)
168
+ server = server_for(machine_spec)
169
+ if server.nil?
170
+ raise "Machine #{machine_spec.name} does not have a server associated with it, or server does not exist."
171
+ end
172
+
173
+ # Start the server if needed, and wait for it to start
174
+ start_server(action_handler, machine_spec, server)
175
+ wait_until_ready(action_handler, machine_spec, machine_options, server)
176
+
177
+ # Attach/detach floating IPs if necessary
178
+ # vCloud Air is funky for network. VM has to be powered off or you get this error:
179
+ # Primary NIC cannot be changed when the VM is not in Powered-off state
180
+ # See code in update_network()
181
+ #DISABLED: converge_floating_ips(action_handler, machine_spec, machine_options, server)
182
+
183
+ begin
184
+ wait_for_transport(action_handler, machine_spec, machine_options, server)
185
+ rescue Fog::Errors::TimeoutError
186
+ # Only ever reboot once, and only if it's been less than 10 minutes since we stopped waiting
187
+ if machine_spec.location['started_at'] || remaining_wait_time(machine_spec, machine_options) < -(10*60)
188
+ raise
189
+ else
190
+ # Sometimes (on EC2) the machine comes up but gets stuck or has
191
+ # some other problem. If this is the case, we restart the server
192
+ # to unstick it. Reboot covers a multitude of sins.
193
+ 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 ..."
194
+ restart_server(action_handler, machine_spec, server)
195
+ wait_until_ready(action_handler, machine_spec, machine_options, server)
196
+ wait_for_transport(action_handler, machine_spec, machine_options, server)
197
+ end
198
+ end
199
+
200
+ machine_for(machine_spec, machine_options, server)
201
+ end
202
+
203
+ def org
204
+ @org ||= compute.organizations.get_by_name(Chef::Config[:knife][:vcair_org])
205
+ end
206
+
207
+ def vdc
208
+ if Chef::Config[:knife][:vcair_vdc]
209
+ @vdc ||= org.vdcs.get_by_name(Chef::Config[:knife][:vcair_vdc])
210
+ else
211
+ @vdc ||= org.vdcs.first
212
+ end
213
+ end
214
+
215
+ def net
216
+ if Chef::Config[:knife][:vcair_net]
217
+ @net ||= org.networks.get_by_name(Chef::Config[:knife][:vcair_net])
218
+ else
219
+ # Grab first non-isolated (bridged, natRouted) network
220
+ @net ||= org.networks.find { |n| n if !n.fence_mode.match("isolated") }
221
+ end
222
+ end
223
+
224
+ def template(bootstrap_options)
225
+ # TODO: find by catalog item ID and/or NAME
226
+ # TODO: add option to search just public and/or private catalogs
227
+
228
+ #TODO: maybe make a hash for caching
229
+ org.catalogs.map do |cat|
230
+ #cat.catalog_items.get_by_name(config_value(:image))
231
+ cat.catalog_items.get_by_name(bootstrap_options[:image_name])
232
+ end.compact.first
233
+ end
234
+
235
+ def instantiate(bootstrap_options)
236
+ begin
237
+ #node_name = config_value(:chef_node_name)
238
+ node_name = bootstrap_options[:name]
239
+ template(bootstrap_options).instantiate(
240
+ node_name,
241
+ vdc_id: vdc.id,
242
+ network_id: net.id,
243
+ description: "id:#{node_name}")
244
+ #rescue CloudExceptions::ServerCreateError => e
245
+ rescue => e
246
+ raise e
247
+ end
248
+ end
249
+
250
+ def update_customization(bootstrap_options, server)
251
+ ## Initialization before first power on.
252
+ c=server.customization
253
+
254
+ if bootstrap_options[:customization_script]
255
+ c.script = open(bootstrap_options[:customization_script]).read
256
+ end
257
+
258
+ # TODO: check machine type and pick accordingly for Chef provisioning
259
+ # password = case config_value(:bootstrap_protocol)
260
+ # when 'winrm'
261
+ # config_value(:winrm_password)
262
+ # when 'ssh'
263
+ # config_value(:ssh_password)
264
+ # end
265
+
266
+ password = bootstrap_options[:ssh_options][:password]
267
+ if password
268
+ c.admin_password = password
269
+ c.admin_password_auto = false
270
+ c.reset_password_required = false
271
+ else
272
+ # Password will be autogenerated
273
+ c.admin_password_auto=true
274
+ # API will force password resets when auto is enabled
275
+ c.reset_password_required = true
276
+ end
277
+
278
+ # TODO: Add support for admin_auto_logon to Fog
279
+ # c.admin_auto_logon_count = 100
280
+ # c.admin_auto_logon_enabled = true
281
+
282
+ # DNS and Windows want AlphaNumeric and dashes for hostnames
283
+ # Windows can only handle 15 character hostnames
284
+ # TODO: only change name for Windows!
285
+ #c.computer_name = config_value(:chef_node_name).gsub(/\W/,"-").slice(0..14)
286
+ c.computer_name = bootstrap_options[:name].gsub(/\W/,"-").slice(0..14)
287
+ c.enabled = true
288
+ c.save
289
+ end
290
+
291
+ ## vCloud Air
292
+ ## TODO: make work with floating_ip junk currently used
293
+ ## NOTE: current vCloud Air networking changes require VM to be powered off
294
+ def update_network(bootstrap_options, vapp, vm)
295
+ ## TODO: allow user to specify network to connect to (see above net used)
296
+ # Define network connection for vm based on existing routed network
297
+
298
+ # vCloud Air inlining vapp() and vm()
299
+ #vapp = vdc.vapps.get_by_name(bootstrap_options[:name])
300
+ #vm = vapp.vms.find {|v| v.vapp_name == bootstrap_options[:name]}
301
+ nc = vapp.network_config.find { |n| n if n[:networkName].match(net.name) }
302
+ networks_config = [nc]
303
+ section = {PrimaryNetworkConnectionIndex: 0}
304
+ section[:NetworkConnection] = networks_config.compact.each_with_index.map do |network, i|
305
+ connection = {
306
+ network: network[:networkName],
307
+ needsCustomization: true,
308
+ NetworkConnectionIndex: i,
309
+ IsConnected: true
310
+ }
311
+ ip_address = network[:ip_address]
312
+ ## TODO: support config options for allocation mode
313
+ #allocation_mode = network[:allocation_mode]
314
+ #allocation_mode = 'manual' if ip_address
315
+ #allocation_mode = 'dhcp' unless %w{dhcp manual pool}.include?(allocation_mode)
316
+ #allocation_mode = 'POOL'
317
+ #connection[:Dns1] = dns1 if dns1
318
+ allocation_mode = 'pool'
319
+ connection[:IpAddressAllocationMode] = allocation_mode.upcase
320
+ connection[:IpAddress] = ip_address if ip_address
321
+ connection
322
+ end
323
+
324
+ ## attach the network to the vm
325
+ nc_task = compute.put_network_connection_system_section_vapp(
326
+ vm.id,section).body
327
+ compute.process_task(nc_task)
328
+ end
329
+
330
+ def bootstrap_options_for(action_handler, machine_spec, machine_options)
331
+ bootstrap_options = symbolize_keys(machine_options[:bootstrap_options] || {})
332
+
333
+ bootstrap_options[:tags] = default_tags(machine_spec, bootstrap_options[:tags] || {})
334
+ bootstrap_options[:name] ||= machine_spec.name
335
+
336
+ bootstrap_options = bootstrap_options.merge(machine_options.configs[1])
337
+ bootstrap_options
338
+ end
339
+
340
+ def destroy_machine(action_handler, machine_spec, machine_options)
341
+ server = server_for(machine_spec)
342
+ if server && server.status != 'archive' # TODO: does vCloud Air do archive?
343
+ action_handler.perform_action "destroy machine #{machine_spec.name} (#{machine_spec.location['server_id']} at #{driver_url})" do
344
+ #NOTE: currently doing 1 vm for 1 vapp
345
+ vapp = vdc.vapps.get_by_name(machine_spec.name)
346
+ if vapp
347
+ vapp.power_off
348
+ vapp.undeploy
349
+ vapp.destroy
350
+ else
351
+ Chef::Log.warn "No VApp named '#{server_name}' was found."
352
+ end
353
+ end
354
+ end
355
+ machine_spec.location = nil
356
+ strategy = convergence_strategy_for(machine_spec, machine_options)
357
+ strategy.cleanup_convergence(action_handler, machine_spec)
358
+ end
359
+
360
+ def self.compute_options_for(provider, id, config)
361
+ new_compute_options = {}
362
+ new_compute_options[:provider] = 'vclouddirector'
363
+ new_config = { :driver_options => { :compute_options => new_compute_options }}
364
+ new_defaults = {
365
+ :driver_options => { :compute_options => {} },
366
+ :machine_options => { :bootstrap_options => {}, :ssh_options => {} }
367
+ }
368
+ result = Cheffish::MergedConfig.new(new_config, config, new_defaults)
369
+
370
+ [result, id]
371
+ end
372
+ end
373
+ end
374
+ end
375
+ end
376
+ end
@@ -23,6 +23,10 @@ class Chef
23
23
  with_fog_driver('Rackspace', driver_options, &block)
24
24
  end
25
25
 
26
+ def with_fog_vcair_driver(driver_options = nil, &block)
27
+ with_fog_driver('Vcair', driver_options, &block)
28
+ end
29
+
26
30
  end
27
31
  end
28
32
  end
@@ -1,7 +1,7 @@
1
1
  class Chef
2
2
  module Provisioning
3
3
  module FogDriver
4
- VERSION = '0.13.2'
4
+ VERSION = '0.14.0'
5
5
  end
6
6
  end
7
7
  end
@@ -8,7 +8,7 @@ describe Chef::Provisioning::FogDriver::Providers::Rackspace do
8
8
  expect(subject).to be_an_instance_of Chef::Provisioning::FogDriver::Providers::Rackspace
9
9
  end
10
10
 
11
- it "has a fog backend" do
11
+ it "has a Fog backend" do
12
12
  pending unless Fog.mock?
13
13
  expect(subject.compute).to be_an_instance_of Fog::Compute::RackspaceV2::Mock
14
14
  end
metadata CHANGED
@@ -1,59 +1,48 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: chef-provisioning-fog
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.13.2
4
+ version: 0.14.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - John Keiser
8
+ - Chris McClimans
9
+ - Taylor Carpenter
10
+ - Wavell Watson
8
11
  autorequire:
9
12
  bindir: bin
10
13
  cert_chain: []
11
- date: 2015-04-02 00:00:00.000000000 Z
14
+ date: 2015-08-12 00:00:00.000000000 Z
12
15
  dependencies:
13
16
  - !ruby/object:Gem::Dependency
14
- name: chef
17
+ name: chef-provisioning
15
18
  requirement: !ruby/object:Gem::Requirement
16
19
  requirements:
17
- - - ">="
20
+ - - "~>"
18
21
  - !ruby/object:Gem::Version
19
- version: '0'
22
+ version: '1.0'
20
23
  type: :runtime
21
24
  prerelease: false
22
25
  version_requirements: !ruby/object:Gem::Requirement
23
26
  requirements:
24
- - - ">="
27
+ - - "~>"
25
28
  - !ruby/object:Gem::Version
26
- version: '0'
29
+ version: '1.0'
27
30
  - !ruby/object:Gem::Dependency
28
- name: cheffish
31
+ name: fog
29
32
  requirement: !ruby/object:Gem::Requirement
30
33
  requirements:
31
34
  - - ">="
32
35
  - !ruby/object:Gem::Version
33
- version: '0.4'
36
+ version: '0'
34
37
  type: :runtime
35
38
  prerelease: false
36
39
  version_requirements: !ruby/object:Gem::Requirement
37
40
  requirements:
38
41
  - - ">="
39
42
  - !ruby/object:Gem::Version
40
- version: '0.4'
41
- - !ruby/object:Gem::Dependency
42
- name: chef-provisioning
43
- requirement: !ruby/object:Gem::Requirement
44
- requirements:
45
- - - "~>"
46
- - !ruby/object:Gem::Version
47
- version: '1.0'
48
- type: :runtime
49
- prerelease: false
50
- version_requirements: !ruby/object:Gem::Requirement
51
- requirements:
52
- - - "~>"
53
- - !ruby/object:Gem::Version
54
- version: '1.0'
43
+ version: '0'
55
44
  - !ruby/object:Gem::Dependency
56
- name: fog
45
+ name: retryable
57
46
  requirement: !ruby/object:Gem::Requirement
58
47
  requirements:
59
48
  - - ">="
@@ -95,7 +84,11 @@ dependencies:
95
84
  - !ruby/object:Gem::Version
96
85
  version: '0'
97
86
  description: Driver for creating Fog instances in Chef Provisioning.
98
- email: jkeiser@getchef.com
87
+ email:
88
+ - jkeiser@getchef.com
89
+ - hh@vulk.co
90
+ - t@vulk.co
91
+ - w@vulk.co
99
92
  executables: []
100
93
  extensions: []
101
94
  extra_rdoc_files:
@@ -112,10 +105,12 @@ files:
112
105
  - lib/chef/provisioning/fog_driver/providers/aws/credentials.rb
113
106
  - lib/chef/provisioning/fog_driver/providers/cloudstack.rb
114
107
  - lib/chef/provisioning/fog_driver/providers/digitalocean.rb
108
+ - lib/chef/provisioning/fog_driver/providers/google.rb
115
109
  - lib/chef/provisioning/fog_driver/providers/joyent.rb
116
110
  - lib/chef/provisioning/fog_driver/providers/openstack.rb
117
111
  - lib/chef/provisioning/fog_driver/providers/rackspace.rb
118
112
  - lib/chef/provisioning/fog_driver/providers/softlayer.rb
113
+ - lib/chef/provisioning/fog_driver/providers/vcair.rb
119
114
  - lib/chef/provisioning/fog_driver/recipe_dsl.rb
120
115
  - lib/chef/provisioning/fog_driver/version.rb
121
116
  - lib/chef/resource/fog_key_pair.rb
@@ -145,9 +140,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
145
140
  version: '0'
146
141
  requirements: []
147
142
  rubyforge_project:
148
- rubygems_version: 2.4.5
143
+ rubygems_version: 2.4.7
149
144
  signing_key:
150
145
  specification_version: 4
151
146
  summary: Driver for creating Fog instances in Chef Provisioning.
152
147
  test_files: []
153
- has_rdoc: