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
@@ -0,0 +1,57 @@
1
+ module AWSSupport
2
+ module DeepMatcher
3
+ #
4
+ # This gets mixed into RSpec::Support::FuzzyMatch, adding the ability to
5
+ # fuzzy match objects against hashes, a la:
6
+ #
7
+ # values_match({ a: 1, b: 2, 'c.d.e' => 3 },
8
+ # <non-hash object with a, b and c methods>
9
+ # end
10
+ #
11
+ module FuzzyMatchObjects
12
+
13
+ require 'rspec/support/fuzzy_matcher'
14
+ require 'aws_support/deep_matcher/matchable_object'
15
+ require 'aws_support/deep_matcher/matchable_array'
16
+
17
+ def values_match?(expected, actual)
18
+ if Hash === expected
19
+ return hash_and_object_match?(expected, actual) if MatchableObject === actual
20
+ elsif Array === expected
21
+ return arrays_match?(expected, actual) if MatchableArray === actual
22
+ end
23
+
24
+ super
25
+ end
26
+
27
+ def hash_and_object_match?(expected, actual)
28
+ expected_hash.all? do |expected_key, expected_value|
29
+ # 'a.b.c' => 1 -> { 'a' => { 'b' => { 'c' => 1 } } }
30
+ # :"a.b.c" => 1 -> { :a => { :b => { :c => 1 } } }
31
+ names = expected_key.to_s.split('.')
32
+ if names.size > 1
33
+ expected_key = expected_key.is_a?(Symbol) ? names.shift.to_sym : names.shift
34
+ while !names.empty?
35
+ expected_value = { names.pop => expected_value }
36
+ end
37
+ end
38
+
39
+ # Grab the actual value from the object
40
+ begin
41
+ actual_value = actual_object.send(expected_key)
42
+ rescue NoMethodError
43
+ if !actual_value.respond_to?(expected_key)
44
+ return false
45
+ else
46
+ raise
47
+ end
48
+ end
49
+
50
+ return false if !values_match?(expected, actual)
51
+ end
52
+
53
+ true
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,145 @@
1
+ module AWSSupport
2
+ module DeepMatcher
3
+ module MatchValuesFailureMessages
4
+
5
+ require 'rspec/matchers'
6
+ require 'rspec/matchers/composable'
7
+ require 'aws_support/deep_matcher'
8
+ require 'aws_support/deep_matcher/matchable_object'
9
+ require 'aws_support/deep_matcher/matchable_array'
10
+
11
+ protected
12
+
13
+ def match_values_failure_messages(expected, actual, identifier=nil)
14
+ if DeepMatcher === expected
15
+ return expected.match_failure_messages(actual, identifier)
16
+ elsif RSpec::Matchers::Composable === expected
17
+ if !expected.matches?(actual)
18
+ return [ expected.failure_message ]
19
+ else
20
+ return []
21
+ end
22
+ elsif Hash === expected
23
+ return match_hashes_failure_messages(expected, actual, identifier) if Hash === actual
24
+ return match_hash_and_object_failure_messages(expected, actual, identifier) if MatchableObject === actual
25
+ elsif Array === expected
26
+ return match_arrays_failure_messages(expected, actual, identifier) if MatchableArray === actual
27
+ end
28
+
29
+ if values_match?(expected, actual)
30
+ []
31
+ elsif expected.respond_to?(:failure_message)
32
+ [ "#{identifier ? "#{identifier}: " : ""}#{expected.failure_message}" ]
33
+ else
34
+ [ "#{identifier ? "#{identifier}: " : ""}expected #{description_of(expected)}, but actual value was #{actual.inspect}" ]
35
+ end
36
+ end
37
+
38
+ def match_hashes_failure_messages(expected_hash, actual_hash, identifier)
39
+ result = []
40
+
41
+ expected_hash.all? do |expected_key, expected_value|
42
+ missing_value = false
43
+ actual_value = actual_hash.fetch(expected_key) do
44
+ result << "expected #{identifier || "hash"}.fetch(#{expected_key.inspect}) to #{description_of(expected_value)}, but it was missing entirely from the hash"
45
+ missing_value = true
46
+ end
47
+ unless missing_value
48
+ result += match_values_failure_messages(expected_value, actual_value, "#{identifier}[#{expected_key.inspect}]")
49
+ end
50
+ end
51
+
52
+ result
53
+ end
54
+
55
+ #
56
+ # Match arrays using Diff::LCS to determine which elements correspond to
57
+ # which.
58
+ #
59
+ def match_arrays_failure_messages(expected_list, actual_list, identifier)
60
+ result = [ "#{identifier || "value"} is different from expected! Differences:" ]
61
+
62
+ different = false
63
+
64
+ expected_list = expected_list.map { |v| ExpectedValue.new(v) }
65
+ Diff::LCS.sdiff(expected_list, actual_list) do |change|
66
+ case change.action
67
+ when '='
68
+ messages = [ change.new_element.inspect ]
69
+ when '+'
70
+ messages = [ change.new_element.inspect ]
71
+ different = true
72
+ when '-'
73
+ messages = [ change.old_element.value.inspect ]
74
+ different = true
75
+ when '!'
76
+ messages = change.old_element.failure_messages(change.new_element)
77
+ different = true
78
+ else
79
+ raise "Unknown operator returned from sdiff: #{op}"
80
+ end
81
+ op = change.action
82
+ op = ' ' if op == '='
83
+ result += messages.flat_map { |m| m.split("\n") }.map { |m| "#{op} #{m}" }
84
+ end
85
+ different ? result : []
86
+ end
87
+
88
+ def match_hash_and_object_failure_messages(expected_hash, actual_object, identifier)
89
+ result = []
90
+
91
+ expected_hash.all? do |expected_key, expected_value|
92
+ # 'a.b.c' => 1 -> { a: { b: { c: 1 }}}
93
+ names = expected_key.to_s.split('.')
94
+ if names.size > 1
95
+ expected_key = names.shift
96
+ while !names.empty?
97
+ expected_value = { names.pop => expected_value }
98
+ end
99
+ end
100
+
101
+ # Grab the actual value from the object
102
+ begin
103
+ actual_value = actual_object.send(expected_key)
104
+ rescue NoMethodError
105
+ if !actual_value.respond_to?(expected_key)
106
+ result << "#{identifier || "object"}.send(#{expected_key.inspect}) is missing, expected value #{description_of(expected_value)}"
107
+ next
108
+ else
109
+ raise
110
+ end
111
+ end
112
+
113
+ # Check if the values are different
114
+ result += match_values_failure_messages(expected_value, actual_value, "#{identifier}.#{expected_key}")
115
+ end
116
+
117
+ result
118
+ end
119
+
120
+ #
121
+ # Handles == by calling match (used for Diff::LCS to do its magic and still
122
+ # work with rspec)
123
+ #
124
+ class ExpectedValue
125
+ include RSpec::Matchers::Composable
126
+ include MatchValuesFailureMessages
127
+
128
+ def initialize(value)
129
+ @value = value
130
+ end
131
+ attr_reader :value
132
+
133
+ def failure_messages(actual)
134
+ @failure_messages[actual]
135
+ end
136
+
137
+ def ==(actual)
138
+ @failure_messages ||= {}
139
+ @failure_messages[actual] ||= match_values_failure_messages(value, actual)
140
+ @failure_messages[actual].empty?
141
+ end
142
+ end
143
+ end
144
+ end
145
+ end
@@ -0,0 +1,24 @@
1
+ module AWSSupport
2
+ module DeepMatcher
3
+ #
4
+ # If a module implements this, it signifies that
5
+ #
6
+ module MatchableArray
7
+
8
+ #
9
+ # TODO allow the user to return a new object that actually implements the
10
+ # enumerable, in case the class in question is non-standard.
11
+ #
12
+
13
+ def self.matchable_classes
14
+ @matchable_classes ||= []
15
+ end
16
+
17
+ def self.===(other)
18
+ return true if matchable_classes.any? { |c| c === other }
19
+ return true if Enumerable === other && !(Struct === other)
20
+ super
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,25 @@
1
+ module AWSSupport
2
+ module DeepMatcher
3
+ #
4
+ # If a module implements this, or is added to `matchable_classes`,
5
+ # RSpec's `match` will match its attributes up to hashes a la:
6
+ #
7
+ # ```ruby
8
+ # expect(my_object).to match({ a: 1, b: 2 })
9
+ # ```
10
+ #
11
+ # Which will compare my_object.a to 1 and my_object.b to 2.
12
+ #
13
+ module MatchableObject
14
+
15
+ def self.matchable_classes
16
+ @matchable_classes ||= []
17
+ end
18
+
19
+ def self.===(other)
20
+ return true if matchable_classes.any? { |c| c === other}
21
+ super
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,25 @@
1
+ # FIRST thing we do in a desperate attempt to get our module in before RSpec loads
2
+
3
+ module AWSSupport
4
+ module DeepMatcher
5
+ module MatchValuesFailureMessages
6
+ end
7
+ end
8
+ end
9
+
10
+ module RSpec
11
+ module Matchers
12
+ module Composable
13
+ include AWSSupport::DeepMatcher::MatchValuesFailureMessages
14
+ end
15
+ end
16
+ end
17
+
18
+ require 'aws_support/deep_matcher/match_values_failure_messages'
19
+ require 'rspec/matchers/composable'
20
+ require 'rspec/support/fuzzy_matcher'
21
+ require 'aws_support/deep_matcher/fuzzy_match_objects'
22
+
23
+ module RSpec::Support::FuzzyMatcher
24
+ prepend AWSSupport::DeepMatcher::FuzzyMatchObjects
25
+ end
@@ -0,0 +1,41 @@
1
+ require 'timeout'
2
+
3
+ module AWSSupport
4
+ class DelayedStream
5
+ def initialize(delay_before_streaming, *streams)
6
+ @streams = streams.flatten.select { |s| !s.nil? }
7
+ if delay_before_streaming > 0
8
+ @buffer = StringIO.new
9
+ @thread = Thread.new do
10
+ sleep delay_before_streaming
11
+ start_streaming
12
+ end
13
+ end
14
+ end
15
+
16
+ attr_reader :streams
17
+ attr_reader :buffer
18
+
19
+ def start_streaming
20
+ if @buffer
21
+ buffer = @buffer
22
+ @buffer = nil
23
+ streams.each { |s| s.write(buffer.string) }
24
+ end
25
+ end
26
+
27
+ def write(*args, &block)
28
+ if buffer.nil?
29
+ streams.each { |s| s.write(*args, &block) }
30
+ else
31
+ buffer.write(*args, &block)
32
+ end
33
+ end
34
+
35
+ def close
36
+ @streams = []
37
+ @thread.kill if @thread
38
+ @thread = nil
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,60 @@
1
+ require 'rspec/matchers'
2
+ require 'chef/provisioning'
3
+ require 'aws_support/deep_matcher'
4
+
5
+ module AWSSupport
6
+ module Matchers
7
+ class CreateAnAWSObject
8
+ include RSpec::Matchers::Composable
9
+ include AWSSupport::DeepMatcher
10
+
11
+ def initialize(example, resource_class, name, expected_values)
12
+ @example = example
13
+ @resource_class = resource_class
14
+ @name = name
15
+ @expected_values = expected_values
16
+ end
17
+
18
+ attr_reader :example
19
+ attr_reader :resource_class
20
+ attr_reader :name
21
+ attr_reader :expected_values
22
+ def resource_name
23
+ @resource_class.resource_name
24
+ end
25
+
26
+ def match_failure_messages(recipe)
27
+ differences = []
28
+
29
+ # We want to record that it was created BEFORE the converge, so that
30
+ # even if it fails, we destroy it.
31
+ example.created_during_test << [ resource_name, name ]
32
+
33
+ # Converge
34
+ begin
35
+ recipe.converge
36
+ rescue
37
+ differences += [ "error trying to create #{resource_name}[#{name}]!\n#{($!.backtrace.map { |line| "- #{line}\n" } + [ recipe.output_for_failure_message ]).join("")}" ]
38
+ end
39
+
40
+ # Check whether the recipe caused an update or not
41
+ differences += match_values_failure_messages(example.be_updated, recipe, "recipe")
42
+
43
+ # Check for object existence and properties
44
+ resource = resource_class.new(name, nil)
45
+ resource.driver example.driver
46
+ resource.managed_entry_store Chef::Provisioning.chef_managed_entry_store
47
+ aws_object = resource.aws_object
48
+
49
+ # Check existence
50
+ if aws_object.nil?
51
+ differences << "#{resource_name}[#{name}] was not created!"
52
+ else
53
+ differences += match_values_failure_messages(expected_values, aws_object, "#{resource_name}[#{name}]")
54
+ end
55
+
56
+ differences
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,66 @@
1
+ require 'rspec/matchers'
2
+ require 'aws_support/deep_matcher'
3
+
4
+ module AWSSupport
5
+ module Matchers
6
+ class UpdateAnAWSObject
7
+
8
+ include RSpec::Matchers::Composable
9
+ include AWSSupport::DeepMatcher
10
+
11
+ require 'chef/provisioning'
12
+
13
+ def initialize(example, resource_class, name, expected_updates)
14
+ @example = example
15
+ @resource_class = resource_class
16
+ @name = name
17
+ @expected_updates = expected_updates
18
+
19
+ # Grab the "before" value
20
+ resource = resource_class.new(name, nil)
21
+ resource.driver example.driver
22
+ resource.managed_entry_store Chef::Provisioning.chef_managed_entry_store
23
+ @had_initial_value = !resource.aws_object.nil?
24
+ end
25
+
26
+ attr_reader :example
27
+ attr_reader :resource_class
28
+ attr_reader :name
29
+ attr_reader :expected_updates
30
+ def resource_name
31
+ @resource_class.resource_name
32
+ end
33
+ attr_reader :had_initial_value
34
+
35
+ def match_failure_messages(recipe)
36
+ differences = []
37
+
38
+ if !had_initial_value
39
+ differences << "expected recipe to update #{resource_name}[#{name}], but the AWS object did not exist before recipe ran!"
40
+ return differences
41
+ end
42
+
43
+ # Converge
44
+ begin
45
+ recipe.converge
46
+ rescue
47
+ differences += [ "error trying to update #{resource_name}[#{name}]!\n#{($!.backtrace.map { |line| "- #{line}\n" } + [ recipe.output_for_failure_message ]).join("")}" ]
48
+ end
49
+
50
+ # Check if the recipe actually caused an update
51
+ differences += match_values_failure_messages(example.be_updated, recipe, "recipe")
52
+
53
+ # Check if any properties that should have been updated, weren't
54
+ resource = resource_class.new(name, nil)
55
+ resource.driver example.driver
56
+ resource.managed_entry_store Chef::Provisioning.chef_managed_entry_store
57
+ aws_object = resource.aws_object
58
+
59
+ # Check to see if properties have the expected values
60
+ differences += match_values_failure_messages(expected_updates, aws_object, "#{resource_name}[#{name}]")
61
+
62
+ differences
63
+ end
64
+ end
65
+ end
66
+ end