stack_master 2.7.0 → 2.8.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
  SHA256:
3
- metadata.gz: f94d2bab69225428425d5e3bc426ca572c781aa5d9e069420c198c86640f84c5
4
- data.tar.gz: f4f164e4390d568e9a7511013a33796ad04862d18510ec5683c2024b9952781f
3
+ metadata.gz: 7d401f198bf80974977c8b031f27422ea84aa171f18747fa6064cf6d9d2af784
4
+ data.tar.gz: dd930994d8034f57d60ba1a77d8bfee0f524301501306a3d8cee64502c27029e
5
5
  SHA512:
6
- metadata.gz: 81122997265f0aa25d24f54ee3ccbb5a0a3689b362b37445bd0c12435dc0fcbb24e0d8ce277c949c1e3af5c01d3b5ff938227f6b6f051b97cc8da66f400519d6
7
- data.tar.gz: b5f8114a90fc7cc402e07e34676b7142b3849c4e8eecdc43cb67651358e5e7e6228ab04b68ef5b769b98ff91e7efd2ec9667a5c197699bccbfb96d49848585ed
6
+ metadata.gz: de26f0ce262ef2b4b11cced8789a8093896d9bd7db6950008ad7e0e469ea43a9752fbbcb0e4b57271656442aa7bb13dc159697690d0a0754fcbc9ecce39d07b0
7
+ data.tar.gz: 0ef954037a433821d09b90acbbd39da5a881044f305353267cba379a07e77bd2ffe7645700b8ce927d15d7f518c823aa17a42f0c9971ce3c37b29f5164a8b219
data/README.md CHANGED
@@ -688,6 +688,7 @@ stacks:
688
688
 
689
689
  In the cases where you want to bypass the account check, there is the StackMaster flag `--skip-account-check` that can be used.
690
690
 
691
+
691
692
  ## Commands
692
693
 
693
694
  ```bash
@@ -701,6 +702,7 @@ stack_master apply # Create or update all stacks
701
702
  stack_master --changed apply # Create or update all stacks that have changed
702
703
  stack_master --yes apply [region-or-alias] [stack-name] # Create or update a stack non-interactively (forcing yes)
703
704
  stack_master diff [region-or-alias] [stack-name] # Display a stack template and parameter diff
705
+ stack_master drift [region-or-alias] [stack-name] # Detects and displays stack drift using the CloudFormation Drift API
704
706
  stack_master delete [region-or-alias] [stack-name] # Delete a stack
705
707
  stack_master events [region-or-alias] [stack-name] # Display events for a stack
706
708
  stack_master outputs [region-or-alias] [stack-name] # Display outputs for a stack
@@ -709,7 +711,7 @@ stack_master status # Displays the status of each stack
709
711
  stack_master tidy # Find missing or extra templates or parameter files
710
712
  ```
711
713
 
712
- ## Applying updates
714
+ ## Applying updates - `stack_master apply`
713
715
 
714
716
  The apply command does the following:
715
717
 
@@ -726,6 +728,18 @@ Demo:
726
728
 
727
729
  ![Apply Demo](/apply_demo.gif?raw=true)
728
730
 
731
+ ## Drift Detection - `stack_master drift`
732
+
733
+ `stack_master drift us-east-1 mystack` uses the CloudFormation APIs to trigger drift detection and display resources
734
+ that have changed outside of the CloudFormation stack. This can happen if a resource has been updated via the console or
735
+ CLI directly rather than via a stack update.
736
+
737
+ ## Diff - `stack_master diff`
738
+
739
+ `stack_master diff us-east-1 mystack` displays whether the computed parameters or template differ to what was last
740
+ applied in CloudFormation. This can happen if the template or computed parameters have changed in code and the change
741
+ hasn't been applied to this stack.
742
+
729
743
  ## Maintainers
730
744
 
