cfn-guardian 0.8.6 → 0.9.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: 0b6ceb0fb842b5d52ef3c36533bc77be47aeecc9f7aa358845fe8ec3a716e838
4
- data.tar.gz: f902b5a1ade88029205fe6dce4b3729a300a1539ee1cef0cc81d2fb9f3bc1e6e
3
+ metadata.gz: 6ada08812236c0f6a899a13847fda66a1e2b5c218f8f422cf210bed1ac04be44
4
+ data.tar.gz: c21ed6acd2eab4f673f9ac06ae06eb3a39059e1ba0bd82eddbb06ab57e908020
5
5
  SHA512:
6
- metadata.gz: cae989c263e5625f069c3f6941290ded67b9d46323ae806b395bcea6f5b2e99bf5e637b8869fc05f7dc62f1ab3345e4912d3d8ae5b8b1fc6d77b52864d74726b
7
- data.tar.gz: 6a6e68d8ab85bf219af792fa0759059f921de279b408163d54f96e059464208073fc4e78aa1fd8e62fed67a5b921d51abf3f265da4a092d149a97403f2dfbe39
6
+ metadata.gz: 2a39f90cea2a6ccf82e5a82d66b4586fe9d5bd227e470bd8322fda671b9bcadf5d82f814c33f602460db3f81b15e2b492e3812b8770568e6f4742c4089f477e8
7
+ data.tar.gz: 26ce905c4f0b8f6e52d13968f74528c125d9d1e43938f9f31ccc40fd8f65d91f76802c804b6bbd4f61550966eee5cdc0b5f918bdad22b2091c9750799ea7eefd
data/cfn-guardian.gemspec CHANGED
@@ -36,6 +36,8 @@ Gem::Specification.new do |spec|
36
36
  spec.add_dependency 'aws-sdk-codecommit', '~> 1.28', '<2'
37
37
  spec.add_dependency 'aws-sdk-codepipeline', '~> 1.28', '<2'
38
38
 
39
+ spec.add_runtime_dependency('rexml', '>= 0')
40
+
39
41
  spec.add_development_dependency "bundler", "~> 2.0"
40
42
  spec.add_development_dependency "rake", "~> 13.0"
41
43
  end
data/exe/cfn-guardian CHANGED
@@ -1,4 +1,4 @@
1
1
  #!/usr/bin/env ruby
2
2
  require "cfnguardian"
3
-
3
+ $stdout.sync = true
4
4
  CfnGuardian::Cli.start( ARGV )
@@ -93,7 +93,7 @@ module CfnGuardian
93
93
  if @templates.has_key?(group) && @templates[group].has_key?('Inherit')
94
94
  begin
95
95
  resource_class = Kernel.const_get("CfnGuardian::Resource::#{@templates[group]['Inherit']}").new(resource, group)
96
- logger.debug "Inheritited resource group #{@templates[group]['Inherit']} for group #{group}"
96
+ logger.debug "Inherited resource group #{@templates[group]['Inherit']} for group #{group}"
97
97
  rescue NameError => e
98
98
  logger.warn "'#{@templates[group]['Inherit']}' resource group doesn't exist and is unable to be inherited from"
99
99
  next
@@ -181,9 +181,7 @@ module CfnGuardian
181
181
  raise CfnGuardian::ValidationError, "#{errors.size} errors found\n[*] #{errors.join("\n[*] ")}" if errors.any?
182
182
  end
183
183
 
184
- def compile_templates(bucket,path)
185
- clean_out_directory()
186
-
184
+ def compile_templates(template_file)
187
185
  main_stack = CfnGuardian::Stacks::Main.new()
188
186
  main_stack.build_template(@stacks,@checks,@topics,@maintenance_groups,@ssm_parameters)
189
187
 
@@ -192,11 +190,7 @@ module CfnGuardian
192
190
 
193
191
  valid = main_stack.template.validate
194
192
  FileUtils.mkdir_p 'out'
195
- File.write("out/guardian.compiled.yaml", JSON.parse(valid.to_json).to_yaml)
196
- end
197
-
198
- def clean_out_directory
199
- Dir["out/*.yaml"].each {|file| File.delete(file)}
193
+ File.write("out/#{template_file}", JSON.parse(valid.to_json).to_yaml)
200
194
  end
