cfn-guardian 0.4.0 → 0.6.2

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 (47) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/build-gem.yml +25 -0
  3. data/.github/workflows/release-gem.yml +25 -0
  4. data/.github/workflows/release-image.yml +33 -0
  5. data/.rspec +1 -0
  6. data/Gemfile.lock +13 -13
  7. data/README.md +3 -819
  8. data/cfn-guardian.gemspec +1 -3
  9. data/docs/alarm_templates.md +130 -0
  10. data/docs/cli.md +182 -0
  11. data/docs/composite_alarms.md +24 -0
  12. data/docs/custom_checks/azure_file_check.md +28 -0
  13. data/docs/custom_checks/domain_expiry.md +10 -0
  14. data/docs/custom_checks/http.md +59 -0
  15. data/docs/custom_checks/log_group_metric_filters.md +27 -0
  16. data/docs/custom_checks/nrpe.md +29 -0
  17. data/docs/custom_checks/port.md +40 -0
  18. data/docs/custom_checks/sftp.md +73 -0
  19. data/docs/custom_checks/sql.md +44 -0
  20. data/docs/custom_checks/tls.md +25 -0
  21. data/docs/custom_metrics.md +71 -0
  22. data/docs/event_subscriptions.md +67 -0
  23. data/docs/maintenance_mode.md +85 -0
  24. data/docs/notifiers.md +33 -0
  25. data/docs/overview.md +22 -0
  26. data/docs/resources.md +93 -0
  27. data/docs/variables.md +58 -0
  28. data/lib/cfnguardian.rb +72 -58
  29. data/lib/cfnguardian/cloudwatch.rb +43 -32
  30. data/lib/cfnguardian/compile.rb +82 -5
  31. data/lib/cfnguardian/deploy.rb +2 -16
  32. data/lib/cfnguardian/display_formatter.rb +1 -2
  33. data/lib/cfnguardian/error.rb +4 -0
  34. data/lib/cfnguardian/models/alarm.rb +40 -28
  35. data/lib/cfnguardian/models/check.rb +30 -12
  36. data/lib/cfnguardian/models/event.rb +43 -15
  37. data/lib/cfnguardian/models/event_subscription.rb +96 -0
  38. data/lib/cfnguardian/resources/azure_file.rb +20 -0
  39. data/lib/cfnguardian/resources/base.rb +111 -26
  40. data/lib/cfnguardian/resources/ec2_instance.rb +11 -0
  41. data/lib/cfnguardian/resources/http.rb +1 -0
  42. data/lib/cfnguardian/resources/rds_cluster.rb +14 -0
  43. data/lib/cfnguardian/resources/rds_instance.rb +71 -0
  44. data/lib/cfnguardian/stacks/main.rb +7 -6
  45. data/lib/cfnguardian/stacks/resources.rb +34 -5
  46. data/lib/cfnguardian/version.rb +1 -1
  47. metadata +35 -10
@@ -2,7 +2,7 @@ require 'cfnguardian/string'
2
2
 
3
3
  module CfnGuardian
4
4
  module Models
5
- class Check
5
+ class BaseCheck
6
6
 
7
7
  attr_reader :type
8
8
  attr_accessor :group,
@@ -13,7 +13,9 @@ module CfnGuardian
13
13
  :runtime,
14
14
  :environment,
15
15
  :subnets,
16
- :vpc
16
+ :vpc,
17
+ :memory,
18
+ :timeout
17
19
 
18
20
  def initialize(resource)
19
21
  @type = 'Check'
@@ -26,17 +28,19 @@ module CfnGuardian
26
28
  @environment = ''
27
29
  @subnets = nil
28
30
  @vpc = nil
31
+ @memory = 128
32
+ @timeout = 120
29
33
  end
30
34
  end
31
35
 
32
- class HttpCheck < Check
36
+ class HttpCheck < BaseCheck
33
37
  def initialize(resource)
34
38
  super(resource)
35
39
  @group = 'Http'
36
40
  @name = 'HttpCheck'
37
41
  @package = 'http-check'
