chef-provisioning-aws 1.3.1 → 1.4.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.
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)