201
195
 
202
196
  def load_parameters(options)
@@ -2,40 +2,37 @@ require 'aws-sdk-cloudformation'
2
2
  require 'fileutils'
3
3
  require 'cfnguardian/version'
4
4
  require 'cfnguardian/log'
5
+ require 'cfnguardian/error'
5
6
 
6
7
  module CfnGuardian
7
8
  class Deploy
8
9
  include Logging
9
10
 
10
- def initialize(opts,bucket,parameters)
11
- @stack_name = opts.fetch(:stack_name,'guardian')
11
+ def initialize(opts,bucket,parameters,template_file,stack_name)
12
+ @stack_name = stack_name
12
13
  @bucket = bucket
13
- @prefix = @stack_name
14
- @template_path = "out/guardian.compiled.yaml"
15
- @template_url = "https://#{@bucket}.s3.amazonaws.com/#{@prefix}/guardian.compiled.yaml"
14
+ @s3_path = "#{stack_name}/#{template_file}"
15
+ @template_path = "out/#{template_file}"
16
+ @template_url = "https://#{@bucket}.s3.amazonaws.com/#{@s3_path}"
16
17
  @parameters = parameters
17
18
  @changeset_role_arn = opts.fetch(:role_arn, nil)
18
19
 
19
- @tags = {}
20
- if opts.has_key?("tag_yaml")
21
- @tags.merge!(YAML.load_file(opts[:tag_yaml]))
20
+ @tags = opts.fetch(:tags, {})
21
+ if ENV.has_key?('CODEBUILD_RESOLVED_SOURCE_VERSION')
22
+ @tags[:'guardian:config:commit'] = ENV['CODEBUILD_RESOLVED_SOURCE_VERSION']
22
23
  end
23
- @tags.merge!(opts.fetch(:tags, {}))
24
24
 
25
25
  @client = Aws::CloudFormation::Client.new()
26
26
  end
27
27
 
28
28
  def upload_templates
29
- Dir["out/*.yaml"].each do |template|
30
- prefix = "#{@prefix}/#{template.split('/').last}"
31
- body = File.read(template)
32
- client = Aws::S3::Client.new()
33
- client.put_object({
34
- body: body,
35
- bucket: @bucket,
36
- key: prefix
37
- })
38
- end
29
+ body = File.read(@template_path)
30
+ client = Aws::S3::Client.new()
31
+ client.put_object({
32
+ body: body,
33
+ bucket: @bucket,
34
+ key: @s3_path
35
+ })
39
36
  end
40
37
 
41
38
  # TODO: check for REVIEW_IN_PROGRESS
@@ -99,8 +96,11 @@ module CfnGuardian
99
96
  @client.wait_until :change_set_create_complete, change_set_name: change_set_id
100
97
  rescue Aws::Waiters::Errors::FailureStateError => e
101
98
  change_set = get_change_set(change_set_id)
102
- logger.error("change set status: #{change_set.status} reason: #{change_set.status_reason}")
103
- exit 1
99
+ if change_set.status_reason.include?("The submitted information didn't contain changes.") || change_set.status_reason.include?("No updates are to be performed") && @ignore_empty_change_set
100
+ raise CfnGuardian::EmptyChangeSetError, "No changes to deploy. Stack #{@stack_name} is up to date"
101
+ else
102
+ raise CfnGuardian::ChangeSetError, "Failed to create the changeset : #{e.message} Status: #{change_set.status} Reason: #{change_set.status_reason}"
103
+ end
104
104
  end
105
105
  end
106
106
 
@@ -1,4 +1,6 @@
1
1
  module CfnGuardian
2
- class ValidationError < StandardError
3
- end
2
+ class ValidationError < StandardError; end
3
+ class TemplateValidationError < StandardError; end
4
+ class EmptyChangeSetError < StandardError; end
5
+ class ChangeSetError < StandardError; end
4
6
  end
@@ -20,13 +20,13 @@ module CfnGuardian::Resource
20
20
  @event_subscriptions = []
21
21
  end
22
22
 