38
42
  @handler = 'handler.http_check'
39
- @version = '0bc33e51abb1f27729ecb170611bf6b440e71a0e'
43
+ @version = 'f739631de74f1a882163b7e584a8b4710ccbc134'
40
44
  @runtime = 'python3.7'
41
45
  end
42
46
  end
@@ -52,7 +56,7 @@ module CfnGuardian
52
56
  end
53
57
  end
54
58
 
55
- class PortCheck < Check
59
+ class PortCheck < BaseCheck
56
60
  def initialize(resource)
57
61
  super(resource)
58
62
  @group = 'Port'
@@ -75,7 +79,7 @@ module CfnGuardian
75
79
  end
76
80
  end
77
81
 
78
- class NrpeCheck < Check
82
+ class NrpeCheck < BaseCheck
79
83
  def initialize(resource)
80
84
  super(resource)
81
85
  @group = 'Nrpe'
@@ -90,7 +94,7 @@ module CfnGuardian
90
94
  end
91
95
  end
92
96
 
93
- class SslCheck < Check
97
+ class SslCheck < BaseCheck
94
98
  def initialize(resource)
95
99
  super(resource)
96
100
  @group = 'Ssl'
@@ -113,7 +117,7 @@ module CfnGuardian
113
117
  end
114
118
  end
115
119
 
116
- class DomainExpiryCheck < Check
120
+ class DomainExpiryCheck < BaseCheck
117
121
  def initialize(resource)
118
122
  super(resource)
119
123
  @group = 'DomainExpiry'
@@ -125,7 +129,7 @@ module CfnGuardian
125
129
  end
126
130
  end
127
131
 
128
- class SqlCheck < Check
132
+ class SqlCheck < BaseCheck
129
133
  def initialize(resource)
130
134
  super(resource)
131
135
  @group = 'Sql'
@@ -140,7 +144,7 @@ module CfnGuardian
140
144
  end
141
145
  end
142
146
 
143
- class ContainerInstanceCheck < Check
147
+ class ContainerInstanceCheck < BaseCheck
144
148
  def initialize(resource)
145
149
  super(resource)
146
150
  @group = 'ContainerInstance'
@@ -152,7 +156,7 @@ module CfnGuardian
152
156
  end
153
157
  end
154
158
 
155
- class TLSCheck < Check
159
+ class TLSCheck < BaseCheck
156
160
  def initialize(resource)
157
161
  super(resource)
158
162
  @group = 'TLS'
@@ -164,7 +168,7 @@ module CfnGuardian
164
168
  end
165
169
  end
166
170
 
167
- class SFTPCheck < Check
171
+ class SFTPCheck < BaseCheck
168
172
  def initialize(resource)
169
173
  super(resource)
170
174
  @group = 'SFTP'
@@ -187,5 +191,19 @@ module CfnGuardian
187
191
  end
188
192
  end
189
193
 
194
+ class AzureFileCheck < BaseCheck
195
+ def initialize(resource)
196
+ super(resource)
197
+ @group = 'AzureFile'
198
+ @name = 'AzureFileCheck'
199
+ @package = 'azure-file-check'
200
+ @handler = 'handler.file_check'
201
+ @version = 'cc37aa8fe4855570132431611b507274b390f4c1'
202
+ @runtime = 'python3.7'
203
+ @memory = 256
204
+ @timeout = 600
205
+ end
206
+ end
207
+
190
208
  end
191
209
  end
@@ -2,7 +2,7 @@ require 'cfnguardian/string'
2
2
 
3
3
  module CfnGuardian
4
4
  module Models
5
- class Event
5
+ class BaseEvent
6
6
 
7
7
  attr_reader :type
8
8
  attr_accessor :group,
@@ -31,7 +31,7 @@ module CfnGuardian
31
31
  end
32
32
  end
33
33
 
34
- class HttpEvent < Event
34
+ class HttpEvent < BaseEvent
35
35
 
36
36
  attr_accessor :endpoint,
37
37
  :method,
@@ -81,7 +81,7 @@ module CfnGuardian
81
81
  end
