stack_master 0.13.0 → 0.14.0
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 +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
|