731
745
  - [Steve Hodgkiss](https://github.com/stevehodgkiss)
@@ -24,6 +24,7 @@ module StackMaster
24
24
  autoload :CLI, 'stack_master/cli'
25
25
  autoload :CtrlC, 'stack_master/ctrl_c'
26
26
  autoload :Command, 'stack_master/command'
27
+ autoload :Diff, 'stack_master/diff'
27
28
  autoload :VERSION, 'stack_master/version'
28
29
  autoload :Stack, 'stack_master/stack'
29
30
  autoload :Prompter, 'stack_master/prompter'
@@ -56,6 +57,7 @@ module StackMaster
56
57
  module Commands
57
58
  autoload :TerminalHelper, 'stack_master/commands/terminal_helper'
58
59
  autoload :Apply, 'stack_master/commands/apply'
60
+ autoload :Drift, 'stack_master/commands/drift'
59
61
  autoload :Events, 'stack_master/commands/events'
60
62
  autoload :Outputs, 'stack_master/commands/outputs'
61
63
  autoload :Init, 'stack_master/commands/init'
@@ -198,4 +200,16 @@ module StackMaster
198
200
  def stderr=(io)
199
201
  @stderr = io
200
202
  end
203
+
204
+ def colorize(text, color)
205
+ if colorize?
206
+ Rainbow(text).color(color)
207
+ else
208
+ text
209
+ end
210
+ end
211
+
212
+ def colorize?
213
+ ENV.fetch('COLORIZE') { 'true' } == 'true'
214
+ end
201
215
  end
@@ -28,7 +28,10 @@ module StackMaster
28
28
  :update_stack,
29
29
  :create_stack,
30
30
  :validate_template,
31
- :describe_stacks
31
+ :describe_stacks,
32
+ :detect_stack_drift,
33
+ :describe_stack_drift_detection_status,
34
+ :describe_stack_resource_drifts
32
35
 
33
36
  private
34
37
 
@@ -215,6 +215,17 @@ module StackMaster
215
215
  end
216
216
  end
217
217
 
218
+ command :drift do |c|
219
+ c.syntax = 'stack_master drift [region_or_alias] [stack_name]'
220
+ c.summary = 'Detects and displays stack drift using the CloudFormation Drift API'
221
+ c.description = 'Detects and displays stack drift'
222
+ c.example 'view stack drift for a stack named myapp-vpc in us-east-1', 'stack_master drift us-east-1 myapp-vpc'
223
+ c.action do |args, options|
224
+ options.default config: default_config_file
225
+ execute_stacks_command(StackMaster::Commands::Drift, args, options)
226
+ end
227
+ end
228
+
218
229
  run!
219
230
  end
220
231
 
@@ -0,0 +1,118 @@
1
+ require 'diffy'
2
+
3
+ module StackMaster
4
+ module Commands
5
+ class Drift
6
+ include Command
7
+ include Commander::UI
8
+
9
+ DETECTION_COMPLETE_STATES = [
10
+ 'DETECTION_COMPLETE',
11
+ 'DETECTION_FAILED'
12
+ ]
13
+
14
+ def perform
15
+ detect_stack_drift_result = cf.detect_stack_drift(stack_name: stack_name)
16
+ drift_results = wait_for_drift_results(detect_stack_drift_result.stack_drift_detection_id)
17
+
18
+ puts colorize("Drift Status: #{drift_results.stack_drift_status}", stack_drift_status_color(drift_results.stack_drift_status))
19
+ return if drift_results.stack_drift_status == 'IN_SYNC'
20
+
21
+ failed
22
+
23
+ resp = cf.describe_stack_resource_drifts(stack_name: stack_name)
24
+ resp.stack_resource_drifts.each do |drift|
25
+ display_drift(drift)
26
+ end
27
+ end
28
+
29
+ private
30
+
31
+ def cf
32
+ @cf ||= StackMaster.cloud_formation_driver
33
+ end
34
+
35
+ def display_drift(drift)
36
+ color = drift_color(drift)
37
+ puts colorize([drift.stack_resource_drift_status,
38
+ drift.resource_type,
39
+ drift.logical_resource_id,
40
+ drift.physical_resource_id].join(' '), color)
41
+ return unless drift.stack_resource_drift_status == 'MODIFIED'
42
+
43
+ unless drift.property_differences.empty?
44
+ puts colorize(' Property differences:', color)
45
+ end
46
+ drift.property_differences.each do |property_difference|
47
+ puts colorize(" - #{property_difference.difference_type} #{property_difference.property_path}", color)
48
+ end
49
+ puts colorize(' Resource diff:', color)
50
+ display_resource_drift(drift)
51
+ end
52
+
53
+ def display_resource_drift(drift)
54
+ diff = ::StackMaster::Diff.new(before: prettify_json(drift.expected_properties),
55
+ after: prettify_json(drift.actual_properties))
56
+ diff.display_colorized_diff
57
+ end
58
+
59
+ def prettify_json(string)
60
+ JSON.pretty_generate(JSON.parse(string)) + "\n"
61
+ rescue StandardError => e
62
+ puts "Failed to prettify drifted resource: #{e.message}"
63
+ string
64
+ end
65
+
66
+ def stack_drift_status_color(stack_drift_status)
67
+ case stack_drift_status
68
+ when 'IN_SYNC'
69
+ :green
70
+ when 'DRIFTED'
71
+ :yellow
72
+ else
73
+ :blue
74
+ end
75
+ end
76
+
77
+ def drift_color(drift)
78
+ case drift.stack_resource_drift_status
79
+ when 'IN_SYNC'
80
+ :green
81
+ when 'MODIFIED'
82
+ :yellow
83
+ when 'DELETED'
84
+ :red
85
+ else
86
+ :blue
87
+ end
88
+ end
89
+
90
+ def wait_for_drift_results(detection_id)
91
+ try_count = 0
92
+ resp = nil
93
+ loop do
94
+ if try_count >= 10
95
+ raise 'Failed to wait for stack drift detection after 10 tries'
96
+ end
97
+
98
+ resp = cf.describe_stack_drift_detection_status(stack_drift_detection_id: detection_id)
99
+ break if DETECTION_COMPLETE_STATES.include?(resp.detection_status)
100
+
101
+ try_count += 1
102
+ sleep SLEEP_SECONDS
103
+ end
104
+ resp
105
+ end
106
+
107
+ def puts(string)
108
+ StackMaster.stdout.puts(string)
109
+ end
110
+
111
+ extend Forwardable
112
+ def_delegators :@stack_definition, :stack_name, :region
113
+ def_delegators :StackMaster, :colorize
114
+
115
+ SLEEP_SECONDS = 1
116
+ end
117
+ end
118
+ end
@@ -0,0 +1,45 @@
1
+ module StackMaster
2
+ class Diff
3
+ def initialize(name: nil, before:, after:, context: 10_000)
4
+ @name = name
5
+ @before = before
6
+ @after = after
7
+ @context = context
8
+ end
9
+
10
+ def display
11
+ stdout.print "#{@name} diff: "
12
+ if diff == ''
13
+ stdout.puts "No changes"
14
+ else
15
+ stdout.puts
16
+ display_colorized_diff
17
+ end
18
+ end
19
+
20
+ def display_colorized_diff
21
+ diff.each_line do |line|
22
+ if line.start_with?('+')
23
+ stdout.print colorize(line, :green)
24
+ elsif line.start_with?('-')
25
+ stdout.print colorize(line, :red)
26
+ else
27
+ stdout.print line
28
+ end
29
+ end
30
+ end
31
+
32
+ def different?
33
+ diff != ''
34
+ end
35
+
36
+ private
37
+
38
+ def diff
39
+ @diff ||= Diffy::Diff.new(@before, @after, context: @context).to_s
40
+ end
41
+
42
+ extend Forwardable
43
+ def_delegators :StackMaster, :colorize, :stdout
44
+ end
45
+ end
@@ -10,13 +10,13 @@ module StackMaster
10
10
 
11
11
  def proposed_template
12
12
  return @proposed_stack.template_body unless @proposed_stack.template_format == :json
13
- JSON.pretty_generate(JSON.parse(@proposed_stack.template_body))
13
+ JSON.pretty_generate(JSON.parse(@proposed_stack.template_body)) + "\n"
14
14
  end
15
15
 
16
16
  def current_template
17
17
  return '' unless @current_stack
18
18
  return @current_stack.template_body unless @current_stack.template_format == :json
19
- JSON.pretty_generate(TemplateUtils.template_hash(@current_stack.template_body))
19
+ JSON.pretty_generate(TemplateUtils.template_hash(@current_stack.template_body)) + "\n"
20
20
  end
21
21
 
22
22
  def current_parameters
@@ -39,24 +39,30 @@ module StackMaster
39
39
  end
40
40
 
41
41
  def body_different?
42
- body_diff != ''
42
+ body_diff.different?
43
43
  end
44
44
 
45
45
  def body_diff
46
- @body_diff ||= Diffy::Diff.new(current_template, proposed_template, context: 7, include_diff_info: true).to_s
46
+ @body_diff ||= Diff.new(name: 'Stack',
47
+ before: current_template,
48
+ after: proposed_template,
49
+ context: 7)
47
50
  end
48
51
 
49
52
  def params_different?
50
- params_diff != ''
53
+ parameters_diff.different?
51
54
  end
52
55
 
53
- def params_diff
54
- @param_diff ||= Diffy::Diff.new(current_parameters, proposed_parameters, {}).to_s
56
+ def parameters_diff
57
+ @param_diff ||= Diff.new(name: 'Parameters',
58
+ before: current_parameters,
59
+ after: proposed_parameters)
55
60
  end
56
61
 
57
62
  def output_diff
58
- display_diff('Stack', body_diff)
59
- display_diff('Parameters', params_diff)
63
+ body_diff.display
64
+ parameters_diff.display
65
+
60
66
  unless noecho_keys.empty?
61
67
  StackMaster.stdout.puts " * can not tell if NoEcho parameters are different."
62
68
  end
@@ -83,38 +89,8 @@ module StackMaster
83
89
 
84
90
  private
85
91
 
86
- def display_diff(thing, diff)
87
- StackMaster.stdout.print "#{thing} diff: "
88
- if diff == ''
89
- StackMaster.stdout.puts "No changes"
90
- else
91
- StackMaster.stdout.puts
92
- diff.each_line do |line|
93
- if line.start_with?('+')
94
- StackMaster.stdout.print colorize(line, :green)
95
- elsif line.start_with?('-')
96
- StackMaster.stdout.print colorize(line, :red)
97
- else
98
- StackMaster.stdout.print line
99
- end
100
- end
101
- end
102
- end
103
-
104
92
  def sort_params(hash)
105
93
  hash.sort.to_h
106
94
  end
107
-
108
- def colorize(text, color)
109
- if colorize?
110
- Rainbow(text).color(color)
111
- else
112
- text
113
- end
114
- end
115
-
116
- def colorize?
117
- ENV.fetch('COLORIZE') { 'true' } == 'true'
118
- end
119
95
  end
120
96
  end
@@ -1,3 +1,3 @@
1
1
  module StackMaster
2
- VERSION = "2.7.0"
2
+ VERSION = "2.8.0"
3
3
  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: 2.7.0
4
+ version: 2.8.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: 2020-06-15 00:00:00.000000000 Z
12
+ date: 2020-06-24 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: bundler
@@ -459,6 +459,7 @@ files:
459
459
  - lib/stack_master/commands/compile.rb
460
460
  - lib/stack_master/commands/delete.rb
461
461
  - lib/stack_master/commands/diff.rb
462
+ - lib/stack_master/commands/drift.rb
462
463
  - lib/stack_master/commands/events.rb
463
464
  - lib/stack_master/commands/init.rb
464
465
  - lib/stack_master/commands/lint.rb
@@ -471,6 +472,7 @@ files:
471
472
  - lib/stack_master/commands/validate.rb
472
473
  - lib/stack_master/config.rb
473
474
  - lib/stack_master/ctrl_c.rb
475
+ - lib/stack_master/diff.rb
474
476
  - lib/stack_master/identity.rb
475
477
  - lib/stack_master/paged_response_accumulator.rb
476
478
  - lib/stack_master/parameter_loader.rb
@@ -539,8 +541,8 @@ licenses:
539
541
  metadata:
540
542
  bug_tracker_uri: https://github.com/envato/stack_master/issues
541
543
  changelog_uri: https://github.com/envato/stack_master/blob/master/CHANGELOG.md
542
- documentation_uri: https://www.rubydoc.info/gems/stack_master/2.7.0
543
- source_code_uri: https://github.com/envato/stack_master/tree/v2.7.0
544
+ documentation_uri: https://www.rubydoc.info/gems/stack_master/2.8.0
545
+ source_code_uri: https://github.com/envato/stack_master/tree/v2.8.0
544
546
  post_install_message:
545
547
  rdoc_options: []
546
548
  require_paths: