chef-provisioning-aws 1.0.4 → 1.1.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 (44) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +18 -0
  3. data/Rakefile +5 -0
  4. data/lib/chef/provider/aws_ebs_volume.rb +14 -4
  5. data/lib/chef/provider/aws_image.rb +31 -0
  6. data/lib/chef/provider/aws_instance.rb +14 -0
  7. data/lib/chef/provider/aws_load_balancer.rb +9 -0
  8. data/lib/chef/provider/aws_network_interface.rb +209 -0
  9. data/lib/chef/provider/aws_security_group.rb +9 -4
  10. data/lib/chef/provider/aws_subnet.rb +16 -1
  11. data/lib/chef/provider/aws_vpc.rb +16 -0
  12. data/lib/chef/provisioning/aws_driver/aws_provider.rb +44 -0
  13. data/lib/chef/provisioning/aws_driver/aws_resource.rb +1 -1
  14. data/lib/chef/provisioning/aws_driver/driver.rb +6 -5
  15. data/lib/chef/provisioning/aws_driver/version.rb +1 -1
  16. data/lib/chef/resource/aws_image.rb +1 -2
  17. data/lib/chef/resource/aws_instance.rb +1 -2
  18. data/lib/chef/resource/aws_load_balancer.rb +1 -1
  19. data/lib/chef/resource/aws_network_interface.rb +23 -5
  20. data/lib/chef/resource/aws_vpc.rb +0 -8
  21. data/spec/aws_support.rb +235 -0
  22. data/spec/aws_support/aws_resource_run_wrapper.rb +45 -0
  23. data/spec/aws_support/deep_matcher.rb +40 -0
  24. data/spec/aws_support/deep_matcher/fuzzy_match_objects.rb +57 -0
  25. data/spec/aws_support/deep_matcher/match_values_failure_messages.rb +145 -0
  26. data/spec/aws_support/deep_matcher/matchable_array.rb +24 -0
  27. data/spec/aws_support/deep_matcher/matchable_object.rb +25 -0
  28. data/spec/aws_support/deep_matcher/rspec_monkeypatches.rb +25 -0
  29. data/spec/aws_support/delayed_stream.rb +41 -0
  30. data/spec/aws_support/matchers/create_an_aws_object.rb +60 -0
  31. data/spec/aws_support/matchers/update_an_aws_object.rb +66 -0
  32. data/spec/integration/aws_ebs_volume_spec.rb +31 -0
  33. data/spec/integration/aws_key_pair_spec.rb +21 -0
  34. data/spec/integration/aws_route_table_spec.rb +40 -0
  35. data/spec/integration/aws_security_group_spec.rb +7 -5
  36. data/spec/integration/aws_subnet_spec.rb +56 -0
  37. data/spec/integration/aws_vpc_spec.rb +109 -0
  38. data/spec/integration/machine_batch_spec.rb +36 -0
  39. data/spec/integration/machine_image_spec.rb +49 -0
  40. data/spec/integration/machine_spec.rb +64 -0
  41. data/spec/spec_helper.rb +8 -2
  42. data/spec/unit/aws_driver/credentials_spec.rb +54 -0
  43. metadata +27 -5
  44. data/spec/support/aws_support.rb +0 -211
@@ -5,7 +5,7 @@ require 'chef/provisioning/chef_managed_entry_store'
5
5
  # Common AWS resource - contains metadata that all AWS resources will need
6
6
  module Chef::Provisioning::AWSDriver
7
7
  class AWSResource < Chef::Provisioning::AWSDriver::SuperLWRP
8
- actions :create, :destroy, :nothing
8
+ actions :create, :destroy, :purge, :nothing
9
9
  default_action :create
10
10
 
11
11
  def initialize(name, run_context=nil)
@@ -54,6 +54,8 @@ module AWSDriver
54
54
  access_key_id: credentials[:aws_access_key_id],
55
55
  secret_access_key: credentials[:aws_secret_access_key],
56
56
  region: region || credentials[:region],
57
+ proxy_uri: credentials[:proxy_uri] || nil,
58
+ session_token: credentials[:aws_session_token] || nil,
57
59
  logger: Chef::Log.logger
58
60
  )
59
61
  end
@@ -331,6 +333,7 @@ module AWSDriver
331
333
  image_options[:description] ||= "Image #{image_spec.name} created from machine #{machine_spec.name}"
332
334
  Chef::Log.debug "AWS Image options: #{image_options.inspect}"
333
335
  image = ec2.images.create(image_options.to_hash)
