stack_master 2.6.1 → 2.11.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 +47 -2
- data/lib/stack_master.rb +15 -0
- data/lib/stack_master/aws_driver/cloud_formation.rb +4 -1
- data/lib/stack_master/cli.rb +31 -0
- data/lib/stack_master/commands/drift.rb +118 -0
- data/lib/stack_master/commands/nag.rb +30 -0
- data/lib/stack_master/commands/tidy.rb +1 -1
- data/lib/stack_master/config.rb +3 -0
- data/lib/stack_master/diff.rb +45 -0
- data/lib/stack_master/parameter_loader.rb +3 -4
- data/lib/stack_master/parameter_validator.rb +24 -7
- data/lib/stack_master/sparkle_formation/compile_time/empty_validator.rb +1 -1
- data/lib/stack_master/stack.rb +2 -2
- data/lib/stack_master/stack_definition.rb +27 -10
- data/lib/stack_master/stack_differ.rb +15 -39
- data/lib/stack_master/version.rb +1 -1
- metadata +24 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 44b54b0c2a5d9050de40c5eb89ec056d423e7e4403071aa295d3165c7eb08128
|
4
|
+
data.tar.gz: 6da32d39a8cb7c1b7f3448516c2fab14fbdb41247a708ebb3bffc59d10111fa4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 10eab8adaca08fb72e5751ade8a554b6892f95553a9581c87fba01f2967fde18fae5111e2b97243af83673ba9b2578ea0541ee0b4e70882cae3c2d5a483c0630
|
7
|
+
data.tar.gz: 7542bd369cd86fdbf5c3a198fb8bf331db3acefbc1271850f4603a67ba1ac5eed69d4c1d056ccba60e644e852ec04460129f48697dfb9df545a9a5a7a2b0221e
|
data/README.md
CHANGED
@@ -123,6 +123,7 @@ stack_defaults:
|
|
123
123
|
```
|
124
124
|
|
125
125
|
Additional files can be configured to be uploaded to S3 alongside the templates:
|
126
|
+
|
126
127
|
```yaml
|
127
128
|
stacks:
|
128
129
|
production:
|
@@ -131,6 +132,7 @@ stacks:
|
|
131
132
|
files:
|
132
133
|
- userdata.sh
|
133
134
|
```
|
135
|
+
|
134
136
|
## Directories
|
135
137
|
|
136
138
|
- `templates` - CloudFormation, SparkleFormation or CfnDsl templates.
|
@@ -155,7 +157,8 @@ template_compilers:
|
|
155
157
|
|
156
158
|
## Parameters
|
157
159
|
|
158
|
-
|
160
|
+
By default, parameters are loaded from multiple YAML files, merged from the
|
161
|
+
following lookup paths from bottom to top:
|
159
162
|
|
160
163
|
- parameters/[stack_name].yaml
|
161
164
|
- parameters/[stack_name].yml
|
@@ -170,6 +173,30 @@ A simple parameter file could look like this:
|
|
170
173
|
key_name: myapp-us-east-1
|
171
174
|
```
|
172
175
|
|
176
|
+
Alternatively, a `parameter_files` array can be defined to explicitly list
|
177
|
+
parameter files that will be loaded. If `parameter_files` are defined, the
|
178
|
+
automatic search locations will not be used.
|
179
|
+
|
180
|
+
```yaml
|
181
|
+
parameters_dir: parameters # the default
|
182
|
+
stacks:
|
183
|
+
us-east-1:
|
184
|
+
my-app:
|
185
|
+
parameter_files:
|
186
|
+
- my-app.yml # parameters/my-app.yml
|
187
|
+
```
|
188
|
+
|
189
|
+
Parameters can also be defined inline with stack definitions:
|
190
|
+
|
191
|
+
```yaml
|
192
|
+
stacks:
|
193
|
+
us-east-1:
|
194
|
+
my-app:
|
195
|
+
parameters:
|
196
|
+
VpcId:
|
197
|
+
stack_output: my-vpc/VpcId
|
198
|
+
```
|
199
|
+
|
173
200
|
### Compile Time Parameters
|
174
201
|
|
175
202
|
Compile time parameters can be used for [SparkleFormation](http://www.sparkleformation.io) templates. It conforms and
|
@@ -661,6 +688,7 @@ stacks:
|
|
661
688
|
|
662
689
|
In the cases where you want to bypass the account check, there is the StackMaster flag `--skip-account-check` that can be used.
|
663
690
|
|
691
|
+
|
664
692
|
## Commands
|
665
693
|
|
666
694
|
```bash
|
@@ -674,15 +702,20 @@ stack_master apply # Create or update all stacks
|
|
674
702
|
stack_master --changed apply # Create or update all stacks that have changed
|
675
703
|
stack_master --yes apply [region-or-alias] [stack-name] # Create or update a stack non-interactively (forcing yes)
|
676
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
|
677
706
|
stack_master delete [region-or-alias] [stack-name] # Delete a stack
|
678
707
|
stack_master events [region-or-alias] [stack-name] # Display events for a stack
|
679
708
|
stack_master outputs [region-or-alias] [stack-name] # Display outputs for a stack
|
680
709
|
stack_master resources [region-or-alias] [stack-name] # Display outputs for a stack
|
681
710
|
stack_master status # Displays the status of each stack
|
682
711
|
stack_master tidy # Find missing or extra templates or parameter files
|
712
|
+
stack_master compile # Print the compiled version of a given stack
|
713
|
+
stack_master validate # Validate a template
|
714
|
+
stack_master lint # Check the stack definition locally using cfn-lint
|
715
|
+
stack_master nag # Check the stack template with cfn_nag
|
683
716
|
```
|
684
717
|
|
685
|
-
## Applying updates
|
718
|
+
## Applying updates - `stack_master apply`
|
686
719
|
|
687
720
|
The apply command does the following:
|
688
721
|
|
@@ -699,6 +732,18 @@ Demo:
|
|
699
732
|
|
700
733
|

|
701
734
|
|
735
|
+
## Drift Detection - `stack_master drift`
|
736
|
+
|
737
|
+
`stack_master drift us-east-1 mystack` uses the CloudFormation APIs to trigger drift detection and display resources
|
738
|
+
that have changed outside of the CloudFormation stack. This can happen if a resource has been updated via the console or
|
739
|
+
CLI directly rather than via a stack update.
|
740
|
+
|
741
|
+
## Diff - `stack_master diff`
|
742
|
+
|
743
|
+
`stack_master diff us-east-1 mystack` displays whether the computed parameters or template differ to what was last
|
744
|
+
applied in CloudFormation. This can happen if the template or computed parameters have changed in code and the change
|
745
|
+
hasn't been applied to this stack.
|
746
|
+
|
702
747
|
## Maintainers
|
703
748
|
|
704
749
|
- [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'
|
@@ -68,6 +70,7 @@ module StackMaster
|
|
68
70
|
autoload :Delete, 'stack_master/commands/delete'
|
69
71
|
autoload :Status, 'stack_master/commands/status'
|
70
72
|
autoload :Tidy, 'stack_master/commands/tidy'
|
73
|
+
autoload :Nag, 'stack_master/commands/nag'
|
71
74
|
end
|
72
75
|
|
73
76
|
module ParameterResolvers
|
@@ -198,4 +201,16 @@ module StackMaster
|
|
198
201
|
def stderr=(io)
|
199
202
|
@stderr = io
|
200
203
|
end
|
204
|
+
|
205
|
+
def colorize(text, color)
|
206
|
+
if colorize?
|
207
|
+
Rainbow(text).color(color)
|
208
|
+
else
|
209
|
+
text
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
def colorize?
|
214
|
+
ENV.fetch('COLORIZE') { 'true' } == 'true'
|
215
|
+
end
|
201
216
|
end
|
data/lib/stack_master/cli.rb
CHANGED
@@ -146,6 +146,17 @@ module StackMaster
|
|
146
146
|
end
|
147
147
|
end
|
148
148
|
|
149
|
+
command :nag do |c|
|
150
|
+
c.syntax = 'stack_master nag [region_or_alias] [stack_name]'
|
151
|
+
c.summary = "Check this stack's template with cfn_nag"
|
152
|
+
c.description = "Runs SAST scan cfn_nag on the template"
|
153
|
+
c.example 'run cfn_nag on stack myapp-vpc with us-east-1 settings', 'stack_master nag us-east-1 myapp-vpc'
|
154
|
+
c.action do |args, options|
|
155
|
+
options.default config: default_config_file
|
156
|
+
execute_stacks_command(StackMaster::Commands::Nag, args, options)
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
149
160
|
command :compile do |c|
|
150
161
|
c.syntax = 'stack_master compile [region_or_alias] [stack_name]'
|
151
162
|
c.summary = "Print the compiled version of a given stack"
|
@@ -215,6 +226,18 @@ module StackMaster
|
|
215
226
|
end
|
216
227
|
end
|
217
228
|
|
229
|
+
command :drift do |c|
|
230
|
+
c.syntax = 'stack_master drift [region_or_alias] [stack_name]'
|
231
|
+
c.summary = 'Detects and displays stack drift using the CloudFormation Drift API'
|
232
|
+
c.description = 'Detects and displays stack drift'
|
233
|
+
c.option '--timeout SECONDS', Integer, "The number of seconds to wait for drift detection to complete"
|
234
|
+
c.example 'view stack drift for a stack named myapp-vpc in us-east-1', 'stack_master drift us-east-1 myapp-vpc'
|
235
|
+
c.action do |args, options|
|
236
|
+
options.default config: default_config_file, timeout: 120
|
237
|
+
execute_stacks_command(StackMaster::Commands::Drift, args, options)
|
238
|
+
end
|
239
|
+
end
|
240
|
+
|
218
241
|
run!
|
219
242
|
end
|
220
243
|
|
@@ -242,6 +265,7 @@ module StackMaster
|
|
242
265
|
stack_definitions = config.filter(region, stack_name)
|
243
266
|
if stack_definitions.empty?
|
244
267
|
StackMaster.stdout.puts "Could not find stack definition #{stack_name} in region #{region}"
|
268
|
+
show_other_region_candidates(config, stack_name)
|
245
269
|
success = false
|
246
270
|
end
|
247
271
|
stack_definitions = stack_definitions.select do |stack_definition|
|
@@ -258,6 +282,13 @@ module StackMaster
|
|
258
282
|
@kernel.exit false unless success
|
259
283
|
end
|
260
284
|
|
285
|
+
def show_other_region_candidates(config, stack_name)
|
286
|
+
candidates = config.filter(region="", stack_name=stack_name)
|
287
|
+
return if candidates.empty?
|
288
|
+
|
289
|
+
StackMaster.stdout.puts "Stack name #{stack_name} exists in regions: #{candidates.map(&:region).join(', ')}"
|
290
|
+
end
|
291
|
+
|
261
292
|
def execute_if_allowed_account(allowed_accounts, &block)
|
262
293
|
raise ArgumentError, "Block required to execute this method" unless block_given?
|
263
294
|
if running_in_allowed_account?(allowed_accounts)
|
@@ -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
|
+
resp = nil
|
92
|
+
start_time = Time.now
|
93
|
+
loop do
|
94
|
+
resp = cf.describe_stack_drift_detection_status(stack_drift_detection_id: detection_id)
|
95
|
+
break if DETECTION_COMPLETE_STATES.include?(resp.detection_status)
|
96
|
+
|
97
|
+
elapsed_time = Time.now - start_time
|
98
|
+
if elapsed_time > @options.timeout
|
99
|
+
raise "Timeout waiting for stack drift detection"
|
100
|
+
end
|
101
|
+
|
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,30 @@
|
|
1
|
+
module StackMaster
|
2
|
+
module Commands
|
3
|
+
class Nag
|
4
|
+
include Command
|
5
|
+
include Commander::UI
|
6
|
+
|
7
|
+
def perform
|
8
|
+
rv = Tempfile.open(['stack', "___#{stack_definition.stack_name}.#{proposed_stack.template_format}"]) do |f|
|
9
|
+
f.write(proposed_stack.template_body)
|
10
|
+
f.flush
|
11
|
+
system('cfn_nag', f.path)
|
12
|
+
$?.exitstatus
|
13
|
+
end
|
14
|
+
|
15
|
+
failed!("cfn_nag check failed with exit status #{rv}") if rv > 0
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def stack_definition
|
21
|
+
@stack_definition ||= @config.find_stack(@region, @stack_name)
|
22
|
+
end
|
23
|
+
|
24
|
+
def proposed_stack
|
25
|
+
@proposed_stack ||= Stack.generate(stack_definition, @config)
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -12,7 +12,7 @@ module StackMaster
|
|
12
12
|
parameter_files = Set.new(find_parameter_files())
|
13
13
|
|
14
14
|
status = @config.stacks.each do |stack_definition|
|
15
|
-
parameter_files.subtract(stack_definition.
|
15
|
+
parameter_files.subtract(stack_definition.parameter_files_from_globs)
|
16
16
|
template = File.absolute_path(stack_definition.template_file_path)
|
17
17
|
|
18
18
|
if template
|
data/lib/stack_master/config.rb
CHANGED
@@ -17,6 +17,7 @@ module StackMaster
|
|
17
17
|
attr_accessor :stacks,
|
18
18
|
:base_dir,
|
19
19
|
:template_dir,
|
20
|
+
:parameters_dir,
|
20
21
|
:stack_defaults,
|
21
22
|
:region_defaults,
|
22
23
|
:region_aliases,
|
@@ -39,6 +40,7 @@ module StackMaster
|
|
39
40
|
@config = config
|
40
41
|
@base_dir = base_dir
|
41
42
|
@template_dir = config.fetch('template_dir', nil)
|
43
|
+
@parameters_dir = config.fetch('parameters_dir', nil)
|
42
44
|
@stack_defaults = config.fetch('stack_defaults', {})
|
43
45
|
@region_aliases = Utils.underscore_keys_to_hyphen(config.fetch('region_aliases', {}))
|
44
46
|
@region_to_aliases = @region_aliases.inject({}) do |hash, (key, value)|
|
@@ -115,6 +117,7 @@ module StackMaster
|
|
115
117
|
'stack_name' => stack_name,
|
116
118
|
'base_dir' => @base_dir,
|
117
119
|
'template_dir' => @template_dir,
|
120
|
+
'parameters_dir' => @parameters_dir,
|
118
121
|
'additional_parameter_lookup_dirs' => @region_to_aliases[region])
|
119
122
|
stack_attributes['allowed_accounts'] = attributes['allowed_accounts'] if attributes['allowed_accounts']
|
120
123
|
@stacks << StackDefinition.new(stack_attributes)
|
@@ -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
|
@@ -5,10 +5,10 @@ module StackMaster
|
|
5
5
|
|
6
6
|
COMPILE_TIME_PARAMETERS_KEY = 'compile_time_parameters'
|
7
7
|
|
8
|
-
def self.load(parameter_files)
|
8
|
+
def self.load(parameter_files: [], parameters: {})
|
9
9
|
StackMaster.debug 'Searching for parameter files...'
|
10
|
-
parameter_files.
|
11
|
-
|
10
|
+
all_parameters = parameter_files.map { |file_name| load_parameters(file_name) } + [parameters]
|
11
|
+
all_parameters.reduce({template_parameters: {}, compile_time_parameters: {}}) do |hash, parameters|
|
12
12
|
template_parameters = create_template_parameters(parameters)
|
13
13
|
compile_time_parameters = create_compile_time_parameters(parameters)
|
14
14
|
|
@@ -16,7 +16,6 @@ module StackMaster
|
|
16
16
|
merge_and_camelize(hash[:compile_time_parameters], compile_time_parameters)
|
17
17
|
hash
|
18
18
|
end
|
19
|
-
|
20
19
|
end
|
21
20
|
|
22
21
|
private
|
@@ -9,15 +9,14 @@ module StackMaster
|
|
9
9
|
|
10
10
|
def error_message
|
11
11
|
return nil unless missing_parameters?
|
12
|
-
message = "Empty/blank parameters detected. Please provide values for these parameters
|
12
|
+
message = "Empty/blank parameters detected. Please provide values for these parameters:\n"
|
13
13
|
missing_parameters.each do |parameter_name|
|
14
|
-
message << "
|
14
|
+
message << " - #{parameter_name}\n"
|
15
15
|
end
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
message << "\n - #{parameter_file}"
|
16
|
+
if @stack_definition.parameter_files.empty?
|
17
|
+
message << message_for_parameter_globs
|
18
|
+
else
|
19
|
+
message << message_for_parameter_files
|
21
20
|
end
|
22
21
|
message
|
23
22
|
end
|
@@ -28,6 +27,24 @@ module StackMaster
|
|
28
27
|
|
29
28
|
private
|
30
29
|
|
30
|
+
def message_for_parameter_files
|
31
|
+
"Parameters are configured to be read from the following files:\n".tap do |message|
|
32
|
+
@stack_definition.parameter_files.each do |parameter_file|
|
33
|
+
message << " - #{parameter_file}\n"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def message_for_parameter_globs
|
39
|
+
"Parameters will be read from files matching the following globs:\n".tap do |message|
|
40
|
+
base_dir = Pathname.new(@stack_definition.base_dir)
|
41
|
+
@stack_definition.parameter_file_globs.each do |glob|
|
42
|
+
parameter_file = Pathname.new(glob).relative_path_from(base_dir)
|
43
|
+
message << " - #{parameter_file}\n"
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
31
48
|
def missing_parameters
|
32
49
|
@missing_parameters ||=
|
33
50
|
@stack.parameters_with_defaults.select { |_key, value| value.nil? }.keys
|
data/lib/stack_master/stack.rb
CHANGED
@@ -56,7 +56,7 @@ module StackMaster
|
|
56
56
|
end
|
57
57
|
|
58
58
|
def self.generate(stack_definition, config)
|
59
|
-
parameter_hash = ParameterLoader.load(stack_definition.
|
59
|
+
parameter_hash = ParameterLoader.load(parameter_files: stack_definition.all_parameter_files, parameters: stack_definition.parameters)
|
60
60
|
template_parameters = ParameterResolver.resolve(config, stack_definition, parameter_hash[:template_parameters])
|
61
61
|
compile_time_parameters = ParameterResolver.resolve(config, stack_definition, parameter_hash[:compile_time_parameters])
|
62
62
|
template_body = TemplateCompiler.compile(config, stack_definition.compiler, stack_definition.template_dir, stack_definition.template, compile_time_parameters, stack_definition.compiler_options)
|
@@ -76,7 +76,7 @@ module StackMaster
|
|
76
76
|
end
|
77
77
|
|
78
78
|
def self.generate_without_parameters(stack_definition, config)
|
79
|
-
parameter_hash = ParameterLoader.load(stack_definition.
|
79
|
+
parameter_hash = ParameterLoader.load(parameter_files: stack_definition.all_parameter_files, parameters: stack_definition.parameters)
|
80
80
|
compile_time_parameters = ParameterResolver.resolve(config, stack_definition, parameter_hash[:compile_time_parameters])
|
81
81
|
template_body = TemplateCompiler.compile(config, stack_definition.compiler, stack_definition.template_dir, stack_definition.template, compile_time_parameters, stack_definition.compiler_options)
|
82
82
|
template_format = TemplateUtils.identify_template_format(template_body)
|
@@ -16,7 +16,10 @@ module StackMaster
|
|
16
16
|
:additional_parameter_lookup_dirs,
|
17
17
|
:s3,
|
18
18
|
:files,
|
19
|
-
:compiler_options
|
19
|
+
:compiler_options,
|
20
|
+
:parameters_dir,
|
21
|
+
:parameters,
|
22
|
+
:parameter_files
|
20
23
|
|
21
24
|
attr_reader :compiler
|
22
25
|
|
@@ -32,8 +35,12 @@ module StackMaster
|
|
32
35
|
@compiler = nil
|
33
36
|
super
|
34
37
|
@additional_parameter_lookup_dirs ||= []
|
38
|
+
@base_dir ||= ""
|
35
39
|
@template_dir ||= File.join(@base_dir, 'templates')
|
40
|
+
@parameters_dir ||= File.join(@base_dir, 'parameters')
|
36
41
|
@allowed_accounts = Array(@allowed_accounts)
|
42
|
+
@parameters ||= {}
|
43
|
+
@parameter_files ||= []
|
37
44
|
end
|
38
45
|
|
39
46
|
def ==(other)
|
@@ -56,13 +63,9 @@ module StackMaster
|
|
56
63
|
@compiler_options == other.compiler_options
|
57
64
|
end
|
58
65
|
|
59
|
-
def compiler=(compiler)
|
60
|
-
@compiler = compiler.&to_sym
|
61
|
-
end
|
62
|
-
|
63
66
|
def template_file_path
|
64
67
|
return unless template
|
65
|
-
File.expand_path(
|
68
|
+
File.expand_path(template, template_dir)
|
66
69
|
end
|
67
70
|
|
68
71
|
def files_dir
|
@@ -85,7 +88,15 @@ module StackMaster
|
|
85
88
|
Utils.change_extension(template, 'json')
|
86
89
|
end
|
87
90
|
|
88
|
-
def
|
91
|
+
def all_parameter_files
|
92
|
+
if parameter_files.empty?
|
93
|
+
parameter_files_from_globs
|
94
|
+
else
|
95
|
+
parameter_files
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def parameter_files_from_globs
|
89
100
|
parameter_file_globs.map(&Dir.method(:glob)).flatten
|
90
101
|
end
|
91
102
|
|
@@ -101,20 +112,26 @@ module StackMaster
|
|
101
112
|
!s3.nil?
|
102
113
|
end
|
103
114
|
|
115
|
+
def parameter_files
|
116
|
+
Array(@parameter_files).map do |file|
|
117
|
+
File.expand_path(file, parameters_dir)
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
104
121
|
private
|
105
122
|
|
106
123
|
def additional_parameter_lookup_globs
|
107
124
|
additional_parameter_lookup_dirs.map do |a|
|
108
|
-
File.join(
|
125
|
+
File.join(parameters_dir, a, "#{stack_name_glob}.y*ml")
|
109
126
|
end
|
110
127
|
end
|
111
128
|
|
112
129
|
def region_parameter_glob
|
113
|
-
File.join(
|
130
|
+
File.join(parameters_dir, "#{region}", "#{stack_name_glob}.y*ml")
|
114
131
|
end
|
115
132
|
|
116
133
|
def default_parameter_glob
|
117
|
-
File.join(
|
134
|
+
File.join(parameters_dir, "#{stack_name_glob}.y*ml")
|
118
135
|
end
|
119
136
|
|
120
137
|
def stack_name_glob
|
@@ -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,15 +1,15 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: stack_master
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.
|
4
|
+
version: 2.11.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Steve Hodgkiss
|
8
8
|
- Glen Stampoultzis
|
9
|
-
autorequire:
|
9
|
+
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2020-
|
12
|
+
date: 2020-10-02 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: bundler
|
@@ -437,6 +437,20 @@ dependencies:
|
|
437
437
|
- - ">="
|
438
438
|
- !ruby/object:Gem::Version
|
439
439
|
version: '0'
|
440
|
+
- !ruby/object:Gem::Dependency
|
441
|
+
name: cfn-nag
|
442
|
+
requirement: !ruby/object:Gem::Requirement
|
443
|
+
requirements:
|
444
|
+
- - "~>"
|
445
|
+
- !ruby/object:Gem::Version
|
446
|
+
version: 0.6.7
|
447
|
+
type: :runtime
|
448
|
+
prerelease: false
|
449
|
+
version_requirements: !ruby/object:Gem::Requirement
|
450
|
+
requirements:
|
451
|
+
- - "~>"
|
452
|
+
- !ruby/object:Gem::Version
|
453
|
+
version: 0.6.7
|
440
454
|
description: ''
|
441
455
|
email:
|
442
456
|
- steve@hodgkiss.me
|
@@ -459,10 +473,12 @@ files:
|
|
459
473
|
- lib/stack_master/commands/compile.rb
|
460
474
|
- lib/stack_master/commands/delete.rb
|
461
475
|
- lib/stack_master/commands/diff.rb
|
476
|
+
- lib/stack_master/commands/drift.rb
|
462
477
|
- lib/stack_master/commands/events.rb
|
463
478
|
- lib/stack_master/commands/init.rb
|
464
479
|
- lib/stack_master/commands/lint.rb
|
465
480
|
- lib/stack_master/commands/list_stacks.rb
|
481
|
+
- lib/stack_master/commands/nag.rb
|
466
482
|
- lib/stack_master/commands/outputs.rb
|
467
483
|
- lib/stack_master/commands/resources.rb
|
468
484
|
- lib/stack_master/commands/status.rb
|
@@ -471,6 +487,7 @@ files:
|
|
471
487
|
- lib/stack_master/commands/validate.rb
|
472
488
|
- lib/stack_master/config.rb
|
473
489
|
- lib/stack_master/ctrl_c.rb
|
490
|
+
- lib/stack_master/diff.rb
|
474
491
|
- lib/stack_master/identity.rb
|
475
492
|
- lib/stack_master/paged_response_accumulator.rb
|
476
493
|
- lib/stack_master/parameter_loader.rb
|
@@ -539,9 +556,9 @@ licenses:
|
|
539
556
|
metadata:
|
540
557
|
bug_tracker_uri: https://github.com/envato/stack_master/issues
|
541
558
|
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
|
-
post_install_message:
|
559
|
+
documentation_uri: https://www.rubydoc.info/gems/stack_master/2.11.0
|
560
|
+
source_code_uri: https://github.com/envato/stack_master/tree/v2.11.0
|
561
|
+
post_install_message:
|
545
562
|
rdoc_options: []
|
546
563
|
require_paths:
|
547
564
|
- lib
|
@@ -557,7 +574,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
557
574
|
version: '0'
|
558
575
|
requirements: []
|
559
576
|
rubygems_version: 3.0.4
|
560
|
-
signing_key:
|
577
|
+
signing_key:
|
561
578
|
specification_version: 4
|
562
579
|
summary: StackMaster is a sure-footed way of creating, updating and keeping track
|
563
580
|
of Amazon (AWS) CloudFormation stacks.
|