23
- # Overidden by inheritted classes to define default alarms
23
+ # Overidden by inherited classes to define default alarms
24
24
  def default_alarms()
25
25
  return @alarms
26
26
  end
27
27
 
28
28
  def get_alarms(group,overides={})
29
- # deep copying the overrides to preserse it's reference before doing any changes to it
29
+ # deep copying the overrides to preserve its reference before doing any changes to it
30
30
  overides = Marshal.load(Marshal.dump(overides))
31
31
 
32
32
  # generate default alarms
@@ -72,12 +72,12 @@ module CfnGuardian::Resource
72
72
  alarm = find_alarm(properties['Inherit'])
73
73
  if !alarm.nil?
74
74
  logger.debug("creating new alarm #{name} for alarm group #{self.class.to_s.split('::').last} inheriting properties from alarm #{properties['Inherit']}")
75
- inheritited_alarm = alarm.clone
75
+ inherited_alarm = alarm.clone
76
76
  alarm.name = name
77
- properties.each {|attr,value| update_object(inheritited_alarm,attr,value)}
78
- @alarms.push(inheritited_alarm)
77
+ properties.each {|attr,value| update_object(inherited_alarm,attr,value)}
78
+ @alarms.push(inherited_alarm)
79
79
  else
80
- logger.warn "alarm '#{properties['Inherit']}' doesn't exists and cannot be inherited"
80
+ logger.warn "alarm '#{properties['Inherit']}' doesn't exist and cannot be inherited"
81
81
  end
82
82
  next
83
83
  end
@@ -124,7 +124,7 @@ module CfnGuardian::Resource
124
124
  return @alarms.select{|a| a.enabled}
125
125
  end
126
126
 
127
- # Overidden by inheritted classes to define default events
127
+ # Overidden by inherited classes to define default events
128
128
  def default_events()
129
129
  return @events
130
130
  end
@@ -134,7 +134,7 @@ module CfnGuardian::Resource
134
134
  return @events.select{|e| e.enabled}
135
135
  end
136
136
 
137
- # Overidden by inheritted classes to define default checks
137
+ # Overidden by inherited classes to define default checks
138
138
  def default_checks()
139
139
  return @checks
140
140
  end
@@ -144,7 +144,7 @@ module CfnGuardian::Resource
144
144
  return @checks
145
145
  end
146
146
 
147
- # Overidden by inheritted classes to define default checks
147
+ # Overidden by inherited classes to define default checks
148
148
  def default_metric_filters()
149
149
  return @metric_filters
150
150
  end
@@ -154,20 +154,20 @@ module CfnGuardian::Resource
154
154
  return @metric_filters
155
155
  end
156
156
 
157
- # Overidden by inheritted classes to define default checks
157
+ # Overidden by inherited classes to define default checks
158
158
  def default_event_subscriptions()
159
159
  return @event_subscriptions
160
160
  end
161
161
 
162
162
  def get_event_subscriptions(group, overides)
163
- # generate defailt event subscriptions
163
+ # generate default event subscriptions
164
164
  default_event_subscriptions()
165
165
 
166
- # overide the defaults
166
+ # override the defaults
167
167
  overides.each do |name, properties|
168
168
  event_subscription = find_event_subscriptions(name)
169
169
 
170
- # disbable the event subscription if the value is false
170
+ # disable the event subscription if the value is false
171
171
  if [false].include?(properties)
172
172
  unless event_subscription.nil?
173
173
  event_subscription.enabled = false
@@ -17,12 +17,14 @@ module CfnGuardian::Resource
17
17
  alarm.name = "#{command.to_camelcase}Warning"
18
18
  alarm.metric_name = command
19
19
  alarm.threshold = 0
20
+ alarm.alarm_action = 'Warning'
20
21
  @alarms.push(alarm)
21
22
 
22
23
  alarm = CfnGuardian::Models::NrpeAlarm.new(host,@environment)
23
24
  alarm.name = "#{command.to_camelcase}Critical"
24
25
  alarm.metric_name = command
25
26
  alarm.threshold = 1
27
+ alarm.alarm_action = 'Critical'
26
28
  @alarms.push(alarm)
27
29
  end
28
30
  end
@@ -3,6 +3,7 @@ require 'fileutils'
3
3
  require 'cfnguardian/version'
