stack_master 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +18 -0
- data/.rspec +2 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +208 -0
- data/Rakefile +11 -0
- data/apply_demo.gif +0 -0
- data/bin/stack_master +16 -0
- data/example/simple/Gemfile +3 -0
- data/example/simple/parameters/myapp_vpc.yml +1 -0
- data/example/simple/parameters/myapp_web.yml +2 -0
- data/example/simple/stack_master.yml +13 -0
- data/example/simple/templates/myapp_vpc.rb +39 -0
- data/example/simple/templates/myapp_web.rb +16 -0
- data/features/apply.feature +241 -0
- data/features/delete.feature +43 -0
- data/features/diff.feature +191 -0
- data/features/events.feature +38 -0
- data/features/init.feature +6 -0
- data/features/outputs.feature +49 -0
- data/features/region_aliases.feature +66 -0
- data/features/resources.feature +45 -0
- data/features/stack_defaults.feature +88 -0
- data/features/status.feature +124 -0
- data/features/step_definitions/stack_steps.rb +50 -0
- data/features/support/env.rb +14 -0
- data/lib/stack_master.rb +81 -0
- data/lib/stack_master/aws_driver/cloud_formation.rb +56 -0
- data/lib/stack_master/cli.rb +164 -0
- data/lib/stack_master/command.rb +13 -0
- data/lib/stack_master/commands/apply.rb +104 -0
- data/lib/stack_master/commands/delete.rb +53 -0
- data/lib/stack_master/commands/diff.rb +31 -0
- data/lib/stack_master/commands/events.rb +39 -0
- data/lib/stack_master/commands/init.rb +109 -0
- data/lib/stack_master/commands/list_stacks.rb +16 -0
- data/lib/stack_master/commands/outputs.rb +27 -0
- data/lib/stack_master/commands/resources.rb +33 -0
- data/lib/stack_master/commands/status.rb +47 -0
- data/lib/stack_master/commands/validate.rb +17 -0
- data/lib/stack_master/config.rb +86 -0
- data/lib/stack_master/ctrl_c.rb +4 -0
- data/lib/stack_master/parameter_loader.rb +17 -0
- data/lib/stack_master/parameter_resolver.rb +45 -0
- data/lib/stack_master/parameter_resolvers/secret.rb +42 -0
- data/lib/stack_master/parameter_resolvers/security_group.rb +20 -0
- data/lib/stack_master/parameter_resolvers/sns_topic_name.rb +29 -0
- data/lib/stack_master/parameter_resolvers/stack_output.rb +53 -0
- data/lib/stack_master/prompter.rb +14 -0
- data/lib/stack_master/security_group_finder.rb +29 -0
- data/lib/stack_master/sns_topic_finder.rb +27 -0
- data/lib/stack_master/stack.rb +96 -0
- data/lib/stack_master/stack_definition.rb +49 -0
- data/lib/stack_master/stack_differ.rb +80 -0
- data/lib/stack_master/stack_events/fetcher.rb +45 -0
- data/lib/stack_master/stack_events/presenter.rb +27 -0
- data/lib/stack_master/stack_events/streamer.rb +55 -0
- data/lib/stack_master/stack_states.rb +34 -0
- data/lib/stack_master/template_compiler.rb +21 -0
- data/lib/stack_master/test_driver/cloud_formation.rb +139 -0
- data/lib/stack_master/testing.rb +7 -0
- data/lib/stack_master/utils.rb +31 -0
- data/lib/stack_master/validator.rb +25 -0
- data/lib/stack_master/version.rb +3 -0
- data/logo.png +0 -0
- data/script/buildkite/bundle.sh +5 -0
- data/script/buildkite/clean.sh +3 -0
- data/script/buildkite_rspec.sh +27 -0
- data/spec/fixtures/parameters/myapp_vpc.yml +1 -0
- data/spec/fixtures/stack_master.yml +35 -0
- data/spec/fixtures/templates/myapp_vpc.json +1 -0
- data/spec/spec_helper.rb +99 -0
- data/spec/stack_master/commands/apply_spec.rb +92 -0
- data/spec/stack_master/commands/delete_spec.rb +40 -0
- data/spec/stack_master/commands/init_spec.rb +17 -0
- data/spec/stack_master/commands/status_spec.rb +38 -0
- data/spec/stack_master/commands/validate_spec.rb +26 -0
- data/spec/stack_master/config_spec.rb +81 -0
- data/spec/stack_master/parameter_loader_spec.rb +81 -0
- data/spec/stack_master/parameter_resolver_spec.rb +58 -0
- data/spec/stack_master/parameter_resolvers/secret_spec.rb +66 -0
- data/spec/stack_master/parameter_resolvers/security_group_spec.rb +17 -0
- data/spec/stack_master/parameter_resolvers/sns_topic_name_spec.rb +43 -0
- data/spec/stack_master/parameter_resolvers/stack_output_spec.rb +77 -0
- data/spec/stack_master/security_group_finder_spec.rb +49 -0
- data/spec/stack_master/sns_topic_finder_spec.rb +25 -0
- data/spec/stack_master/stack_definition_spec.rb +37 -0
- data/spec/stack_master/stack_differ_spec.rb +34 -0
- data/spec/stack_master/stack_events/fetcher_spec.rb +65 -0
- data/spec/stack_master/stack_events/presenter_spec.rb +18 -0
- data/spec/stack_master/stack_events/streamer_spec.rb +33 -0
- data/spec/stack_master/stack_spec.rb +157 -0
- data/spec/stack_master/template_compiler_spec.rb +48 -0
- data/spec/stack_master/test_driver/cloud_formation_spec.rb +24 -0
- data/spec/stack_master/utils_spec.rb +30 -0
- data/spec/stack_master/validator_spec.rb +38 -0
- data/stack_master.gemspec +38 -0
- data/stacktemplates/parameter_region.yml +3 -0
- data/stacktemplates/parameter_stack_name.yml +3 -0
- data/stacktemplates/stack.json.erb +20 -0
- data/stacktemplates/stack_master.yml.erb +6 -0
- metadata +427 -0
@@ -0,0 +1,49 @@
|
|
1
|
+
RSpec.describe StackMaster::SecurityGroupFinder do
|
2
|
+
subject(:finder) { described_class.new(region) }
|
3
|
+
let(:region) { 'us-east-1' }
|
4
|
+
let(:group_name) { "our-api-BeanstalkSg-T4RKD99YOY2F" }
|
5
|
+
let(:filter) do
|
6
|
+
{
|
7
|
+
filters: [
|
8
|
+
{
|
9
|
+
name: "group-name",
|
10
|
+
values: [group_name],
|
11
|
+
},
|
12
|
+
],
|
13
|
+
}
|
14
|
+
end
|
15
|
+
|
16
|
+
describe "#find" do
|
17
|
+
before do
|
18
|
+
allow_any_instance_of(Aws::EC2::Resource).to receive(:security_groups).with(filter).and_return(security_groups)
|
19
|
+
end
|
20
|
+
|
21
|
+
context "one sg match" do
|
22
|
+
let(:security_groups) { [
|
23
|
+
double(id: 'sg-a7d2ccc0')
|
24
|
+
] }
|
25
|
+
it "returns the id" do
|
26
|
+
expect(finder.find(group_name)).to eq 'sg-a7d2ccc0'
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
context "more than one sg matches" do
|
31
|
+
let(:security_groups) { [
|
32
|
+
double(id: 'sg-a7d2ccc0'),
|
33
|
+
double(id: 'sg-a7d2ccc2'),
|
34
|
+
] }
|
35
|
+
it "returns the id" do
|
36
|
+
err = StackMaster::SecurityGroupFinder::MultipleSecurityGroupsFound
|
37
|
+
expect { finder.find(group_name) }.to raise_error(err)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
context "no matches" do
|
42
|
+
let(:security_groups) { [] }
|
43
|
+
it "returns the id" do
|
44
|
+
err = StackMaster::SecurityGroupFinder::SecurityGroupNotFound
|
45
|
+
expect { finder.find(group_name) }.to raise_error(err)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
RSpec.describe StackMaster::SnsTopicFinder do
|
2
|
+
|
3
|
+
subject(:finder) { described_class.new(region) }
|
4
|
+
let(:region) { 'us-east-1' }
|
5
|
+
let(:topics) do
|
6
|
+
[
|
7
|
+
double(arn: 'arn:aws:sns:us-east-1:581634149801:topic1name'),
|
8
|
+
double(arn: 'arn:aws:sns:us-east-1:581634149801:topic2name'),
|
9
|
+
]
|
10
|
+
end
|
11
|
+
before do
|
12
|
+
allow_any_instance_of(Aws::SNS::Resource).to receive(:topics).and_return topics
|
13
|
+
end
|
14
|
+
|
15
|
+
describe '#find' do
|
16
|
+
it 'finds the topics that exist' do
|
17
|
+
expect(finder.find('topic1name')).to eq 'arn:aws:sns:us-east-1:581634149801:topic1name'
|
18
|
+
expect(finder.find('topic2name')).to eq 'arn:aws:sns:us-east-1:581634149801:topic2name'
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'raises an exception for topics that do not exist' do
|
22
|
+
expect { finder.find('unknowntopics') }.to raise_error StackMaster::SnsTopicFinder::TopicNotFound
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
RSpec.describe StackMaster::StackDefinition do
|
2
|
+
subject(:stack_definition) do
|
3
|
+
StackMaster::StackDefinition.new(
|
4
|
+
region: region,
|
5
|
+
stack_name: stack_name,
|
6
|
+
template: template,
|
7
|
+
tags: tags,
|
8
|
+
base_dir: base_dir)
|
9
|
+
end
|
10
|
+
|
11
|
+
let(:region) { 'us-east-1' }
|
12
|
+
let(:stack_name) { 'stack_name' }
|
13
|
+
let(:template) { 'template.json' }
|
14
|
+
let(:tags) { {'environment' => 'production'} }
|
15
|
+
let(:base_dir) { '/base_dir' }
|
16
|
+
|
17
|
+
it 'has default and region specific parameter file locations' do
|
18
|
+
expect(stack_definition.parameter_files).to eq([
|
19
|
+
"/base_dir/parameters/#{stack_name}.yml",
|
20
|
+
"/base_dir/parameters/#{region}/#{stack_name}.yml"
|
21
|
+
])
|
22
|
+
end
|
23
|
+
|
24
|
+
context 'with additional parameter lookup dirs' do
|
25
|
+
before do
|
26
|
+
stack_definition.send(:additional_parameter_lookup_dirs=, ['production'])
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'includes a parameter lookup dir for it' do
|
30
|
+
expect(stack_definition.parameter_files).to eq([
|
31
|
+
"/base_dir/parameters/#{stack_name}.yml",
|
32
|
+
"/base_dir/parameters/#{region}/#{stack_name}.yml",
|
33
|
+
"/base_dir/parameters/production/#{stack_name}.yml"
|
34
|
+
])
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
RSpec.describe StackMaster::StackDiffer do
|
2
|
+
subject(:differ) { described_class.new(proposed_stack, stack) }
|
3
|
+
let(:stack) { StackMaster::Stack.new(stack_name: stack_name,
|
4
|
+
region: region,
|
5
|
+
stack_id: 123,
|
6
|
+
template_body: '{}',
|
7
|
+
parameters: {}) }
|
8
|
+
let(:proposed_stack) { StackMaster::Stack.new(stack_name: stack_name,
|
9
|
+
region: region,
|
10
|
+
parameters: { 'param1' => 'hello'},
|
11
|
+
template_body: "{\"a\": 1}") }
|
12
|
+
let(:stack_name) { 'myapp-vpc' }
|
13
|
+
let(:region) { 'us-east-1' }
|
14
|
+
|
15
|
+
describe "#output_diff" do
|
16
|
+
context "entirely new stack" do
|
17
|
+
let(:stack) { nil }
|
18
|
+
|
19
|
+
it "outputs the entire stack" do
|
20
|
+
expect { differ.output_diff }.to output(/\+ \"a\"\: 1/).to_stdout
|
21
|
+
expect { differ.output_diff }.to output(/param1\: hello/).to_stdout
|
22
|
+
expect { differ.output_diff }.to output(/No stack found/).to_stdout
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
context "stack update" do
|
27
|
+
it "outputs the stack diff" do
|
28
|
+
expect { differ.output_diff }.to output(/\+ \"a\"\: 1/).to_stdout
|
29
|
+
expect { differ.output_diff }.to output(/param1\: hello/).to_stdout
|
30
|
+
expect { differ.output_diff }.to_not output(/No stack found/).to_stdout
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
RSpec.describe StackMaster::StackEvents::Fetcher do
|
2
|
+
let(:cf) { Aws::CloudFormation::Client.new }
|
3
|
+
|
4
|
+
before do
|
5
|
+
allow(Aws::CloudFormation::Client).to receive(:new).and_return(cf)
|
6
|
+
allow(StackMaster::StackEvents::Streamer).to receive(:stream)
|
7
|
+
end
|
8
|
+
|
9
|
+
context 'with 2 stack events' do
|
10
|
+
let(:events) { [
|
11
|
+
{ event_id: '1', stack_id: '1', stack_name: 'blah', timestamp: Time.now},
|
12
|
+
{ event_id: '2', stack_id: '1', stack_name: 'blah', timestamp: Time.now}
|
13
|
+
] }
|
14
|
+
|
15
|
+
before do
|
16
|
+
cf.stub_responses(:describe_stack_events, stack_events: events)
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'returns stack events' do
|
20
|
+
events = StackMaster::StackEvents::Fetcher.fetch('blah', 'us-east-1')
|
21
|
+
expect(events.count).to eq 2
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
context 'when more than one page is present' do
|
26
|
+
let(:page_one_events) { [
|
27
|
+
{ event_id: '1', stack_id: '1', stack_name: 'blah', timestamp: Time.now},
|
28
|
+
{ event_id: '2', stack_id: '1', stack_name: 'blah', timestamp: Time.now}
|
29
|
+
] }
|
30
|
+
let(:page_two_events) { [
|
31
|
+
{ event_id: '3', stack_id: '1', stack_name: 'blah', timestamp: Time.now}
|
32
|
+
] }
|
33
|
+
|
34
|
+
before do
|
35
|
+
cf.stub_responses(:describe_stack_events, { stack_events: page_one_events, next_token: 'blah' }, { stack_events: page_two_events } )
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'returns all the stack events combined' do
|
39
|
+
events = StackMaster::StackEvents::Fetcher.fetch('blah', 'us-east-1')
|
40
|
+
expect(events.count).to eq 3
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
context 'filtering with a from timestamp' do
|
45
|
+
let(:two_pm) { Time.parse('2015-10-27 14:00') }
|
46
|
+
let(:three_pm) { Time.parse('2015-10-27 15:00') }
|
47
|
+
let(:four_pm) { Time.parse('2015-10-27 16:00') }
|
48
|
+
|
49
|
+
let(:events) {
|
50
|
+
[
|
51
|
+
{ event_id: '1', stack_id: '1', stack_name: 'blah', timestamp: two_pm },
|
52
|
+
{ event_id: '2', stack_id: '1', stack_name: 'blah', timestamp: four_pm },
|
53
|
+
]
|
54
|
+
}
|
55
|
+
|
56
|
+
before do
|
57
|
+
cf.stub_responses(:describe_stack_events, stack_events: events)
|
58
|
+
end
|
59
|
+
|
60
|
+
it 'only returns events after the timestamp' do
|
61
|
+
events = StackMaster::StackEvents::Fetcher.fetch('blah', 'us-east-1', from: three_pm)
|
62
|
+
expect(events.map(&:event_id)).to eq ['2']
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
RSpec.describe StackMaster::StackEvents::Presenter do
|
2
|
+
describe "#print_event" do
|
3
|
+
let(:time) { Time.new(2001,1,1,2,2,2) }
|
4
|
+
let(:event) do
|
5
|
+
double(:event,
|
6
|
+
timestamp: time,
|
7
|
+
logical_resource_id: 'MyAwesomeQueue',
|
8
|
+
resource_type: 'AWS::SQS::Queue',
|
9
|
+
resource_status: 'CREATE_IN_PROGRESS',
|
10
|
+
resource_status_reason: 'Resource creation Initiated')
|
11
|
+
end
|
12
|
+
subject(:print_event) { described_class.print_event($stdout, event) }
|
13
|
+
|
14
|
+
it "nicely presents event data" do
|
15
|
+
expect { print_event }.to output("\e[0;33;49m2001-01-01 02:02:02 +1100 MyAwesomeQueue AWS::SQS::Queue CREATE_IN_PROGRESS Resource creation Initiated\e[0m\n").to_stdout
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
RSpec.describe StackMaster::StackEvents::Streamer do
|
2
|
+
let(:events_first_call) { [
|
3
|
+
OpenStruct.new(event_id: '1', resource_status: 'BLAH', timestamp: Time.now),
|
4
|
+
OpenStruct.new(event_id: '2', resource_status: 'BLAH', timestamp: Time.now),
|
5
|
+
OpenStruct.new(event_id: '3', resource_status: 'BLAH', timestamp: Time.now),
|
6
|
+
] }
|
7
|
+
let(:events_second_call) {
|
8
|
+
events_first_call + [
|
9
|
+
OpenStruct.new(event_id: '4', resource_status: 'UPDATE_COMPLETE', resource_type: 'AWS::CloudFormation::Stack', logical_resource_id: stack_name, timestamp: Time.now)
|
10
|
+
]
|
11
|
+
}
|
12
|
+
let(:stack_name) { 'stack-name' }
|
13
|
+
let(:region) { 'us-east-1' }
|
14
|
+
let(:now) { Time.now }
|
15
|
+
|
16
|
+
before do
|
17
|
+
allow(StackMaster::StackEvents::Fetcher).to receive(:fetch).with(stack_name, region, from: now).and_return(events_first_call, events_second_call)
|
18
|
+
allow(Time).to receive(:now).and_return(now)
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'returns after seeing a finish state' do
|
22
|
+
events = []
|
23
|
+
StackMaster::StackEvents::Streamer.stream(stack_name, region, sleep_between_fetches: 0) do |event|
|
24
|
+
events << event
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'streams events to an io object' do
|
29
|
+
io = StringIO.new
|
30
|
+
StackMaster::StackEvents::Streamer.stream(stack_name, region, sleep_between_fetches: 0, io: io)
|
31
|
+
expect(io.string).to include('UPDATE_COMPLETE')
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,157 @@
|
|
1
|
+
RSpec.describe StackMaster::Stack do
|
2
|
+
let(:region) { 'us-east-1' }
|
3
|
+
let(:stack_name) { 'myapp_vpc' }
|
4
|
+
let(:stack_id) { '1' }
|
5
|
+
let(:stack_policy_body) { '{}' }
|
6
|
+
let(:cf) { Aws::CloudFormation::Client.new }
|
7
|
+
subject(:stack) { StackMaster::Stack.find(region, stack_name) }
|
8
|
+
|
9
|
+
before do
|
10
|
+
allow(Aws::CloudFormation::Client).to receive(:new).and_return(cf)
|
11
|
+
end
|
12
|
+
|
13
|
+
describe '.find' do
|
14
|
+
context 'when the stack exists in AWS' do
|
15
|
+
let(:parameters) {
|
16
|
+
[
|
17
|
+
{parameter_key: 'param1', parameter_value: 'value1'},
|
18
|
+
{parameter_key: 'param2', parameter_value: 'value2'}
|
19
|
+
]
|
20
|
+
}
|
21
|
+
before do
|
22
|
+
cf.stub_responses(:describe_stacks, stacks: [{ stack_id: stack_id, stack_name: stack_name, creation_time: Time.now, stack_status: 'UPDATE_COMPLETE', parameters: parameters, notification_arns: ['test_arn']}])
|
23
|
+
cf.stub_responses(:get_template, template_body: "{}")
|
24
|
+
cf.stub_responses(:get_stack_policy, stack_policy_body: stack_policy_body)
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'returns a stack object with a stack_id' do
|
28
|
+
expect(stack.stack_id).to eq stack_id
|
29
|
+
end
|
30
|
+
|
31
|
+
it "returns a template body" do
|
32
|
+
expect(stack.template_body).to eq "{}"
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'parses parameters into a hash' do
|
36
|
+
expect(stack.parameters).to eq({'param1' => 'value1', 'param2' => 'value2'})
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'sets notification_arns' do
|
40
|
+
expect(stack.notification_arns).to eq(['test_arn'])
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'sets the stack policy' do
|
44
|
+
expect(stack.stack_policy_body).to eq stack_policy_body
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
context 'when the stack does not exist in AWS' do
|
49
|
+
before do
|
50
|
+
cf.stub_responses(:describe_stacks, Aws::CloudFormation::Errors::ValidationError.new('a', 'b'))
|
51
|
+
end
|
52
|
+
|
53
|
+
it 'returns nil' do
|
54
|
+
stack = StackMaster::Stack.find(region, stack_name)
|
55
|
+
expect(stack).to be_nil
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
context 'when CF returns no stacks' do
|
60
|
+
before do
|
61
|
+
cf.stub_responses(:describe_stacks, stacks: [])
|
62
|
+
end
|
63
|
+
|
64
|
+
it 'returns nil' do
|
65
|
+
stack = StackMaster::Stack.find(region, stack_name)
|
66
|
+
expect(stack).to be_nil
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
describe '.generate' do
|
72
|
+
let(:tags) { { 'tag1' => 'value1' } }
|
73
|
+
let(:stack_definition) { StackMaster::StackDefinition.new(region: region, stack_name: stack_name, tags: tags, base_dir: '/base_dir', template: template_file_name, notification_arns: ['test_arn'], stack_policy_file: 'no_replace_rds.json') }
|
74
|
+
let(:config) { StackMaster::Config.new({'stacks' => {}}, '/base_dir') }
|
75
|
+
subject(:stack) { StackMaster::Stack.generate(stack_definition, config) }
|
76
|
+
let(:parameter_hash) { { 'DbPassword' => { 'secret' => 'db_password' } } }
|
77
|
+
let(:resolved_parameters) { { 'DbPassword' => 'sdfgjkdhlfjkghdflkjghdflkjg', 'InstanceType' => 't2.medium' } }
|
78
|
+
let(:template_file_name) { 'template.rb' }
|
79
|
+
let(:template_body) { '{"Parameters": { "VpcId": { "Description": "VPC ID" }, "InstanceType": { "Description": "Instance Type", "Default": "t2.micro" }} }' }
|
80
|
+
let(:stack_policy_body) { '{}' }
|
81
|
+
|
82
|
+
before do
|
83
|
+
allow(StackMaster::ParameterLoader).to receive(:load).and_return(parameter_hash)
|
84
|
+
allow(StackMaster::ParameterResolver).to receive(:resolve).and_return(resolved_parameters)
|
85
|
+
allow(StackMaster::TemplateCompiler).to receive(:compile).with(stack_definition.template_file_path).and_return(template_body)
|
86
|
+
allow(File).to receive(:read).with(stack_definition.stack_policy_file_path).and_return(stack_policy_body)
|
87
|
+
end
|
88
|
+
|
89
|
+
it 'has the stack definitions region' do
|
90
|
+
expect(stack.region).to eq region
|
91
|
+
end
|
92
|
+
|
93
|
+
it 'has the stack definitions name' do
|
94
|
+
expect(stack.stack_name).to eq stack_name
|
95
|
+
end
|
96
|
+
|
97
|
+
it 'has the stack definitions tags' do
|
98
|
+
expect(stack.tags).to eq tags
|
99
|
+
end
|
100
|
+
|
101
|
+
it 'resolves the parameters' do
|
102
|
+
expect(stack.parameters).to eq resolved_parameters
|
103
|
+
end
|
104
|
+
|
105
|
+
it 'compiles the template body' do
|
106
|
+
expect(stack.template_body).to eq template_body
|
107
|
+
end
|
108
|
+
|
109
|
+
it 'has notification_arns' do
|
110
|
+
expect(stack.notification_arns).to eq ['test_arn']
|
111
|
+
end
|
112
|
+
|
113
|
+
it 'has the stack policy' do
|
114
|
+
expect(stack.stack_policy_body).to eq stack_policy_body
|
115
|
+
end
|
116
|
+
|
117
|
+
it 'extracts default template parameters' do
|
118
|
+
expect(stack.template_default_parameters).to eq('VpcId' => nil, 'InstanceType' => 't2.micro')
|
119
|
+
end
|
120
|
+
|
121
|
+
it 'exposes parameters with defaults taken into account' do
|
122
|
+
expect(stack.parameters_with_defaults).to eq('DbPassword' => 'sdfgjkdhlfjkghdflkjghdflkjg', 'InstanceType' => 't2.medium', 'VpcId' => nil)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
describe "#maybe_compressed_template_body" do
|
127
|
+
subject(:maybe_compressed_template_body) do
|
128
|
+
stack.maybe_compressed_template_body
|
129
|
+
end
|
130
|
+
context "undersized json" do
|
131
|
+
let(:stack) { described_class.new(template_body: '{ }' ) }
|
132
|
+
|
133
|
+
it "leaves the json alone if it's not too large" do
|
134
|
+
expect(maybe_compressed_template_body).to eq('{ }')
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
context "oversized json" do
|
139
|
+
let(:stack) { described_class.new(template_body: "{#{' ' * 60000}}" ) }
|
140
|
+
it "compresses the json when it's overly bulbous" do
|
141
|
+
expect(maybe_compressed_template_body).to eq('{}')
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
describe '#too_big?' do
|
147
|
+
let(:big_stack) { described_class.new(template_body: "{\"a\":\"#{'x' * 60000}\"}") }
|
148
|
+
let(:little_stack) { described_class.new(template_body: "{\"a\":\"#{'x' * 1000}\"}") }
|
149
|
+
|
150
|
+
it 'returns true for big stacks' do
|
151
|
+
expect(big_stack.too_big?).to be_truthy
|
152
|
+
end
|
153
|
+
it 'returns false for small stacks' do
|
154
|
+
expect(little_stack.too_big?).to be_falsey
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
RSpec.describe StackMaster::TemplateCompiler do
|
2
|
+
describe ".compile" do
|
3
|
+
def compile
|
4
|
+
StackMaster::TemplateCompiler.compile(template_file_path)
|
5
|
+
end
|
6
|
+
|
7
|
+
context 'json template' do
|
8
|
+
let(:template_file_path) { '/base_dir/templates/template.json' }
|
9
|
+
|
10
|
+
context "small json template" do
|
11
|
+
before do
|
12
|
+
allow(File).to receive(:read).with(template_file_path).and_return('{ }')
|
13
|
+
end
|
14
|
+
|
15
|
+
it "reads from the template file path" do
|
16
|
+
expect(compile).to eq('{ }')
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
context 'extra big json template' do
|
21
|
+
before do
|
22
|
+
allow(File).to receive(:read).with(template_file_path).and_return("{ #{' ' * 60000} }")
|
23
|
+
end
|
24
|
+
|
25
|
+
it "reads from the template file path" do
|
26
|
+
expect(compile).to eq('{}')
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
context 'sparkleformation template' do
|
32
|
+
let(:template_file_path) { '/base_dir/templates/template.rb' }
|
33
|
+
|
34
|
+
before do
|
35
|
+
allow(SparkleFormation).to receive(:compile).with(template_file_path).and_return({})
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'compiles with sparkleformation' do
|
39
|
+
expect(compile).to eq("{\n}")
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'sets the appropriate sparkle_path' do
|
43
|
+
compile
|
44
|
+
expect(SparkleFormation.sparkle_path).to eq File.dirname(template_file_path)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|