82
82
  end
83
83
 
84
- class PortEvent < Event
84
+ class PortEvent < BaseEvent
85
85
  def initialize(resource)
86
86
  super(resource)
87
87
  @group = 'Port'
@@ -112,7 +112,7 @@ module CfnGuardian
112
112
  end
113
113
  end
114
114
 
115
- class NrpeEvent < Event
115
+ class NrpeEvent < BaseEvent
116
116
  def initialize(resource,environment,command)
117
117
  super(resource)
118
118
  @group = 'Nrpe'
@@ -121,6 +121,7 @@ module CfnGuardian
121
121
  @host = resource['Id']
122
122
  @environment = environment
123
123
  @region = resource.fetch('Region',"${AWS::Region}")
124
+ @hash = Digest::MD5.hexdigest "#{resource['Id']}#{command}"
124
125
  @command = command
125
126
  end
126
127
 
@@ -134,13 +135,13 @@ module CfnGuardian
134
135
  end
135
136
  end
136
137
 
137
- class SslEvent < Event
138
+ class SslEvent < BaseEvent
138
139
  def initialize(resource)
139
140
  super(resource)
140
141
  @group = 'Ssl'
141
142
  @name = 'SslEvent'
142
143
  @target = 'SslCheckFunction'
143
- @cron = "0 12 * * ? *"
144
+ @cron = resource.fetch('Schedule', "0 12 * * ? *")
144
145
  @url = resource['Id']
145
146
  @region = resource.fetch('Region',"${AWS::Region}")
146
147
  end
@@ -163,7 +164,7 @@ module CfnGuardian
163
164
  end
164
165
  end
165
166
 
166
- class DomainExpiryEvent < Event
167
+ class DomainExpiryEvent < BaseEvent
167
168
 
168
169
  attr_accessor :domain,
169
170
  :region
@@ -173,7 +174,7 @@ module CfnGuardian
173
174
  @group = 'DomainExpiry'
174
175
  @name = 'DomainExpiryEvent'
175
176
  @target = 'DomainExpiryCheckFunction'
176
- @cron = "0 12 * * ? *"
177
+ @cron = resource.fetch('Schedule', "0 12 * * ? *")
177
178
  @domain = resource['Id']
178
179
  @region = resource.fetch('Region',"${AWS::Region}")
179
180
  end
@@ -183,7 +184,7 @@ module CfnGuardian
183
184
  end
184
185
  end
185
186
 
186
- class SqlEvent < Event
187
+ class SqlEvent < BaseEvent
187
188
  def initialize(resource,query,environment)
188
189
  super(resource)
189
190
  @group = 'Sql'
@@ -221,13 +222,13 @@ module CfnGuardian
221
222
  end
222
223
  end
223
224
 
224
- class ContainerInstanceEvent < Event
225
+ class ContainerInstanceEvent < BaseEvent
225
226
  def initialize(resource)
226
227
  super(resource)
227
228
  @group = 'ContainerInstance'
228
229
  @name = 'ContainerInstanceEvent'
229
230
  @target = 'ContainerInstanceCheckFunction'
230
- @cron = "0/5 * * * ? *"
231
+ @cron = resource.fetch('Schedule', "0/5 * * * ? *")
231
232
  @cluster = resource['Id']
232
233
  end
233
234
 
@@ -236,13 +237,13 @@ module CfnGuardian
236
237
  end
237
238
  end
238
239
 
239
- class SFTPEvent < Event
240
+ class SFTPEvent < BaseEvent
240
241
  def initialize(resource)
241
242
  super(resource)
242
243
  @group = 'SFTP'
243
244
  @name = 'SFTPEvent'
244
245
  @target = 'SFTPCheckFunction'
245
- @cron = "0/5 * * * ? *"
246
+ @cron = resource.fetch('Schedule', "0/5 * * * ? *")
246
247
  @host = resource['Id']
247
248
  @user = resource['User']
248
249
  @port = resource.fetch('Port', nil)
@@ -288,13 +289,13 @@ module CfnGuardian
288
289
  end
