chef-provisioning-aws 1.3.1 → 1.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (94) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +70 -69
  3. data/Rakefile +22 -2
  4. data/lib/chef/provider/aws_auto_scaling_group.rb +3 -2
  5. data/lib/chef/provider/aws_cache_cluster.rb +3 -2
  6. data/lib/chef/provider/aws_cache_replication_group.rb +5 -4
  7. data/lib/chef/provider/aws_cache_subnet_group.rb +5 -4
  8. data/lib/chef/provider/aws_cloudsearch_domain.rb +163 -0
  9. data/lib/chef/provider/aws_dhcp_options.rb +9 -6
  10. data/lib/chef/provider/aws_ebs_volume.rb +7 -3
  11. data/lib/chef/provider/aws_eip_address.rb +8 -7
  12. data/lib/chef/provider/aws_image.rb +8 -3
  13. data/lib/chef/provider/aws_instance.rb +14 -2
  14. data/lib/chef/provider/aws_key_pair.rb +2 -1
  15. data/lib/chef/provider/aws_launch_configuration.rb +4 -2
  16. data/lib/chef/provider/aws_load_balancer.rb +18 -0
  17. data/lib/chef/provider/aws_network_acl.rb +6 -2
  18. data/lib/chef/provider/aws_network_interface.rb +11 -24
  19. data/lib/chef/provider/aws_rds_instance.rb +66 -0
  20. data/lib/chef/provider/aws_rds_subnet_group.rb +89 -0
  21. data/lib/chef/provider/aws_route_table.rb +42 -23
  22. data/lib/chef/provider/aws_s3_bucket.rb +32 -8
  23. data/lib/chef/provider/aws_security_group.rb +11 -4
  24. data/lib/chef/provider/aws_server_certificate.rb +23 -0
  25. data/lib/chef/provider/aws_sns_topic.rb +4 -3
  26. data/lib/chef/provider/aws_sqs_queue.rb +3 -2
  27. data/lib/chef/provider/aws_subnet.rb +10 -7
  28. data/lib/chef/provider/aws_vpc.rb +54 -21
  29. data/lib/chef/provider/aws_vpc_peering_connection.rb +88 -0
  30. data/lib/chef/provisioning/aws_driver.rb +8 -0
  31. data/lib/chef/provisioning/aws_driver/aws_provider.rb +45 -76
  32. data/lib/chef/provisioning/aws_driver/aws_rds_resource.rb +11 -0
  33. data/lib/chef/provisioning/aws_driver/aws_resource.rb +14 -2
  34. data/lib/chef/provisioning/aws_driver/aws_resource_with_entry.rb +2 -8
  35. data/lib/chef/provisioning/aws_driver/aws_taggable.rb +18 -0
  36. data/lib/chef/provisioning/aws_driver/aws_tagger.rb +61 -0
  37. data/lib/chef/provisioning/aws_driver/credentials2.rb +51 -0
  38. data/lib/chef/provisioning/aws_driver/driver.rb +214 -162
  39. data/lib/chef/provisioning/aws_driver/tagging_strategy/ec2.rb +64 -0
  40. data/lib/chef/provisioning/aws_driver/tagging_strategy/elb.rb +39 -0
  41. data/lib/chef/provisioning/aws_driver/tagging_strategy/rds.rb +92 -0
  42. data/lib/chef/provisioning/aws_driver/tagging_strategy/s3.rb +41 -0
  43. data/lib/chef/provisioning/aws_driver/version.rb +1 -1
  44. data/lib/chef/resource/aws_cache_cluster.rb +1 -2
  45. data/lib/chef/resource/aws_cloudsearch_domain.rb +46 -0
  46. data/lib/chef/resource/aws_dhcp_options.rb +2 -0
  47. data/lib/chef/resource/aws_ebs_volume.rb +3 -1
  48. data/lib/chef/resource/aws_eip_address.rb +0 -3
  49. data/lib/chef/resource/aws_image.rb +3 -0
  50. data/lib/chef/resource/aws_instance.rb +7 -2
  51. data/lib/chef/resource/aws_internet_gateway.rb +2 -0
  52. data/lib/chef/resource/aws_load_balancer.rb +3 -0
  53. data/lib/chef/resource/aws_network_acl.rb +2 -0
  54. data/lib/chef/resource/aws_network_interface.rb +3 -1
  55. data/lib/chef/resource/aws_rds_instance.rb +42 -0
  56. data/lib/chef/resource/aws_rds_subnet_group.rb +29 -0
  57. data/lib/chef/resource/aws_route_table.rb +7 -5
  58. data/lib/chef/resource/aws_s3_bucket.rb +3 -0
  59. data/lib/chef/resource/aws_security_group.rb +2 -7
  60. data/lib/chef/resource/aws_server_certificate.rb +21 -0
  61. data/lib/chef/resource/aws_subnet.rb +2 -0
  62. data/lib/chef/resource/aws_vpc.rb +4 -1
  63. data/lib/chef/resource/aws_vpc_peering_connection.rb +73 -0
  64. data/spec/acceptance/aws_ebs_volume/nodes/ettores-mbp.lan.json +3 -0
  65. data/spec/aws_support.rb +25 -8
  66. data/spec/aws_support/aws_resource_run_wrapper.rb +5 -1
  67. data/spec/aws_support/deep_matcher/match_values_failure_messages.rb +19 -0
  68. data/spec/aws_support/matchers/create_an_aws_object.rb +1 -1
  69. data/spec/aws_support/matchers/destroy_an_aws_object.rb +1 -1
  70. data/spec/aws_support/matchers/have_aws_object_tags.rb +9 -15
  71. data/spec/aws_support/matchers/match_an_aws_object.rb +1 -1
  72. data/spec/aws_support/matchers/update_an_aws_object.rb +1 -1
  73. data/spec/integration/aws_cloudsearch_domain_spec.rb +31 -0
  74. data/spec/integration/aws_dhcp_options_spec.rb +73 -0
  75. data/spec/integration/aws_ebs_volume_spec.rb +97 -0
  76. data/spec/integration/aws_network_acl_spec.rb +51 -0
  77. data/spec/integration/aws_network_interface_spec.rb +89 -0
  78. data/spec/integration/aws_rds_instance_spec.rb +150 -0
  79. data/spec/integration/aws_rds_subnet_group_spec.rb +105 -0
  80. data/spec/integration/aws_route_table_spec.rb +94 -7
  81. data/spec/integration/aws_s3_bucket_spec.rb +88 -0
  82. data/spec/integration/aws_security_group_spec.rb +47 -0
  83. data/spec/integration/aws_server_certificate_spec.rb +24 -0
  84. data/spec/integration/aws_subnet_spec.rb +51 -2
  85. data/spec/integration/aws_vpc_peering_connection_spec.rb +99 -0
  86. data/spec/integration/aws_vpc_spec.rb +73 -0
  87. data/spec/integration/load_balancer_spec.rb +101 -0
  88. data/spec/integration/machine_image_spec.rb +61 -6
  89. data/spec/integration/machine_spec.rb +26 -0
  90. data/spec/spec_helper.rb +3 -0
  91. data/spec/unit/{aws_driver → chef/provisioning/aws_driver}/credentials_spec.rb +0 -0
  92. data/spec/unit/chef/provisioning/aws_driver/driver_spec.rb +88 -0
  93. metadata +63 -20
  94. data/spec/integration/aws_tagged_items_spec.rb +0 -166