4
4
  require 'cfnguardian/log'
5
5
  require 'cfnguardian/s3'
6
+ require 'cfnguardian/error'
6
7
 
7
8
  module CfnGuardian
8
9
  class Validate
@@ -25,7 +26,10 @@ module CfnGuardian
25
26
  success << validate_local(template)
26
27
  end
27
28
  end
28
- return success.include?(false)
29
+
30
+ if success.include?(false)
31
+ raise CfnGuardian::TemplateValidationError, "One or more templates failed to validate"
32
+ end
29
33
  end
30
34
 
31
35
  def validate_local(path)
@@ -1,4 +1,4 @@
1
1
  module CfnGuardian
2
- VERSION = "0.8.6"
2
+ VERSION = "0.9.0"
3
3
  CHANGE_SET_VERSION = VERSION.gsub('.', '-').freeze
4
4
  end
data/lib/cfnguardian.rb CHANGED
@@ -16,6 +16,10 @@ require "cfnguardian/tagger"
16
16
  module CfnGuardian
17
17
  class Cli < Thor
18
18
  include Logging
19
+
20
+ def self.exit_on_failure?
21
+ true
22
+ end
19
23
 
20
24
  map %w[--version -v] => :__print_version
21
25
  desc "--version, -v", "print the version"
@@ -40,28 +44,26 @@ module CfnGuardian
40
44
  method_option :sns_task, type: :string, desc: "sns topic arn for the task alarms"
41
45
  method_option :sns_informational, type: :string, desc: "sns topic arn for the informational alarms"
42
46
  method_option :sns_events, type: :string, desc: "sns topic arn for the informational alarms"
43
-
47
+ method_option :template_file, type: :string, default: 'guardian.compiled.yaml', desc: "name of the compiled cloudformation template file"
44
48
 
45
49
  def compile
46
50
  set_log_level(options[:debug])
47
51
 
48
52
  set_region(options[:region],options[:validate])
49
53
  s3 = CfnGuardian::S3.new(options[:bucket],options[:path])
50
-
54
+
55
+ clean_out_directory()
56
+
51
57
  compiler = CfnGuardian::Compile.new(options[:config])
52
58
  compiler.get_resources
53
- compiler.compile_templates(s3.bucket,s3.path)
59
+ compiler.compile_templates(options[:template_file])
54
60
  logger.info "Cloudformation templates compiled successfully in out/ directory"
55
61
  if options[:validate]
56
62
  s3.create_bucket_if_not_exists()
57
63
  validator = CfnGuardian::Validate.new(s3.bucket)
58
- if validator.validate
59
- logger.error("One or more templates failed to validate")
60
- exit(1)
61
- else
62
- logger.info "Cloudformation templates were validated successfully"
63
- end
64
+ validator.validate
64
65
  end
66
+
65
67
  logger.warn "AWS cloudwatch alarms defined in the templates will cost roughly $#{'%.2f' % compiler.cost} per month"
66
68
 
67
69
  if options[:template_config]
@@ -80,15 +82,16 @@ module CfnGuardian
80
82
  method_option :bucket, type: :string, desc: "provide custom bucket name, will create a default bucket if not provided"
81
83
  method_option :path, type: :string, default: "guardian", desc: "S3 path location for nested stacks"
82
84
  method_option :region, aliases: :r, type: :string, desc: "set the AWS region"
83
- method_option :stack_name, aliases: :s, type: :string, desc: "set the Cloudformation stack name. Defaults to `guardian`"
85
+ method_option :stack_name, aliases: :s, type: :string, default: 'guardian', desc: "set the Cloudformation stack name. Defaults to `guardian`"
84
86
  method_option :sns_critical, type: :string, desc: "sns topic arn for the critical alarms"
85
87
  method_option :sns_warning, type: :string, desc: "sns topic arn for the warning alarms"
86
88
  method_option :sns_task, type: :string, desc: "sns topic arn for the task alarms"
87
89
  method_option :sns_informational, type: :string, desc: "sns topic arn for the informational alarms"
88
90
  method_option :sns_events, type: :string, desc: "sns topic arn for the informational alarms"