289
290
  end
290
291
 
291
- class TLSEvent < Event
292
+ class TLSEvent < BaseEvent
292
293
  def initialize(resource)
293
294
  super(resource)
294
295
  @group = 'TLS'
295
296
  @name = 'TLSEvent'
296
297
  @target = 'TLSCheckFunction'
297
- @cron = "0/5 * * * ? *"
298
+ @cron = resource.fetch('Schedule', "0/5 * * * ? *")
298
299
  @host = resource['Id']
299
300
  @port = resource.fetch('Port', 443)
300
301
  @check_max = resource.fetch('MaxSupported', nil)
@@ -312,5 +313,32 @@ module CfnGuardian
312
313
  end
313
314
  end
314
315
 
316
+ class AzureFileEvent < BaseEvent
317
+ def initialize(resource)
318
+ super(resource)
319
+ @group = 'AzureFile'
320
+ @name = 'AzureFileEvent'
321
+ @target = 'AzureFileCheckFunction'
322
+ @cron = resource.fetch('Schedule', "0/5 * * * ? *")
323
+ @storage_account = resource['Id']
324
+ @container = resource['Container']
325
+ @connection_string = resource['ConnectionString']
326
+ @search = resource['Search']
327
+ end
328
+
329
+ def payload
330
+ return {
331
+ 'STORAGE_ACCOUNT' => @storage_account,
332
+ 'CONTAINER' => @container,
333
+ 'CONNECTION_STRING' => @connection_string,
334
+ 'SEARCH' => @search
335
+ }.to_json
336
+ end
337
+
338
+ def ssm_parameters
339
+ return [@connection_string]
340
+ end
341
+ end
342
+
315
343
  end
316
344
  end
@@ -0,0 +1,96 @@
1
+ module CfnGuardian
2
+ module Models
3
+ class BaseEventSubscription
4
+
5
+ attr_reader :type, :group
6
+ attr_writer :detail
7
+ attr_accessor :name,
8
+ :enabled,
9
+ :hash,
10
+ :topic,
11
+ :resource_id,
12
+ :resource_arn,
13
+ :source,
14
+ :detail_type,
15
+ :detail
16
+
17
+ def initialize(resource)
18
+ @type = 'EventSubscription'
19
+ @group = self.class.name.split('::').last
20
+ @name = ''
21
+ @hash = Digest::MD5.hexdigest resource['Id']
22
+ @enabled = true
23
+ @events = []
24
+ @topic = 'Events'
25
+ @resource_id = resource['Id']
26
+ @resource_arn = ''
27
+ @source = ''
28
+ @detail_type = ''
29
+ @detail = {}
30
+ end
31
+
32
+ def detail
33
+ return @detail
34
+ end
35
+ end
36
+
37
+ class RDSEventSubscription < BaseEventSubscription
38
+ attr_accessor :source_id, :rds_event_category, :message
39
+
40
+ def initialize(resource)
41
+ super(resource)
42
+ @source = 'aws.rds'
43
+ @detail_type = 'RDS DB Instance Event'
44
+ @source_id = ''
45
+ @rds_event_category = ''
46
+ @message = ''
47
+ end
48
+
49
+ def detail
50
+ return {
51
+ EventCategories: [@rds_event_category],
52
+ SourceType: [@source_type],
53
+ SourceIdentifier: ["rds:#{@resource_id}"],
54
+ Message: [@message]
55
+ }
56
+ end
57
+ end
58
+
59
+ class RDSInstanceEventSubscription < RDSEventSubscription
60
+ def initialize(resource)
61
+ super(resource)
62
+ @source_type = 'DB_INSTANCE'
63
+ end
64
+ end
65
+
66
+ class RDSClusterEventSubscription < RDSEventSubscription
67
+ def initialize(resource)
68
+ super(resource)
69
+ @source_type = 'DB_CLUSTER'
70
+ end
71
+ end
72
+
73
+ class Ec2InstanceEventSubscription < BaseEventSubscription
74
+ def initialize(resource)
75
+ super(resource)
76
+ @source = 'aws.ec2'
77
+ end
78
+ end
79
+
80
+ class ApiGatewayEventSubscription < BaseEventSubscription; end
81
+ class ApplicationTargetGroupEventSubscription < BaseEventSubscription; end
82
+ class AmazonMQBrokerEventSubscription < BaseEventSubscription; end
83
+ class CloudFrontDistributionEventSubscription < BaseEventSubscription; end
84
+ class AutoScalingGroupEventSubscription < BaseEventSubscription; end
85
+ class DynamoDBTableEventSubscription < BaseEventSubscription; end
86
+ class Ec2InstanceEventSubscription < BaseEventSubscription; end
87
+ class ECSClusterEventSubscription < BaseEventSubscription; end
88
+ class ECSServiceEventSubscription < BaseEventSubscription; end
89
+ class ElastiCacheReplicationGroupEventSubscription < BaseEventSubscription; end
90
+ class ElasticLoadBalancerEventSubscription < BaseEventSubscription; end
91
+ class ElasticFileSystemEventSubscription < BaseEventSubscription; end
92
+ class LambdaEventSubscription < BaseEventSubscription; end
93
+ class NetworkTargetGroupEventSubscription < BaseEventSubscription; end
94
+ class RedshiftClusterEventSubscription < BaseEventSubscription; end
95
+ end
96
+ end
@@ -0,0 +1,20 @@
1
+ module CfnGuardian::Resource
2
+ class AzureFile < Base
3
+
4
+ def default_alarms
5
+ alarm = CfnGuardian::Models::AzureFileAlarm.new(@resource)
6
+ alarm.name = 'FileExpired'
7
+ alarm.metric_name = 'FileExpired'
8
+ @alarms.push(alarm)
9
+ end
10
+
11
+ def default_events
12
+ @events.push(CfnGuardian::Models::AzureFileEvent.new(@resource))
13
+ end
14
+
15
+ def default_checks
16
+ @checks.push(CfnGuardian::Models::AzureFileCheck.new(@resource))
17
+ end
18
+
19
+ end
20
+ end
@@ -4,6 +4,7 @@ require 'cfnguardian/models/alarm'
4
4
  require 'cfnguardian/models/event'
