stack_master 0.1.1 → 0.2.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 +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
|