89
91
  method_option :tags, type: :hash, desc: "additional tags on the cloudformation stack"
90
- method_option :tag_yaml, type: :string, desc: "additional tags on the cloudformation stack in a yaml file"
91
92
  method_option :role_arn, type: :string, desc: "IAM role arn that CloudFormation assumes when executing the change set"
93
+ method_option :template_file, type: :string, default: 'guardian.compiled.yaml', desc: "name of the compiled cloudformation template file"
94
+ method_option :fail_empty_change_set, type: :boolean, default: true, desc: "fail a cloudformation changeset if it contains no changes"
92
95
 
93
96
  def deploy
94
97
  set_log_level(options[:debug])
@@ -96,22 +99,19 @@ module CfnGuardian
96
99
  set_region(options[:region],true)
97
100
  s3 = CfnGuardian::S3.new(options[:bucket],options[:path])
98
101
 
102
+ clean_out_directory()
103
+
99
104
  compiler = CfnGuardian::Compile.new(options[:config])
100
105
  compiler.get_resources
101
- compiler.compile_templates(s3.bucket,s3.path)
106
+ compiler.compile_templates(options[:template_file])
102
107
  parameters = compiler.load_parameters(options)
103
108
  logger.info "Cloudformation templates compiled successfully in out/ directory"
104
109
 
105
110
  s3.create_bucket_if_not_exists
106
111
  validator = CfnGuardian::Validate.new(s3.bucket)
107
- if validator.validate
108
- logger.error("One or more templates failed to validate")
109
- exit(1)
110
- else
111
- logger.info "Cloudformation templates were validated successfully"
112
- end
112
+ validator.validate
113
113
 
114
- deployer = CfnGuardian::Deploy.new(options,s3.bucket,parameters)
114
+ deployer = CfnGuardian::Deploy.new(options,s3.bucket,parameters,options[:template_file],options[:stack_name])
115
115
  deployer.upload_templates
116
116
  change_set, change_set_type = deployer.create_change_set()
117
117
  deployer.wait_for_changeset(change_set.id)
@@ -119,26 +119,139 @@ module CfnGuardian
119
119
  deployer.wait_for_execute(change_set_type)
120
120
  end
121
121
 
