chef-provisioning-fog 0.13.2 → 0.14.0

Sign up to get free protection for your applications and to get access to all the features.
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: