cloudformation_rspec 0.0.9
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/Gemfile +3 -0
- data/README.md +80 -0
- data/Rakefile +5 -0
- data/cloudformation-rspec.gemspec +22 -0
- data/lib/cloudformation_rspec.rb +7 -0
- data/lib/cloudformation_rspec/change_set.rb +99 -0
- data/lib/cloudformation_rspec/matchers.rb +7 -0
- data/lib/cloudformation_rspec/matchers/change_set.rb +78 -0
- data/lib/cloudformation_rspec/matchers/output.rb +47 -0
- data/lib/cloudformation_rspec/matchers/validate.rb +48 -0
- data/lib/cloudformation_rspec/resource_change.rb +12 -0
- data/lib/cloudformation_rspec/sparkle.rb +20 -0
- data/spec/change_set_spec.rb +142 -0
- data/spec/fixtures/invalid_sparkle_vpc_template.rb +8 -0
- data/spec/fixtures/template_with_compile_parameters.rb +16 -0
- data/spec/fixtures/valid_sparkle_vpc_template.rb +20 -0
- data/spec/fixtures/valid_vpc_template.json +20 -0
- data/spec/fixtures/valid_vpc_template.yml +15 -0
- data/spec/matchers/change_set_spec.rb +136 -0
- data/spec/matchers/output_spec.rb +59 -0
- data/spec/matchers/validate_spec.rb +88 -0
- data/spec/sparkle_spec.rb +30 -0
- data/spec/spec_helper.rb +2 -0
- metadata +133 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 9e741127a74f674f19e97e2d5c25dedfb96cab8eb471fe65a4ba8ba5d5e18fd4
|
4
|
+
data.tar.gz: 84004c6abe9054bdd0eccaf8a81996916976c29b45794fb591ef5ba098ffdbe2
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 7b0df6dabb6d58df4bc7bbbdfc70d9ced98dea284522ffc30eabf5fe31f8e15a0f39ba42ed18b293f1944af44a385e5b056370b367f5c4181c5f669b1f79e7a7
|
7
|
+
data.tar.gz: 98b37a583ed876cbd4fcf60ede9f642e7202010ea61ff23936ca319791bacefb70efb6000647d2c4ea2309e0158c3cfd4d0d3bc34365ab9ef7ca7ceba6633d61
|
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
# CloudFormation RSpec
|
2
|
+
|
3
|
+
CloudFormation provides a black-box orchestration model; you feed templates in and infrastructure pops out the other end. This is good in that we design our templates without having to understand how the sausage gets made, but we want a way to test, quickly, what it's actually doing is what we expect.
|
4
|
+
|
5
|
+
CloudFormation RSpec enables us to use RSpec matchers test your templates against the actual CloudFormation APIs without creating actual infrastructure. This is a tradeoff to get faster feedback (typically in under 2 minutes) without waiting for CloudFormation stacks to build and is designed to complement but not replace Acceptance Testing.
|
6
|
+
|
7
|
+
## Usage
|
8
|
+
|
9
|
+
### Testing template Change Set with Parameters
|
10
|
+
|
11
|
+
```ruby
|
12
|
+
describe 'vpc_template' do
|
13
|
+
let(:template_json) { File.read('fixtures/vpc.json') }
|
14
|
+
let(:stack) {
|
15
|
+
template_body: template_json,
|
16
|
+
parameters: {
|
17
|
+
"VpcCidr" => cidr,
|
18
|
+
}
|
19
|
+
}
|
20
|
+
|
21
|
+
it 'is valid' do
|
22
|
+
expect(template_json).to be_valid
|
23
|
+
end
|
24
|
+
|
25
|
+
context 'with a valid cidr' do
|
26
|
+
let(:cidr) { "10.0.0.0/16" }
|
27
|
+
|
28
|
+
it 'creates a vpc' do
|
29
|
+
expect(stack).to contain_in_change_set("AWS::EC2::VPC", "vpc")
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
context 'with invalid cidr' do
|
34
|
+
let(:cidr) { "1.1.1.0/16" }
|
35
|
+
|
36
|
+
it 'fails to create a change set' do
|
37
|
+
expect(stack).to have_change_set_failed
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
```
|
42
|
+
|
43
|
+
### Testing SparkleFormation templates
|
44
|
+
|
45
|
+
```ruby
|
46
|
+
describe 'vpc_template' do
|
47
|
+
let(:stack) {
|
48
|
+
compiler: :sparkleformation,
|
49
|
+
template_file: "templates/vpc.rb",
|
50
|
+
compile_state: {public_subnets: ["10.0.0.0/24", "10.0.1.0/24"], private_subnets: ["10.0.2.0/24", "10.0.3.0/24"]},
|
51
|
+
parameters: {
|
52
|
+
"VpcCidr" => cidr,
|
53
|
+
}
|
54
|
+
}
|
55
|
+
|
56
|
+
it 'is valid' do
|
57
|
+
expect(stack).to be_valid_sparkleformation
|
58
|
+
end
|
59
|
+
|
60
|
+
context 'with a valid cidr' do
|
61
|
+
let(:cidr) { "10.0.0.0/16" }
|
62
|
+
|
63
|
+
it 'creates a vpc' do
|
64
|
+
expect(stack).to contain_in_change_set("AWS::EC2::VPC", "vpc")
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
context 'with invalid cidr' do
|
69
|
+
let(:cidr) { "1.1.1.0/16" }
|
70
|
+
|
71
|
+
it 'fails to create a change set' do
|
72
|
+
expect(stack).to have_change_set_failed
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
```
|
77
|
+
|
78
|
+
## Limitations
|
79
|
+
|
80
|
+
Currently we don't support templates larger than 51,200 bytes, as this requires uploading the template to S3 first.
|
data/Rakefile
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
$:.unshift File.expand_path("../lib", __FILE__)
|
2
|
+
|
3
|
+
Gem::Specification.new do |gem|
|
4
|
+
gem.name = "cloudformation_rspec"
|
5
|
+
gem.version = "0.0.9"
|
6
|
+
|
7
|
+
gem.authors = ["Patrick Robinson"]
|
8
|
+
gem.email = ["patrick.robinson@envato.com"]
|
9
|
+
gem.description = %q{CloudFormation RSpec matchers}
|
10
|
+
gem.summary = %q{Test your CloudFormation templates}
|
11
|
+
gem.homepage = "https://github.com/envato/cloudformation_rspec"
|
12
|
+
|
13
|
+
gem.files = `git ls-files`.split($\)
|
14
|
+
gem.executables = gem.files.grep(%r{^bin/}).map { |f| File.basename(f) }
|
15
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
16
|
+
gem.require_paths = ["lib"]
|
17
|
+
|
18
|
+
gem.add_dependency 'rspec', '~> 3'
|
19
|
+
gem.add_dependency 'sparkle_formation', '~> 3'
|
20
|
+
gem.add_dependency 'aws-sdk-cloudformation', '~> 1'
|
21
|
+
gem.add_development_dependency 'rake'
|
22
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
require 'aws-sdk-cloudformation'
|
2
|
+
require 'securerandom'
|
3
|
+
require 'digest'
|
4
|
+
require 'sparkle_formation'
|
5
|
+
|
6
|
+
class CloudFormationRSpec::ChangeSet
|
7
|
+
END_STATES = [
|
8
|
+
'CREATE_COMPLETE',
|
9
|
+
'DELETE_COMPLETE',
|
10
|
+
'FAILED'
|
11
|
+
]
|
12
|
+
WAIT_DELAY = 3
|
13
|
+
|
14
|
+
ChangeSetNotComplete = Class.new(StandardError)
|
15
|
+
|
16
|
+
@change_set_cache = {}
|
17
|
+
|
18
|
+
attr_reader :changes, :status
|
19
|
+
|
20
|
+
def initialize(template_body:, parameters: {})
|
21
|
+
@template_body = template_body
|
22
|
+
@parameters = parameters ? parameters : {}
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.from_cloudformation_template(template_body:, parameters:)
|
26
|
+
new(template_body: template_body, parameters: parameters).tap { |change_set| change_set.create_change_set }
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.from_sparkleformation_template(template_file:, compile_state:, parameters:)
|
30
|
+
template_body = CloudFormationRSpec::Sparkle.compile_sparkle_template(template_file, compile_state)
|
31
|
+
|
32
|
+
new(template_body: template_body, parameters: parameters).tap { |change_set| change_set.create_change_set }
|
33
|
+
end
|
34
|
+
|
35
|
+
def create_change_set
|
36
|
+
change_set_hash = generate_change_set_hash
|
37
|
+
|
38
|
+
if change_set = self.class.get_from_cache(change_set_hash)
|
39
|
+
@status = change_set.status
|
40
|
+
@changes = change_set.changes.map { |change| CloudFormationRSpec::ResourceChange.new(change.resource_change.resource_type, change.resource_change.logical_resource_id) }
|
41
|
+
return change_set
|
42
|
+
end
|
43
|
+
|
44
|
+
client = Aws::CloudFormation::Client.new
|
45
|
+
change_set = client.create_change_set(
|
46
|
+
change_set_name: change_set_name,
|
47
|
+
stack_name: change_set_name,
|
48
|
+
change_set_type: 'CREATE',
|
49
|
+
template_body: @template_body,
|
50
|
+
parameters: flat_parameters,
|
51
|
+
capabilities: ['CAPABILITY_IAM', 'CAPABILITY_NAMED_IAM'],
|
52
|
+
)
|
53
|
+
@change_set_id = change_set.id
|
54
|
+
stack_created = wait_change_set_review(client, change_set_name)
|
55
|
+
response = client.describe_change_set(change_set_name: @change_set_id, stack_name: change_set_name)
|
56
|
+
if !END_STATES.include? response.status
|
57
|
+
raise ChangeSetNotComplete.new("Change set did not complete in time. #{response.status}")
|
58
|
+
end
|
59
|
+
@status = response.status
|
60
|
+
@changes = response.changes.map { |change| CloudFormationRSpec::ResourceChange.new(change.resource_change.resource_type, change.resource_change.logical_resource_id) }
|
61
|
+
client.delete_change_set(change_set_name: change_set_name, stack_name: change_set_name)
|
62
|
+
client.delete_stack(stack_name: change_set_name) if stack_created
|
63
|
+
self.class.add_to_cache(change_set_hash, response)
|
64
|
+
response
|
65
|
+
end
|
66
|
+
|
67
|
+
private
|
68
|
+
|
69
|
+
def change_set_name
|
70
|
+
@change_set_name ||= "CloudFormationRSpec-#{SecureRandom.uuid}"
|
71
|
+
end
|
72
|
+
|
73
|
+
def flat_parameters
|
74
|
+
@parameters.map { |k, v| {parameter_key: k, parameter_value: v} }
|
75
|
+
end
|
76
|
+
|
77
|
+
def wait_change_set_review(client, change_set_name)
|
78
|
+
client.wait_until(:stack_exists, {stack_name: change_set_name}, {delay: WAIT_DELAY})
|
79
|
+
client.wait_until(:change_set_create_complete, {change_set_name: change_set_name, stack_name: change_set_name}, {delay: WAIT_DELAY})
|
80
|
+
rescue Aws::Waiters::Errors::WaiterFailed, Aws::Waiters::Errors::TooManyAttemptsError
|
81
|
+
false
|
82
|
+
end
|
83
|
+
|
84
|
+
def generate_change_set_hash
|
85
|
+
Digest::MD5.hexdigest(@template_body + @parameters.to_json)
|
86
|
+
end
|
87
|
+
|
88
|
+
def self.get_from_cache(change_set_hash)
|
89
|
+
@change_set_cache[change_set_hash]
|
90
|
+
end
|
91
|
+
|
92
|
+
def self.add_to_cache(change_set_hash, change_set)
|
93
|
+
@change_set_cache[change_set_hash] = change_set
|
94
|
+
end
|
95
|
+
|
96
|
+
def self.flush_cache
|
97
|
+
@change_set_cache = {}
|
98
|
+
end
|
99
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
require 'aws-sdk-cloudformation'
|
2
|
+
|
3
|
+
module CloudFormationRSpec::Matchers::ChangeSet
|
4
|
+
def generate_change_set(stack)
|
5
|
+
if stack[:compiler] == :sparkleformation
|
6
|
+
CloudFormationRSpec::ChangeSet.from_sparkleformation_template(
|
7
|
+
template_file: stack[:template_file],
|
8
|
+
compile_state: stack[:compile_state],
|
9
|
+
parameters: stack[:parameters]
|
10
|
+
)
|
11
|
+
else
|
12
|
+
if !stack[:template_body]
|
13
|
+
raise ArgumentError, "You must pass either :template_body or set :compiler and pass in compiler specific options"
|
14
|
+
end
|
15
|
+
CloudFormationRSpec::ChangeSet.from_cloudformation_template(template_body: stack[:template_body], parameters: stack[:parameters])
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def resource_ids_with_resource_type(change_set_result, resource_type)
|
20
|
+
change_set_result.changes.select do |change|
|
21
|
+
change.resource_type == resource_type
|
22
|
+
end.map(&:logical_resource_id)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
RSpec::Matchers.define :contain_in_change_set do |resource_type, resource_id|
|
27
|
+
include CloudFormationRSpec::Matchers::ChangeSet
|
28
|
+
|
29
|
+
match do |stack|
|
30
|
+
if !stack.is_a?(Hash)
|
31
|
+
raise ArgumentError, "You must pass a hash to this expectation"
|
32
|
+
end
|
33
|
+
|
34
|
+
change_set_result = generate_change_set(stack)
|
35
|
+
|
36
|
+
if change_set_result.status == 'FAILED'
|
37
|
+
@error = "Change set creation failed: #{change_set_result.status_reason}"
|
38
|
+
return false
|
39
|
+
end
|
40
|
+
|
41
|
+
if !change_set_result.changes.any? { |change| change.resource_type == resource_type }
|
42
|
+
@error = "Change set does not include resource type #{resource_type}"
|
43
|
+
return false
|
44
|
+
end
|
45
|
+
|
46
|
+
if !change_set_result.changes.any? { |change| change.logical_resource_id == resource_id }
|
47
|
+
@error = %Q(
|
48
|
+
Change set does not include a resource type #{resource_type} with the id #{resource_id}
|
49
|
+
Found the following resources with the same Resource Type:\n#{resource_ids_with_resource_type(change_set_result, resource_type).join("\n")}
|
50
|
+
)
|
51
|
+
return false
|
52
|
+
end
|
53
|
+
true
|
54
|
+
end
|
55
|
+
|
56
|
+
failure_message do
|
57
|
+
@error
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
RSpec::Matchers.define :have_change_set_failed do
|
62
|
+
include CloudFormationRSpec::Matchers::ChangeSet
|
63
|
+
|
64
|
+
match do |stack|
|
65
|
+
change_set_result = generate_change_set(stack)
|
66
|
+
|
67
|
+
if change_set_result.status != 'FAILED'
|
68
|
+
@error = "Change set creation succeeded"
|
69
|
+
return false
|
70
|
+
end
|
71
|
+
|
72
|
+
true
|
73
|
+
end
|
74
|
+
|
75
|
+
failure_message do
|
76
|
+
@error
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'yaml'
|
3
|
+
|
4
|
+
RSpec::Matchers.define :have_output_including do |output_name|
|
5
|
+
def json_template?(template_body)
|
6
|
+
template_body =~ /^\s*{/ # ignore leading whitespaces
|
7
|
+
end
|
8
|
+
|
9
|
+
def compile_sparkle_template(stack)
|
10
|
+
stack[:compile_state] ||= {}
|
11
|
+
|
12
|
+
template = CloudFormationRSpec::Sparkle.compile_sparkle_template(stack[:template_file], stack[:compile_state])
|
13
|
+
JSON.load(template)
|
14
|
+
end
|
15
|
+
|
16
|
+
match do |input|
|
17
|
+
if input.is_a?(Hash) && input[:compiler] == :sparkleformation
|
18
|
+
unless input[:template_file]
|
19
|
+
raise ArgumentError, "You must pass a hash to this expectation with at least the :template_file option"
|
20
|
+
end
|
21
|
+
|
22
|
+
decode_function = lambda { |s| compile_sparkle_template(s) }
|
23
|
+
elsif !input.is_a?(String)
|
24
|
+
raise ArgumentError, "You must pass a hash for SparkleFormation templates, or a string for YAML/JSON templates"
|
25
|
+
elsif json_template?(input)
|
26
|
+
decode_function = lambda { |tmpl| JSON.load(tmpl) }
|
27
|
+
else
|
28
|
+
decode_function = lambda { |tmpl| YAML.safe_load(tmpl) }
|
29
|
+
end
|
30
|
+
|
31
|
+
begin
|
32
|
+
template = decode_function.call(input)
|
33
|
+
rescue JSON::ParserError, Psych::SyntaxError, CloudFormationRSpec::Sparkle::InvalidTemplate => error
|
34
|
+
raise SyntaxError, "Unable to parse template: #{error}"
|
35
|
+
end
|
36
|
+
outputs = template["Outputs"].nil? ? [] : template["Outputs"].keys
|
37
|
+
|
38
|
+
@actual = outputs
|
39
|
+
outputs.include?(output_name)
|
40
|
+
end
|
41
|
+
|
42
|
+
diffable
|
43
|
+
|
44
|
+
failure_message do
|
45
|
+
@error
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'aws-sdk-cloudformation'
|
2
|
+
|
3
|
+
module CloudFormationRSpec::Matchers::Validate
|
4
|
+
def validate_cf_template(template_body)
|
5
|
+
client = Aws::CloudFormation::Client.new
|
6
|
+
begin
|
7
|
+
client.validate_template(template_body: template_body)
|
8
|
+
rescue Aws::CloudFormation::Errors::ValidationError => e
|
9
|
+
@error = e.message
|
10
|
+
return false
|
11
|
+
end
|
12
|
+
true
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
RSpec::Matchers.define :be_valid do
|
17
|
+
include CloudFormationRSpec::Matchers::Validate
|
18
|
+
match do |cf_template|
|
19
|
+
validate_cf_template(cf_template)
|
20
|
+
end
|
21
|
+
|
22
|
+
failure_message do
|
23
|
+
@error
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
RSpec::Matchers.define :be_valid_sparkleformation do
|
28
|
+
include CloudFormationRSpec::Matchers::Validate
|
29
|
+
match do |stack|
|
30
|
+
if !stack.is_a?(Hash) || !stack[:template_file]
|
31
|
+
raise ArgumentError, "You must pass a hash to this expectation with at least the :template_file option"
|
32
|
+
end
|
33
|
+
|
34
|
+
stack[:compile_state] ||= {}
|
35
|
+
|
36
|
+
begin
|
37
|
+
template_body = CloudFormationRSpec::Sparkle.compile_sparkle_template(stack[:template_file], stack[:compile_state])
|
38
|
+
rescue CloudFormationRSpec::Sparkle::InvalidTemplate => error
|
39
|
+
@error = error
|
40
|
+
return false
|
41
|
+
end
|
42
|
+
validate_cf_template(template_body)
|
43
|
+
end
|
44
|
+
|
45
|
+
failure_message do
|
46
|
+
@error
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
class CloudFormationRSpec::ResourceChange
|
2
|
+
attr_reader :resource_type, :logical_resource_id
|
3
|
+
|
4
|
+
def initialize(resource_type, logical_resource_id)
|
5
|
+
@resource_type = resource_type
|
6
|
+
@logical_resource_id = logical_resource_id
|
7
|
+
end
|
8
|
+
|
9
|
+
def ==(expected)
|
10
|
+
expected.is_a?(self.class) && self.resource_type == expected.resource_type && self.logical_resource_id == expected.logical_resource_id
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
class CloudFormationRSpec::Sparkle
|
2
|
+
InvalidTemplate = Class.new(StandardError)
|
3
|
+
InvalidSparkleTemplate = Class.new(InvalidTemplate)
|
4
|
+
InvalidCloudFormationTemplate = Class.new(InvalidTemplate)
|
5
|
+
|
6
|
+
def self.compile_sparkle_template(template_file, compile_state)
|
7
|
+
begin
|
8
|
+
sparkle_template = ::SparkleFormation.compile(template_file, :sparkle)
|
9
|
+
rescue RuntimeError, SyntaxError => error
|
10
|
+
raise InvalidSparkleTemplate.new("Error compiling template into SparkleTemplate #{error.message}")
|
11
|
+
end
|
12
|
+
|
13
|
+
begin
|
14
|
+
sparkle_template.compile_state = compile_state
|
15
|
+
sparkle_template.to_json
|
16
|
+
rescue => error
|
17
|
+
raise InvalidCloudFormationTemplate.new("Error compiling template into CloudFormation #{error.message}")
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,142 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe CloudFormationRSpec::ChangeSet do
|
4
|
+
let(:template_body) { '{"Description": "Foo"}' }
|
5
|
+
let(:parameters) { {"VpcCidr" => "10.0.0.0/16"} }
|
6
|
+
let(:cf_stub) { instance_double(Aws::CloudFormation::Client) }
|
7
|
+
let(:change_set_mock) { instance_double(Aws::CloudFormation::Types::DescribeChangeSetOutput) }
|
8
|
+
let(:change_set_create_mock) { instance_double(Aws::CloudFormation::Types::CreateChangeSetOutput) }
|
9
|
+
let(:stacks_mock) { instance_double(Aws::CloudFormation::Types::DescribeStacksOutput) }
|
10
|
+
let(:stack_mock) { instance_double(Aws::CloudFormation::Types::Stack) }
|
11
|
+
let(:aws_change_mock) { instance_double(Aws::CloudFormation::Types::Change) }
|
12
|
+
let(:aws_resource_change_mock) { instance_double(Aws::CloudFormation::Types::ResourceChange) }
|
13
|
+
let(:uuid) { "a7ad0965-7395-4660-b607-47b13b1d16c2" }
|
14
|
+
|
15
|
+
before do
|
16
|
+
allow(Aws::CloudFormation::Client).to receive(:new).and_return(cf_stub)
|
17
|
+
allow(cf_stub).to receive(:create_change_set).and_return(change_set_create_mock)
|
18
|
+
allow(change_set_create_mock).to receive(:id).and_return("123")
|
19
|
+
allow(cf_stub).to receive(:delete_stack)
|
20
|
+
allow(cf_stub).to receive(:delete_change_set)
|
21
|
+
allow(cf_stub).to receive(:describe_change_set).and_return(change_set_mock)
|
22
|
+
allow(cf_stub).to receive(:describe_stacks).and_return(stacks_mock)
|
23
|
+
allow(cf_stub).to receive(:wait_until).and_return(true)
|
24
|
+
allow(change_set_mock).to receive(:status).and_return("CREATE_COMPLETE")
|
25
|
+
allow(change_set_mock).to receive(:changes).and_return([aws_change_mock])
|
26
|
+
allow(aws_change_mock).to receive(:resource_change).and_return(aws_resource_change_mock)
|
27
|
+
allow(stacks_mock).to receive(:stacks).and_return([stack_mock])
|
28
|
+
allow(stack_mock).to receive(:stack_status).and_return("REVIEW_IN_PROGRESS")
|
29
|
+
allow(aws_resource_change_mock).to receive(:resource_type).and_return("AWS::EC2::VPC")
|
30
|
+
allow(aws_resource_change_mock).to receive(:logical_resource_id).and_return("Foo")
|
31
|
+
allow(SecureRandom).to receive(:uuid).and_return(uuid)
|
32
|
+
end
|
33
|
+
|
34
|
+
after do
|
35
|
+
CloudFormationRSpec::ChangeSet.flush_cache
|
36
|
+
end
|
37
|
+
|
38
|
+
context 'a sparkleformation template' do
|
39
|
+
let(:template_file) { File.join("spec", "fixtures", "vpc.rb") }
|
40
|
+
let(:parameters) { {} }
|
41
|
+
let(:compile_state) { {} }
|
42
|
+
subject { described_class.from_sparkleformation_template(template_file: template_file, compile_state: compile_state, parameters: parameters) }
|
43
|
+
|
44
|
+
context 'that does compile' do
|
45
|
+
let(:template_file) { File.join('spec', 'fixtures', 'valid_sparkle_vpc_template.rb') }
|
46
|
+
|
47
|
+
context 'with parameters' do
|
48
|
+
let(:parameters) { {"VpcCidr" => "10.0.0.0/16"} }
|
49
|
+
it 'succeeds' do
|
50
|
+
expect(subject.status).to eq("CREATE_COMPLETE")
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
context 'with compile state' do
|
55
|
+
let(:compile_state) { {public_subnets: ["10.0.0.0/24", "10.0.1.0/24"], private_subnets: ["10.0.2.0/24", "10.0.3.0/24"]} }
|
56
|
+
it 'succeeds' do
|
57
|
+
expect(subject.status).to eq("CREATE_COMPLETE")
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
context 'with no parameters or compile state' do
|
62
|
+
it 'succeeds' do
|
63
|
+
expect(subject.status).to eq("CREATE_COMPLETE")
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
context 'a valid change set' do
|
70
|
+
subject(:subject_default) { described_class.new(template_body: template_body, parameters: parameters) }
|
71
|
+
subject(:cached_subject_default) { described_class.new(template_body: template_body, parameters: parameters) }
|
72
|
+
subject(:different_parameters) { described_class.new(template_body: template_body, parameters: {}) }
|
73
|
+
subject(:different_template) { described_class.new(template_body: "{}", parameters: parameters) }
|
74
|
+
|
75
|
+
it 'calls create_change_set with the required parameters' do
|
76
|
+
expect(cf_stub).to receive(:create_change_set).with(
|
77
|
+
change_set_name: "CloudFormationRSpec-#{uuid}",
|
78
|
+
stack_name: "CloudFormationRSpec-#{uuid}",
|
79
|
+
change_set_type: 'CREATE',
|
80
|
+
template_body: template_body,
|
81
|
+
parameters: [
|
82
|
+
{
|
83
|
+
parameter_key: "VpcCidr",
|
84
|
+
parameter_value: "10.0.0.0/16"
|
85
|
+
}
|
86
|
+
],
|
87
|
+
capabilities: ['CAPABILITY_IAM', 'CAPABILITY_NAMED_IAM'],
|
88
|
+
)
|
89
|
+
subject_default.create_change_set
|
90
|
+
end
|
91
|
+
|
92
|
+
it 'caches the change set between runs' do
|
93
|
+
expect(cf_stub).to receive(:create_change_set).once
|
94
|
+
subject_default.create_change_set
|
95
|
+
cached_subject_default.create_change_set
|
96
|
+
end
|
97
|
+
|
98
|
+
it 'restores the status from the cached run' do
|
99
|
+
subject_default.create_change_set
|
100
|
+
cached_subject_default.create_change_set
|
101
|
+
expect(cached_subject_default.status).to eq("CREATE_COMPLETE")
|
102
|
+
end
|
103
|
+
|
104
|
+
it 'restores the changes from the cached run' do
|
105
|
+
subject_default.create_change_set
|
106
|
+
cached_subject_default.create_change_set
|
107
|
+
expect(cached_subject_default.changes).to match_array([CloudFormationRSpec::ResourceChange.new("AWS::EC2::VPC", "Foo")])
|
108
|
+
end
|
109
|
+
|
110
|
+
it 'does not cache change sets when parameters are different' do
|
111
|
+
expect(cf_stub).to receive(:create_change_set).twice
|
112
|
+
subject_default.create_change_set
|
113
|
+
different_parameters.create_change_set
|
114
|
+
end
|
115
|
+
|
116
|
+
it 'does not cache change sets when the template is different' do
|
117
|
+
expect(cf_stub).to receive(:create_change_set).twice
|
118
|
+
subject_default.create_change_set
|
119
|
+
different_template.create_change_set
|
120
|
+
end
|
121
|
+
|
122
|
+
it 'deletes the stack and change set' do
|
123
|
+
expect(cf_stub).to receive(:delete_stack)
|
124
|
+
expect(cf_stub).to receive(:delete_change_set)
|
125
|
+
subject_default.create_change_set
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
context 'an invalid change' do
|
130
|
+
subject { described_class.new(template_body: template_body, parameters: parameters) }
|
131
|
+
before do
|
132
|
+
allow(cf_stub).to receive(:wait_until).and_raise(Aws::Waiters::Errors::WaiterFailed)
|
133
|
+
allow(change_set_mock).to receive(:status).and_return("FAILED")
|
134
|
+
end
|
135
|
+
|
136
|
+
it 'just deletes the change set' do
|
137
|
+
expect(cf_stub).not_to receive(:delete_stack)
|
138
|
+
expect(cf_stub).to receive(:delete_change_set)
|
139
|
+
subject.create_change_set
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
SparkleFormation.new(:vpc,
|
2
|
+
{
|
3
|
+
compile_time_parameters: {
|
4
|
+
vpc_cidr: {
|
5
|
+
type: :string,
|
6
|
+
}
|
7
|
+
}
|
8
|
+
}
|
9
|
+
) do
|
10
|
+
parameters.vpc_cidr do
|
11
|
+
description 'VPC CIDR'
|
12
|
+
type 'String'
|
13
|
+
constraint_description 'CIDR block parameter must be in the form x.x.x.x/16-28'
|
14
|
+
default state!(:vpc_cidr)
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
SparkleFormation.new(:vpc) do
|
2
|
+
resources.vpc do
|
3
|
+
type "AWS::EC2::VPC"
|
4
|
+
properties do
|
5
|
+
cidr "10.0.0.0/16"
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
outputs do
|
10
|
+
vpc_id do
|
11
|
+
value ref!(:vpc)
|
12
|
+
description "The VPC ID"
|
13
|
+
end
|
14
|
+
|
15
|
+
vpc_cidr do
|
16
|
+
value "10.0.0.0/16"
|
17
|
+
description "The VPC CIDR"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
{
|
2
|
+
"AWSTemplateFormatVersion" : "2010-09-09",
|
3
|
+
"Resources" : {
|
4
|
+
"myVPC" : {
|
5
|
+
"Type" : "AWS::EC2::VPC",
|
6
|
+
"Properties" : {
|
7
|
+
"CidrBlock" : "10.0.0.0/16",
|
8
|
+
"EnableDnsSupport" : "false",
|
9
|
+
"EnableDnsHostnames" : "false",
|
10
|
+
"InstanceTenancy" : "dedicated",
|
11
|
+
"Tags" : [ {"Key" : "foo", "Value" : "bar"} ]
|
12
|
+
}
|
13
|
+
}
|
14
|
+
},
|
15
|
+
"Outputs" : {
|
16
|
+
"VpcId": {
|
17
|
+
"Value": {"Ref": "myVPC"}
|
18
|
+
}
|
19
|
+
}
|
20
|
+
}
|
@@ -0,0 +1,15 @@
|
|
1
|
+
AWSTemplateFormatVersion: '2010-09-09'
|
2
|
+
Resources:
|
3
|
+
myVPC:
|
4
|
+
Type: AWS::EC2::VPC
|
5
|
+
Properties:
|
6
|
+
CidrBlock: 10.0.0.0/16
|
7
|
+
EnableDnsSupport: 'false'
|
8
|
+
EnableDnsHostnames: 'false'
|
9
|
+
InstanceTenancy: dedicated
|
10
|
+
Tags:
|
11
|
+
- Key: foo
|
12
|
+
Value: bar
|
13
|
+
Outputs:
|
14
|
+
VpcId:
|
15
|
+
Value: !Ref myVPC
|
@@ -0,0 +1,136 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe 'contain_in_change_set' do
|
4
|
+
let(:change_set_mock) { instance_double(CloudFormationRSpec::ChangeSet) }
|
5
|
+
let(:stack) {{
|
6
|
+
template_body: '{"Parameters": {"VpcCidr": {"Type": "String"}},"Resources": {"Foo": {"Type" : "AWS::EC2::VPC","Properties": {"Cidr" : {"Ref": "VpcCidr"}}}}}',
|
7
|
+
parameters: {
|
8
|
+
"VpcCidr" => "10.0.0.0/16"
|
9
|
+
}
|
10
|
+
}}
|
11
|
+
let(:sparkle_stack) {{
|
12
|
+
compiler: :sparkleformation,
|
13
|
+
template_file: File.join("templates", "vpc.rb"),
|
14
|
+
compile_state: {public_subnets: ["10.0.0.0/24", "10.0.1.0/24"], private_subnets: ["10.0.2.0/24", "10.0.3.0/24"]},
|
15
|
+
parameters: {
|
16
|
+
"VpcCidr" => "10.0.0.0/16",
|
17
|
+
}
|
18
|
+
}}
|
19
|
+
let(:uuid) { "d7ad0965-7395-4660-b607-47b13b1d16c2" }
|
20
|
+
let(:web_server_change_mock) { instance_double(CloudFormationRSpec::ResourceChange) }
|
21
|
+
let(:vpc_change_mock) { instance_double(CloudFormationRSpec::ResourceChange) }
|
22
|
+
before do
|
23
|
+
allow(CloudFormationRSpec::ChangeSet).to receive(:new).and_return(change_set_mock)
|
24
|
+
allow(CloudFormationRSpec::ChangeSet).to receive(:from_sparkleformation_template).and_return(change_set_mock)
|
25
|
+
allow(CloudFormationRSpec::ChangeSet).to receive(:from_cloudformation_template).and_return(change_set_mock)
|
26
|
+
allow(SecureRandom).to receive(:uuid).and_return(uuid)
|
27
|
+
end
|
28
|
+
|
29
|
+
after do
|
30
|
+
CloudFormationRSpec::ChangeSet.flush_cache
|
31
|
+
end
|
32
|
+
|
33
|
+
context 'a valid cloudformation template' do
|
34
|
+
before do
|
35
|
+
allow(change_set_mock).to receive(:status).and_return("CREATE_COMPLETE")
|
36
|
+
allow(change_set_mock).to receive(:changes).and_return([vpc_change_mock, web_server_change_mock])
|
37
|
+
allow(web_server_change_mock).to receive(:resource_type).and_return("AWS::EC2::Instance")
|
38
|
+
allow(web_server_change_mock).to receive(:logical_resource_id).and_return("WebServer")
|
39
|
+
allow(vpc_change_mock).to receive(:resource_type).and_return("AWS::EC2::VPC")
|
40
|
+
allow(vpc_change_mock).to receive(:logical_resource_id).and_return("vpc")
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'succeeds when there is a matching resource' do
|
44
|
+
expect(stack).to contain_in_change_set("AWS::EC2::VPC", "vpc")
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'fails when there is no matching resource id' do
|
48
|
+
expect(stack).not_to contain_in_change_set("AWS::EC2::VPC", "foo")
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'fails when there is no matching resource type' do
|
52
|
+
expect(stack).not_to contain_in_change_set("AWS::EC2::Foo", "vpc")
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
context 'an invalid sparkleformation template' do
|
57
|
+
let(:template_file) { File.join('spec', 'fixtures', 'invalid_sparkle_vpc_template.rb') }
|
58
|
+
let(:stack) {{
|
59
|
+
compiler: :sparkleformation,
|
60
|
+
template_file: template_file,
|
61
|
+
compile_state: {}
|
62
|
+
}}
|
63
|
+
before do
|
64
|
+
allow(CloudFormationRSpec::ChangeSet).to receive(:from_sparkleformation_template).and_raise(CloudFormationRSpec::Sparkle::InvalidSparkleTemplate)
|
65
|
+
allow(change_set_mock).to receive(:status).and_return("FAILED")
|
66
|
+
end
|
67
|
+
|
68
|
+
it 'fails' do
|
69
|
+
expect { expect(stack).not_to contain_in_change_set("AWS::EC2::Foo", "vpc") }.to raise_error(CloudFormationRSpec::Sparkle::InvalidSparkleTemplate)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
describe 'have_change_set_failed' do
|
75
|
+
let(:change_set_mock) { instance_double(CloudFormationRSpec::ChangeSet) }
|
76
|
+
let(:stack) {{
|
77
|
+
template_body: '{"Parameters": {"VpcCidr": {"Type": "String"}},"Resources": {"Foo": {"Type" : "AWS::EC2::VPC","Properties": {"Cidr" : {"Ref": "VpcCidr"}}}}}',
|
78
|
+
parameters: {
|
79
|
+
"VpcCidr" => "10.0.0.0/16"
|
80
|
+
}
|
81
|
+
}}
|
82
|
+
let(:sparkle_stack) {{
|
83
|
+
compiler: :sparkleformation,
|
84
|
+
template_file: File.join("templates", "vpc.rb"),
|
85
|
+
compile_state: {public_subnets: ["10.0.0.0/24", "10.0.1.0/24"], private_subnets: ["10.0.2.0/24", "10.0.3.0/24"]},
|
86
|
+
parameters: {
|
87
|
+
"VpcCidr" => "10.0.0.0/16",
|
88
|
+
}
|
89
|
+
}}
|
90
|
+
before do
|
91
|
+
allow(CloudFormationRSpec::ChangeSet).to receive(:new).and_return(change_set_mock)
|
92
|
+
allow(CloudFormationRSpec::ChangeSet).to receive(:from_sparkleformation_template).and_return(change_set_mock)
|
93
|
+
allow(CloudFormationRSpec::ChangeSet).to receive(:from_cloudformation_template).and_return(change_set_mock)
|
94
|
+
end
|
95
|
+
|
96
|
+
after do
|
97
|
+
CloudFormationRSpec::ChangeSet.flush_cache
|
98
|
+
end
|
99
|
+
|
100
|
+
context 'a valid cloudformation template' do
|
101
|
+
before do
|
102
|
+
allow(change_set_mock).to receive(:status).and_return("CREATE_COMPLETE")
|
103
|
+
end
|
104
|
+
|
105
|
+
it 'fails' do
|
106
|
+
expect(stack).not_to have_change_set_failed
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
context 'an invalid change' do
|
111
|
+
before do
|
112
|
+
allow(change_set_mock).to receive(:status).and_return("FAILED")
|
113
|
+
end
|
114
|
+
|
115
|
+
it 'succeeds' do
|
116
|
+
expect(stack).to have_change_set_failed
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
context 'an invalid sparkleformation template' do
|
121
|
+
let(:template_file) { File.join('spec', 'fixtures', 'invalid_sparkle_vpc_template.rb') }
|
122
|
+
let(:stack) {{
|
123
|
+
compiler: :sparkleformation,
|
124
|
+
template_file: template_file,
|
125
|
+
compile_state: {}
|
126
|
+
}}
|
127
|
+
before do
|
128
|
+
allow(CloudFormationRSpec::ChangeSet).to receive(:from_sparkleformation_template).and_raise(CloudFormationRSpec::Sparkle::InvalidSparkleTemplate)
|
129
|
+
allow(change_set_mock).to receive(:status).and_return("FAILED")
|
130
|
+
end
|
131
|
+
|
132
|
+
it 'fails' do
|
133
|
+
expect { expect(stack).to have_change_set_failed }.to raise_error(CloudFormationRSpec::Sparkle::InvalidSparkleTemplate)
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe 'have_output_including' do
|
4
|
+
context 'sparkleformation template' do
|
5
|
+
let(:template_file) { File.join('spec', 'fixtures', 'valid_sparkle_vpc_template.rb') }
|
6
|
+
let(:stack) {{
|
7
|
+
compiler: :sparkleformation,
|
8
|
+
template_file: template_file,
|
9
|
+
compile_state: {}
|
10
|
+
}}
|
11
|
+
|
12
|
+
it 'has a vpc_id output' do
|
13
|
+
expect(stack).to have_output_including("VpcId")
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'has vpc_cidr output' do
|
17
|
+
expect(stack).to have_output_including("VpcCidr")
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'does not have subnet_id output' do
|
21
|
+
expect(stack).not_to have_output_including("SubnetId")
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
context 'yaml template' do
|
26
|
+
let(:template_body) { File.read(File.join('spec', 'fixtures', 'valid_vpc_template.yml')) }
|
27
|
+
|
28
|
+
it 'has a vpc_id output' do
|
29
|
+
expect(template_body).to have_output_including("VpcId")
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'does not have vpc_cidr output' do
|
33
|
+
expect(template_body).not_to have_output_including("VpcCidr")
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
context 'json template' do
|
38
|
+
let(:template_body) { File.read(File.join('spec', 'fixtures', 'valid_vpc_template.json')) }
|
39
|
+
|
40
|
+
it 'has a vpc_id output' do
|
41
|
+
expect(template_body).to have_output_including("VpcId")
|
42
|
+
end
|
43
|
+
|
44
|
+
it 'does not have vpc_cidr output' do
|
45
|
+
expect(template_body).not_to have_output_including("VpcCidr")
|
46
|
+
end
|
47
|
+
|
48
|
+
it 'provides a diff of the outputs' do
|
49
|
+
expect { expect(template_body).not_to have_output_including("VpcId") }.to raise_error(RSpec::Expectations::ExpectationNotMetError, 'expected ["VpcId"] not to have output including "VpcId"')
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
context 'garbage template' do
|
54
|
+
let(:template_body) { ' {lkajdflkasdjf' }
|
55
|
+
it 'raises an ArgumentError' do
|
56
|
+
expect{ expect(template_body).to have_output_including("VpcId") }.to raise_error(SyntaxError)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe 'be_valid' do
|
4
|
+
let(:cf_stub) { instance_double(Aws::CloudFormation::Client) }
|
5
|
+
before do
|
6
|
+
allow(Aws::CloudFormation::Client).to receive(:new).and_return(cf_stub)
|
7
|
+
end
|
8
|
+
|
9
|
+
context 'the stack is valid' do
|
10
|
+
before do
|
11
|
+
allow(cf_stub).to receive(:validate_template)
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'succeeds' do
|
15
|
+
expect('{"Description": "My Template", "Resources": {"Foo": {"Type" : "AWS::EC2::Instance","Properties" : {"ImageId" : "ami-2f726546"}}}}').to be_valid
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
context 'the stack is not valid' do
|
20
|
+
let(:template) { '{"Description": "My Template"}' }
|
21
|
+
before do
|
22
|
+
allow(cf_stub).to receive(:validate_template).with(template_body: template).and_raise(Aws::CloudFormation::Errors::ValidationError.new(Seahorse::Client::RequestContext.new(), "Template format error: At least one Resources member must be defined."))
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'fails' do
|
26
|
+
expect(template).not_to be_valid
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
describe 'be_valid_sparkleformation' do
|
32
|
+
let(:cf_stub) { instance_double(Aws::CloudFormation::Client) }
|
33
|
+
let(:compile_state) { {} }
|
34
|
+
let(:stack) {{
|
35
|
+
compiler: :sparkleformation,
|
36
|
+
template_file: template_file,
|
37
|
+
compile_state: compile_state
|
38
|
+
}}
|
39
|
+
before do
|
40
|
+
allow(Aws::CloudFormation::Client).to receive(:new).and_return(cf_stub)
|
41
|
+
end
|
42
|
+
|
43
|
+
context 'the stack is valid' do
|
44
|
+
let(:template_file) { File.join('spec', 'fixtures', 'valid_sparkle_vpc_template.rb') }
|
45
|
+
before do
|
46
|
+
allow(cf_stub).to receive(:validate_template)
|
47
|
+
end
|
48
|
+
|
49
|
+
it 'succeeds' do
|
50
|
+
expect(stack).to be_valid_sparkleformation
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
context 'the stack is not valid cloudformation' do
|
55
|
+
let(:template_file) { File.join('spec', 'fixtures', 'valid_sparkle_vpc_template.rb') }
|
56
|
+
before do
|
57
|
+
allow(cf_stub).to receive(:validate_template).and_raise(Aws::CloudFormation::Errors::ValidationError.new(Seahorse::Client::RequestContext.new(), "Template format error: At least one Resources member must be defined."))
|
58
|
+
end
|
59
|
+
|
60
|
+
it 'fails' do
|
61
|
+
expect(stack).not_to be_valid_sparkleformation
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
context 'the stack is not valid sparkleformation' do
|
66
|
+
let(:template_file) { File.join('spec', 'fixtures', 'invalid_sparkle_vpc_template.rb') }
|
67
|
+
before do
|
68
|
+
allow(cf_stub).to receive(:validate_template)
|
69
|
+
end
|
70
|
+
|
71
|
+
it 'fails' do
|
72
|
+
expect(cf_stub).not_to receive(:validate_template)
|
73
|
+
expect(stack).not_to be_valid_sparkleformation
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
context 'the stack has compile time parameters' do
|
78
|
+
let(:template_file) { File.join('spec', 'fixtures', 'template_with_compile_parameters.rb') }
|
79
|
+
let(:compile_state) { {vpc_cidr: "10.0.0.0/16"} }
|
80
|
+
before do
|
81
|
+
allow(cf_stub).to receive(:validate_template)
|
82
|
+
end
|
83
|
+
|
84
|
+
it 'succeed' do
|
85
|
+
expect(stack).to be_valid_sparkleformation
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe CloudFormationRSpec::Sparkle do
|
4
|
+
let(:template_file) { File.join("spec", "fixtures", "vpc.rb") }
|
5
|
+
let(:compile_state) { {} }
|
6
|
+
subject { described_class.compile_sparkle_template(template_file, compile_state) }
|
7
|
+
|
8
|
+
context 'that doesnt compile to sparkleformation' do
|
9
|
+
before do
|
10
|
+
allow(SparkleFormation).to receive(:compile).and_raise(RuntimeError)
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'raises InvalidSparkleTemplate' do
|
14
|
+
expect { subject }.to raise_error(described_class::InvalidSparkleTemplate)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
context 'that doesnt compile to cloudformation' do
|
19
|
+
let(:sparkle_stub) { instance_double(SparkleFormation) }
|
20
|
+
before do
|
21
|
+
allow(SparkleFormation).to receive(:compile).and_return(sparkle_stub)
|
22
|
+
allow(sparkle_stub).to receive(:compile_state=)
|
23
|
+
allow(sparkle_stub).to receive(:to_json).and_raise(RuntimeError)
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'raises InvalidCloudFormationTemplate' do
|
27
|
+
expect { subject }.to raise_error(described_class::InvalidCloudFormationTemplate)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,133 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: cloudformation_rspec
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.9
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Patrick Robinson
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2018-08-14 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rspec
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '3'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '3'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: sparkle_formation
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '3'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '3'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: aws-sdk-cloudformation
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '1'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '1'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rake
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
description: CloudFormation RSpec matchers
|
70
|
+
email:
|
71
|
+
- patrick.robinson@envato.com
|
72
|
+
executables: []
|
73
|
+
extensions: []
|
74
|
+
extra_rdoc_files: []
|
75
|
+
files:
|
76
|
+
- Gemfile
|
77
|
+
- README.md
|
78
|
+
- Rakefile
|
79
|
+
- cloudformation-rspec.gemspec
|
80
|
+
- lib/cloudformation_rspec.rb
|
81
|
+
- lib/cloudformation_rspec/change_set.rb
|
82
|
+
- lib/cloudformation_rspec/matchers.rb
|
83
|
+
- lib/cloudformation_rspec/matchers/change_set.rb
|
84
|
+
- lib/cloudformation_rspec/matchers/output.rb
|
85
|
+
- lib/cloudformation_rspec/matchers/validate.rb
|
86
|
+
- lib/cloudformation_rspec/resource_change.rb
|
87
|
+
- lib/cloudformation_rspec/sparkle.rb
|
88
|
+
- spec/change_set_spec.rb
|
89
|
+
- spec/fixtures/invalid_sparkle_vpc_template.rb
|
90
|
+
- spec/fixtures/template_with_compile_parameters.rb
|
91
|
+
- spec/fixtures/valid_sparkle_vpc_template.rb
|
92
|
+
- spec/fixtures/valid_vpc_template.json
|
93
|
+
- spec/fixtures/valid_vpc_template.yml
|
94
|
+
- spec/matchers/change_set_spec.rb
|
95
|
+
- spec/matchers/output_spec.rb
|
96
|
+
- spec/matchers/validate_spec.rb
|
97
|
+
- spec/sparkle_spec.rb
|
98
|
+
- spec/spec_helper.rb
|
99
|
+
homepage: https://github.com/envato/cloudformation_rspec
|
100
|
+
licenses: []
|
101
|
+
metadata: {}
|
102
|
+
post_install_message:
|
103
|
+
rdoc_options: []
|
104
|
+
require_paths:
|
105
|
+
- lib
|
106
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
112
|
+
requirements:
|
113
|
+
- - ">="
|
114
|
+
- !ruby/object:Gem::Version
|
115
|
+
version: '0'
|
116
|
+
requirements: []
|
117
|
+
rubyforge_project:
|
118
|
+
rubygems_version: 2.7.6
|
119
|
+
signing_key:
|
120
|
+
specification_version: 4
|
121
|
+
summary: Test your CloudFormation templates
|
122
|
+
test_files:
|
123
|
+
- spec/change_set_spec.rb
|
124
|
+
- spec/fixtures/invalid_sparkle_vpc_template.rb
|
125
|
+
- spec/fixtures/template_with_compile_parameters.rb
|
126
|
+
- spec/fixtures/valid_sparkle_vpc_template.rb
|
127
|
+
- spec/fixtures/valid_vpc_template.json
|
128
|
+
- spec/fixtures/valid_vpc_template.yml
|
129
|
+
- spec/matchers/change_set_spec.rb
|
130
|
+
- spec/matchers/output_spec.rb
|
131
|
+
- spec/matchers/validate_spec.rb
|
132
|
+
- spec/sparkle_spec.rb
|
133
|
+
- spec/spec_helper.rb
|