5
5
  require 'cfnguardian/models/check'
6
6
  require 'cfnguardian/models/metric_filter'
7
+ require 'cfnguardian/models/event_subscription'
7
8
 
8
9
  module CfnGuardian::Resource
9
10
  class Base
@@ -16,6 +17,7 @@ module CfnGuardian::Resource
16
17
  @events = []
17
18
  @checks = []
18
19
  @metric_filters = []
20
+ @event_subscriptions = []
19
21
  end
20
22
 
21
23
  # Overidden by inheritted classes to define default alarms
@@ -23,7 +25,7 @@ module CfnGuardian::Resource
23
25
  return @alarms
24
26
  end
25
27
 
26
- def get_alarms(resource,group,overides={})
28
+ def get_alarms(group,overides={})
27
29
  # generate default alarms
28
30
  default_alarms()
29
31
 
@@ -32,20 +34,22 @@ module CfnGuardian::Resource
32
34
  overides.delete('GroupOverrides')
33
35
  if group_overrides.any?
34
36
  @alarms.each do |alarm|
35
- group_overrides.each {|attr,value| update_alarm(alarm,attr,value)}
37
+ logger.debug("overriding #{alarm.name} alarm properties for resource #{alarm.resource_id} in resource group #{group} via group overrides")
38
+ group_overrides.each {|attr,value| update_object(alarm,attr,value)}
36
39
  end
37
40
  end
38
41
 
39
42
  # loop over each override template for the service
40
- overides.each do |name,properties|
41
-
43
+ overides.each do |name,properties|
42
44
  # disable default alarms
43
45
  if [false].include?(properties)
44
- alarm = find_alarm(name)
46
+ alarms = find_alarms(name)
45
47
 
