cfn-guardian 0.12.1 → 0.13.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.
- checksums.yaml +4 -4
- data/.github/workflows/build-gem.yml +3 -2
- data/Dockerfile +1 -1
- data/Gemfile.lock +63 -37
- data/cfn-guardian.gemspec +1 -0
- data/docs/custom_checks/http.md +1 -1
- data/docs/overview.md +2 -1
- data/docs/search_expressions.md +120 -0
- data/docs/variables.md +1 -1
- data/lib/cfnguardian/compile.rb +18 -3
- data/lib/cfnguardian/models/alarm.rb +5 -1
- data/lib/cfnguardian/resources/base.rb +21 -7
- data/lib/cfnguardian/stacks/resources.rb +27 -8
- data/lib/cfnguardian/version.rb +1 -1
- metadata +17 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: eb1ea2bebda2731175f9c469a64e461c7bceeb232edf05b2c4c7a93c5229a36a
|
|
4
|
+
data.tar.gz: a2a994f4e1fbc4a2c4f616c079c00ec4972bcf2ab7b52afc648fab15a51ad69e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f7fc64feea4f5e3e3742de29ea27395d77c51379adbc96b2329d659d81c5a306431396cccd5c8a8834307043e9d40fab1373da866340c516db2ec0acfa0987c1
|
|
7
|
+
data.tar.gz: e381d7b7d87ae20dc4af84f90243d8744c013e934e769764dc9fce9a8448fc8a8d6b5bad6ff8bda914aa2fc0a97063944ab342f982e83558e2c63052952eb412
|
data/Dockerfile
CHANGED
data/Gemfile.lock
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
cfn-guardian (0.
|
|
4
|
+
cfn-guardian (0.13.0)
|
|
5
5
|
aws-sdk-cloudformation (~> 1.76, < 2)
|
|
6
6
|
aws-sdk-cloudwatch (~> 1.72, < 2)
|
|
7
7
|
aws-sdk-codecommit (~> 1.53, < 2)
|
|
@@ -10,71 +10,97 @@ PATH
|
|
|
10
10
|
aws-sdk-rds (~> 1.174, < 2)
|
|
11
11
|
aws-sdk-s3 (~> 1.119, < 2)
|
|
12
12
|
cfndsl (~> 1.0, < 2)
|
|
13
|
-
rexml
|
|
13
|
+
rexml (= 3.3.0)
|
|
14
14
|
term-ansicolor (~> 1, < 2)
|
|
15
15
|
terminal-table (~> 1, < 2)
|
|
16
16
|
thor (~> 0.20)
|
|
17
|
+
tins (~> 1.42.0)
|
|
17
18
|
|
|
18
19
|
GEM
|
|
19
20
|
remote: https://rubygems.org/
|
|
20
21
|
specs:
|
|
21
|
-
aws-eventstream (1.
|
|
22
|
-
aws-partitions (1.
|
|
23
|
-
aws-sdk-cloudformation (1.
|
|
24
|
-
aws-sdk-core (~> 3, >= 3.
|
|
25
|
-
aws-sigv4 (~> 1.
|
|
26
|
-
aws-sdk-cloudwatch (1.
|
|
27
|
-
aws-sdk-core (~> 3, >= 3.
|
|
28
|
-
aws-sigv4 (~> 1.
|
|
29
|
-
aws-sdk-codecommit (1.
|
|
30
|
-
aws-sdk-core (~> 3, >= 3.
|
|
31
|
-
aws-sigv4 (~> 1.
|
|
32
|
-
aws-sdk-codepipeline (1.
|
|
33
|
-
aws-sdk-core (~> 3, >= 3.
|
|
34
|
-
aws-sigv4 (~> 1.1)
|
|
35
|
-
aws-sdk-core (3.171.0)
|
|
36
|
-
aws-eventstream (~> 1, >= 1.0.2)
|
|
37
|
-
aws-partitions (~> 1, >= 1.651.0)
|
|
22
|
+
aws-eventstream (1.4.0)
|
|
23
|
+
aws-partitions (1.1239.0)
|
|
24
|
+
aws-sdk-cloudformation (1.150.0)
|
|
25
|
+
aws-sdk-core (~> 3, >= 3.244.0)
|
|
26
|
+
aws-sigv4 (~> 1.5)
|
|
27
|
+
aws-sdk-cloudwatch (1.134.0)
|
|
28
|
+
aws-sdk-core (~> 3, >= 3.244.0)
|
|
29
|
+
aws-sigv4 (~> 1.5)
|
|
30
|
+
aws-sdk-codecommit (1.97.0)
|
|
31
|
+
aws-sdk-core (~> 3, >= 3.244.0)
|
|
32
|
+
aws-sigv4 (~> 1.5)
|
|
33
|
+
aws-sdk-codepipeline (1.113.0)
|
|
34
|
+
aws-sdk-core (~> 3, >= 3.244.0)
|
|
38
35
|
aws-sigv4 (~> 1.5)
|
|
36
|
+
aws-sdk-core (3.244.0)
|
|
37
|
+
aws-eventstream (~> 1, >= 1.3.0)
|
|
38
|
+
aws-partitions (~> 1, >= 1.992.0)
|
|
39
|
+
aws-sigv4 (~> 1.9)
|
|
40
|
+
base64
|
|
41
|
+
bigdecimal
|
|
39
42
|
jmespath (~> 1, >= 1.6.1)
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
aws-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
aws-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
aws-
|
|
49
|
-
|
|
50
|
-
|
|
43
|
+
logger
|
|
44
|
+
aws-sdk-ec2 (1.611.0)
|
|
45
|
+
aws-sdk-core (~> 3, >= 3.244.0)
|
|
46
|
+
aws-sigv4 (~> 1.5)
|
|
47
|
+
aws-sdk-kms (1.123.0)
|
|
48
|
+
aws-sdk-core (~> 3, >= 3.244.0)
|
|
49
|
+
aws-sigv4 (~> 1.5)
|
|
50
|
+
aws-sdk-rds (1.311.0)
|
|
51
|
+
aws-sdk-core (~> 3, >= 3.244.0)
|
|
52
|
+
aws-sigv4 (~> 1.5)
|
|
53
|
+
aws-sdk-s3 (1.219.0)
|
|
54
|
+
aws-sdk-core (~> 3, >= 3.244.0)
|
|
51
55
|
aws-sdk-kms (~> 1)
|
|
52
|
-
aws-sigv4 (~> 1.
|
|
53
|
-
aws-sigv4 (1.
|
|
56
|
+
aws-sigv4 (~> 1.5)
|
|
57
|
+
aws-sigv4 (1.12.1)
|
|
54
58
|
aws-eventstream (~> 1, >= 1.0.2)
|
|
55
|
-
|
|
59
|
+
base64 (0.3.0)
|
|
60
|
+
bigdecimal (4.1.1)
|
|
61
|
+
cfndsl (1.7.3)
|
|
56
62
|
hana (~> 1.3)
|
|
63
|
+
diff-lcs (1.6.2)
|
|
57
64
|
hana (1.3.7)
|
|
58
65
|
jmespath (1.6.2)
|
|
66
|
+
logger (1.7.0)
|
|
59
67
|
rake (13.0.6)
|
|
60
|
-
rexml (3.
|
|
68
|
+
rexml (3.3.0)
|
|
69
|
+
strscan
|
|
70
|
+
rspec (3.13.2)
|
|
71
|
+
rspec-core (~> 3.13.0)
|
|
72
|
+
rspec-expectations (~> 3.13.0)
|
|
73
|
+
rspec-mocks (~> 3.13.0)
|
|
74
|
+
rspec-core (3.13.6)
|
|
75
|
+
rspec-support (~> 3.13.0)
|
|
76
|
+
rspec-expectations (3.13.5)
|
|
77
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
|
78
|
+
rspec-support (~> 3.13.0)
|
|
79
|
+
rspec-mocks (3.13.8)
|
|
80
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
|
81
|
+
rspec-support (~> 3.13.0)
|
|
82
|
+
rspec-support (3.13.7)
|
|
83
|
+
strscan (3.1.8)
|
|
61
84
|
sync (0.5.0)
|
|
62
|
-
term-ansicolor (1.
|
|
63
|
-
tins (~> 1
|
|
85
|
+
term-ansicolor (1.11.3)
|
|
86
|
+
tins (~> 1)
|
|
64
87
|
terminal-table (1.8.0)
|
|
65
88
|
unicode-display_width (~> 1.1, >= 1.1.1)
|
|
66
89
|
thor (0.20.3)
|
|
67
|
-
tins (1.
|
|
90
|
+
tins (1.42.0)
|
|
91
|
+
bigdecimal
|
|
68
92
|
sync
|
|
69
93
|
unicode-display_width (1.8.0)
|
|
70
94
|
|
|
71
95
|
PLATFORMS
|
|
96
|
+
aarch64-linux
|
|
72
97
|
x86_64-darwin-21
|
|
73
98
|
|
|
74
99
|
DEPENDENCIES
|
|
75
100
|
bundler (~> 2.0)
|
|
76
101
|
cfn-guardian!
|
|
77
102
|
rake (~> 13.0)
|
|
103
|
+
rspec (~> 3.0)
|
|
78
104
|
|
|
79
105
|
BUNDLED WITH
|
|
80
106
|
2.3.19
|
data/cfn-guardian.gemspec
CHANGED
data/docs/custom_checks/http.md
CHANGED
|
@@ -42,7 +42,7 @@ Resources:
|
|
|
42
42
|
|
|
43
43
|
For health endpoints that require authenticated requests, Guardian can send HMAC-signed headers so your application can verify the request came from Guardian and resist replay attacks.
|
|
44
44
|
|
|
45
|
-
When enabled, the HTTP check Lambda (see [aws-lambda-http-check](https://github.com/
|
|
45
|
+
When enabled, the HTTP check Lambda (see [aws-lambda-http-check](https://github.com/base2services/aws-lambda-http-check)) adds these headers to each request:
|
|
46
46
|
|
|
47
47
|
| Header (default prefix `X-Health`) | Description |
|
|
48
48
|
|-----------------------------------|-------------|
|
data/docs/overview.md
CHANGED
|
@@ -22,4 +22,5 @@
|
|
|
22
22
|
8. [Composite Alarms](composite_alarms.md)
|
|
23
23
|
9. [Alarms for Custom Metrics](custom_metrics.md)
|
|
24
24
|
10. [Dimension Variables](variables.md)
|
|
25
|
-
11. [
|
|
25
|
+
11. [Search Expression Alarms](search_expressions.md)
|
|
26
|
+
12. [Alarm Tags](alarm_tags.md)
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# Search Expression Alarms
|
|
2
|
+
|
|
3
|
+
Search expression alarms use CloudWatch [SEARCH()](https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/search-expression-syntax.html) to dynamically match metrics instead of targeting a fixed set of dimensions. This is useful when the physical resource ID changes between deployments, such as Auto Scaling Groups that use replacement update policies in CloudFormation.
|
|
4
|
+
|
|
5
|
+
## The Problem
|
|
6
|
+
|
|
7
|
+
When a CloudFormation stack replaces an ASG on deployment, the physical ASG name changes (e.g. `my-app-AsgGroup-abc123` becomes `my-app-AsgGroup-xyz789`). Standard alarms use fixed dimensions that reference the exact ASG name, so they break after every deployment until Guardian is recompiled and redeployed with the new name.
|
|
8
|
+
|
|
9
|
+
## How It Works
|
|
10
|
+
|
|
11
|
+
Instead of emitting a CloudWatch alarm with fixed `Dimensions`, `MetricName`, `Namespace`, and `Statistic` properties, a search expression alarm emits the CloudFormation `Metrics` property (a list of `MetricDataQuery` objects) with:
|
|
12
|
+
|
|
13
|
+
1. A **SEARCH()** expression that dynamically matches metrics by partial or exact name
|
|
14
|
+
2. An **aggregation function** (e.g. `MAX`, `AVG`, `SUM`) that reduces the matched metrics to a single time series for threshold evaluation
|
|
15
|
+
|
|
16
|
+
## Configuration
|
|
17
|
+
|
|
18
|
+
Add `SearchExpression` and optionally `SearchAggregation` to an alarm template. When `SearchExpression` is set, the `Dimensions`, `MetricName`, `Namespace`, `Statistic`, and `Period` properties are not used since CloudWatch treats these as mutually exclusive with the alarm `Metrics` property.
|
|
19
|
+
|
|
20
|
+
### Properties
|
|
21
|
+
|
|
22
|
+
| Property | Required | Default | Description |
|
|
23
|
+
| --- | --- | --- | --- |
|
|
24
|
+
| `SearchExpression` | Yes | - | A CloudWatch SEARCH() expression string. Supports `${Resource::...}` [variables](variables.md). |
|
|
25
|
+
| `SearchAggregation` | No | `MAX` | Aggregation function applied to the search results. Valid values: `MAX`, `MIN`, `AVG`, `SUM`. |
|
|
26
|
+
|
|
27
|
+
### Overriding Default Alarms
|
|
28
|
+
|
|
29
|
+
You can convert existing default alarms to use search expressions by overriding them in the template:
|
|
30
|
+
|
|
31
|
+
```yaml
|
|
32
|
+
Resources:
|
|
33
|
+
AutoScalingGroup:
|
|
34
|
+
- Id: my-app-AsgGroup
|
|
35
|
+
|
|
36
|
+
Templates:
|
|
37
|
+
AutoScalingGroup:
|
|
38
|
+
CPUUtilizationHighBase:
|
|
39
|
+
SearchExpression: "SEARCH('{AWS/EC2,AutoScalingGroupName} MetricName=\"CPUUtilization\" \"${Resource::Id}\"', 'Minimum', 60)"
|
|
40
|
+
SearchAggregation: MAX
|
|
41
|
+
StatusCheckFailed:
|
|
42
|
+
SearchExpression: "SEARCH('{AWS/EC2,AutoScalingGroupName} MetricName=\"StatusCheckFailed\" \"${Resource::Id}\"', 'Maximum', 60)"
|
|
43
|
+
SearchAggregation: MAX
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
In this example the `Id` is the stable prefix of the ASG name. The double quotes around `\"${Resource::Id}\"` inside the SEARCH expression perform an exact substring match, so `my-app-AsgGroup-abc123` and `my-app-AsgGroup-xyz789` both match but unrelated ASGs do not.
|
|
47
|
+
|
|
48
|
+
### Creating New Alarms
|
|
49
|
+
|
|
50
|
+
You can also create new search expression alarms that don't override any defaults:
|
|
51
|
+
|
|
52
|
+
```yaml
|
|
53
|
+
Templates:
|
|
54
|
+
AutoScalingGroup:
|
|
55
|
+
NetworkOutHigh:
|
|
56
|
+
SearchExpression: "SEARCH('{AWS/EC2,AutoScalingGroupName} MetricName=\"NetworkOut\" \"${Resource::Id}\"', 'Average', 300)"
|
|
57
|
+
SearchAggregation: SUM
|
|
58
|
+
Threshold: 1000000000
|
|
59
|
+
ComparisonOperator: GreaterThanThreshold
|
|
60
|
+
EvaluationPeriods: 3
|
|
61
|
+
AlarmAction: Warning
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### Using With Other Resource Groups
|
|
65
|
+
|
|
66
|
+
Search expressions work with any resource group, not just AutoScalingGroup:
|
|
67
|
+
|
|
68
|
+
```yaml
|
|
69
|
+
Resources:
|
|
70
|
+
ECSService:
|
|
71
|
+
- Id: my-service
|
|
72
|
+
Cluster: my-cluster
|
|
73
|
+
|
|
74
|
+
Templates:
|
|
75
|
+
ECSService:
|
|
76
|
+
CPUUtilizationHigh:
|
|
77
|
+
SearchExpression: "SEARCH('{AWS/ECS,ServiceName,ClusterName} MetricName=\"CPUUtilization\" \"${Resource::Id}\"', 'Average', 60)"
|
|
78
|
+
SearchAggregation: MAX
|
|
79
|
+
Threshold: 90
|
|
80
|
+
EvaluationPeriods: 5
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Variables
|
|
84
|
+
|
|
85
|
+
`${Resource::...}` variables are interpolated inside search expressions the same way as in [dimension variables](variables.md). You can reference any key from the resource definition:
|
|
86
|
+
|
|
87
|
+
```yaml
|
|
88
|
+
Resources:
|
|
89
|
+
AutoScalingGroup:
|
|
90
|
+
- Id: my-app-AsgGroup
|
|
91
|
+
Environment: production
|
|
92
|
+
|
|
93
|
+
Templates:
|
|
94
|
+
AutoScalingGroup:
|
|
95
|
+
CPUUtilizationHighBase:
|
|
96
|
+
SearchExpression: "SEARCH('{AWS/EC2,AutoScalingGroupName} MetricName=\"CPUUtilization\" \"${Resource::Id}\"', 'Minimum', 60)"
|
|
97
|
+
SearchAggregation: MAX
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## CloudWatch SEARCH() Syntax Quick Reference
|
|
101
|
+
|
|
102
|
+
The general format is:
|
|
103
|
+
|
|
104
|
+
```
|
|
105
|
+
SEARCH('{Namespace,DimensionName} SearchTerm', 'Statistic', Period)
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
- **Partial match**: `my-app` matches any metric with a token `my` or `app` in any dimension value
|
|
109
|
+
- **Exact match**: `"my-app-AsgGroup"` matches only the exact substring `my-app-AsgGroup`
|
|
110
|
+
- **Boolean operators**: `AND`, `OR`, `NOT` can be used to combine terms
|
|
111
|
+
- **Property designators**: `MetricName="CPUUtilization"` restricts matching to the metric name
|
|
112
|
+
|
|
113
|
+
See the [CloudWatch search expression syntax documentation](https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/search-expression-syntax.html) for full details.
|
|
114
|
+
|
|
115
|
+
## Limitations
|
|
116
|
+
|
|
117
|
+
- **2-week lookback**: SEARCH() only finds metrics that have reported data within the last 2 weeks
|
|
118
|
+
- **100 metric limit**: A single SEARCH expression can match up to 100 time series
|
|
119
|
+
- **1024 character limit**: The search expression query string cannot exceed 1024 characters
|
|
120
|
+
- **Aggregation required**: Since SEARCH can return multiple time series, the aggregation function reduces them to a single series for threshold comparison
|
data/docs/variables.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
## Dimension Variables
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Variables can be used to reference resource group values such as the resource Id within the dimensions section of an alarm template. They are also supported inside [search expressions](search_expressions.md).
|
|
4
4
|
|
|
5
5
|
For example here we are creating an alarm for a disk usage metric for a group of EC2 instances.
|
|
6
6
|
|
data/lib/cfnguardian/compile.rb
CHANGED
|
@@ -190,9 +190,24 @@ module CfnGuardian
|
|
|
190
190
|
@resources.each do |resource|
|
|
191
191
|
case resource.type
|
|
192
192
|
when 'Alarm'
|
|
193
|
-
|
|
194
|
-
if resource.
|
|
195
|
-
@errors << "CfnGuardian::AlarmPropertyError - alarm #{resource.name} for resource #{resource.resource_id} has
|
|
193
|
+
if resource.search_expression
|
|
194
|
+
if !resource.search_expression.is_a?(String) || resource.search_expression.strip.empty?
|
|
195
|
+
@errors << "CfnGuardian::AlarmPropertyError - alarm #{resource.name} for resource #{resource.resource_id} has an invalid SearchExpression. Must be a non-empty string."
|
|
196
|
+
end
|
|
197
|
+
if resource.search_aggregation
|
|
198
|
+
valid_aggregations = %w(MAX MIN AVG SUM)
|
|
199
|
+
normalized = resource.search_aggregation.to_s.upcase
|
|
200
|
+
if valid_aggregations.include?(normalized)
|
|
201
|
+
resource.search_aggregation = normalized
|
|
202
|
+
else
|
|
203
|
+
@errors << "CfnGuardian::AlarmPropertyError - alarm #{resource.name} for resource #{resource.resource_id} has invalid SearchAggregation '#{resource.search_aggregation}'. Must be one of: #{valid_aggregations.join(', ')}."
|
|
204
|
+
end
|
|
205
|
+
end
|
|
206
|
+
else
|
|
207
|
+
%w(metric_name namespace).each do |property|
|
|
208
|
+
if resource.send(property).nil?
|
|
209
|
+
@errors << "CfnGuardian::AlarmPropertyError - alarm #{resource.name} for resource #{resource.resource_id} has nil value for property #{property.to_camelcase}. This could be due to incorrect spelling of a default alarm name or missing property #{property.to_camelcase} on a new alarm."
|
|
210
|
+
end
|
|
196
211
|
end
|
|
197
212
|
end
|
|
198
213
|
when 'Check'
|
|
@@ -31,7 +31,9 @@ module CfnGuardian
|
|
|
31
31
|
:unit,
|
|
32
32
|
:maintenance_groups,
|
|
33
33
|
:additional_notifiers,
|
|
34
|
-
:tags
|
|
34
|
+
:tags,
|
|
35
|
+
:search_expression,
|
|
36
|
+
:search_aggregation
|
|
35
37
|
|
|
36
38
|
def initialize(resource)
|
|
37
39
|
@type = 'Alarm'
|
|
@@ -60,6 +62,8 @@ module CfnGuardian
|
|
|
60
62
|
@maintenance_groups = []
|
|
61
63
|
@additional_notifiers = []
|
|
62
64
|
@tags = {}
|
|
65
|
+
@search_expression = nil
|
|
66
|
+
@search_aggregation = nil
|
|
63
67
|
end
|
|
64
68
|
|
|
65
69
|
def metric_name=(metric_name)
|
|
@@ -110,15 +110,29 @@ module CfnGuardian::Resource
|
|
|
110
110
|
@alarms.each {|a| a.group = @override_group}
|
|
111
111
|
end
|
|
112
112
|
|
|
113
|
-
# String interpolation for alarm dimensions
|
|
114
113
|
@alarms.each do |alarm|
|
|
115
|
-
|
|
116
|
-
alarm.dimensions.
|
|
117
|
-
|
|
118
|
-
|
|
114
|
+
# String interpolation for alarm dimensions
|
|
115
|
+
unless alarm.dimensions.nil?
|
|
116
|
+
alarm.dimensions.each do |k,v|
|
|
117
|
+
if v.is_a?(String) && v.match?(/^\${Resource::.*[A-Za-z]}$/)
|
|
118
|
+
resource_key = v.tr('${}', '').split('Resource::').last
|
|
119
|
+
if @resource.has_key?(resource_key)
|
|
120
|
+
logger.debug "overriding alarm #{alarm.name} dimension key '#{k}' with value '#{@resource[resource_key]}'"
|
|
121
|
+
alarm.dimensions[k] = @resource[resource_key]
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# String interpolation for search expressions
|
|
128
|
+
if alarm.search_expression.is_a?(String)
|
|
129
|
+
alarm.search_expression = alarm.search_expression.gsub(/\${Resource::([A-Za-z0-9_]+)}/) do
|
|
130
|
+
resource_key = Regexp.last_match(1)
|
|
119
131
|
if @resource.has_key?(resource_key)
|
|
120
|
-
logger.debug "
|
|
121
|
-
|
|
132
|
+
logger.debug "interpolating search_expression variable '#{resource_key}' with value '#{@resource[resource_key]}' for alarm #{alarm.name}"
|
|
133
|
+
@resource[resource_key]
|
|
134
|
+
else
|
|
135
|
+
"${Resource::#{resource_key}}"
|
|
122
136
|
end
|
|
123
137
|
end
|
|
124
138
|
end
|
|
@@ -33,6 +33,7 @@ module CfnGuardian
|
|
|
33
33
|
def add_alarm(alarm)
|
|
34
34
|
actions = alarm.alarm_action.kind_of?(Array) ? alarm.alarm_action.map{|action| Ref(action)} : [Ref(alarm.alarm_action)]
|
|
35
35
|
actions.concat alarm.maintenance_groups.map {|mg| Ref(mg)} if alarm.maintenance_groups.any?
|
|
36
|
+
use_search = alarm.search_expression.is_a?(String) && !alarm.search_expression.strip.empty?
|
|
36
37
|
|
|
37
38
|
@template.declare do
|
|
38
39
|
CloudWatch_Alarm("#{alarm.resource_hash}#{alarm.group}#{alarm.name.gsub(/[^0-9a-zA-Z]/i, '')}#{alarm.type}"[0..255]) do
|
|
@@ -40,20 +41,38 @@ module CfnGuardian
|
|
|
40
41
|
AlarmDescription "Guardian alarm #{alarm.name} for the resource #{alarm.resource_id} in alarm group #{alarm.group}"
|
|
41
42
|
AlarmName CfnGuardian::CloudWatch.get_alarm_name(alarm)
|
|
42
43
|
ComparisonOperator alarm.comparison_operator
|
|
43
|
-
Dimensions alarm.dimensions.map {|k,v| {Name: k, Value: v}} unless alarm.dimensions.nil?
|
|
44
44
|
EvaluationPeriods alarm.evaluation_periods
|
|
45
|
-
Statistic alarm.statistic if alarm.extended_statistic.nil?
|
|
46
|
-
Period alarm.period
|
|
47
45
|
Threshold alarm.threshold
|
|
48
|
-
MetricName alarm.metric_name
|
|
49
|
-
Namespace alarm.namespace
|
|
50
46
|
AlarmActions actions
|
|
51
47
|
OKActions actions unless alarm.ok_action_disabled
|
|
52
48
|
TreatMissingData alarm.treat_missing_data unless alarm.treat_missing_data.nil?
|
|
53
49
|
DatapointsToAlarm alarm.datapoints_to_alarm unless alarm.datapoints_to_alarm.nil?
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
50
|
+
|
|
51
|
+
if use_search
|
|
52
|
+
aggregation = alarm.search_aggregation || 'MAX'
|
|
53
|
+
Metrics [
|
|
54
|
+
{
|
|
55
|
+
Id: 'search_expression',
|
|
56
|
+
Expression: alarm.search_expression,
|
|
57
|
+
Period: alarm.period,
|
|
58
|
+
ReturnData: false
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
Id: 'aggregate',
|
|
62
|
+
Expression: "#{aggregation}(search_expression)",
|
|
63
|
+
ReturnData: true
|
|
64
|
+
}
|
|
65
|
+
]
|
|
66
|
+
else
|
|
67
|
+
Dimensions alarm.dimensions.map {|k,v| {Name: k, Value: v}} unless alarm.dimensions.nil?
|
|
68
|
+
Statistic alarm.statistic if alarm.extended_statistic.nil?
|
|
69
|
+
Period alarm.period
|
|
70
|
+
MetricName alarm.metric_name
|
|
71
|
+
Namespace alarm.namespace
|
|
72
|
+
ExtendedStatistic alarm.extended_statistic unless alarm.extended_statistic.nil?
|
|
73
|
+
EvaluateLowSampleCountPercentile alarm.evaluate_low_sample_count_percentile unless alarm.evaluate_low_sample_count_percentile.nil?
|
|
74
|
+
Unit alarm.unit unless alarm.unit.nil?
|
|
75
|
+
end
|
|
57
76
|
end
|
|
58
77
|
end
|
|
59
78
|
end
|
data/lib/cfnguardian/version.rb
CHANGED
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.
|
|
4
|
+
version: 0.13.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Guslington
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-
|
|
11
|
+
date: 2026-07-06 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: thor
|
|
@@ -280,6 +280,20 @@ dependencies:
|
|
|
280
280
|
- - "~>"
|
|
281
281
|
- !ruby/object:Gem::Version
|
|
282
282
|
version: '13.0'
|
|
283
|
+
- !ruby/object:Gem::Dependency
|
|
284
|
+
name: rspec
|
|
285
|
+
requirement: !ruby/object:Gem::Requirement
|
|
286
|
+
requirements:
|
|
287
|
+
- - "~>"
|
|
288
|
+
- !ruby/object:Gem::Version
|
|
289
|
+
version: '3.0'
|
|
290
|
+
type: :development
|
|
291
|
+
prerelease: false
|
|
292
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
293
|
+
requirements:
|
|
294
|
+
- - "~>"
|
|
295
|
+
- !ruby/object:Gem::Version
|
|
296
|
+
version: '3.0'
|
|
283
297
|
description: Manages AWS cloudwatch alarms with default templates using cloudformation
|
|
284
298
|
email:
|
|
285
299
|
- itsupport@base2services.com
|
|
@@ -323,6 +337,7 @@ files:
|
|
|
323
337
|
- docs/notifiers.md
|
|
324
338
|
- docs/overview.md
|
|
325
339
|
- docs/resources.md
|
|
340
|
+
- docs/search_expressions.md
|
|
326
341
|
- docs/variables.md
|
|
327
342
|
- exe/cfn-guardian
|
|
328
343
|
- lib/cfnguardian.rb
|