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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 0290ee1dd10780abd86abcd6818ff3e4ad913380
4
- data.tar.gz: 57dd36502a7e98b1940336f9f3a8aa3364a2a6bd
3
+ metadata.gz: 106f5899a9d5076230bdf26135167adf3d658182
4
+ data.tar.gz: 8fd1dea5cd5026d8c316b641928ba14457c5fa8c
5
5
  SHA512:
6
- metadata.gz: c2e1bec9d3deefa251ba550f75fb7c43013efedafd0c5e37ccccc3eb65c2d26e367b6386ad370d3b4bea584c5fef1ac04306e15ea901c4595883c0502b10b943
7
- data.tar.gz: c4f02a55dbd4ed0b82747ff3172ba023f99aff8445dcfd085eb91d6498ce9b491e6b41ef6cc05266a4d0ecc5ac7e07e8b757453d13199243997bc94de556242f
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
@@ -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
  """
@@ -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"
@@ -18,10 +18,11 @@ module StackMaster
18
18
 
19
19
  def execute!
20
20
  program :name, 'StackMaster'
21
- program :version, '0.0.1'
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
- say "Could not find stack definition #{stack_name} in region #{region}"
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
@@ -8,6 +8,10 @@ module StackMaster
8
8
  def perform(*args)
9
9
  new(*args).tap { |command| command.perform }
10
10
  end
11
+
12
+ def command_name
13
+ name.split('::').last.underscore
14
+ end
11
15
  end
12
16
 
13
17
  def failed
@@ -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
- status = get_status(stack_definition)
15
+ stack_status = StackStatus.new(@config, stack_definition)
16
16
  progress.increment if @show_progress
17
- status
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
- JSON.pretty_generate(@current_stack.template_hash)
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
- YAML.dump(sort_params(@current_stack.parameters_with_defaults))
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
- Diffy::Diff.new(current_template, proposed_template, {}).to_s != ''
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
- Diffy::Diff.new(current_parameters, proposed_parameters, {}).to_s != ''
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
- if @current_stack
41
- text_diff('Stack', current_template, proposed_template, context: 7, include_diff_info: true)
42
- text_diff('Parameters', current_parameters, proposed_parameters)
43
- unless noecho_keys.empty?
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 text_diff(thing, current, proposed, diff_opts = {})
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
@@ -1,3 +1,3 @@
1
1
  module StackMaster
2
- VERSION = "0.1.1"
2
+ VERSION = "0.2.0"
3
3
  end
@@ -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.1.1
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-02-17 00:00:00.000000000 Z
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