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.
- checksums.yaml +4 -4
- data/README.md +18 -0
- data/Rakefile +5 -0
- data/lib/chef/provider/aws_ebs_volume.rb +14 -4
- data/lib/chef/provider/aws_image.rb +31 -0
- data/lib/chef/provider/aws_instance.rb +14 -0
- data/lib/chef/provider/aws_load_balancer.rb +9 -0
- data/lib/chef/provider/aws_network_interface.rb +209 -0
- data/lib/chef/provider/aws_security_group.rb +9 -4
- data/lib/chef/provider/aws_subnet.rb +16 -1
- data/lib/chef/provider/aws_vpc.rb +16 -0
- data/lib/chef/provisioning/aws_driver/aws_provider.rb +44 -0
- data/lib/chef/provisioning/aws_driver/aws_resource.rb +1 -1
- data/lib/chef/provisioning/aws_driver/driver.rb +6 -5
- data/lib/chef/provisioning/aws_driver/version.rb +1 -1
- data/lib/chef/resource/aws_image.rb +1 -2
- data/lib/chef/resource/aws_instance.rb +1 -2
- data/lib/chef/resource/aws_load_balancer.rb +1 -1
- data/lib/chef/resource/aws_network_interface.rb +23 -5
- data/lib/chef/resource/aws_vpc.rb +0 -8
- data/spec/aws_support.rb +235 -0
- data/spec/aws_support/aws_resource_run_wrapper.rb +45 -0
- data/spec/aws_support/deep_matcher.rb +40 -0
- data/spec/aws_support/deep_matcher/fuzzy_match_objects.rb +57 -0
- data/spec/aws_support/deep_matcher/match_values_failure_messages.rb +145 -0
- data/spec/aws_support/deep_matcher/matchable_array.rb +24 -0
- data/spec/aws_support/deep_matcher/matchable_object.rb +25 -0
- data/spec/aws_support/deep_matcher/rspec_monkeypatches.rb +25 -0
- data/spec/aws_support/delayed_stream.rb +41 -0
- data/spec/aws_support/matchers/create_an_aws_object.rb +60 -0
- data/spec/aws_support/matchers/update_an_aws_object.rb +66 -0
- data/spec/integration/aws_ebs_volume_spec.rb +31 -0
- data/spec/integration/aws_key_pair_spec.rb +21 -0
- data/spec/integration/aws_route_table_spec.rb +40 -0
- data/spec/integration/aws_security_group_spec.rb +7 -5
- data/spec/integration/aws_subnet_spec.rb +56 -0
- data/spec/integration/aws_vpc_spec.rb +109 -0
- data/spec/integration/machine_batch_spec.rb +36 -0
- data/spec/integration/machine_image_spec.rb +49 -0
- data/spec/integration/machine_spec.rb +64 -0
- data/spec/spec_helper.rb +8 -2
- data/spec/unit/aws_driver/credentials_spec.rb +54 -0
- metadata +27 -5
- 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
|