stack_master 2.3.0 → 2.14.1

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.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE.txt +22 -0
  3. data/README.md +100 -23
  4. data/lib/stack_master/aws_driver/cloud_formation.rb +5 -2
  5. data/lib/stack_master/aws_driver/s3.rb +5 -5
  6. data/lib/stack_master/change_set.rb +5 -5
  7. data/lib/stack_master/cli.rb +49 -15
  8. data/lib/stack_master/cloudformation_interpolating_eruby.rb +57 -0
  9. data/lib/stack_master/cloudformation_template_eruby.rb +32 -0
  10. data/lib/stack_master/commands/apply.rb +2 -14
  11. data/lib/stack_master/commands/compile.rb +1 -1
  12. data/lib/stack_master/commands/drift.rb +118 -0
  13. data/lib/stack_master/commands/init.rb +1 -1
  14. data/lib/stack_master/commands/nag.rb +30 -0
  15. data/lib/stack_master/commands/resources.rb +1 -1
  16. data/lib/stack_master/commands/status.rb +1 -1
  17. data/lib/stack_master/commands/tidy.rb +1 -1
  18. data/lib/stack_master/commands/validate.rb +1 -1
  19. data/lib/stack_master/config.rb +9 -1
  20. data/lib/stack_master/diff.rb +45 -0
  21. data/lib/stack_master/identity.rb +37 -5
  22. data/lib/stack_master/parameter_loader.rb +4 -5
  23. data/lib/stack_master/parameter_resolvers/acm_certificate.rb +2 -2
  24. data/lib/stack_master/parameter_resolvers/ami_finder.rb +3 -3
  25. data/lib/stack_master/parameter_resolvers/latest_container.rb +1 -1
  26. data/lib/stack_master/parameter_resolvers/parameter_store.rb +3 -3
  27. data/lib/stack_master/parameter_resolvers/stack_output.rb +1 -1
  28. data/lib/stack_master/parameter_validator.rb +53 -0
  29. data/lib/stack_master/role_assumer.rb +3 -2
  30. data/lib/stack_master/security_group_finder.rb +1 -1
  31. data/lib/stack_master/sns_topic_finder.rb +1 -1
  32. data/lib/stack_master/sparkle_formation/compile_time/empty_validator.rb +1 -1
  33. data/lib/stack_master/sparkle_formation/template_file.rb +2 -50
  34. data/lib/stack_master/stack.rb +23 -10
  35. data/lib/stack_master/stack_definition.rb +27 -10
  36. data/lib/stack_master/stack_differ.rb +15 -39
  37. data/lib/stack_master/stack_events/presenter.rb +1 -1
  38. data/lib/stack_master/template_compilers/cfndsl.rb +3 -2
  39. data/lib/stack_master/template_compilers/sparkle_formation.rb +1 -1
  40. data/lib/stack_master/template_compilers/yaml_erb.rb +19 -0
  41. data/lib/stack_master/template_utils.rb +9 -3
  42. data/lib/stack_master/validator.rb +25 -8
  43. data/lib/stack_master/version.rb +1 -1
  44. data/lib/stack_master.rb +23 -2
  45. metadata +73 -17
@@ -1,5 +1,3 @@
1
- require 'pathname'
2
-
3
1
  module StackMaster
4
2
  module Commands
5
3
  class Apply
@@ -205,18 +203,8 @@ module StackMaster
205
203
  end
206
204
 
207
205
  def ensure_valid_parameters!
208
- if @proposed_stack.missing_parameters?
209
- message = <<~MESSAGE
210
- Empty/blank parameters detected, ensure values exist for those parameters.
211
- Parameters will be read from the following locations:
212
- MESSAGE
213
- base_dir = Pathname.new(@stack_definition.base_dir)
214
- @stack_definition.parameter_file_globs.each do |glob|
215
- parameter_file = Pathname.new(glob).relative_path_from(base_dir)
216
- message << " - #{parameter_file}\n"
217
- end
218
- failed!(message)
219
- end
206
+ pv = ParameterValidator.new(stack: @proposed_stack, stack_definition: @stack_definition)
207
+ failed!(pv.error_message) if pv.missing_parameters?
220
208
  end
221
209
 
222
210
  def ensure_valid_template_body_size!
@@ -5,7 +5,7 @@ module StackMaster
5
5
  include Commander::UI
6
6
 
7
7
  def perform
8
- puts(proposed_stack.template_body)
8
+ StackMaster.stdout.puts(proposed_stack.template_body)
9
9
  end
10
10
 
11
11
  private
@@ -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
@@ -29,7 +29,7 @@ module StackMaster
29
29
 
30
30
  if !@options.overwrite
31
31
  [@stack_master_filename, @stack_json_filename, @parameters_filename, @region_parameters_filename].each do |filename|
