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 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