stack_master 0.0.1
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 +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,26 @@
|
|
|
1
|
+
RSpec.describe StackMaster::Commands::Validate do
|
|
2
|
+
|
|
3
|
+
subject(:validate) { described_class.new(config, stack_definition) }
|
|
4
|
+
let(:config) { instance_double(StackMaster::Config) }
|
|
5
|
+
let(:region) { "us-east-1" }
|
|
6
|
+
let(:stack_name) { "mystack" }
|
|
7
|
+
let(:stack_definition) do
|
|
8
|
+
StackMaster::StackDefinition.new(
|
|
9
|
+
region: region,
|
|
10
|
+
stack_name: stack_name,
|
|
11
|
+
template: 'myapp_vpc.json',
|
|
12
|
+
tags: { 'environment' => 'production' },
|
|
13
|
+
base_dir: File.expand_path('spec/fixtures')
|
|
14
|
+
)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
describe "#perform" do
|
|
18
|
+
context "can find stack" do
|
|
19
|
+
it "calls the validator to validate the stack definition" do
|
|
20
|
+
expect(StackMaster::Validator).to receive(:perform).with(stack_definition)
|
|
21
|
+
validate.perform
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
end
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
RSpec.describe StackMaster::Config do
|
|
2
|
+
subject(:loaded_config) { StackMaster::Config.load!('spec/fixtures/stack_master.yml') }
|
|
3
|
+
let(:base_dir) { File.expand_path('spec/fixtures') }
|
|
4
|
+
let(:myapp_vpc_definition) {
|
|
5
|
+
StackMaster::StackDefinition.new(
|
|
6
|
+
region: 'us-east-1',
|
|
7
|
+
stack_name: 'myapp-vpc',
|
|
8
|
+
template: 'myapp_vpc.json',
|
|
9
|
+
tags: { 'application' => 'my-awesome-blog', 'environment' => 'production' },
|
|
10
|
+
notification_arns: ['test_arn', 'test_arn_2'],
|
|
11
|
+
base_dir: base_dir,
|
|
12
|
+
secret_file: 'production.yml.gpg',
|
|
13
|
+
stack_policy_file: 'my_policy.json',
|
|
14
|
+
additional_parameter_lookup_dirs: ['production']
|
|
15
|
+
)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
it 'returns an object that can find stack definitions' do
|
|
19
|
+
stack = loaded_config.find_stack('us-east-1', 'myapp-vpc')
|
|
20
|
+
expect(stack).to eq(myapp_vpc_definition)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
it 'can find things with underscores instead of hyphens' do
|
|
24
|
+
stack = loaded_config.find_stack('us_east_1', 'myapp_vpc')
|
|
25
|
+
expect(stack).to eq(myapp_vpc_definition)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
it 'exposes the base_dir' do
|
|
29
|
+
expect(loaded_config.base_dir).to eq base_dir
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
it 'loads stack defaults' do
|
|
33
|
+
expect(loaded_config.stack_defaults).to eq({
|
|
34
|
+
'tags' => { 'application' => 'my-awesome-blog' }
|
|
35
|
+
})
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
it 'loads region defaults' do
|
|
39
|
+
expect(loaded_config.region_defaults).to eq({
|
|
40
|
+
'us-east-1' => {
|
|
41
|
+
'tags' => { 'environment' => 'production' },
|
|
42
|
+
'notification_arns' => ['test_arn'],
|
|
43
|
+
'secret_file' => 'production.yml.gpg',
|
|
44
|
+
'stack_policy_file' => 'my_policy.json'
|
|
45
|
+
},
|
|
46
|
+
'ap-southeast-2' => {
|
|
47
|
+
'tags' => {'environment' => 'staging'},
|
|
48
|
+
'notification_arns' => ['test_arn_3'],
|
|
49
|
+
'secret_file' => 'staging.yml.gpg'
|
|
50
|
+
}
|
|
51
|
+
})
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
it 'loads region_aliases' do
|
|
55
|
+
expect(loaded_config.region_aliases).to eq(
|
|
56
|
+
'production' => 'us-east-1',
|
|
57
|
+
'staging' => 'ap-southeast-2'
|
|
58
|
+
)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
it 'deep merges stack attributes' do
|
|
62
|
+
expect(loaded_config.find_stack('ap-southeast-2', 'myapp-vpc')).to eq(StackMaster::StackDefinition.new(
|
|
63
|
+
stack_name: 'myapp-vpc',
|
|
64
|
+
region: 'ap-southeast-2',
|
|
65
|
+
tags: {
|
|
66
|
+
'application' => 'my-awesome-blog',
|
|
67
|
+
'environment' => 'staging'
|
|
68
|
+
},
|
|
69
|
+
notification_arns: ['test_arn_3', 'test_arn_4'],
|
|
70
|
+
template: 'myapp_vpc.rb',
|
|
71
|
+
base_dir: base_dir,
|
|
72
|
+
secret_file: 'staging.yml.gpg',
|
|
73
|
+
additional_parameter_lookup_dirs: ['staging']
|
|
74
|
+
))
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
it 'allows region aliases in region defaults' do
|
|
78
|
+
config = StackMaster::Config.new({'region_aliases' => { 'production' => 'us-east-1' }, 'region_defaults' => { 'production' => { 'secret_file' => 'production.yml.gpg' }}, 'stacks' => {}}, '/base')
|
|
79
|
+
expect(config.region_defaults).to eq('us-east-1' => { 'secret_file' => 'production.yml.gpg' })
|
|
80
|
+
end
|
|
81
|
+
end
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
RSpec.describe StackMaster::ParameterLoader do
|
|
2
|
+
subject(:parameters) { StackMaster::ParameterLoader.load(parameter_files) }
|
|
3
|
+
let(:parameter_files) { [
|
|
4
|
+
'/base_dir/parameters/stack_name.yml',
|
|
5
|
+
'/base_dir/parameters/us-east-1/stack_name.yml'
|
|
6
|
+
] }
|
|
7
|
+
|
|
8
|
+
context "no parameter file" do
|
|
9
|
+
before do
|
|
10
|
+
allow(File).to receive(:exists?).and_return(false)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
it "returns empty parameters" do
|
|
14
|
+
expect(parameters).to eq({})
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
context "an empty stack parameter file" do
|
|
19
|
+
before do
|
|
20
|
+
allow(File).to receive(:exists?).with('/base_dir/parameters/stack_name.yml').and_return(true)
|
|
21
|
+
allow(File).to receive(:exists?).with('/base_dir/parameters/us-east-1/stack_name.yml').and_return(false)
|
|
22
|
+
allow(File).to receive(:read).with('/base_dir/parameters/stack_name.yml').and_return("")
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
it "returns an empty hash" do
|
|
26
|
+
expect(parameters).to eq({})
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
context "stack parameter file" do
|
|
31
|
+
before do
|
|
32
|
+
allow(File).to receive(:exists?).with('/base_dir/parameters/stack_name.yml').and_return(true)
|
|
33
|
+
allow(File).to receive(:exists?).with('/base_dir/parameters/us-east-1/stack_name.yml').and_return(false)
|
|
34
|
+
allow(File).to receive(:read).with('/base_dir/parameters/stack_name.yml').and_return("Param1: value1")
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
it "returns params from stack_name.yml" do
|
|
38
|
+
expect(parameters).to eq({ 'Param1' => 'value1' })
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
context "region parameter file" do
|
|
43
|
+
before do
|
|
44
|
+
allow(File).to receive(:exists?).with('/base_dir/parameters/stack_name.yml').and_return(false)
|
|
45
|
+
allow(File).to receive(:exists?).with('/base_dir/parameters/us-east-1/stack_name.yml').and_return(true)
|
|
46
|
+
allow(File).to receive(:read).with('/base_dir/parameters/us-east-1/stack_name.yml').and_return("Param2: value2")
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
it "returns params from the region base stack_name.yml" do
|
|
50
|
+
expect(parameters).to eq({ 'Param2' => 'value2' })
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
context "stack and region parameter file" do
|
|
55
|
+
before do
|
|
56
|
+
allow(File).to receive(:exists?).with('/base_dir/parameters/stack_name.yml').and_return(true)
|
|
57
|
+
allow(File).to receive(:exists?).with('/base_dir/parameters/us-east-1/stack_name.yml').and_return(true)
|
|
58
|
+
allow(File).to receive(:read).with('/base_dir/parameters/stack_name.yml').and_return("Param1: value1\nParam2: valueX")
|
|
59
|
+
allow(File).to receive(:read).with('/base_dir/parameters/us-east-1/stack_name.yml').and_return("Param2: value2")
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
it "returns params from the region base stack_name.yml" do
|
|
63
|
+
expect(parameters).to eq({
|
|
64
|
+
'Param1' => 'value1',
|
|
65
|
+
'Param2' => 'value2'
|
|
66
|
+
})
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
context 'underscored parameter names' do
|
|
71
|
+
before do
|
|
72
|
+
allow(File).to receive(:exists?).with('/base_dir/parameters/stack_name.yml').and_return(true)
|
|
73
|
+
allow(File).to receive(:exists?).with('/base_dir/parameters/us-east-1/stack_name.yml').and_return(false)
|
|
74
|
+
allow(File).to receive(:read).with('/base_dir/parameters/stack_name.yml').and_return("vpc_id: vpc-xxxxxx")
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
it "camelcases them" do
|
|
78
|
+
expect(parameters).to eq({'VpcId' => 'vpc-xxxxxx'})
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
RSpec.describe StackMaster::ParameterResolver do
|
|
2
|
+
let(:config) { double }
|
|
3
|
+
let(:my_resolver) {
|
|
4
|
+
Class.new do
|
|
5
|
+
def initialize(config, region)
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def resolve(value)
|
|
9
|
+
value.to_i * 5
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
}
|
|
13
|
+
before do
|
|
14
|
+
stub_const('StackMaster::ParameterResolvers::MyResolver', my_resolver)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def resolve(params)
|
|
19
|
+
StackMaster::ParameterResolver.resolve(config, 'us-east-1', params)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
it 'returns the same value for strings' do
|
|
23
|
+
expect(resolve(param1: 'value1')).to eq(param1: 'value1')
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
it 'it throws an error when the hash contains more than one key' do
|
|
27
|
+
expect {
|
|
28
|
+
resolve(param: { nested1: 'value1', nested2: 'value2' })
|
|
29
|
+
}.to raise_error(StackMaster::ParameterResolver::InvalidParameter)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
it 'throws an error when given an array' do
|
|
33
|
+
expect {
|
|
34
|
+
resolve(param: [1, 2])
|
|
35
|
+
}.to raise_error(StackMaster::ParameterResolver::InvalidParameter)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
context 'when given a proper resolve hash' do
|
|
39
|
+
it 'returns the value returned by the resolver as the parameter value' do
|
|
40
|
+
expect(resolve(param: { my_resolver: 2 })).to eq(param: 10)
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
context 'when the resolver is unknown' do
|
|
45
|
+
it 'throws an error' do
|
|
46
|
+
expect {
|
|
47
|
+
resolve(param: { my_unknown_resolver: 2 })
|
|
48
|
+
}.to raise_error StackMaster::ParameterResolver::ResolverNotFound
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
context 'resolver class caching' do
|
|
53
|
+
it "uses the same instance of the resolver for the duration of the resolve run" do
|
|
54
|
+
expect(my_resolver).to receive(:new).once.and_call_original
|
|
55
|
+
expect(resolve(param: { my_resolver: 2 }, param2: { my_resolver: 2 })).to eq(param: 10, param2: 10)
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
RSpec.describe StackMaster::ParameterResolvers::Secret do
|
|
2
|
+
let(:base_dir) { '/base_dir' }
|
|
3
|
+
let(:config) { double(base_dir: base_dir) }
|
|
4
|
+
let(:stack_definition) { double(secret_file: secrets_file_name, stack_name: 'mystack', region: 'us-east-1') }
|
|
5
|
+
subject(:resolve_secret) { StackMaster::ParameterResolvers::Secret.new(config, stack_definition).resolve(value) }
|
|
6
|
+
let(:value) { 'my_file/my_secret_key' }
|
|
7
|
+
let(:secrets_file_name) { "my_file.yml.gpg" }
|
|
8
|
+
let(:file_path) { "#{base_dir}/secrets/#{secrets_file_name}" }
|
|
9
|
+
|
|
10
|
+
context 'the secret file does not exist' do
|
|
11
|
+
before do
|
|
12
|
+
allow(File).to receive(:exist?).with(file_path).and_return(false)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
it 'raises an ArgumentError with the location of the expected secret file' do
|
|
16
|
+
expect {
|
|
17
|
+
resolve_secret
|
|
18
|
+
}.to raise_error(ArgumentError, /#{file_path}/)
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
context 'no secret file is specified for the stack definition' do
|
|
23
|
+
before do
|
|
24
|
+
allow(stack_definition).to receive(:secret_file).and_return(nil)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
it 'raises an ArgumentError with the location of the expected secret file' do
|
|
28
|
+
expect {
|
|
29
|
+
resolve_secret
|
|
30
|
+
}.to raise_error(ArgumentError, /No secret_file defined/)
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
context 'the secret file exists' do
|
|
35
|
+
let(:dir) { double(Dotgpg::Dir) }
|
|
36
|
+
let(:decrypted_file) { <<EOF }
|
|
37
|
+
secret_key_1: secret_value_1
|
|
38
|
+
secret_key_2: secret_value_2
|
|
39
|
+
EOF
|
|
40
|
+
|
|
41
|
+
before do
|
|
42
|
+
allow(File).to receive(:exist?).with(file_path).and_return(true)
|
|
43
|
+
allow(Dotgpg::Dir).to receive(:closest).with(file_path).and_return(dir)
|
|
44
|
+
allow(dir).to receive(:decrypt).with("secrets/#{secrets_file_name}", anything)
|
|
45
|
+
allow(StringIO).to receive(:new).and_return(double(string: decrypted_file))
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
context 'the secret key does not exist' do
|
|
49
|
+
let(:value) { 'unknown_secret' }
|
|
50
|
+
|
|
51
|
+
it 'raises a secret not found error' do
|
|
52
|
+
expect {
|
|
53
|
+
resolve_secret
|
|
54
|
+
}.to raise_error(StackMaster::ParameterResolvers::Secret::SecretNotFound)
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
context 'the secret key exists' do
|
|
59
|
+
let(:value) { 'secret_key_2' }
|
|
60
|
+
|
|
61
|
+
it 'returns the secret' do
|
|
62
|
+
expect(resolve_secret).to eq('secret_value_2')
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
RSpec.describe StackMaster::ParameterResolvers::SecurityGroup do
|
|
2
|
+
describe "#resolve" do
|
|
3
|
+
subject(:resolver) { described_class.new(nil, double(region: 'us-east-1')) }
|
|
4
|
+
let(:finder) { instance_double(StackMaster::SecurityGroupFinder) }
|
|
5
|
+
let(:sg_id) { 'sg-id' }
|
|
6
|
+
let(:sg_name) { 'sg-name' }
|
|
7
|
+
|
|
8
|
+
before do
|
|
9
|
+
allow(StackMaster::SecurityGroupFinder).to receive(:new).with('us-east-1').and_return finder
|
|
10
|
+
allow(finder).to receive(:find).with(sg_name).and_return sg_id
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
it "resolves the security group" do
|
|
14
|
+
expect(resolver.resolve(sg_name)).to eq sg_id
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
RSpec.describe StackMaster::ParameterResolvers::SnsTopicName do
|
|
2
|
+
let(:region) { 'us-east-1' }
|
|
3
|
+
let(:stack_name) { 'my-stack' }
|
|
4
|
+
let(:config) { double }
|
|
5
|
+
|
|
6
|
+
def resolve(value)
|
|
7
|
+
described_class.new(config, double(region: 'us-east-1')).resolve(value)
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
subject(:resolved_value) { resolve(value) }
|
|
11
|
+
|
|
12
|
+
context 'when given a hash' do
|
|
13
|
+
let(:value) { { not_expected: 1} }
|
|
14
|
+
|
|
15
|
+
it 'raises an error' do
|
|
16
|
+
expect {
|
|
17
|
+
resolved_value
|
|
18
|
+
}.to raise_error(ArgumentError)
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
context 'when given a string value' do
|
|
23
|
+
let(:value) { 'my-topic-name' }
|
|
24
|
+
|
|
25
|
+
context 'the stack and sns topic name exist' do
|
|
26
|
+
before do
|
|
27
|
+
allow_any_instance_of(StackMaster::SnsTopicFinder).to receive(:find).with(value).and_return('myresolvedvalue')
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
it 'resolves the value' do
|
|
31
|
+
expect(resolved_value).to eq 'myresolvedvalue'
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
context "the topic doesn't exist" do
|
|
36
|
+
it 'raises topic not found' do
|
|
37
|
+
expect {
|
|
38
|
+
resolved_value
|
|
39
|
+
}.to raise_error(StackMaster::ParameterResolvers::SnsTopicName::TopicNotFound)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
RSpec.describe StackMaster::ParameterResolvers::StackOutput do
|
|
2
|
+
let(:region) { 'us-east-1' }
|
|
3
|
+
let(:stack_name) { 'my-stack' }
|
|
4
|
+
let(:config) { double }
|
|
5
|
+
let(:resolver) { described_class.new(config, double(region: 'us-east-1')) }
|
|
6
|
+
let(:cf) { Aws::CloudFormation::Client.new }
|
|
7
|
+
|
|
8
|
+
def resolve(value)
|
|
9
|
+
resolver.resolve(value)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
subject(:resolved_value) { resolve(value) }
|
|
13
|
+
|
|
14
|
+
context 'when given an invalid string value' do
|
|
15
|
+
let(:value) { 'stack-name-without-output' }
|
|
16
|
+
|
|
17
|
+
it 'raises an error' do
|
|
18
|
+
expect {
|
|
19
|
+
resolved_value
|
|
20
|
+
}.to raise_error(ArgumentError)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
context 'when given a hash' do
|
|
25
|
+
let(:value) { { not_expected: 1} }
|
|
26
|
+
|
|
27
|
+
it 'raises an error' do
|
|
28
|
+
expect {
|
|
29
|
+
resolved_value
|
|
30
|
+
}.to raise_error(ArgumentError)
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
context 'when given a valid string value' do
|
|
35
|
+
let(:value) { 'my-stack/MyOutput' }
|
|
36
|
+
let(:stacks) { [{ stack_name: 'blah', creation_time: Time.now, stack_status: 'CREATE_COMPLETE', outputs: outputs}] }
|
|
37
|
+
let(:outputs) { [] }
|
|
38
|
+
|
|
39
|
+
before do
|
|
40
|
+
allow(Aws::CloudFormation::Client).to receive(:new).and_return(cf)
|
|
41
|
+
cf.stub_responses(:describe_stacks, stacks: stacks)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
context 'the stack and output exist' do
|
|
45
|
+
let(:outputs) { [{output_key: 'MyOutput', output_value: 'myresolvedvalue'}] }
|
|
46
|
+
|
|
47
|
+
it 'resolves the value' do
|
|
48
|
+
expect(resolved_value).to eq 'myresolvedvalue'
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
it 'caches stacks for the lifetime of the instance' do
|
|
52
|
+
resolver.resolve(value)
|
|
53
|
+
resolver.resolve(value)
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
context "the stack doesn't exist" do
|
|
58
|
+
let(:stacks) { nil }
|
|
59
|
+
|
|
60
|
+
it 'resolves the value' do
|
|
61
|
+
expect {
|
|
62
|
+
resolved_value
|
|
63
|
+
}.to raise_error(StackMaster::ParameterResolvers::StackOutput::StackNotFound)
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
context "the output doesn't exist" do
|
|
68
|
+
let(:outputs) { [] }
|
|
69
|
+
|
|
70
|
+
it 'resolves the value' do
|
|
71
|
+
expect {
|
|
72
|
+
resolved_value
|
|
73
|
+
}.to raise_error(StackMaster::ParameterResolvers::StackOutput::StackOutputNotFound)
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|