32
- if File.exists?(filename)
32
+ if File.exist?(filename)
33
33
  StackMaster.stderr.puts("Aborting: #{filename} already exists. Use --overwrite to force overwriting file.")
34
34
  return false
35
35
  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
@@ -17,7 +17,7 @@ module StackMaster
17
17
  private
18
18
 
19
19
  def stack_resources
20
- @stack_resources ||= cf.describe_stack_resources(stack_name: @stack_definition.stack_name).stack_resources
20
+ @stack_resources ||= cf.describe_stack_resources({ stack_name: @stack_definition.stack_name }).stack_resources
21
21
  rescue Aws::CloudFormation::Errors::ValidationError
22
22
  nil
23
23
  end
@@ -44,7 +44,7 @@ module StackMaster
44
44
  end
45
45
 
46
46
  def running_in_allowed_account?(allowed_accounts)
47
- StackMaster.skip_account_check? || identity.running_in_allowed_account?(allowed_accounts)
47
+ StackMaster.skip_account_check? || identity.running_in_account?(allowed_accounts)
48
48
  end
49
49
 
50
50
  def identity
@@ -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
@@ -5,7 +5,7 @@ module StackMaster
5
5
  include Commander::UI
6
6
 
7
7
  def perform
8
- failed unless Validator.valid?(@stack_definition, @config)
8
+ failed unless Validator.valid?(@stack_definition, @config, @options)
9
9
  end
10
10
  end
11
11
  end
@@ -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,
@@ -27,7 +28,7 @@ module StackMaster
27
28
 
28
29
  dir = Dir.pwd
29
30
  parent_dir = File.expand_path("..", Dir.pwd)
30
- while parent_dir != dir && !File.exists?(File.join(dir, config_file))
31
+ while parent_dir != dir && !File.exist?(File.join(dir, config_file))
31
32
  dir = parent_dir
32
33
  parent_dir = File.expand_path("..", dir)
33
34
  end
@@ -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)|
@@ -48,6 +50,8 @@ module StackMaster
48
50
  end
49
51
  @region_defaults = normalise_region_defaults(config.fetch('region_defaults', {}))
50
52
  @stacks = []
53
+
54
+ raise ConfigParseError.new("Stack defaults can't be undefined") if @stack_defaults.nil?
51
55
  load_template_compilers(config)
52
56
  load_config
53
57
  end
@@ -90,6 +94,7 @@ module StackMaster
90
94
  json: :json,
91
95
  yml: :yaml,
92
96
  yaml: :yaml,
97
+ erb: :yaml_erb,
93
98
  }
94
99
  end
95
100
 
@@ -109,12 +114,15 @@ module StackMaster
109
114
  stacks.each do |region, stacks_for_region|
110
115
  region = Utils.underscore_to_hyphen(region)
111
116
  stacks_for_region.each do |stack_name, attributes|
117
+ raise ConfigParseError.new("Entry for stack #{stack_name} has no attributes") if attributes.nil?
118
+
112
119
  stack_name = Utils.underscore_to_hyphen(stack_name)
113
120
  stack_attributes = build_stack_defaults(region).deeper_merge!(attributes).merge(
114
121
  'region' => region,
115
122
  'stack_name' => stack_name,
116
123
  'base_dir' => @base_dir,
117
124
  'template_dir' => @template_dir,
125
+ 'parameters_dir' => @parameters_dir,
118
126
  'additional_parameter_lookup_dirs' => @region_to_aliases[region])
119
127
  stack_attributes['allowed_accounts'] = attributes['allowed_accounts'] if attributes['allowed_accounts']
120
128
  @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
@@ -1,23 +1,55 @@
1
1
  module StackMaster
2
2
  class Identity
3
- def running_in_allowed_account?(allowed_accounts)
4
- allowed_accounts.nil? || allowed_accounts.empty? || allowed_accounts.include?(account)
3
+ AllowedAccountAliasesError = Class.new(StandardError)
4
+ MissingIamPermissionsError = Class.new(StandardError)
5
+
6
+ def running_in_account?(accounts)
7
+ return true if accounts.nil? || accounts.empty? || contains_account_id?(accounts)
8
+
9
+ # skip alias check (which makes an API call) if all values are account IDs
10
+ return false if accounts.all? { |account| account_id?(account) }
11
+
12
+ contains_account_alias?(accounts)
13
+ rescue MissingIamPermissionsError
14
+ raise AllowedAccountAliasesError, 'Failed to validate whether the current AWS account is allowed'
5
15
  end
6
16
 
7
17
  def account
8
18
  @account ||= sts.get_caller_identity.account
9
19
  end
10
20
 
11
- private
21
+ def account_aliases
22
+ @aliases ||= iam.list_account_aliases.account_aliases
23
+ rescue Aws::IAM::Errors::AccessDenied
24
+ raise MissingIamPermissionsError, 'Failed to retrieve account aliases. Missing required IAM permission: iam:ListAccountAliases'
25
+ end
12
26
 
