Skip to content

Commit f274be8

Browse files
committed
Address second round of PR review feedback
1 parent efb4049 commit f274be8

File tree

3 files changed

+104
-15
lines changed

3 files changed

+104
-15
lines changed

docs/search_expressions.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ Add `SearchExpression` and optionally `SearchAggregation` to an alarm template.
2020
### Properties
2121

2222
| Property | Required | Default | Description |
23-
|---|---|---|---|
23+
| --- | --- | --- | --- |
2424
| `SearchExpression` | Yes | - | A CloudWatch SEARCH() expression string. Supports `${Resource::...}` [variables](variables.md). |
2525
| `SearchAggregation` | No | `MAX` | Aggregation function applied to the search results. Valid values: `MAX`, `MIN`, `AVG`, `SUM`. |
2626

lib/cfnguardian/stacks/resources.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ def build_template(resources)
3333
def add_alarm(alarm)
3434
actions = alarm.alarm_action.kind_of?(Array) ? alarm.alarm_action.map{|action| Ref(action)} : [Ref(alarm.alarm_action)]
3535
actions.concat alarm.maintenance_groups.map {|mg| Ref(mg)} if alarm.maintenance_groups.any?
36-
use_search = alarm.search_expression ? true : false
36+
use_search = alarm.search_expression.is_a?(String) && !alarm.search_expression.strip.empty?
3737

3838
@template.declare do
3939
CloudWatch_Alarm("#{alarm.resource_hash}#{alarm.group}#{alarm.name.gsub(/[^0-9a-zA-Z]/i, '')}#{alarm.type}"[0..255]) do

spec/search_expression_spec.rb

Lines changed: 102 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
require 'spec_helper'
22
require 'json'
33
require 'yaml'
4-
require 'fileutils'
54
require 'tmpdir'
65
require 'term/ansicolor'
76
require 'cfnguardian/log'
@@ -181,31 +180,121 @@
181180
end
182181

183182
describe 'Validation' do
183+
def compile_config(config)
184+
Dir.mktmpdir do |tmpdir|
185+
fixture = File.join(tmpdir, 'test_alarms.yaml')
186+
File.write(fixture, config.to_yaml)
187+
compile = CfnGuardian::Compile.new(fixture, false)
188+
compile.get_resources
189+
compile
190+
end
191+
end
192+
184193
context 'when search expression alarm has no metric_name or namespace' do
185194
it 'does not raise validation errors' do
186-
Dir.mktmpdir do |tmpdir|
187-
fixture = File.join(tmpdir, 'search_expression_alarms.yaml')
188-
File.write(fixture, {
195+
result = compile_config({
196+
'Resources' => {
197+
'AutoScalingGroup' => [{ 'Id' => 'my-app-AsgGroup-abc123' }]
198+
},
199+
'Templates' => {
200+
'AutoScalingGroup' => {
201+
'CPUUtilizationHighBase' => {
202+
'SearchExpression' => "SEARCH('{AWS/EC2,AutoScalingGroupName} MetricName=\"CPUUtilization\" my-app', 'Minimum', 60)",
203+
'SearchAggregation' => 'MAX'
204+
},
205+
'StatusCheckFailed' => false
206+
}
207+
}
208+
})
209+
210+
search_alarms = result.alarms.select { |a| a.search_expression }
211+
expect(search_alarms.length).to eq(1)
212+
expect(search_alarms.first.search_expression).to include('SEARCH')
213+
end
214+
end
215+
216+
context 'when search expression is blank' do
217+
it 'raises a validation error' do
218+
expect {
219+
compile_config({
189220
'Resources' => {
190221
'AutoScalingGroup' => [{ 'Id' => 'my-app-AsgGroup-abc123' }]
191222
},
192223
'Templates' => {
193224
'AutoScalingGroup' => {
194225
'CPUUtilizationHighBase' => {
195-
'SearchExpression' => "SEARCH('{AWS/EC2,AutoScalingGroupName} MetricName=\"CPUUtilization\" my-app', 'Minimum', 60)",
226+
'SearchExpression' => ' ',
227+
'SearchAggregation' => 'MAX'
228+
},
229+
'StatusCheckFailed' => false
230+
}
231+
}
232+
})
233+
}.to raise_error(CfnGuardian::ValidationError, /invalid SearchExpression/)
234+
end
235+
end
236+
237+
context 'when search expression is not a string' do
238+
it 'raises a validation error' do
239+
expect {
240+
compile_config({
241+
'Resources' => {
242+
'AutoScalingGroup' => [{ 'Id' => 'my-app-AsgGroup-abc123' }]
243+
},
244+
'Templates' => {
245+
'AutoScalingGroup' => {
246+
'CPUUtilizationHighBase' => {
247+
'SearchExpression' => ['not', 'a', 'string'],
196248
'SearchAggregation' => 'MAX'
197249
},
198250
'StatusCheckFailed' => false
199251
}
200252
}
201-
}.to_yaml)
202-
203-
compile = CfnGuardian::Compile.new(fixture, false)
204-
compile.get_resources
205-
search_alarms = compile.alarms.select { |a| a.search_expression }
206-
expect(search_alarms.length).to eq(1)
207-
expect(search_alarms.first.search_expression).to include('SEARCH')
208-
end
253+
})
254+
}.to raise_error(CfnGuardian::ValidationError, /invalid SearchExpression/)
255+
end
256+
end
257+
258+
context 'when search aggregation is invalid' do
259+
it 'raises a validation error' do
260+
expect {
261+
compile_config({
262+
'Resources' => {
263+
'AutoScalingGroup' => [{ 'Id' => 'my-app-AsgGroup-abc123' }]
264+
},
265+
'Templates' => {
266+
'AutoScalingGroup' => {
267+
'CPUUtilizationHighBase' => {
268+
'SearchExpression' => "SEARCH('{AWS/EC2,AutoScalingGroupName} MetricName=\"CPUUtilization\" my-app', 'Minimum', 60)",
269+
'SearchAggregation' => 'MEDIAN'
270+
},
271+
'StatusCheckFailed' => false
272+
}
273+
}
274+
})
275+
}.to raise_error(CfnGuardian::ValidationError, /invalid SearchAggregation/)
276+
end
277+
end
278+
279+
context 'when search aggregation is lowercase' do
280+
it 'normalizes to uppercase' do
281+
result = compile_config({
282+
'Resources' => {
283+
'AutoScalingGroup' => [{ 'Id' => 'my-app-AsgGroup-abc123' }]
284+
},
285+
'Templates' => {
286+
'AutoScalingGroup' => {
287+
'CPUUtilizationHighBase' => {
288+
'SearchExpression' => "SEARCH('{AWS/EC2,AutoScalingGroupName} MetricName=\"CPUUtilization\" my-app', 'Minimum', 60)",
289+
'SearchAggregation' => 'avg'
290+
},
291+
'StatusCheckFailed' => false
292+
}
293+
}
294+
})
295+
296+
search_alarms = result.alarms.select { |a| a.search_expression }
297+
expect(search_alarms.first.search_aggregation).to eq('AVG')
209298
end
210299
end
211300
end

0 commit comments

Comments
 (0)