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,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
|