chef-provisioning-aws 1.0.4 → 1.1.0

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