122
+ desc "bulk-deploy", "Generates and deploys multiple monitoring CloudFormation templates from multiple config yamls"
123
+ long_desc <<-LONG
124
+ For each alarm configuration yamll file provided guardian will generate a CloudFormation template in output to the out/ directory.
125
+ The templates are copied to the s3 bucket and the cloudformation stacks are deployed.
126
+ The names of the Cloudformation stacks are determined by the config yaml name. e.g. alarms.myenv.yaml will deploy the stack myenv-guardian
127
+ LONG
128
+ method_option :config, aliases: :c, type: :array, desc: "yaml config files", required: true
129
+ method_option :bucket, type: :string, desc: "provide custom bucket name, will create a default bucket if not provided"
130
+ method_option :path, type: :string, default: "guardian", desc: "S3 path location for nested stacks"
131
+ method_option :region, aliases: :r, type: :string, desc: "set the AWS region"
132
+ method_option :sns_critical, type: :string, desc: "sns topic arn for the critical alarms"
133
+ method_option :sns_warning, type: :string, desc: "sns topic arn for the warning alarms"
134
+ method_option :sns_task, type: :string, desc: "sns topic arn for the task alarms"
135
+ method_option :sns_informational, type: :string, desc: "sns topic arn for the informational alarms"
136
+ method_option :sns_events, type: :string, desc: "sns topic arn for the informational alarms"
137
+ method_option :tags, type: :hash, desc: "additional tags on the cloudformation stack"
138
+ method_option :role_arn, type: :string, desc: "IAM role arn that CloudFormation assumes when executing the change set"
139
+ method_option :fail_empty_change_set, type: :boolean, default: true, desc: "fail a cloudformation changeset if it contains no changes"
140
+
141
+ def bulk_deploy
142
+ set_log_level(options[:debug])
143
+
144
+ set_region(options[:region],true)
145
+ s3 = CfnGuardian::S3.new(options[:bucket],options[:path])
146
+ s3.create_bucket_if_not_exists
147
+
148
+ clean_out_directory()
149
+
150
+ template_file_suffix = 'compiled.yaml'
151
+
152
+ compiled = []
153
+
154
+ options[:config].each do |config|
155
+ config_basename = File.basename(config, ".alarms.yaml")
156
+ guardian_name = config_basename == "alarms.yaml" ? "" : "-#{config_basename}"
157
+ template_file = "guardian#{guardian_name}.#{template_file_suffix}"
158
+
159
+ compiler = CfnGuardian::Compile.new(config)
160
+ compiler.get_resources
161
+ compiler.compile_templates(template_file)
162
+ logger.info "compiled template to out/#{template_file} from yaml config #{config}"
163
+ parameters = compiler.load_parameters(options)
164
+
165
+ compiled << {template_file: template_file, parameters: parameters}
166
+ logger.debug("template file #{template_file} generated with parameters: #{parameters}")
167
+ end
168
+
169
+ validator = CfnGuardian::Validate.new(s3.bucket)
170
+ validator.validate
171
+
172
+ changesets = []
173
+
174
+ compiled.each do |stack|
175
+ stack_name = stack[:template_file].gsub(".#{template_file_suffix}", "")
176
+ deployer = CfnGuardian::Deploy.new(options,s3.bucket,stack[:parameters],stack[:template_file],stack_name)
177
+ deployer.upload_templates
178
+ logger.info("creating changeset for stack #{stack_name}")
179
+ change_set, change_set_type = deployer.create_change_set()
180
+ changesets << {deployer: deployer, id: change_set.id, type: change_set_type}
181
+ end
182
+
183
+ changesets_executed = []
184
+ changesets.each do |changeset|
185
+ begin
186
+ changeset[:deployer].wait_for_changeset(changeset[:id])
187
+ rescue CfnGuardian::EmptyChangeSetError => e
188
+ if options[:fail_empty_change_set]
189
+ raise e
190
+ else
191
+ logger.info e.message
192
+ next
193
+ end
194
+ end
195
+ logger.info("executing changeset #{changeset[:id]}")
196
+ changeset[:deployer].execute_change_set(changeset[:id])
197
+ changesets_executed << changeset
198
+ end
199
+
200
+ changesets_executed.each do |changeset|
201
+ logger.info("waiting for changeset #{changeset[:id]} to complete")
202
+ changeset[:deployer].wait_for_execute(changeset[:type])
203
+ end
204
+ end
205
+
122
206
  desc "tag-alarms", "apply tags to the cloudwatch alarms deployed"
123
207
  long_desc <<-LONG
124
208
  Because Cloudformation desn't support tagging cloudwatch alarms this command
125
209
  applies tags to each cloudwatch alarm created by guardian.
126
210
  Guardian defines default tags and this can be added to through the alarms.yaml config.
127
211
  LONG
128
- method_option :config, aliases: :c, type: :string, desc: "yaml config file", required: true
212
+ method_option :config, aliases: :c, type: :array, desc: "yaml config files", required: true
129
213
  method_option :region, aliases: :r, type: :string, desc: "set the AWS region"
214
+ method_option :tags, type: :hash, desc: "additional tags on the cloudformation stack"
130
215
 
131
216
  def tag_alarms
132
217
  set_log_level(options[:debug])
133
218
  set_region(options[:region],true)
134
219
 
135
- compiler = CfnGuardian::Compile.new(options[:config])
136
- compiler.get_resources
137
- alarms = compiler.alarms
220
+ tags = options.fetch(:tags, {})
138
221
 