@@ -0,0 +1,51 @@
1
+ require "aws-sdk"
2
+ require "aws-sdk-core/credentials"
3
+ require "aws-sdk-core/shared_credentials"
4
+ require "aws-sdk-core/instance_profile_credentials"
5
+ require "aws-sdk-core/assume_role_credentials"
6
+
7
+ class Chef
8
+ module Provisioning
9
+ module AWSDriver
10
+
11
+ class LoadCredentialsError < RuntimeError; end
12
+
13
+ # Loads the credentials for the AWS SDK V2
14
+ # Attempts to load credentials in the order specified at http://docs.aws.amazon.com/sdkforruby/api/index.html#Configuration
15
+ class Credentials2
16
+
17
+ attr_reader :profile_name
18
+
19
+ # @param [Hash] options
20
+ # @option options [String] :profile_name (ENV["AWS_DEFAULT_PROFILE"]) The profile name to use
21
+ # when loading the config from '~/.aws/credentials'. This can be nil.
22
+ def initialize(options = {})
23
+ @profile_name = options[:profile_name] || ENV["AWS_DEFAULT_PROFILE"]
24
+ end
25
+
26
+ # Try to load the credentials from an ordered list of sources and return the first one that
27
+ # can be loaded successfully.
28
+ def get_credentials
29
+ shared_creds = ::Aws::SharedCredentials.new(:profile_name => profile_name)
30
+ instance_profile_creds = ::Aws::InstanceProfileCredentials.new(:retries => 1)
31
+
32
+ if ENV["AWS_ACCESS_KEY_ID"] && ENV["AWS_SECRET_ACCESS_KEY"]
33
+ creds = ::Aws::Credentials.new(
34
+ ENV["AWS_ACCESS_KEY_ID"],
35
+ ENV["AWS_SECRET_ACCESS_KEY"],
36
+ ENV["AWS_SESSION_TOKEN"]
37
+ )
38
+ elsif shared_creds.set?
39
+ creds = shared_creds
40
+ elsif instance_profile_creds.set?
41
+ creds = instance_profile_creds
42
+ else
43
+ raise LoadCredentialsError.new("Could not load credentials from the environment variables, the .aws/credentials file or the metadata service")
44
+ end
45
+ creds
46
+ end
47
+ end
48
+
49
+ end
50
+ end
51
+ end
@@ -11,21 +11,50 @@ require 'chef/provisioning/machine/windows_machine'
11
11
  require 'chef/provisioning/machine/unix_machine'
12
12
  require 'chef/provisioning/machine_spec'
13
13
 
14
- require 'chef/resource/aws_key_pair'
15
- require 'chef/resource/aws_instance'
16
- require 'chef/resource/aws_image'
17
- require 'chef/resource/aws_load_balancer'
18
14
  require 'chef/provisioning/aws_driver/aws_resource'