46
- if !alarm.nil?
47
- alarm.enabled = false
48
- logger.debug "Disabling alarm '#{name}' for resource #{alarm.resource_id}"
48
+ if !alarms.nil?
49
+ alarms.each do |alarm|
50
+ alarm.enabled = false
51
+ logger.info "disabling alarm '#{name}' for resource #{alarm.resource_id}"
52
+ end
49
53
  next
50
54
  end
51
55
  end
@@ -53,7 +57,7 @@ module CfnGuardian::Resource
53
57
  # continue if the override is in the incorrect format
54
58
  unless properties.is_a?(Hash)
55
59
  if name != 'Inherit'
56
- logger.warn "Incorrect format for alarm '#{name}'. Should be of type 'Hash', instead got type '#{properties.group}'"
60
+ logger.warn "incorrect format for alarm '#{name}'. Should be of type 'Hash', instead got type '#{properties.group}'"
57
61
  end
58
62
  next
59
63
  end
@@ -64,31 +68,35 @@ module CfnGuardian::Resource
64
68
  if properties.has_key?('Inherit')
65
69
  alarm = find_alarm(properties['Inherit'])
66
70
  if !alarm.nil?
71
+ logger.debug("creating new alarm #{name} for alarm group #{self.class.to_s.split('::').last} inheriting properties from alarm #{properties['Inherit']}")
67
72
  inheritited_alarm = alarm.clone
68
73
  alarm.name = name
69
- properties.each {|attr,value| update_alarm(inheritited_alarm,attr,value)}
74
+ properties.each {|attr,value| update_object(inheritited_alarm,attr,value)}
70
75
  @alarms.push(inheritited_alarm)
71
76
  else
72
- logger.warn "Alarm '#{properties['Inherit']}' doesn't exists and cannot be inherited"
77
+ logger.warn "alarm '#{properties['Inherit']}' doesn't exists and cannot be inherited"
73
78
  end
74
79
  next
75
80
  end
76
81
 
77
- alarm = find_alarm(name)
82
+ alarms = find_alarms(name)
78
83
 
79
- if alarm.nil?
80
- if @resource.has_key?('Hosts')
81
- logger.warn("this resource doesn't support adding new alarms")
82
- next
84
+ if alarms.empty?
85
+ # if the alarm doesn't exist and it's not being inherited from another alarm create a new alarm
86
+ resources = @resource.has_key?('Hosts') ? @resource['Hosts'] : [@resource]
87
+ resources.each do |res|
88
+ alarm = Kernel.const_get("CfnGuardian::Models::#{self.class.to_s.split('::').last}Alarm").new(res)
89
+ properties.each {|attr,value| update_object(alarm,attr,value)}
90
+ alarm.name = name
91
+ logger.debug("created new alarm #{alarm.name} for resource #{alarm.resource_id} in resource group #{group}")
92
+ @alarms.push(alarm)
83
93
  end
84
- # if alarm doesn't exist create a new one
85
- alarm = Kernel.const_get("CfnGuardian::Models::#{self.class.to_s.split('::').last}Alarm").new(@resource)
86
- properties.each {|attr,value| update_alarm(alarm,attr,value)}
87
- alarm.name = name
88
- @alarms.push(alarm)
89
94
  else
90
95
  # if there is an existing alarm update the properties
91
- properties.each {|attr,value| update_alarm(alarm,attr,value)}
96
+ alarms.each do |alarm|
97
+ logger.debug("overriding #{alarm.name} alarm properties for resource #{alarm.resource_id} in resource group #{group} via alarm overrides")
98
+ properties.each {|attr,value| update_object(alarm,attr,value)}
99
+ end
92
100
  end
93
101
  end
94
102
 
@@ -96,6 +104,20 @@ module CfnGuardian::Resource
96
104
  @alarms.each {|a| a.group = @override_group}
97
105
  end
98
106
 