13
- attr_reader :sts
27
+ private
14
28
 
15
29
  def region
16
30
  @region ||= ENV['AWS_REGION'] || Aws.config[:region] || Aws.shared_config.region || 'us-east-1'
17
31
  end
18
32
 
19
33
  def sts
20
- @sts ||= Aws::STS::Client.new(region: region)
34
+ @sts ||= Aws::STS::Client.new({ region: region })
35
+ end
36
+
37
+ def iam
38
+ @iam ||= Aws::IAM::Client.new({ region: region })
39
+ end
40
+
41
+ def contains_account_id?(ids)
42
+ ids.include?(account)
43
+ end
44
+
45
+ def contains_account_alias?(aliases)
46
+ account_aliases.any? { |account_alias| aliases.include?(account_alias) }
47
+ end
48
+
49
+ def account_id?(id_or_alias)
50
+ # While it's not explicitly documented as prohibited, it cannot (currently) be possible to set an account alias of
51
+ # 12 digits, as that could cause one console sign-in URL to resolve to two separate accounts.
52
+ /^[0-9]{12}$/.match?(id_or_alias)
21
53
  end
22
54
  end
23
55
  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,13 +16,12 @@ 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
23
22
 
24
23
  def self.load_parameters(file_name)
25
- file_exists = File.exists?(file_name)
24
+ file_exists = File.exist?(file_name)
26
25
  StackMaster.debug file_exists ? " #{file_name} found" : " #{file_name} not found"
27
26
  file_exists ? load_file(file_name) : {}
28
27
  end
@@ -19,9 +19,9 @@ module StackMaster
19
19
  def all_certs
20
20
  certs = []
21
21
  next_token = nil
22
- client = Aws::ACM::Client.new(region: @stack_definition.region)
22
+ client = Aws::ACM::Client.new({ region: @stack_definition.region })
23
23
  loop do
24
- resp = client.list_certificates(certificate_statuses: ['ISSUED'], next_token: next_token)
24
+ resp = client.list_certificates({ certificate_statuses: ['ISSUED'], next_token: next_token })
25
25
  certs << resp.certificate_summary_list
26
26
  next_token = resp.next_token
27
27
  break if next_token.nil?
@@ -19,7 +19,7 @@ module StackMaster
19
19
  end
20
20
 
21
21
  def find_latest_ami(filters, owners = ['self'])
22
- images = ec2.describe_images(owners: owners, filters: filters).images
22
+ images = ec2.describe_images({ owners: owners, filters: filters }).images
23
23
  sorted_images = images.sort do |a, b|
24
24
  Time.parse(a.creation_date) <=> Time.parse(b.creation_date)
25
25
  end
@@ -29,8 +29,8 @@ module StackMaster
29
29
  private
30
30
 
31
31
  def ec2
32
- @ec2 ||= Aws::EC2::Client.new(region: @region)
32
+ @ec2 ||= Aws::EC2::Client.new({ region: @region })
33
33
  end
34
34
  end
35
35
  end
36
- end
36
+ end
@@ -14,7 +14,7 @@ module StackMaster
14
14
  end
15
15
 
16
16
  @region = parameters['region'] || @stack_definition.region
17
- ecr_client = Aws::ECR::Client.new(region: @region)
17
+ ecr_client = Aws::ECR::Client.new({ region: @region })
18
18
 
19
19
  images = fetch_images(parameters['repository_name'], parameters['registry_id'], ecr_client)
20
20
 
@@ -11,11 +11,11 @@ module StackMaster
11
11
 
12
12
  def resolve(value)
13
13
  begin
14
- ssm = Aws::SSM::Client.new(region: @stack_definition.region)
15
- resp = ssm.get_parameter(
14
+ ssm = Aws::SSM::Client.new({ region: @stack_definition.region })
15
+ resp = ssm.get_parameter({
16
16
  name: value,
17
17
  with_decryption: true
18
- )
18
+ })
19
19
  rescue Aws::SSM::Errors::ParameterNotFound
20
20
  raise ParameterNotFound, "Unable to find #{value} in Parameter Store"
21
21
  end
@@ -53,7 +53,7 @@ module StackMaster
53
53
 
54
54
  @stacks.fetch(stack_key) do
55
55
  regional_cf = cf_for_region(unaliased_region)
56
- cf_stack = regional_cf.describe_stacks(stack_name: stack_name).stacks.first
56
+ cf_stack = regional_cf.describe_stacks({ stack_name: stack_name }).stacks.first
57
57
  @stacks[stack_key] = cf_stack
58
58
  end
59
59
  end