336
+ image.add_tag('From-Instance', :value => image_options[:instance_id]) if image_options[:instance_id]
334
337
  image_spec.reference = {
335
338
  'driver_url' => driver_url,
336
339
  'driver_version' => Chef::Provisioning::AWSDriver::VERSION,
@@ -428,9 +431,7 @@ EOD
428
431
  end
429
432
 
430
433
  def allocate_machines(action_handler, specs_and_options, parallelizer)
431
- #Chef::Log.warn("#{specs_and_options}")
432
434
  create_servers(action_handler, specs_and_options, parallelizer) do |machine_spec, server|
433
- #Chef::Log.warn("#{machine_spec}")
434
435
  yield machine_spec
435
436
  end
436
437
  specs_and_options.keys
@@ -812,7 +813,7 @@ EOD
812
813
  image ||= image_for(image_spec)
813
814
  time_elapsed = 0
814
815
  sleep_time = 10
815
- max_wait_time = 120
816
+ max_wait_time = 300
816
817
  if !yield(image)
817
818
  action_handler.report_progress "waiting for #{image_spec.name} (#{image.id} on #{driver_url}) to be ready ..."
818
819
  while time_elapsed < max_wait_time && !yield(image)
@@ -821,7 +822,7 @@ EOD
821
822
  time_elapsed += sleep_time
822
823
  end
823
824
  unless yield(image)
824
- raise "Image #{image.id} did not become ready within 120 seconds"
825
+ raise "Image #{image.id} did not become ready within #{max_wait_time} seconds"
825
826
  end
826
827
  action_handler.report_progress "Image #{image_spec.name} is now ready"
827
828
  end
@@ -845,7 +846,7 @@ EOD
845
846
  time_elapsed += sleep_time
846
847
  end
847
848
  unless yield(instance)
848
- raise "Image #{instance.id} did not become ready within 120 seconds"
849
+ raise "Image #{instance.id} did not become ready within #{max_wait_time} seconds"
849
850
  end
850
851
  action_handler.report_progress "#{machine_spec.name} is now ready"
851
852
  end
@@ -1,7 +1,7 @@
1
1
  class Chef
2
2
  module Provisioning
3
3
  module AWSDriver
4
- VERSION = '1.0.4'
4
+ VERSION = '1.1.0'
5
5
  end
6
6
  end
7
7
  end
@@ -3,8 +3,7 @@ require 'chef/provisioning/aws_driver/aws_resource_with_entry'
3
3
  class Chef::Resource::AwsImage < Chef::Provisioning::AWSDriver::AWSResourceWithEntry
4
4
  aws_sdk_type AWS::EC2::Image,
5
5
  managed_entry_type: :machine_image,
6
- managed_entry_id_name: 'image_id',
7
- load_provider: false
6
+ managed_entry_id_name: 'image_id'
8
7
 
9
8
  attribute :name, kind_of: String, name_attribute: true
10
9
 
@@ -3,8 +3,7 @@ require 'chef/provisioning/aws_driver/aws_resource_with_entry'
3
3
  class Chef::Resource::AwsInstance < Chef::Provisioning::AWSDriver::AWSResourceWithEntry
4
4
  aws_sdk_type AWS::EC2::Instance,
5
5
  managed_entry_type: :machine,
6
- managed_entry_id_name: 'instance_id',
7
- load_provider: false
6
+ managed_entry_id_name: 'instance_id'
8
7
 
9
8
  attribute :name, kind_of: String, name_attribute: true
10
9
 
@@ -1,7 +1,7 @@
1
1
  require 'chef/provisioning/aws_driver/aws_resource'
2
2
 
3
3
  class Chef::Resource::AwsLoadBalancer < Chef::Provisioning::AWSDriver::AWSResource
4
- aws_sdk_type AWS::ELB::LoadBalancer, load_provider: false
4
+ aws_sdk_type AWS::ELB::LoadBalancer
5
5
 
6
6
  attribute :name, kind_of: String, name_attribute: true
7
7
 
@@ -1,16 +1,34 @@
1
1
  require 'chef/provisioning/aws_driver/aws_resource'
2
+ require 'chef/resource/aws_subnet'
3
+ require 'chef/resource/aws_eip_address'
2
4
 
3
- class Chef::Resource::AwsNetworkInterface < Chef::Provisioning::AWSDriver::AWSResource
4
- aws_sdk_type AWS::EC2::NetworkInterface, load_provider: false, id: :id
5
+ class Chef::Resource::AwsNetworkInterface < Chef::Provisioning::AWSDriver::AWSResourceWithEntry
6
+ aws_sdk_type AWS::EC2::NetworkInterface
5
7
 
6
- attribute :name, kind_of: String, name_attribute: true
8
+ attribute :name, kind_of: String, name_attribute: true
7
9
 
8
- attribute :network_interface_id, kind_of: String, aws_id_attribute: true, lazy_default: proc {
10
+ attribute :network_interface_id, kind_of: String, aws_id_attribute: true, lazy_default: proc {
9
11
  name =~ /^eni-[a-f0-9]{8}$/ ? name : nil
10
12
  }
11
13
 
14
+ attribute :subnet, kind_of: [ String, AWS::EC2::Subnet, AwsSubnet ]
15
+
16
+ attribute :private_ip_address, kind_of: String
17
+
18
+ attribute :description, kind_of: String
19
+
20
+ attribute :security_groups, kind_of: Array #(Array<SecurityGroup>, Array<String>)
21
+
22
+ attribute :machine, kind_of: [ String, FalseClass, AwsInstance, AWS::EC2::Instance ]
23
+
24
+ attribute :device_index, kind_of: Integer
25
+
26
+ # TODO implement eip address association
27
+ #attribute :elastic_ip_address, kind_of: [ String, AWS::EC2::ElasticIp, AwsEipAddress, FalseClass ]
28
+
12
29
  def aws_object
13
- result = driver.ec2.network_interfaces[network_interface_id]
30
+ driver, id = get_driver_and_id
31
+ result = driver.ec2.network_interfaces[id] if id
14
32
  result && result.exists? ? result : nil
15
33
  end
16
34
  end
@@ -125,14 +125,6 @@ class Chef::Resource::AwsVpc < Chef::Provisioning::AWSDriver::AWSResourceWithEnt
125
125
  #
126
126
  attribute :enable_dns_hostnames, equal_to: [ true, false ]
127
127
 
128
- #
129
- # A list of tags to put on the VPC.
130
- #
131
- # The "Name" tag will always be set to the Chef name of the instance if you do
132
- # not specify it.
133
- #
134
- attribute :tags, kind_of: Array
135
-
136
128
  attribute :vpc_id, kind_of: String, aws_id_attribute: true, lazy_default: proc {
137
129
  name =~ /^vpc-[a-f0-9]{8}$/ ? name : nil
138
130
  }
@@ -0,0 +1,235 @@
1
+ #
2
+ # Provides a `with_aws` method that when used in your tests will create a new
3
+ # context pointed at the user's chosen driver, and helper methods to create
4
+ # AWS objects and clean them up.
5
+ #
6
+ module AWSSupport
7
+ require 'cheffish/rspec/chef_run_support'
8
+ def self.extended(other)
9
+ other.extend Cheffish::RSpec::ChefRunSupport
10
+ end
11
+
12
+ require 'chef/provisioning/aws_driver'
13
+ require 'aws_support/matchers/create_an_aws_object'
14
+ require 'aws_support/matchers/update_an_aws_object'
15
+ require 'aws_support/delayed_stream'
16
+ require 'chef/provisioning/aws_driver/resources'
17
+ require 'aws_support/aws_resource_run_wrapper'
18
+
19
+ # Add AWS to the list of objects which can be matched against a Hash or Array
20
+ require 'aws'
21
+ require 'aws_support/deep_matcher/matchable_object'
22
+ require 'aws_support/deep_matcher/matchable_array'
23
+ DeepMatcher::MatchableObject.matchable_classes << proc { |o| o.class.name =~ /^AWS::EC2($|::)/ }
24
+ DeepMatcher::MatchableArray.matchable_classes << AWS::Core::Data::List
25
+
26
+ def purge_all
27
+ before :all do
28
+ driver = self.driver
29
+ recipe do
30
+ driver.ec2.vpcs.with_tag('Name', 'test_vpc').each do |vpc|
31
+ aws_vpc vpc do
32
+ action :purge
33
+ end
34
+ end
35
+ aws_key_pair 'test_key_pair' do
36
+ action :purge
37
+ end
38
+ end.converge
39
+ end
40
+ end
41
+
42
+ def setup_public_vpc
43
+ aws_vpc 'test_vpc' do
44
+ cidr_block '10.0.0.0/24'
45
+ internet_gateway true
46
+ enable_dns_hostnames true
47
+ main_routes '0.0.0.0/0' => :internet_gateway
48
+ end
49
+
50
+ aws_key_pair 'test_key_pair' do
51
+ allow_overwrite true
52
+ end
53
+
54
+ before :context do
55
+ image = driver.ec2.images.filter('name', 'test_machine_image').first
56
+ image.delete if image
57
+
58
+ default_sg = test_vpc.aws_object.security_groups.filter('group-name', 'default').first
59
+ recipe do
60
+ aws_security_group default_sg do
61
+ inbound_rules '0.0.0.0/0' => 22
62
+ end
63
+ end.converge
64
+ end
65
+
66
+ aws_subnet 'test_public_subnet' do
67
+ vpc 'test_vpc'
68
+ map_public_ip_on_launch true
69
+ end
70
+ end
71
+
72
+ def with_aws(description, *tags, &block)
73
+ aws_driver = nil
74
+ context_block = proc do
75
+ extend WithAWSClassMethods
76
+ include WithAWSInstanceMethods
77
+
78
+ @@driver = aws_driver
79
+ def self.driver
80
+ @@driver
81
+ end
82
+
83
+ module_eval(&block)
84
+ end
85
+
86
+ if ENV['AWS_TEST_DRIVER']
87
+ aws_driver = Chef::Provisioning.driver_for_url(ENV['AWS_TEST_DRIVER'])
88
+ when_the_repository "exists #{description ? "and #{description}" : ""}", *tags, &context_block
89
+ else
90
+ # warn <<EOM
91
+ # --------------------------------------------------------------------------------------------------------------------------
92
+ # AWS_TEST_DRIVER not set ... cannot run AWS test. Set AWS_TEST_DRIVER=aws or aws:profile:region to run tests that hit AWS.
93
+ # --------------------------------------------------------------------------------------------------------------------------
94
+ # EOM
95
+ skip "AWS_TEST_DRIVER not set ... cannot run AWS tests. Set AWS_TEST_DRIVER=aws or aws:profile:region to run tests that hit AWS." do
96
+ context description, *tags, &context_block
97
+ end
98
+ end
99
+ end
100
+
101
+ module WithAWSClassMethods
102
+ instance_eval do
103
+ #
104
+ # Create a context-level method for each AWS resource:
105
+ #
106
+ # with_aws do
107
+ # context 'mycontext' do
108
+ # aws_vpc 'myvpc' do
109
+ # ...
110
+ # end
111
+ # end
112
+ # end
113
+ #
114
+ # Creates the AWS thing when the first example in the context runs.
115
+ # Destroys it after the last example in the context runs. Objects created
116
+ # in the order declared, and destroyed in reverse order.
117
+ #
118
+ Chef::Provisioning::AWSDriver::Resources.constants.each do |resource_class|
119
+ resource_class = Chef::Provisioning::AWSDriver::Resources.const_get(resource_class)
120
+ resource_name = resource_class.resource_name
121
+ # def aws_vpc(name, &block)
122
+ define_method(resource_name) do |name, &block|
123
+ # def myvpc
124
+ # @@myvpc
125
+ # end
126
+ instance_eval do
127
+ define_method(name) { class_variable_get(:"@@#{name}") }
128
+ end
129
+ module_eval do
130
+ define_method(name) { self.class.class_variable_get(:"@@#{name}") }
131
+ end
132
+
133
+ resource = nil
134
+
135
+ before :context do
136
+ resource = AWSResourceRunWrapper.new(self, resource_name, name, &block)
137
+ # @myvpc = resource
138
+ begin
139
+ self.class.class_variable_set(:"@@#{name}", resource.resource)
140
+ rescue NameError
141
+ end
142
+ begin
143
+ resource.converge
144
+ rescue
145
+ puts "ERROR #{$!}"
146
+ puts $!.backtrace.join("\n")
147
+ raise
148
+ end
149
+ end
150
+
151
+ after :context do
152
+ resource.destroy if resource
153
+ end
154
+ end
155
+ end
156
+ end
157
+ end
158
+
159
+ module WithAWSInstanceMethods
160
+ def self.included(context)
161
+ context.module_eval do
162
+ after :example do
163
+ # Close up delayed streams so they don't print out their garbage later in the run
164
+ delayed_streams.each { |s| s.close }
165
+
166
+ # Destroy any objects we know got created during the test
167
+ created_during_test.reverse_each do |resource_name, name|
168
+ (recipe do
169
+ public_send(resource_name, name) do
170
+ action :purge
171
+ end
172
+ end).converge
173
+ end
174
+ end
175
+ end
176
+ end
177
+
178
+ #
179
+ # expect_recipe { }.to create_an_aws_vpc
180
+ # expect_recipe { }.to update_an_aws_security_object
181
+ #
182
+ Chef::Provisioning::AWSDriver::Resources.constants.each do |resource_class|
183
+ resource_class = Chef::Provisioning::AWSDriver::Resources.const_get(resource_class)
184
+ resource_name = resource_class.resource_name
185
+ define_method("update_an_#{resource_name}") do |name, expected_updates={}|
186
+ AWSSupport::Matchers::UpdateAnAWSObject.new(self, resource_class, name, expected_updates)
187
+ end
188
+ define_method("create_an_#{resource_name}") do |name, expected_values={}|
189
+ AWSSupport::Matchers::CreateAnAWSObject.new(self, resource_class, name, expected_values)
190
+ end
191
+ end
192
+
193
+ def chef_config
194
+ @chef_config ||= {
195
+ driver: driver,
196
+ stdout: delayed_stream(delay_before_streaming, STDOUT),
197
+ stderr: delayed_stream(delay_before_streaming, STDERR),
198
+ log_location: delayed_stream(delay_before_streaming_logs, STDOUT),
199
+ log_level: :info
200
+ }
201
+ end
202
+
203
+ def delayed_streams
204
+ @delayed_streams ||= []
205
+ end
206
+
207
+ def delayed_stream(delay, stream)
208
+ stream = DelayedStream.new(delay, stream)
209
+ delayed_streams << stream
210
+ stream
211
+ end
212
+
213
+ # Override in tests if you want different numbers
214
+ def delay_before_streaming_logs
215
+ 30
216
+ end
217
+
218
+ def delay_before_streaming
219
+ 10
220
+ end
221
+
222
+ def created_during_test
223
+ @created_during_test ||= []
224
+ end
225
+
226
+ def default_vpc
227
+ @default_vpc ||= driver.ec2.vpcs.filter('isDefault', 'true').first
228
+ end
229
+
230
+ def driver
231
+ self.class.driver
232
+ end
233
+ end
234
+
235
+ end
@@ -0,0 +1,45 @@
1
+ require 'cheffish/rspec/recipe_run_wrapper'
2
+
3
+ module AWSSupport
4
+ class AWSResourceRunWrapper < Cheffish::RSpec::RecipeRunWrapper
5
+ def initialize(example, resource_type, name, &properties)
6
+ super(example.chef_config) do
7
+ if properties && properties.parameters.size > 0
8
+ public_send(resource_type, name) { instance_exec(example, &properties) }
9
+ else
10
+ public_send(resource_type, name, &properties)
11
+ end
12
+ end
13
+ @example = example
14
+ @resource_type = resource_type
15
+ @name = name
16
+ @properties = properties
17
+ end
18
+
19
+ attr_reader :example
20
+ attr_reader :resource_type
21
+ attr_reader :name
22
+
23
+ def resource
24
+ resources.first
25
+ end
26
+
27
+ def to_s
28
+ "#{resource_type}[#{name}]"
29
+ end
30
+
31
+ def destroy
32
+ resource_type = self.resource_type
33
+ name = self.name
34
+ example.recipe do
35
+ public_send(resource_type, name) do
36
+ action :purge
37
+ end
38
+ end.converge
39
+ end
40
+
41
+ def aws_object
42
+ resource.aws_object
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,40 @@
1
+ module AWSSupport
2
+ #
3
+ # Include this and override `match_failure_messages`, and your class becomes
4
+ # a matcher which will have `matches?` call `match_failure_messages` and
5
+ # cache the result, which is then returned verbatim from failure_message.
6
+ #
7
+ module DeepMatcher
8
+
9
+ require 'aws_support/deep_matcher/match_values_failure_messages'
10
+
11
+ include MatchValuesFailureMessages
12
+
13
+ def matches?(actual)
14
+ @failure_messages = match_failure_messages(actual)
15
+ @failure_messages.empty?
16
+ end
17
+
18
+ def failure_message
19
+ @failure_messages.flat_map { |message| message.split("\n") }.join("\n")
20
+ end
21
+
22
+ def failure_message_when_negated
23
+ "expected #{@actual.inspect} not to #{description}"
24
+ end
25
+
26
+ #
27
+ # Return the failure message resulting from matching `actual`. Meant to be
28
+ # overridden in implementors.
29
+ #
30
+ # @param actual The actual value to compare against
31
+ # @param identifier The name of the thing being compared (may not be passed,
32
+ # in which case the class will choose an appropriate default.)
33
+ #
34
+ # @return A failure message, or empty string if it does not fail.
35
+ #
36
+ def match_failure_messages(actual, identifier='value')
37
+ raise NotImplementedError, :match_failure_messages
38
+ end
39
+ end
40
+ end