stack_master 0.1.1 → 0.2.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 +1 -0
- data/features/apply.feature +107 -0
- data/lib/stack_master.rb +1 -0
- data/lib/stack_master/cli.rb +7 -2
- data/lib/stack_master/command.rb +4 -0
- data/lib/stack_master/commands/status.rb +7 -28
- data/lib/stack_master/stack_differ.rb +26 -16
- data/lib/stack_master/stack_status.rb +61 -0
- data/lib/stack_master/version.rb +1 -1
- data/spec/stack_master/commands/status_spec.rb +2 -0
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 106f5899a9d5076230bdf26135167adf3d658182
|
4
|
+
data.tar.gz: 8fd1dea5cd5026d8c316b641928ba14457c5fa8c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6db154802da177625841f36cbc9924140990a92133c57cbd080643849b2d58cc1b2a6e7ed0bdc80c4fca5d2df2e0391a5d1e077a361914cfb6cc533f18e9e20d
|
7
|
+
data.tar.gz: 6d657581c952323eb3fe878094fd3f86847f01525f65bddd21cdb5be473a47d9770e095b5cbd51d8abfe9ff373e8f3649f27917cbac8ef6803defbb7819c4265
|
data/README.md
CHANGED
@@ -255,6 +255,7 @@ stack_master apply [region-or-alias] [stack-name] # Create or update a stack
|
|
255
255
|
stack_master apply [region-or-alias] [stack-name] [region-or-alias] [stack-name] # Create or update multiple stacks
|
256
256
|
stack_master apply [region-or-alias] # Create or update stacks in the given region
|
257
257
|
stack_master apply # Create or update all stacks
|
258
|
+
stack_master --changed apply # Create or update all stacks that have changed
|
258
259
|
stack_master --yes apply [region-or-alias] [stack-name] # Create or update a stack non-interactively (forcing yes)
|
259
260
|
stack_master diff [region-or-alias] [stack-name] # Display a stack tempalte and parameter diff
|
260
261
|
stack_master delete [region-or-alias] [stack-name] # Delete a stack
|
data/features/apply.feature
CHANGED
@@ -123,6 +123,20 @@ Feature: Apply command
|
|
123
123
|
And the output should match /2020-10-29 00:00:00 \+[0-9]{4} myapp-web AWS::CloudFormation::Stack CREATE_COMPLETE/
|
124
124
|
Then the exit status should be 0
|
125
125
|
|
126
|
+
Scenario: Create stack with --changed
|
127
|
+
Given I stub the following stack events:
|
128
|
+
| stack_id | event_id | stack_name | logical_resource_id | resource_status | resource_type | timestamp |
|
129
|
+
| 1 | 1 | myapp-vpc | TestSg | CREATE_COMPLETE | AWS::EC2::SecurityGroup | 2020-10-29 00:00:00 |
|
130
|
+
| 1 | 1 | myapp-vpc | myapp-vpc | CREATE_COMPLETE | AWS::CloudFormation::Stack | 2020-10-29 00:00:00 |
|
131
|
+
| 1 | 1 | myapp-web | TestSg | CREATE_COMPLETE | AWS::EC2::SecurityGroup | 2020-10-29 00:00:00 |
|
132
|
+
| 1 | 1 | myapp-web | myapp-web | CREATE_COMPLETE | AWS::CloudFormation::Stack | 2020-10-29 00:00:00 |
|
133
|
+
When I run `stack_master --changed apply us-east-1 --trace`
|
134
|
+
And the output should contain all of these lines:
|
135
|
+
| Stack diff: |
|
136
|
+
| + "Vpc": { |
|
137
|
+
| Parameters diff: |
|
138
|
+
| KeyName: my-key |
|
139
|
+
|
126
140
|
Scenario: Run apply with 2 specific stacks and create 2 stacks
|
127
141
|
Given I stub the following stack events:
|
128
142
|
| stack_id | event_id | stack_name | logical_resource_id | resource_status | resource_type | timestamp |
|
@@ -191,6 +205,99 @@ Feature: Apply command
|
|
191
205
|
| Parameters diff: No changes |
|
192
206
|
Then the exit status should be 0
|
193
207
|
|
208
|
+
Scenario: Update an existing stack that has changed with --changed
|
209
|
+
Given I stub the following stack events:
|
210
|
+
| stack_id | event_id | stack_name | logical_resource_id | resource_status | resource_type | timestamp |
|
211
|
+
| 1 | 1 | myapp-vpc | TestSg | CREATE_COMPLETE | AWS::EC2::SecurityGroup | 2020-10-29 00:00:00 |
|
212
|
+
| 1 | 1 | myapp-vpc | myapp-vpc | CREATE_COMPLETE | AWS::CloudFormation::Stack | 2020-10-29 00:00:00 |
|
213
|
+
And I stub the following stacks:
|
214
|
+
| stack_id | stack_name | parameters | region |
|
215
|
+
| 1 | myapp-vpc | KeyName=my-key | us-east-1 |
|
216
|
+
And I stub a template for the stack "myapp-vpc":
|
217
|
+
"""
|
218
|
+
{
|
219
|
+
"Description": "Test template",
|
220
|
+
"AWSTemplateFormatVersion": "2010-09-09",
|
221
|
+
"Parameters": {
|
222
|
+
"KeyName": {
|
223
|
+
"Description": "Key Name",
|
224
|
+
"Type": "String"
|
225
|
+
}
|
226
|
+
},
|
227
|
+
"Resources": {
|
228
|
+
"TestSg": {
|
229
|
+
"Type": "AWS::EC2::SecurityGroup",
|
230
|
+
"Properties": {
|
231
|
+
"GroupDescription": "Test SG",
|
232
|
+
"VpcId": {
|
233
|
+
"Ref": "VpcId"
|
234
|
+
}
|
235
|
+
}
|
236
|
+
},
|
237
|
+
"TestSg2": {
|
238
|
+
"Type": "AWS::EC2::SecurityGroup",
|
239
|
+
"Properties": {
|
240
|
+
"GroupDescription": "Test SG 2",
|
241
|
+
"VpcId": {
|
242
|
+
"Ref": "VpcId"
|
243
|
+
}
|
244
|
+
}
|
245
|
+
}
|
246
|
+
}
|
247
|
+
}
|
248
|
+
"""
|
249
|
+
When I run `stack_master --changed apply us-east-1 myapp-vpc --trace`
|
250
|
+
And the output should contain all of these lines:
|
251
|
+
| Stack diff: |
|
252
|
+
| - "TestSg2": { |
|
253
|
+
| Parameters diff: No changes |
|
254
|
+
Then the exit status should be 0
|
255
|
+
|
256
|
+
Scenario: Update an existing stack that hasn't changed with --changed
|
257
|
+
Given I stub the following stack events:
|
258
|
+
| stack_id | event_id | stack_name | logical_resource_id | resource_status | resource_type | timestamp |
|
259
|
+
| 1 | 1 | myapp-vpc | TestSg | CREATE_COMPLETE | AWS::EC2::SecurityGroup | 2020-10-29 00:00:00 |
|
260
|
+
| 1 | 1 | myapp-vpc | myapp-vpc | CREATE_COMPLETE | AWS::CloudFormation::Stack | 2020-10-29 00:00:00 |
|
261
|
+
And I stub the following stacks:
|
262
|
+
| stack_id | stack_name | parameters | region |
|
263
|
+
| 1 | myapp-vpc | KeyName=my-key | us-east-1 |
|
264
|
+
And I stub a template for the stack "myapp-vpc":
|
265
|
+
"""
|
266
|
+
{
|
267
|
+
"Description": "Test template",
|
268
|
+
"AWSTemplateFormatVersion": "2010-09-09",
|
269
|
+
"Parameters": {
|
270
|
+
"KeyName": {
|
271
|
+
"Description": "Key name",
|
272
|
+
"Type": "String"
|
273
|
+
}
|
274
|
+
},
|
275
|
+
"Resources": {
|
276
|
+
"Vpc": {
|
277
|
+
"Type": "AWS::EC2::VPC",
|
278
|
+
"Properties": {
|
279
|
+
"CidrBlock": "10.200.0.0/16"
|
280
|
+
}
|
281
|
+
}
|
282
|
+
},
|
283
|
+
"Outputs": {
|
284
|
+
"VpcId": {
|
285
|
+
"Description": "A VPC ID",
|
286
|
+
"Value": {
|
287
|
+
"Ref": "Vpc"
|
288
|
+
}
|
289
|
+
}
|
290
|
+
}
|
291
|
+
}
|
292
|
+
"""
|
293
|
+
When I run `stack_master --changed apply us-east-1 myapp-vpc --trace`
|
294
|
+
And the output should not contain all of these lines:
|
295
|
+
| Stack diff: |
|
296
|
+
| - "TestSg2": { |
|
297
|
+
| Parameters diff: No changes |
|
298
|
+
Then the exit status should be 0
|
299
|
+
|
300
|
+
|
194
301
|
Scenario: Create a stack using a stack output resolver
|
195
302
|
Given a file named "parameters/myapp_web.yml" with:
|
196
303
|
"""
|
data/lib/stack_master.rb
CHANGED
@@ -22,6 +22,7 @@ require "stack_master/stack_events/fetcher"
|
|
22
22
|
require "stack_master/stack_events/presenter"
|
23
23
|
require "stack_master/stack_events/streamer"
|
24
24
|
require "stack_master/stack_states"
|
25
|
+
require "stack_master/stack_status"
|
25
26
|
require "stack_master/sns_topic_finder"
|
26
27
|
require "stack_master/security_group_finder"
|
27
28
|
require "stack_master/parameter_loader"
|
data/lib/stack_master/cli.rb
CHANGED
@@ -18,10 +18,11 @@ module StackMaster
|
|
18
18
|
|
19
19
|
def execute!
|
20
20
|
program :name, 'StackMaster'
|
21
|
-
program :version,
|
21
|
+
program :version, StackMaster::VERSION
|
22
22
|
program :description, 'AWS Stack Management'
|
23
23
|
|
24
24
|
global_option '-c', '--config FILE', String, 'Config file to use'
|
25
|
+
global_option '--changed', 'filter stack selection to only ones that have changed'
|
25
26
|
global_option '-y', '--yes', 'Run in non-interactive mode answering yes to any prompts' do
|
26
27
|
StackMaster.non_interactive!
|
27
28
|
StackMaster.non_interactive_answer = 'y'
|
@@ -183,10 +184,14 @@ module StackMaster
|
|
183
184
|
stack_name = Utils.underscore_to_hyphen(stack_name)
|
184
185
|
stack_definitions = config.filter(region, stack_name)
|
185
186
|
if stack_definitions.empty?
|
186
|
-
|
187
|
+
StackMaster.stdout.puts "Could not find stack definition #{stack_name} in region #{region}"
|
187
188
|
end
|
189
|
+
stack_definitions = stack_definitions.select do |stack_definition|
|
190
|
+
StackStatus.new(config, stack_definition).changed?
|
191
|
+
end if options.changed
|
188
192
|
stack_definitions.each do |stack_definition|
|
189
193
|
StackMaster.cloud_formation_driver.set_region(stack_definition.region)
|
194
|
+
StackMaster.stdout.puts "Executing #{command.command_name} on #{stack_definition.stack_name} in #{stack_definition.region}"
|
190
195
|
command_results.push command.perform(config, stack_definition, options).success?
|
191
196
|
end
|
192
197
|
end
|
data/lib/stack_master/command.rb
CHANGED
@@ -12,9 +12,14 @@ module StackMaster
|
|
12
12
|
def perform
|
13
13
|
progress if @show_progress
|
14
14
|
status = @config.stacks.map do |stack_definition|
|
15
|
-
|
15
|
+
stack_status = StackStatus.new(@config, stack_definition)
|
16
16
|
progress.increment if @show_progress
|
17
|
-
|
17
|
+
{
|
18
|
+
region: stack_definition.region,
|
19
|
+
stack_name: stack_definition.stack_name,
|
20
|
+
stack_status: stack_status.status,
|
21
|
+
different: stack_status.changed_message,
|
22
|
+
}
|
18
23
|
end
|
19
24
|
tp.set :max_width, self.window_size
|
20
25
|
tp.set :io, StackMaster.stdout
|
@@ -33,32 +38,6 @@ module StackMaster
|
|
33
38
|
def sort_params(hash)
|
34
39
|
hash.sort.to_h
|
35
40
|
end
|
36
|
-
|
37
|
-
def get_status(stack_definition)
|
38
|
-
region = stack_definition.region
|
39
|
-
stack_name = stack_definition.stack_name
|
40
|
-
begin
|
41
|
-
driver = StackMaster.cloud_formation_driver
|
42
|
-
driver.set_region(region)
|
43
|
-
stack = Stack.find(region, stack_name)
|
44
|
-
if stack
|
45
|
-
proposed_stack = Stack.generate(stack_definition, @config)
|
46
|
-
differ = StackMaster::StackDiffer.new(proposed_stack, stack)
|
47
|
-
different = differ.body_different? || differ.params_different?
|
48
|
-
stack_status = stack.stack_status
|
49
|
-
noecho = !differ.noecho_keys.empty?
|
50
|
-
else
|
51
|
-
different = true
|
52
|
-
stack_status = nil
|
53
|
-
end
|
54
|
-
rescue Aws::CloudFormation::Errors::ValidationError
|
55
|
-
stack_status = "missing"
|
56
|
-
different = true
|
57
|
-
end
|
58
|
-
|
59
|
-
{ region: region, stack_name: stack_name, stack_status: stack_status, different: different ? "Yes" : (noecho ? "No *" : "No") }
|
60
|
-
end
|
61
|
-
|
62
41
|
end
|
63
42
|
end
|
64
43
|
end
|
@@ -10,11 +10,19 @@ module StackMaster
|
|
10
10
|
end
|
11
11
|
|
12
12
|
def current_template
|
13
|
-
|
13
|
+
if @current_stack
|
14
|
+
JSON.pretty_generate(@current_stack.template_hash)
|
15
|
+
else
|
16
|
+
''
|
17
|
+
end
|
14
18
|
end
|
15
19
|
|
16
20
|
def current_parameters
|
17
|
-
|
21
|
+
if @current_stack
|
22
|
+
YAML.dump(sort_params(@current_stack.parameters_with_defaults))
|
23
|
+
else
|
24
|
+
''
|
25
|
+
end
|
18
26
|
end
|
19
27
|
|
20
28
|
def proposed_parameters
|
@@ -29,25 +37,28 @@ module StackMaster
|
|
29
37
|
end
|
30
38
|
|
31
39
|
def body_different?
|
32
|
-
|
40
|
+
body_diff != ''
|
41
|
+
end
|
42
|
+
|
43
|
+
def body_diff
|
44
|
+
@body_diff ||= Diffy::Diff.new(current_template, proposed_template, context: 7, include_diff_info: true).to_s
|
33
45
|
end
|
34
46
|
|
35
47
|
def params_different?
|
36
|
-
|
48
|
+
params_diff != ''
|
49
|
+
end
|
50
|
+
|
51
|
+
def params_diff
|
52
|
+
@param_diff ||= Diffy::Diff.new(current_parameters, proposed_parameters, {}).to_s
|
37
53
|
end
|
38
54
|
|
39
55
|
def output_diff
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
StackMaster.stdout.puts " * can not tell if NoEcho parameters are different."
|
45
|
-
end
|
46
|
-
else
|
47
|
-
text_diff('Stack', '', proposed_template)
|
48
|
-
text_diff('Parameters', '', proposed_parameters)
|
49
|
-
StackMaster.stdout.puts "No stack found"
|
56
|
+
display_diff('Stack', body_diff)
|
57
|
+
display_diff('Parameters', params_diff)
|
58
|
+
unless noecho_keys.empty?
|
59
|
+
StackMaster.stdout.puts " * can not tell if NoEcho parameters are different."
|
50
60
|
end
|
61
|
+
StackMaster.stdout.puts "No stack found" if @current_stack.nil?
|
51
62
|
end
|
52
63
|
|
53
64
|
def noecho_keys
|
@@ -62,8 +73,7 @@ module StackMaster
|
|
62
73
|
|
63
74
|
private
|
64
75
|
|
65
|
-
def
|
66
|
-
diff = Diffy::Diff.new(current, proposed, diff_opts).to_s
|
76
|
+
def display_diff(thing, diff)
|
67
77
|
StackMaster.stdout.print "#{thing} diff: "
|
68
78
|
if diff == ''
|
69
79
|
StackMaster.stdout.puts "No changes"
|
@@ -0,0 +1,61 @@
|
|
1
|
+
module StackMaster
|
2
|
+
class StackStatus
|
3
|
+
def initialize(config, stack_definition)
|
4
|
+
@config = config
|
5
|
+
@stack_definition = stack_definition
|
6
|
+
end
|
7
|
+
|
8
|
+
def changed_message
|
9
|
+
if changed?
|
10
|
+
'Yes'
|
11
|
+
elsif no_echo_params?
|
12
|
+
'No *'
|
13
|
+
else
|
14
|
+
'No'
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def changed?
|
19
|
+
stack.nil? || body_changed? || parameters_changed?
|
20
|
+
end
|
21
|
+
|
22
|
+
def status
|
23
|
+
stack ? stack.stack_status : nil
|
24
|
+
end
|
25
|
+
|
26
|
+
def body_changed?
|
27
|
+
stack.nil? || differ.body_different?
|
28
|
+
end
|
29
|
+
|
30
|
+
def parameters_changed?
|
31
|
+
stack.nil? || differ.params_different?
|
32
|
+
end
|
33
|
+
|
34
|
+
def no_echo_params?
|
35
|
+
!differ.noecho_keys.empty?
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def stack
|
41
|
+
return @stack if defined?(@stack)
|
42
|
+
StackMaster.cloud_formation_driver.set_region(stack_definition.region)
|
43
|
+
@stack = find_stack
|
44
|
+
end
|
45
|
+
|
46
|
+
def find_stack
|
47
|
+
Stack.find(stack_definition.region, stack_definition.stack_name)
|
48
|
+
rescue Aws::CloudFormation::Errors::ValidationError
|
49
|
+
end
|
50
|
+
|
51
|
+
def differ
|
52
|
+
@differ ||= StackMaster::StackDiffer.new(proposed_stack, stack)
|
53
|
+
end
|
54
|
+
|
55
|
+
def proposed_stack
|
56
|
+
@proposed_stack ||= Stack.generate(stack_definition, @config)
|
57
|
+
end
|
58
|
+
|
59
|
+
attr_reader :stack_definition
|
60
|
+
end
|
61
|
+
end
|
data/lib/stack_master/version.rb
CHANGED
@@ -21,6 +21,7 @@ RSpec.describe StackMaster::Commands::Status do
|
|
21
21
|
let(:stack2) { double(:stack2, template_hash: {}, parameters_with_defaults: {a: 2}, stack_status: 'CREATE_COMPLETE') }
|
22
22
|
let(:proposed_stack1) { double(:proposed_stack1, template_body: "{}", parameters_with_defaults: {a: 1}) }
|
23
23
|
let(:proposed_stack2) { double(:proposed_stack2, template_body: "{}", parameters_with_defaults: {a: 1}) }
|
24
|
+
|
24
25
|
it "returns the status of call stacks" do
|
25
26
|
out = "REGION | STACK_NAME | STACK_STATUS | DIFFERENT\n----------|------------|-----------------|----------\nus-east-1 | stack1 | UPDATE_COMPLETE | No \nus-east-1 | stack2 | CREATE_COMPLETE | Yes \n * No echo parameters can't be diffed\n"
|
26
27
|
expect { status.perform }.to output(out).to_stdout
|
@@ -32,6 +33,7 @@ RSpec.describe StackMaster::Commands::Status do
|
|
32
33
|
let(:stack2) { double(:stack2, template_hash: {}, parameters_with_defaults: {a: 1}, stack_status: 'CREATE_COMPLETE') }
|
33
34
|
let(:proposed_stack1) { double(:proposed_stack1, template_body: "{}", parameters_with_defaults: {a: 1}) }
|
34
35
|
let(:proposed_stack2) { double(:proposed_stack2, template_body: "{}", parameters_with_defaults: {a: 1}) }
|
36
|
+
|
35
37
|
it "returns the status of call stacks" do
|
36
38
|
out = "REGION | STACK_NAME | STACK_STATUS | DIFFERENT\n----------|------------|-----------------|----------\nus-east-1 | stack1 | UPDATE_COMPLETE | Yes \nus-east-1 | stack2 | CREATE_COMPLETE | No \n * No echo parameters can't be diffed\n"
|
37
39
|
expect { status.perform }.to output(out).to_stdout
|
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.2.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: 2016-
|
12
|
+
date: 2016-03-09 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: bundler
|
@@ -335,6 +335,7 @@ files:
|
|
335
335
|
- lib/stack_master/stack_events/presenter.rb
|
336
336
|
- lib/stack_master/stack_events/streamer.rb
|
337
337
|
- lib/stack_master/stack_states.rb
|
338
|
+
- lib/stack_master/stack_status.rb
|
338
339
|
- lib/stack_master/template_compiler.rb
|
339
340
|
- lib/stack_master/test_driver/cloud_formation.rb
|
340
341
|
- lib/stack_master/testing.rb
|