stack_master 2.7.0 → 2.8.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 +15 -1
- data/lib/stack_master.rb +14 -0
- data/lib/stack_master/aws_driver/cloud_formation.rb +4 -1
- data/lib/stack_master/cli.rb +11 -0
- data/lib/stack_master/commands/drift.rb +118 -0
- data/lib/stack_master/diff.rb +45 -0
- data/lib/stack_master/stack_differ.rb +15 -39
- data/lib/stack_master/version.rb +1 -1
- metadata +6 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 7d401f198bf80974977c8b031f27422ea84aa171f18747fa6064cf6d9d2af784
|
|
4
|
+
data.tar.gz: dd930994d8034f57d60ba1a77d8bfee0f524301501306a3d8cee64502c27029e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|

|
|
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)
|
data/lib/stack_master.rb
CHANGED
|
@@ -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
|
data/lib/stack_master/cli.rb
CHANGED
|
@@ -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 ||=
|
|
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
|
-
|
|
53
|
+
parameters_diff.different?
|
|
51
54
|
end
|
|
52
55
|
|
|
53
|
-
def
|
|
54
|
-
@param_diff ||=
|
|
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
|
-
|
|
59
|
-
|
|
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
|
data/lib/stack_master/version.rb
CHANGED
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.
|
|
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-
|
|
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.
|
|
543
|
-
source_code_uri: https://github.com/envato/stack_master/tree/v2.
|
|
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:
|