107
+ # String interpolation for alarm dimensions
108
+ @alarms.each do |alarm|
109
+ next if alarm.dimensions.nil?
110
+ alarm.dimensions.each do |k,v|
111
+ if v.match?(/^\${Resource::.*[A-Za-z]}$/)
112
+ resource_key = v.tr('${}', '').split('Resource::').last
113
+ if @resource.has_key?(resource_key)
114
+ logger.debug "overriding alarm #{alarm.name} dimension key '#{k}' with value '#{@resource[resource_key]}'"
115
+ alarm.dimensions[k] = @resource[resource_key]
116
+ end
117
+ end
118
+ end
119
+ end
120
+
99
121
  return @alarms.select{|a| a.enabled}
100
122
  end
101
123
 
@@ -128,6 +150,60 @@ module CfnGuardian::Resource
128
150
  default_metric_filters()
129
151
  return @metric_filters
130
152
  end
153
+
154
+ # Overidden by inheritted classes to define default checks
155
+ def default_event_subscriptions()
156
+ return @event_subscriptions
157
+ end
158
+
159
+ def get_event_subscriptions(group, overides)
160
+ # generate defailt event subscriptions
161
+ default_event_subscriptions()
162
+
163
+ # overide the defaults
164
+ overides.each do |name, properties|
165
+ event_subscription = find_event_subscriptions(name)
166
+
167
+ # disbable the event subscription if the value is false
168
+ if [false].include?(properties)
169
+ unless event_subscription.nil?
170
+ event_subscription.enabled = false
171
+ logger.info "Disabling event subscription #{name} for #{group} #{event_subscription.resource_id}"
172
+ end
173
+
174
+ next
175
+ end
176
+
177
+ # ignore all properties not in a proper format
178
+ next unless properties.is_a?(Hash)
179
+
180
+ # Create a new event subscription by inheriting an existing one
181
+ if properties.has_key?('Inherit')
182
+ inherit_event_subscription = find_event_subscriptions(properties['Inherit'])
183
+
184
+ if inherit_event_subscription.nil?
185
+ logger.warn "Unable to create #{topic} RDSEventSubscription by inheriting #{properties['Inherit']} as it cannot be found"
186
+ next
187
+ end
188
+
189
+ event_subscription = inherit_event_subscription.clone
190
+ event_subscription.enabled = true
191
+ event_subscription.name = name
192
+ @event_subscriptions.push(event_subscription)
193
+ logger.debug "Inheriting RDSEventSubscription #{properties['Inherit']}"
194
+ end
195
+
196
+ if event_subscription.nil?
197
+ event_subscription = Kernel.const_get("CfnGuardian::Models::#{self.class.to_s.split('::').last}EventSubscription").new(@resource)
198
+ event_subscription.name = name
199
+ @event_subscriptions.push(event_subscription)
200
+ end
201
+
202
+ properties.each {|attr,value| update_object(event_subscription,attr,value)}
203
+ end
204
+
205
+ return @event_subscriptions.select {|es| es.enabled }
206
+ end
131
207
 
132
208
  def get_cost()
133
209
  return @alarms.length * 0.10
@@ -138,13 +214,22 @@ module CfnGuardian::Resource
138
214
  def find_alarm(name)
139
215
  @alarms.detect {|alarm| alarm.name == name}
140
216
  end
141
-
142
- def update_alarm(alarm,attr,value)
217
+
218
+ def find_alarms(name)
219
+ @alarms.find_all {|alarm| alarm.name == name}
220
+ end
221
+
222
+ def find_event_subscriptions(name)
223
+ @event_subscriptions.detect {|es| es.name == name}
224
+ end
225
+
226
+ def update_object(obj,attr,value)
227
+ logger.debug("overriding #{obj.type} property '#{attr}' with value #{value} for resource id: #{obj.resource_id}")
143
228
  begin
144
- alarm.send("#{attr.to_underscore}=",value)
229
+ obj.send("#{attr.to_underscore}=",value.clone)
145
230
  rescue NoMethodError => e
146
231
  if !e.message.match?(/inherit/)
147
- logger.warn "Unknown key '#{attr}' for #{alarm.resource_id} alarm #{alarm.name}"
232
+ logger.warn "Unknown property '#{attr}' for type: #{obj.type} and resource id: #{obj.resource_id}"
148
233
  end
149
234
  end
150
235
  end