15
+ require 'chef/provisioning/aws_driver/tagging_strategy/ec2'
16
+ require 'chef/provisioning/aws_driver/tagging_strategy/elb'
19
17
  require 'chef/provisioning/aws_driver/version'
20
18
  require 'chef/provisioning/aws_driver/credentials'
19
+ require 'chef/provisioning/aws_driver/credentials2'
20
+ require 'chef/provisioning/aws_driver/aws_tagger'
21
21
 
22
22
  require 'yaml'
23
23
  require 'aws-sdk-v1'
24
+ require 'aws-sdk'
24
25
  require 'retryable'
25
26
  require 'ubuntu_ami'
26
27
 
27
28
  # loads the entire aws-sdk
28
29
  AWS.eager_autoload!
30
+ AWS_V2_SERVICES = {"EC2" => "ec2", "S3" => "s3", "ElasticLoadBalancing" => "elb"}
31
+ Aws.eager_autoload!(:services => AWS_V2_SERVICES.keys)
32
+
33
+ # Need to load the resources after the SDK because `aws_sdk_types` can mess
34
+ # up AWS loading if they are loaded too early
35
+ require 'chef/resource/aws_key_pair'
36
+ require 'chef/resource/aws_instance'
37
+ require 'chef/resource/aws_image'
38
+ require 'chef/resource/aws_load_balancer'
39
+
40
+ # We add the appropriate attributes to the base resources for tagging support
41
+ class Chef
42
+ class Resource
43
+ class Machine
44
+ include Chef::Provisioning::AWSDriver::AWSTaggable
45
+ end
46
+ class MachineImage
47
+ include Chef::Provisioning::AWSDriver::AWSTaggable
48
+ end
49
+ class LoadBalancer
50
+ include Chef::Provisioning::AWSDriver::AWSTaggable
51
+ end
52
+ end
53
+ end
54
+
55
+ Chef::Provider::Machine.additional_machine_option_keys << :aws_tags
56
+ Chef::Provider::MachineImage.additional_image_option_keys << :aws_tags
57
+ Chef::Provider::LoadBalancer.additional_lb_option_keys << :aws_tags
29
58
 
30
59
  class Chef
31
60
  module Provisioning
@@ -62,6 +91,20 @@ module AWSDriver
62
91
  session_token: credentials[:aws_session_token] || nil,
63
92
  logger: Chef::Log.logger
64
93
  )
94
+
95
+ # TODO document how users could add something to the Aws.config themselves if they want to
96
+ # Right now we are supporting both V1 and V2, so we create 2 config sets
97
+ credentials2 = Credentials2.new(:profile_name => profile_name)
98
+ Chef::Config.chef_provisioning ||= {}
99
+ ::Aws.config.update(
100
+ credentials: credentials2.get_credentials,
101
+ region: region || ENV["AWS_DEFAULT_REGION"] || credentials[:region],
102
+ # TODO when we get rid of V1 replace the credentials class with something that knows how
103
+ # to read ~/.aws/config
104
+ :http_proxy => credentials[:proxy_uri] || nil,
105
+ logger: Chef::Log.logger,
106
+ retry_limit: Chef::Config.chef_provisioning[:aws_retry_limit] || 5
107
+ )
65
108
  end
66
109
 
67
110
  def self.canonicalize_url(driver_url, config)
@@ -70,10 +113,13 @@ module AWSDriver
70
113
 
71
114
  # Load balancer methods
72
115
  def allocate_load_balancer(action_handler, lb_spec, lb_options, machine_specs)
73
- lb_options = AWSResource.lookup_options(lb_options || {}, managed_entry_store: lb_spec.managed_entry_store, driver: self)
74
- # We delete the attributes here because they are not valid in the create call
116
+ lb_options = (lb_options || {}).to_h
117
+ lb_options = AWSResource.lookup_options(lb_options, managed_entry_store: lb_spec.managed_entry_store, driver: self)
118
+ # We delete the attributes and a health check here because they are not valid in the create call
75
119
  # and must be applied afterward
76
120
  lb_attributes = lb_options.delete(:attributes)
121
+ lb_aws_tags = lb_options.delete(:aws_tags)
122
+ health_check = lb_options.delete(:health_check)
77
123
 
78
124
  old_elb = nil
79
125
  actual_elb = load_balancer_for(lb_spec)
@@ -93,12 +139,8 @@ module AWSDriver
93
139
  updates << " with security groups #{lb_options[:security_groups]}" if lb_options[:security_groups]
94
140
  updates << " with tags #{lb_options[:aws_tags]}" if lb_options[:aws_tags]
95
141
 
96
-
97
- lb_aws_tags = lb_options[:aws_tags]
98
- lb_options.delete(:aws_tags)
99
142
  action_handler.perform_action updates do
100
143
  actual_elb = elb.load_balancers.create(lb_spec.name, lb_options)
101
- lb_options[:aws_tags] = lb_aws_tags
102
144
 
