stack_master 2.6.1 → 2.11.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: 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.