stack_master 2.3.0 → 2.14.1

Sign up to get free protection for your applications and to get access to all the features.
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