@@ -0,0 +1,53 @@
1
+ require 'pathname'
2
+
3
+ module StackMaster
4
+ class ParameterValidator
5
+ def initialize(stack:, stack_definition:)
6
+ @stack = stack
7
+ @stack_definition = stack_definition
8
+ end
9
+
10
+ def error_message
11
+ return nil unless missing_parameters?
12
+ message = "Empty/blank parameters detected. Please provide values for these parameters:\n"
13
+ missing_parameters.each do |parameter_name|
14
+ message << " - #{parameter_name}\n"
15
+ end
16
+ if @stack_definition.parameter_files.empty?
17
+ message << message_for_parameter_globs
18
+ else
19
+ message << message_for_parameter_files
20
+ end
21
+ message
22
+ end
23
+
24
+ def missing_parameters?
25
+ missing_parameters.any?
26
+ end
27
+
28
+ private
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
+
48
+ def missing_parameters
49
+ @missing_parameters ||=
50
+ @stack.parameters_with_defaults.select { |_key, value| value.nil? }.keys
51
+ end
52
+ end
53
+ end
@@ -44,10 +44,11 @@ module StackMaster
44
44
  def assume_role_credentials(account, role)
45
45
  credentials_key = "#{account}:#{role}"
46
46
  @credentials.fetch(credentials_key) do
47
- @credentials[credentials_key] = Aws::AssumeRoleCredentials.new(
47
+ @credentials[credentials_key] = Aws::AssumeRoleCredentials.new({
48
+ region: StackMaster.cloud_formation_driver.region,
48
49
  role_arn: "arn:aws:iam::#{account}:role/#{role}",
49
50
  role_session_name: "stack-master-role-assumer"
50
- )
51
+ })
51
52
  end
52
53
  end
53
54
  end
@@ -4,7 +4,7 @@ module StackMaster
4
4
  MultipleSecurityGroupsFound = Class.new(StandardError)
5
5
 
6
6
  def initialize(region)
7
- @resource = Aws::EC2::Resource.new(region: region)
7
+ @resource = Aws::EC2::Resource.new({ region: region })
8
8
  end
9
9
 
10
10
  def find(reference)
@@ -3,7 +3,7 @@ module StackMaster
3
3
  TopicNotFound = Class.new(StandardError)
4
4
 
5
5
  def initialize(region)
6
- @resource = Aws::SNS::Resource.new(region: region)
6
+ @resource = Aws::SNS::Resource.new({ region: region })
7
7
  end
8
8
 
9
9
  def find(reference)
@@ -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
@@ -5,19 +5,6 @@ module StackMaster
5
5
  module SparkleFormation
6
6
  TemplateFileNotFound = ::Class.new(StandardError)
7
7
 
8
- class SfEruby < Erubis::Eruby
9
- include Erubis::ArrayEnhancer
10
-
11
- def add_expr(src, code, indicator)
12
- case indicator
13
- when '='
14
- src << " #{@bufvar} << (" << code << ');'
15
- else
16
- super
17
- end
18
- end
19
- end
20
-
21
8
  class TemplateContext < AttributeStruct
22
9
  include ::SparkleFormation::SparkleAttribute
23
10
  include ::SparkleFormation::SparkleAttribute::Aws
@@ -49,47 +36,12 @@ module StackMaster
49
36
  end
50
37
  end
51
38
 
52
- # Splits up long strings with multiple lines in them to multiple strings
53
- # in the CF array. Makes the compiled template and diffs more readable.
54
- class CloudFormationLineFormatter
55
- def self.format(template)
56
- new(template).format
57
- end
58
-
59
- def initialize(template)
60
- @template = template
61
- end
62
-
63
- def format
64
- @template.flat_map do |lines|
65
- lines = lines.to_s if Symbol === lines
66
- if String === lines
67
- newlines = []
68
- lines.count("\n").times do
69
- newlines << "\n"
70
- end
71
- newlines = lines.split("\n").map do |line|
72
- "#{line}#{newlines.pop}"
73
- end
74
- if lines.start_with?("\n")
75
- newlines.insert(0, "\n")
76
- end
77
- newlines
78
- else
79
- lines
80
- end
81
- end
82
- end
83
- end
84
-
85
39
  module Template
86
40
  def self.render(prefix, file_name, vars)
87
41
  file_path = File.join(::SparkleFormation.sparkle_path, prefix, file_name)
88
- template = File.read(file_path)
89
42
  template_context = TemplateContext.build(vars, prefix)
90
- compiled_template = SfEruby.new(template).evaluate(template_context)
91
- CloudFormationLineFormatter.format(compiled_template)
92
- rescue Errno::ENOENT => e
43
+ CloudFormationInterpolatingEruby.evaluate_file(file_path, template_context)
44
+ rescue Errno::ENOENT
93
45
  Kernel.raise TemplateFileNotFound, "Could not find template file at path: #{file_path}"
94
46
  end
95
47
  end