stack_master 0.13.0 → 0.14.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +11 -2
- data/lib/stack_master/aws_driver/cloud_formation.rb +13 -33
- data/lib/stack_master/parameter_resolvers/stack_output.rb +34 -13
- data/lib/stack_master/test_driver/cloud_formation.rb +4 -0
- data/lib/stack_master/version.rb +1 -1
- data/spec/stack_master/commands/delete_spec.rb +1 -1
- data/spec/stack_master/commands/status_spec.rb +0 -19
- data/spec/stack_master/parameter_resolvers/stack_output_spec.rb +51 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8693e4d791f64b4704d3e5b068d0e3a77fa8c6a6
|
4
|
+
data.tar.gz: 69c0fcb456e40b8dc933ea73697bc194b65e20ea
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6805b7c93f992d0d83f6728732bf58001e5ebdda5bc2cb59d4438ede7e8ef725feb29f77512b5e3d673210405f7e869d197443b77545c602564f0704efe515ed
|
7
|
+
data.tar.gz: 42bc841676942028c0f8cc59206e5d7ca3dfb8c38301aa52e543c921eff25bd3afd97f0ba2e1a02bbf386e4d81d34684aa3c71e9e32390e69fa8cc35abb00bbd
|
data/README.md
CHANGED
@@ -168,11 +168,20 @@ region/account, even though the resolved values will be different.
|
|
168
168
|
### Stack Output
|
169
169
|
|
170
170
|
The stack output parameter resolver looks up outputs from other stacks in the
|
171
|
-
same region. The expected format is `[stack-name
|
171
|
+
same or different region. The expected format is `[(region|region-alias):]stack-name/(OutputName|output_name)`.
|
172
172
|
|
173
173
|
```yaml
|
174
174
|
vpc_id:
|
175
|
+
# Output from a stack in the same region
|
175
176
|
stack_output: my-vpc-stack/VpcId
|
177
|
+
|
178
|
+
bucket_name:
|
179
|
+
# Output from a stack in a different region
|
180
|
+
stack_output: us-east-1:init-bucket/bucket_name
|
181
|
+
|
182
|
+
zone_name:
|
183
|
+
# Output from a stack in a different region using its alias
|
184
|
+
stack_output: global:hosted-zone/ZoneName
|
176
185
|
```
|
177
186
|
|
178
187
|
This is the most used parameter resolver because it enables stacks to be split
|
@@ -307,7 +316,7 @@ vpc_id:
|
|
307
316
|
|
308
317
|
Most resolvers support taking an array of values that will each be resolved.
|
309
318
|
Unless stated otherwise in the documentation, the array version of the
|
310
|
-
resolver will be named with the [pluralized](http://api.rubyonrails.org/classes/ActiveSupport/Inflector.html#method-i-pluralize)
|
319
|
+
resolver will be named with the [pluralized](http://api.rubyonrails.org/classes/ActiveSupport/Inflector.html#method-i-pluralize)
|
311
320
|
name of the original resolver.
|
312
321
|
|
313
322
|
When creating a new resolver, one can automatically create the array resolver by adding a `array_resolver` statement
|
@@ -3,9 +3,15 @@ module StackMaster
|
|
3
3
|
class CloudFormation
|
4
4
|
extend Forwardable
|
5
5
|
|
6
|
-
def
|
7
|
-
@region
|
8
|
-
|
6
|
+
def region
|
7
|
+
@region ||= ENV['AWS_REGION'] || Aws.config[:region] || Aws.shared_config.region
|
8
|
+
end
|
9
|
+
|
10
|
+
def set_region(value)
|
11
|
+
if region != value
|
12
|
+
@region = value
|
13
|
+
@cf = nil
|
14
|
+
end
|
9
15
|
end
|
10
16
|
|
11
17
|
def_delegators :cf, :create_change_set,
|
@@ -19,42 +25,16 @@ module StackMaster
|
|
19
25
|
:get_stack_policy,
|
20
26
|
:describe_stack_events,
|
21
27
|
:update_stack,
|
22
|
-
:create_stack
|
23
|
-
|
24
|
-
|
25
|
-
retry_with_backoff do
|
26
|
-
cf.describe_stacks(options)
|
27
|
-
end
|
28
|
-
end
|
29
|
-
|
30
|
-
def validate_template(options)
|
31
|
-
retry_with_backoff do
|
32
|
-
cf.validate_template(options)
|
33
|
-
end
|
34
|
-
end
|
28
|
+
:create_stack,
|
29
|
+
:validate_template,
|
30
|
+
:describe_stacks
|
35
31
|
|
36
32
|
private
|
37
33
|
|
38
34
|
def cf
|
39
|
-
@cf ||= Aws::CloudFormation::Client.new(region:
|
35
|
+
@cf ||= Aws::CloudFormation::Client.new(region: region, retry_limit: 10)
|
40
36
|
end
|
41
37
|
|
42
|
-
def retry_with_backoff
|
43
|
-
delay = 1
|
44
|
-
max_delay = 30
|
45
|
-
begin
|
46
|
-
yield
|
47
|
-
rescue Aws::CloudFormation::Errors::Throttling => e
|
48
|
-
if e.message =~ /Rate exceeded/
|
49
|
-
sleep delay
|
50
|
-
delay *= 2
|
51
|
-
if delay > max_delay
|
52
|
-
delay = max_delay
|
53
|
-
end
|
54
|
-
retry
|
55
|
-
end
|
56
|
-
end
|
57
|
-
end
|
58
38
|
end
|
59
39
|
end
|
60
40
|
end
|
@@ -10,14 +10,15 @@ module StackMaster
|
|
10
10
|
@config = config
|
11
11
|
@stack_definition = stack_definition
|
12
12
|
@stacks = {}
|
13
|
+
@cf_drivers = {}
|
14
|
+
@output_regex = %r{(?:(?<region>[^:]+):)?(?<stack_name>[^:/]+)/(?<output_name>.+)}
|
13
15
|
end
|
14
16
|
|
15
17
|
def resolve(value)
|
16
|
-
|
17
|
-
|
18
|
-
stack = find_stack(stack_name)
|
18
|
+
region, stack_name, output_name = parse!(value)
|
19
|
+
stack = find_stack(stack_name, region)
|
19
20
|
if stack
|
20
|
-
output = stack.outputs.find { |
|
21
|
+
output = stack.outputs.find { |stack_output| stack_output.output_key == output_name.camelize }
|
21
22
|
if output
|
22
23
|
output.output_value
|
23
24
|
else
|
@@ -34,21 +35,41 @@ module StackMaster
|
|
34
35
|
@cf ||= StackMaster.cloud_formation_driver
|
35
36
|
end
|
36
37
|
|
37
|
-
def
|
38
|
-
if !value.is_a?(String) || !
|
39
|
-
raise ArgumentError, 'Stack output values must be in the form of stack-name/
|
38
|
+
def parse!(value)
|
39
|
+
if !value.is_a?(String) || !(match = @output_regex.match(value))
|
40
|
+
raise ArgumentError, 'Stack output values must be in the form of [region:]stack-name/output_name'
|
40
41
|
end
|
42
|
+
|
43
|
+
[
|
44
|
+
match[:region] || cf.region,
|
45
|
+
match[:stack_name],
|
46
|
+
match[:output_name]
|
47
|
+
]
|
41
48
|
end
|
42
49
|
|
43
|
-
def find_stack(stack_name)
|
44
|
-
@
|
45
|
-
|
46
|
-
|
50
|
+
def find_stack(stack_name, region)
|
51
|
+
unaliased_region = @config.unalias_region(region)
|
52
|
+
stack_key = stack_key(stack_name, unaliased_region)
|
53
|
+
|
54
|
+
@stacks.fetch(stack_key) do
|
55
|
+
regional_cf = cf_for_region(unaliased_region)
|
56
|
+
cf_stack = regional_cf.describe_stacks(stack_name: stack_name).stacks.first
|
57
|
+
@stacks[stack_key] = cf_stack
|
47
58
|
end
|
48
59
|
end
|
49
60
|
|
50
|
-
def
|
51
|
-
|
61
|
+
def stack_key(stack_name, region)
|
62
|
+
"#{region}:#{stack_name}"
|
63
|
+
end
|
64
|
+
|
65
|
+
def cf_for_region(region)
|
66
|
+
return cf if cf.region == region
|
67
|
+
|
68
|
+
@cf_drivers.fetch(region) do
|
69
|
+
cloud_formation_driver = cf.class.new
|
70
|
+
cloud_formation_driver.set_region(region)
|
71
|
+
@cf_drivers[region] = cloud_formation_driver
|
72
|
+
end
|
52
73
|
end
|
53
74
|
end
|
54
75
|
end
|
data/lib/stack_master/version.rb
CHANGED
@@ -7,7 +7,7 @@ RSpec.describe StackMaster::Commands::Delete do
|
|
7
7
|
|
8
8
|
before do
|
9
9
|
StackMaster.cloud_formation_driver.set_region(region)
|
10
|
-
allow(Aws::CloudFormation::Client).to receive(:new).with(region: region).and_return(cf)
|
10
|
+
allow(Aws::CloudFormation::Client).to receive(:new).with(region: region, retry_limit: 10).and_return(cf)
|
11
11
|
allow(delete).to receive(:ask?).and_return('y')
|
12
12
|
allow(StackMaster::StackEvents::Streamer).to receive(:stream)
|
13
13
|
end
|
@@ -41,23 +41,4 @@ RSpec.describe StackMaster::Commands::Status do
|
|
41
41
|
end
|
42
42
|
end
|
43
43
|
|
44
|
-
context "handles AWS throttling" do
|
45
|
-
let(:throttle_exception) { Aws::CloudFormation::Errors::Throttling.new(double(), "Rate exceeded.") }
|
46
|
-
let(:stack1) { double(:stack1, template_hash: {}, parameters_with_defaults: {a: 1}, stack_status: 'UPDATE_COMPLETE') }
|
47
|
-
let(:stack2) { double(:stack2, template_hash: {}, parameters_with_defaults: {a: 2}, stack_status: 'CREATE_COMPLETE') }
|
48
|
-
let(:proposed_stack1) { double(:proposed_stack1, template_body: "{}", parameters_with_defaults: {a: 1}) }
|
49
|
-
let(:proposed_stack2) { double(:proposed_stack2, template_body: "{}", parameters_with_defaults: {a: 1}) }
|
50
|
-
|
51
|
-
it "doubles the sleep time across calls" do
|
52
|
-
call_count = 0
|
53
|
-
expect(cf).to receive(:describe_stacks).at_least(1).times do
|
54
|
-
call_count += 1
|
55
|
-
call_count <= 3 ? raise(throttle_exception) : double(stacks: double(first: nil))
|
56
|
-
end
|
57
|
-
expect(StackMaster.cloud_formation_driver).to receive(:sleep).with(1).ordered
|
58
|
-
expect(StackMaster.cloud_formation_driver).to receive(:sleep).with(2).ordered
|
59
|
-
expect(StackMaster.cloud_formation_driver).to receive(:sleep).with(4).ordered
|
60
|
-
expect { status.perform }.to_not raise_exception
|
61
|
-
end
|
62
|
-
end
|
63
44
|
end
|
@@ -1,9 +1,9 @@
|
|
1
1
|
RSpec.describe StackMaster::ParameterResolvers::StackOutput do
|
2
2
|
let(:region) { 'us-east-1' }
|
3
3
|
let(:stack_name) { 'my-stack' }
|
4
|
-
let(:config) { double }
|
5
4
|
let(:resolver) { described_class.new(config, double(region: 'us-east-1')) }
|
6
5
|
let(:cf) { Aws::CloudFormation::Client.new }
|
6
|
+
let(:config) { double(:unalias_region => region) }
|
7
7
|
|
8
8
|
def resolve(value)
|
9
9
|
resolver.resolve(value)
|
@@ -74,4 +74,54 @@ RSpec.describe StackMaster::ParameterResolvers::StackOutput do
|
|
74
74
|
end
|
75
75
|
end
|
76
76
|
end
|
77
|
+
|
78
|
+
context 'when given a valid string value including region' do
|
79
|
+
let(:value) { 'us-east-1:my-stack/MyOutput' }
|
80
|
+
let(:stacks) { [{ stack_name: 'my-stack', creation_time: Time.now, stack_status: 'CREATE_COMPLETE', outputs: outputs}] }
|
81
|
+
let(:outputs) { [] }
|
82
|
+
|
83
|
+
before do
|
84
|
+
allow(Aws::CloudFormation::Client).to receive(:new).and_return(cf)
|
85
|
+
cf.stub_responses(:describe_stacks, stacks: stacks)
|
86
|
+
end
|
87
|
+
|
88
|
+
context 'the stack and output exist' do
|
89
|
+
let(:outputs) { [{output_key: 'MyOutput', output_value: 'myresolvedvalue'}] }
|
90
|
+
|
91
|
+
it 'resolves the value' do
|
92
|
+
expect(resolved_value).to eq 'myresolvedvalue'
|
93
|
+
end
|
94
|
+
|
95
|
+
context 'the stack and output exist in a different region with the same name' do
|
96
|
+
let(:value_in_region_alias) { 'global:my-stack/MyOutput' }
|
97
|
+
let(:value_in_region_2) { 'ap-southeast-2:my-stack/MyOutput' }
|
98
|
+
let(:outputs_in_region_2) { [{output_key: 'MyOutput', output_value: 'myresolvedvalue2'}] }
|
99
|
+
let(:stacks_in_region_2) { [{ stack_name: 'my-stack', creation_time: Time.now, stack_status: 'CREATE_COMPLETE', outputs: outputs_in_region_2}] }
|
100
|
+
|
101
|
+
before do
|
102
|
+
cf.stub_responses(
|
103
|
+
:describe_stacks,
|
104
|
+
{ stacks: stacks },
|
105
|
+
{ stacks: stacks_in_region_2 }
|
106
|
+
)
|
107
|
+
allow(config).to receive(:unalias_region) do |aliased_region|
|
108
|
+
if aliased_region == 'global'
|
109
|
+
'us-east-1'
|
110
|
+
else
|
111
|
+
aliased_region
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
it 'resolves the value to the right region' do
|
117
|
+
resolver.resolve(value)
|
118
|
+
expect(resolver.resolve(value_in_region_2)).to eq 'myresolvedvalue2'
|
119
|
+
end
|
120
|
+
|
121
|
+
it 'resolves to the same region if it is an alias' do
|
122
|
+
expect(resolver.resolve(value_in_region_alias)).to eq 'myresolvedvalue'
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
77
127
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: stack_master
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.14.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Steve Hodgkiss
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2017-03-
|
12
|
+
date: 2017-03-28 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: bundler
|