103
145
  lb_spec.reference = {
104
146
  'driver_version' => Chef::Provisioning::AWSDriver::VERSION,
@@ -281,34 +323,7 @@ module AWSDriver
281
323
  end
282
324
  end
283
325
 
284
- # GRRRR curse you AWS and your crappy tagging support for ELBs
285
- read_tags_block = lambda {|aws_object|
286
- resp = elb.client.describe_tags load_balancer_names: [aws_object.name]
287
- tags = {}
288
- resp.data[:tag_descriptions] && resp.data[:tag_descriptions].each do |td|
289
- td[:tags].each do |t|
290
- tags[t[:key]] = t[:value]
291
- end
292
- end
293
- tags
294
- }
295
-
296
- set_tags_block = lambda {|aws_object, desired_tags|
297
- aws_form_tags = []
298
- desired_tags.each do |k, v|
299
- aws_form_tags << {key: k, value: v}
300
- end
301
- elb.client.add_tags load_balancer_names: [aws_object.name], tags: aws_form_tags
302
- }
303
-
304
- delete_tags_block=lambda {|aws_object, tags_to_delete|
305
- aws_form_tags = []
306
- tags_to_delete.each do |k, v|
307
- aws_form_tags << {key: k}
308
- end
309
- elb.client.remove_tags load_balancer_names: [aws_object.name], tags: aws_form_tags
310
- }
311
- converge_tags(actual_elb, lb_options[:aws_tags], action_handler, read_tags_block, set_tags_block, delete_tags_block)
326
+ converge_elb_tags(actual_elb, lb_aws_tags, action_handler)
312
327
 
313
328
  # Update load balancer attributes
314
329
  if lb_attributes
@@ -325,12 +340,26 @@ module AWSDriver
325
340
  end
326
341
  end
327
342
 
343
+ # Update the load balancer health check, as above
344
+ if health_check
345
+ current = elb.client.describe_load_balancers(load_balancer_names: [actual_elb.name])[:load_balancer_descriptions][0][:health_check]
346
+ desired = deep_merge!(health_check, Marshal.load(Marshal.dump(current)))
347
+ if current != desired
348
+ perform_action.call(" updating health check to #{desired.inspect}") do
349
+ elb.client.configure_health_check(
350
+ load_balancer_name: actual_elb.name,
351
+ health_check: desired.to_hash
352
+ )
353
+ end
354
+ end
355
+ end
356
+
328
357
  # Update instance list, but only if there are machines specified
329
358
  if machine_specs
330
- actual_instance_ids = actual_elb.instances.map { |i| i.instance_id }
359
+ assigned_instance_ids = actual_elb.instances.map { |i| i.instance_id }
331
360
 
332
- instances_to_add = machine_specs.select { |s| !actual_instance_ids.include?(s.reference['instance_id']) }
333
- instance_ids_to_remove = actual_instance_ids - machine_specs.map { |s| s.reference['instance_id'] }
361
+ instances_to_add = machine_specs.select { |s| !assigned_instance_ids.include?(s.reference['instance_id']) }
362
+ instance_ids_to_remove = assigned_instance_ids - machine_specs.map { |s| s.reference['instance_id'] }
334
363
 
335
364
  if instances_to_add.size > 0
336
365
  perform_action.call(" add machines #{instances_to_add.map { |s| s.name }.join(', ')}") do
@@ -410,6 +439,8 @@ module AWSDriver
410
439
  # Image methods
411
440
  def allocate_image(action_handler, image_spec, image_options, machine_spec, machine_options)
412
441
  actual_image = image_for(image_spec)
442
+ image_options = (image_options || {}).to_h.dup
443
+ machine_options = (machine_options || {}).to_h.dup
413
444
  aws_tags = image_options.delete(:aws_tags) || {}
414
445
  if actual_image.nil? || !actual_image.exists? || actual_image.state == :failed
415
446
  action_handler.perform_action "Create image #{image_spec.name} from machine #{machine_spec.name} with options #{image_options.inspect}" do
@@ -421,13 +452,14 @@ module AWSDriver
421
452
  image_spec.reference = {
422
453
  'driver_version' => Chef::Provisioning::AWSDriver::VERSION,
423
454
  'image_id' => actual_image.id,
424
- 'allocated_at' => Time.now.to_i
455
+ 'allocated_at' => Time.now.to_i,
456
+ 'from-instance' => image_options[:instance_id]
425
457
  }
426
458
  image_spec.driver_url = driver_url
427
459
  end
428
460
  end
429
- aws_tags['From-Instance'] = image_options[:instance_id] if image_options[:instance_id]
430
- converge_tags(actual_image, aws_tags, action_handler)
461
+ aws_tags['from-instance'] = image_options[:instance_id] if image_options[:instance_id]
462
+ converge_ec2_tags(actual_image, aws_tags, action_handler)
431
463
  end
432
464
 
433
465
  def ready_image(action_handler, image_spec, image_options)
@@ -435,11 +467,13 @@ module AWSDriver
435
467
  if actual_image.nil? || !actual_image.exists?
436
468
  raise 'Cannot ready an image that does not exist'
437
469
  else
470
+ image_options = (image_options || {}).to_h.dup
471
+ aws_tags = image_options.delete(:aws_tags) || {}
472
+ aws_tags['from-instance'] = image_spec.reference['from-instance'] if image_spec.reference['from-instance']
473
+ converge_ec2_tags(actual_image, aws_tags, action_handler)
438
474
  if actual_image.state != :available
439
475
  action_handler.report_progress 'Waiting for image to be ready ...'
440
476
  wait_until_ready_image(action_handler, image_spec, actual_image)
441
- else
442
- action_handler.report_progress "Image #{image_spec.name} is ready!"
443
477
  end
444
478
  end
445
479
  end
@@ -477,38 +511,16 @@ EOD
477
511
 
478
512
  # Machine methods
479
513
  def allocate_machine(action_handler, machine_spec, machine_options)
480
- actual_instance = instance_for(machine_spec)
514
+ instance = instance_for(machine_spec)
481
515
  bootstrap_options = bootstrap_options_for(action_handler, machine_spec, machine_options)
482
516
 
483
- if actual_instance == nil || !actual_instance.exists? || actual_instance.status == :terminated
484
-
517
+ if instance == nil || !instance.exists? || instance.state.name == "terminated"
485
518
  action_handler.perform_action "Create #{machine_spec.name} with AMI #{bootstrap_options[:image_id]} in #{aws_config.region}" do
486
519
  Chef::Log.debug "Creating instance with bootstrap options #{bootstrap_options}"
487
-
488
- actual_instance = ec2.instances.create(bootstrap_options.to_hash)
489
- # Make sure the instance is ready to be tagged
490
- wait_until_taggable(actual_instance)
491
-
492
- # TODO add other tags identifying user / node url (same as fog)
493
- actual_instance.tags['Name'] = machine_spec.name
494
- actual_instance.source_dest_check = machine_options[:source_dest_check] if machine_options.has_key?(:source_dest_check)
495
- machine_spec.reference = {
496
- 'driver_version' => Chef::Provisioning::AWSDriver::VERSION,
497
- 'allocated_at' => Time.now.utc.to_s,
498
- 'host_node' => action_handler.host_node,
499
- 'image_id' => bootstrap_options[:image_id],
500
- 'instance_id' => actual_instance.id
501
- }
502
- machine_spec.driver_url = driver_url
503
- machine_spec.reference['key_name'] = bootstrap_options[:key_name] if bootstrap_options[:key_name]
504
- %w(is_windows ssh_username sudo use_private_ip_for_ssh ssh_gateway).each do |key|
505
- machine_spec.reference[key] = machine_options[key.to_sym] if machine_options[key.to_sym]
506
- end
520
+ instance = create_instance_and_reference(bootstrap_options, action_handler, machine_spec, machine_options)
507
521
  end
508
522
  end
509
- # TODO because we don't want to add `provider_tags` as a base attribute,
510
- # we have to update the tags here in driver.rb instead of the providers
511
- converge_tags(actual_instance, machine_options[:aws_tags], action_handler)
523
+ converge_ec2_tags(instance, machine_options[:aws_tags], action_handler)
512
524
  end
513
525
 
514
526
  def allocate_machines(action_handler, specs_and_options, parallelizer)
@@ -520,14 +532,15 @@ EOD
520
532
 
521
533
  def ready_machine(action_handler, machine_spec, machine_options)
522
534
  instance = instance_for(machine_spec)
535
+ converge_ec2_tags(instance, machine_options[:aws_tags], action_handler)
523
536
 
524
537
  if instance.nil?
525
538
  raise "Machine #{machine_spec.name} does not have an instance associated with it, or instance does not exist."
526
539
  end
527
540
 
528
- if instance.status != :running
529
- wait_until_machine(action_handler, machine_spec, instance) { instance.status != :stopping }
530
- if instance.status == :stopped
541
+ if instance.state.name != "running"
542
+ wait_until_machine(action_handler, machine_spec, "finish stopping", instance) { instance.state.name != "stopping" }
543
+ if instance.state.name == "stopped"
531
544
  action_handler.perform_action "Start #{machine_spec.name} (#{machine_spec.reference['instance_id']}) in #{aws_config.region} ..." do
532
545
  instance.start
533
546
  end
@@ -563,10 +576,30 @@ EOD
563
576
  strategy.cleanup_convergence(action_handler, machine_spec)
564
577
  end
565
578
 
579
+ def cloudsearch(api_version="20130101")
580
+ @cloudsearch ||= {}
581
+ @cloudsearch[api_version] ||= AWS::CloudSearch::Client.const_get("V#{api_version}").new
582
+ @cloudsearch[api_version]
583
+ end
584
+
566
585
  def ec2
567
586
  @ec2 ||= AWS::EC2.new(config: aws_config)
568
587
  end
569
588
 
589
+ AWS_V2_SERVICES.each do |load_name, short_name|
590
+ class_eval <<-META
591
+
592
+ def #{short_name}_client
593
+ @#{short_name}_client ||= ::Aws::#{load_name}::Client.new
594
+ end
595
+
596
+ def #{short_name}_resource
597
+ @#{short_name}_resource ||= ::Aws::#{load_name}::Resource.new(#{short_name}_client)
598
+ end
599
+
600
+ META
601
+ end
602
+
570
603
  def elb
571
604
  @elb ||= AWS::ELB.new(config: aws_config)
572
605
  end
@@ -579,10 +612,18 @@ EOD
579
612
  @iam ||= AWS::IAM.new(config: aws_config)
580
613
  end
581
614
 
615
+ def rds
616
+ @rds ||= AWS::RDS.new(config: aws_config)
617
+ end
618
+
582
619
  def s3
583
620
  @s3 ||= AWS::S3.new(config: aws_config)
584
621
  end
585
622
 
623
+ def rds
624
+ @rds ||= AWS::RDS.new(config: aws_config)
625
+ end
626
+
586
627
  def sns
587
628
  @sns ||= AWS::SNS.new(config: aws_config)
588
629
  end
@@ -645,8 +686,10 @@ EOD
645
686
 
646
687
  def bootstrap_options_for(action_handler, machine_spec, machine_options)
647
688
  bootstrap_options = (machine_options[:bootstrap_options] || {}).to_h.dup
689
+ # These are hardcoded for now - only 1 machine at a time
690
+ bootstrap_options[:min_count] = bootstrap_options[:max_count] = 1
648
691
  bootstrap_options[:instance_type] ||= default_instance_type
649
- image_id = bootstrap_options[:image_id] || machine_options[:image_id] || default_ami_for_region(aws_config.region)
692
+ image_id = machine_options[:from_image] || bootstrap_options[:image_id] || machine_options[:image_id] || default_ami_for_region(aws_config.region)
650
693
  bootstrap_options[:image_id] = image_id
651
694
  if !bootstrap_options[:key_name]
652
695
  Chef::Log.debug('No key specified, generating a default one...')
@@ -834,10 +877,19 @@ EOD
834
877
  end
835
878
 
836
879
  def determine_remote_host(machine_spec, instance)
880
+ transport_address_location = (machine_spec.reference['transport_address_location'] || :none).to_sym
837
881
  if machine_spec.reference['use_private_ip_for_ssh']
882
+ # The machine_spec has the old config key, lets update it - a successful chef converge will save the machine_spec
883
+ # TODO in 2.0 get rid of this update
884
+ machine_spec.reference.delete('use_private_ip_for_ssh')
885
+ machine_spec.reference['transport_address_location'] = :private_ip
838
886
  instance.private_ip_address
839
- elsif !instance.public_ip_address
840
- Chef::Log.warn("Server #{machine_spec.name} has no public ip address. Using private ip '#{instance.private_ip_address}'. Set driver option 'use_private_ip_for_ssh' => true if this will always be the case ...")
887
+ elsif transport_address_location == :private_ip
888
+ instance.private_ip_address
889
+ elsif transport_address_location == :dns
890
+ instance.dns_name
891
+ elsif !instance.public_ip_address && instance.private_ip_address
892
+ Chef::Log.warn("Server #{machine_spec.name} has no public ip address. Using private ip '#{instance.private_ip_address}'. Set machine_options ':transport_address_location => :private_ip' if this will always be the case ...")
841
893
  instance.private_ip_address
842
894
  elsif instance.public_ip_address
843
895
  instance.public_ip_address
@@ -924,28 +976,31 @@ EOD
924
976
  end
925
977
 
926
978
  def wait_until_ready_machine(action_handler, machine_spec, instance=nil)
927
- wait_until_machine(action_handler, machine_spec, instance) { instance.status == :running }
979
+ wait_until_machine(action_handler, machine_spec, "be ready", instance) { |instance|
980
+ instance.state.name == "running"
981
+ }
928
982
  end
929
983
 
930
- def wait_until_machine(action_handler, machine_spec, instance=nil, &block)
984
+ def wait_until_machine(action_handler, machine_spec, output_msg, instance=nil, &block)
931
985
  instance ||= instance_for(machine_spec)
932
- time_elapsed = 0
933
- sleep_time = 10
934
- max_wait_time = 120
935
- if !yield(instance)
936
- if action_handler.should_perform_actions
937
- action_handler.report_progress "waiting for #{machine_spec.name} (#{instance.id} on #{driver_url}) to be ready ..."
938
- while time_elapsed < max_wait_time && !yield(instance)
939
- action_handler.report_progress "been waiting #{time_elapsed}/#{max_wait_time} -- sleeping #{sleep_time} seconds for #{machine_spec.name} (#{instance.id} on #{driver_url}) to be ready ..."
940
- sleep(sleep_time)
941
- time_elapsed += sleep_time
942
- end
943
- unless yield(instance)
944
- raise "Image #{instance.id} did not become ready within #{max_wait_time} seconds"
986
+ # TODO make these configurable from Chef::Config
987
+ max_attempts = 12
988
+ delay = 10
989
+ log_progress = Proc.new do |attempts, response|
990
+ action_handler.report_progress "been waiting #{delay*attempts}/#{delay*max_attempts} -- sleeping #{delay} seconds for #{machine_spec.name} (#{instance.id} on #{driver_url}) to #{output_msg} ..."
991
+ end
992
+ if action_handler.should_perform_actions
993
+ no_wait = yield(instance)
994
+ unless no_wait
995
+ action_handler.report_progress "waiting for #{machine_spec.name} (#{instance.id} on #{driver_url}) to #{output_msg} ..."
996
+ instance.wait_until(:max_attempts => max_attempts, :delay => delay, before_wait: log_progress) do |instance|
997
+ yield(instance)
945
998
  end
946
- action_handler.report_progress "#{machine_spec.name} is now ready"
947
999
  end
948
1000
  end
1001
+ # We need an instance.reload here because the `wait_until` does not reload the instance it is called on,
1002
+ # only the instance that is passed to the block
1003
+ instance.reload
949
1004
  end
950
1005
 
951
1006
  def wait_for_transport(action_handler, machine_spec, machine_options)
@@ -996,18 +1051,20 @@ EOD
996
1051
  default_key_name
997
1052
  end
998
1053
 
999
- def create_servers(action_handler, specs_and_options, parallelizer, &block)
1054
+ def create_servers(action_handler, specs_and_options, parallelizer)
1000
1055
  specs_and_servers = instances_for(specs_and_options.keys)
1001
1056
 
1002
1057
  by_bootstrap_options = {}
1003
1058
  specs_and_options.each do |machine_spec, machine_options|
1004
- actual_instance = specs_and_servers[machine_spec]
1005
- if actual_instance
1006
- if actual_instance.status == :terminated
1007
- Chef::Log.warn "Machine #{machine_spec.name} (#{actual_instance.id}) is terminated. Recreating ..."
1059
+ instance = specs_and_servers[machine_spec]
1060
+ if instance
1061
+ if instance.state.name == "terminated"
1062
+ Chef::Log.warn "Machine #{machine_spec.name} (#{instance.id}) is terminated. Recreating ..."
1008
1063
  else
1009
- converge_tags(actual_instance, machine_options[:aws_tags], action_handler)
1010
- yield machine_spec, actual_instance if block_given?
1064
+ # Even though the instance has been created the tags could be incorrect if it
1065
+ # was created before tags were introduced
1066
+ converge_ec2_tags(instance, machine_options[:aws_tags], action_handler)
1067
+ yield machine_spec, instance if block_given?
1011
1068
  next
1012
1069
  end
1013
1070
  elsif machine_spec.reference
@@ -1031,30 +1088,20 @@ EOD
1031
1088
  action_handler.report_progress description
1032
1089
  if action_handler.should_perform_actions
1033
1090
  # Actually create the servers
1034
- create_many_instances(machine_specs.size, bootstrap_options, parallelizer) do |instance|
1091
+ parallelizer.parallelize(1.upto(machine_specs.size)) do |i|
1035
1092
 
1036
1093
  # Assign each one to a machine spec
1037
1094
  machine_spec = machine_specs.pop
1038
1095
  machine_options = specs_and_options[machine_spec]
1039
- machine_spec.reference = {
1040
- 'driver_version' => Chef::Provisioning::AWSDriver::VERSION,
1041
- 'allocated_at' => Time.now.utc.to_s,
1042
- 'host_node' => action_handler.host_node,
1043
- 'image_id' => bootstrap_options[:image_id],
1044
- 'instance_id' => instance.id
1045
- }
1046
- machine_spec.driver_url = driver_url
1047
- instance.tags['Name'] = machine_spec.name
1048
- instance.source_dest_check = machine_options[:source_dest_check] if machine_options.has_key?(:source_dest_check)
1049
- converge_tags(instance, machine_options[:aws_tags], action_handler)
1050
- machine_spec.reference['key_name'] = bootstrap_options[:key_name] if bootstrap_options[:key_name]
1051
- %w(is_windows ssh_username sudo use_private_ip_for_ssh ssh_gateway).each do |key|
1052
- machine_spec.reference[key] = machine_options[key.to_sym] if machine_options[key.to_sym]
1053
- end
1096
+
1097
+ clean_bootstrap_options = Marshal.load(Marshal.dump(bootstrap_options))
1098
+ instance = create_instance_and_reference(clean_bootstrap_options, action_handler, machine_spec, machine_options)
1099
+ converge_ec2_tags(instance, machine_options[:aws_tags], action_handler)
1100
+
1054
1101
  action_handler.performed_action "machine #{machine_spec.name} created as #{instance.id} on #{driver_url}"
1055
1102
 
1056
1103
  yield machine_spec, instance if block_given?
1057
- end
1104
+ end.to_a
1058
1105
 
1059
1106
  if machine_specs.size > 0
1060
1107
  raise "Not all machines were created by create_servers"
@@ -1063,58 +1110,63 @@ EOD
1063
1110
  end.to_a
1064
1111
  end
1065
1112
 
1066
- def create_many_instances(num_servers, bootstrap_options, parallelizer)
1067
- parallelizer.parallelize(1.upto(num_servers)) do |i|
1068
- clean_bootstrap_options = Marshal.load(Marshal.dump(bootstrap_options))
1069
- instance = ec2.instances.create(clean_bootstrap_options.to_hash)
1070
- wait_until_taggable(instance)
1113
+ def converge_ec2_tags(aws_object, tags, action_handler)
1114
+ ec2_strategy = Chef::Provisioning::AWSDriver::TaggingStrategy::EC2.new(
1115
+ ec2_client,
1116
+ aws_object.id,
1117
+ tags
1118
+ )
1119
+ aws_tagger = Chef::Provisioning::AWSDriver::AWSTagger.new(ec2_strategy, action_handler)
1120
+ aws_tagger.converge_tags
1121
+ end
1071
1122
 
1072
- yield instance if block_given?
1073
- instance
1074
- end.to_a
1123
+ def converge_elb_tags(aws_object, tags, action_handler)
1124
+ elb_strategy = Chef::Provisioning::AWSDriver::TaggingStrategy::ELB.new(
1125
+ elb_client,
1126
+ aws_object.name,
1127
+ tags
1128
+ )
1129
+ aws_tagger = Chef::Provisioning::AWSDriver::AWSTagger.new(elb_strategy, action_handler)
1130
+ aws_tagger.converge_tags
1075
1131
  end
1076
1132
 
1077
- # TODO This is currently duplicated from AWS Provider
1078
- # Set the tags on the aws object to desired_tags, while ignoring any `Name` tag
1079
- # If no tags need to be modified, will not perform a write call on AWS
1080
- def converge_tags(
1081
- aws_object,
1082
- desired_tags,
1083
- action_handler,
1084
- read_tags_block=lambda {|aws_object| aws_object.tags.to_h},
1085
- set_tags_block=lambda {|aws_object, desired_tags| aws_object.tags.set(desired_tags) },
1086
- delete_tags_block=lambda {|aws_object, tags_to_delete| aws_object.tags.delete(*tags_to_delete) }
1087
- )
1088
- # If aws_tags were not provided we exit
1089
- if desired_tags.nil?
1090
- Chef::Log.debug "aws_tags not provided, nothing to converge"
1091
- return
1133
+ def create_instance_and_reference(bootstrap_options, action_handler, machine_spec, machine_options)
1134
+ instance = ec2_resource.create_instances(bootstrap_options.to_hash)[0]
1135
+ # Make sure the instance is ready to be tagged
1136
+ instance.wait_until_exists
1137
+
1138
+ # Sometimes tagging fails even though the instance 'exists'
1139
+ Retryable.retryable(:tries => 12, :sleep => 5, :on => [::Aws::EC2::Errors::InvalidInstanceIDNotFound]) do
1140
+ instance.create_tags({tags: [{key: "Name", value: machine_spec.name}]})
1092
1141
  end
1093
- current_tags = read_tags_block.call(aws_object)
1094
- # AWS always returns tags as strings, and we don't want to overwrite a
1095
- # tag-as-string with the same tag-as-symbol
1096
- desired_tags = Hash[desired_tags.map {|k, v| [k.to_s, v.to_s] }]
1097
- tags_to_update = desired_tags.reject {|k,v| current_tags[k] == v}
1098
- tags_to_delete = current_tags.keys - desired_tags.keys
1099
- # We don't want to delete `Name`, just all other tags
1100
- tags_to_delete.delete('Name')
1101
-
1102
- unless tags_to_update.empty?
1103
- action_handler.perform_action "applying tags #{tags_to_update}" do
1104
- set_tags_block.call(aws_object, tags_to_update)
1105
- end
1142
+ if machine_options.has_key?(:source_dest_check)
1143
+ instance.modify_attribute({
1144
+ source_dest_check: {
1145
+ value: machine_options[:source_dest_check]
1146
+ }
1147
+ })
1106
1148
  end
1107
- unless tags_to_delete.empty?
1108
- action_handler.perform_action "deleting tags #{tags_to_delete.inspect}" do
1109
- delete_tags_block.call(aws_object, tags_to_delete)
1149
+ machine_spec.reference = {
1150
+ 'driver_version' => Chef::Provisioning::AWSDriver::VERSION,
1151
+ 'allocated_at' => Time.now.utc.to_s,
1152
+ 'host_node' => action_handler.host_node,
1153
+ 'image_id' => bootstrap_options[:image_id],
1154
+ 'instance_id' => instance.id
1155
+ }
1156
+ machine_spec.driver_url = driver_url
1157
+ machine_spec.reference['key_name'] = bootstrap_options[:key_name] if bootstrap_options[:key_name]
1158
+ # TODO 2.0 We no longer support `use_private_ip_for_ssh`, only `transport_address_location`
1159
+ if machine_options[:use_private_ip_for_ssh]
1160
+ unless @transport_address_location_warned
1161
+ Chef::Log.warn("The machine_option ':use_private_ip_for_ssh' has been deprecated, use ':transport_address_location'")
1162
+ @transport_address_location_warned = true
1110
1163
  end
1164
+ machine_options = Cheffish::MergedConfig.new(machine_options, {:transport_address_location => :private_ip})
1111
1165
  end
1112
- end
1113
-
1114
- def wait_until_taggable(instance)
1115
- Retryable.retryable(:tries => 12, :sleep => 5, :on => [AWS::EC2::Errors::InvalidInstanceID::NotFound, TimeoutError]) do
1116
- raise TimeoutError unless instance.status == :pending || instance.status == :running
1166
+ %w(is_windows ssh_username sudo transport_address_location ssh_gateway).each do |key|
1167
+ machine_spec.reference[key] = machine_options[key.to_sym] if machine_options[key.to_sym]
1117
1168
  end
1169
+ instance
1118
1170
  end
1119
1171
 
1120
1172
  def get_listeners(listeners)