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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4f146a3b9b4ca20e6c3fdf98d3620886663ec113cb81f7d763918aad28d5d42c
4
- data.tar.gz: c7ebfef287797cff8cbb1ee74bafcbe583da1eb14079ea4b94d2102fbc48ea18
3
+ metadata.gz: 44b54b0c2a5d9050de40c5eb89ec056d423e7e4403071aa295d3165c7eb08128
4
+ data.tar.gz: 6da32d39a8cb7c1b7f3448516c2fab14fbdb41247a708ebb3bffc59d10111fa4
5
5
  SHA512:
6
- metadata.gz: 45ca7571feb3989ba167eec7b7dc27ba3ed003aec6890e6c172c4ced6dc432fb29bcc618481880604783f02138ca644b17d9f329f73dcab81d73585104a768c9
7
- data.tar.gz: dead78c859b1d6cda3148a045c99d31c116a2849ccb2112d231561ae708e619236e4951754d7cdeecf53e08dd68ce99f2f8e242f255c6a168257c6b7cdaec021
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
- Parameters are loaded from multiple YAML files, merged from the following lookup paths from bottom to top:
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
  ![Apply Demo](/apply_demo.gif?raw=true)
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)
@@ -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
@@ -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
 
@@ -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.parameter_files)
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
@@ -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.reduce({template_parameters: {}, compile_time_parameters: {}}) do |hash, file_name|
11
- parameters = load_parameters(file_name)
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 << "\n - #{parameter_name}"
14
+ message << " - #{parameter_name}\n"
15
15
  end
16
- message << "\nParameters will be read from files matching the following globs:"
17
- base_dir = Pathname.new(@stack_definition.base_dir)
18
- @stack_definition.parameter_file_globs.each do |glob|
19
- parameter_file = Pathname.new(glob).relative_path_from(base_dir)
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
@@ -19,7 +19,7 @@ module StackMaster
19
19
 
20
20
  def has_invalid_values?
21
21
  values = build_values(@definition, @parameter)
22
- values.include?(nil) || values.include?('')
22
+ values.include?(nil)
23
23
  end
24
24
 
25
25
  def create_error
@@ -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.parameter_files)
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.parameter_files)
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(File.join(template_dir, template))
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 parameter_files
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(base_dir, 'parameters', a, "#{stack_name_glob}.y*ml")
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(base_dir, 'parameters', "#{region}", "#{stack_name_glob}.y*ml")
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(base_dir, 'parameters', "#{stack_name_glob}.y*ml")
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 ||= 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.6.1"
2
+ VERSION = "2.11.0"
3
3
  end
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.6.1
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-05-29 00:00:00.000000000 Z
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.6.1
543
- source_code_uri: https://github.com/envato/stack_master/tree/v2.6.1
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.