139
- tagger = CfnGuardian::Tagger.new()
140
- alarms.each do |alarm|
141
- tagger.tag_alarm(alarm, compiler.global_tags)
222
+ if ENV.has_key?('CODEBUILD_RESOLVED_SOURCE_VERSION')
223
+ tags[:'guardian:config:commit'] = ENV['CODEBUILD_RESOLVED_SOURCE_VERSION']
224
+ end
225
+
226
+ options[:config].each do |config|
227
+ config_basename = File.basename(config, "alarms.yaml")
228
+ stack_name_suffix = config_basename != "" ? "#{config_basename}-" : ""
229
+ tags[:'guardian:stack:name'] = "guardian#{stack_name_suffix}"
230
+ tags[:'guardian:config:yaml'] = config
231
+
232
+ logger.info "tagging alarms from config file #{config}"
233
+ compiler = CfnGuardian::Compile.new(config)
234
+ compiler.get_resources
235
+ alarms = compiler.alarms
236
+ global_tags = compiler.global_tags.merge(tags)
237
+
238
+ tagger = CfnGuardian::Tagger.new()
239
+
240
+ counter = 0
241
+ max_retries = 3
242
+ alarms.each do |alarm|
243
+ begin
244
+ tagger.tag_alarm(alarm, global_tags)
245
+ rescue Aws::CloudWatch::Errors::Throttling => e
246
+ logger.info "cloud watch throttling alarm tagging requests, retrying ..."
247
+ if (counter += 1) < max_retries
248
+ sleep(1)
249
+ redo
250
+ else
251
+ loger.warn "throttled max times (#{max_retries}) tagging alarm #{alarm.name}, skipping ..."
252
+ end
253
+ end
254
+ end
142
255
  end
143
256
  end
144
257
 
@@ -165,7 +278,6 @@ module CfnGuardian
165
278
  :title => "Guardian Alarm Drift".green,
166
279
  :headings => ['Alarm Name', 'Property', 'Expected', 'Actual', 'Type'],
167
280
  :rows => rows.flatten(1))
168
- exit(1)
169
281
  end
170
282
  end
171
283
 
@@ -189,8 +301,7 @@ module CfnGuardian
189
301
  elsif options[:defaults]
190
302
  config_file = default_config()
191
303
  else
192
- logger.error('one of `--config YAML` or `--defaults` must be supplied')
193
- exit -1
304
+ raise Thor::Error, 'one of `--config YAML` or `--defaults` must be supplied'
194
305
  end
195
306
 
196
307
  compiler = CfnGuardian::Compile.new(config_file)
@@ -198,8 +309,7 @@ module CfnGuardian
198
309
  alarms = filter_compiled_alarms(compiler.alarms,options[:filter])
199
310
 
200
311
  if alarms.empty?
201
- logger.error "No matches found"
202
- exit 1
312
+ raise Thor::Error, "No matches found"
203
313
  end
204
314
 
205
315
  headings = ['Property', 'Config']
@@ -429,8 +539,7 @@ module CfnGuardian
429
539
  Aws.config.update({region: ENV['AWS_DEFAULT_REGION']})
430
540
  else
431
541
  if required
432
- logger.error("No AWS region found. Please suppy the region using option `--region` or setting environment variables `AWS_REGION` `AWS_DEFAULT_REGION`")
433
- exit(1)
542
+ raise Thor::Error "No AWS region found. Please suppy the region using option `--region` or setting environment variables `AWS_REGION` `AWS_DEFAULT_REGION`"
434
543
  end
435
544
  end
436
545
  end
@@ -461,5 +570,9 @@ module CfnGuardian
461
570
  return !parameter.nil? ? parameter.parameter_value : nil
462
571
  end
463
572
 
573
+ def clean_out_directory
574
+ Dir["out/*.yaml"].each {|file| File.delete(file)}
575
+ end
576
+
464
577
  end
465
578
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cfn-guardian
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.6
4
+ version: 0.9.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Guslington
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-08-24 00:00:00.000000000 Z
11
+ date: 2022-09-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: thor
@@ -184,6 +184,20 @@ dependencies:
184
184
  - - "<"
185
185
  - !ruby/object:Gem::Version
186
186
  version: '2'
187
+ - !ruby/object:Gem::Dependency
188
+ name: rexml
189
+ requirement: !ruby/object:Gem::Requirement
190
+ requirements:
191
+ - - ">="
192
+ - !ruby/object:Gem::Version
193
+ version: '0'
194
+ type: :runtime
195
+ prerelease: false
196
+ version_requirements: !ruby/object:Gem::Requirement
197
+ requirements:
198
+ - - ">="
199
+ - !ruby/object:Gem::Version
200
+ version: '0'
187
201
  - !ruby/object:Gem::Dependency
188
202
  name: bundler
189
203
  requirement: !ruby/object:Gem::Requirement