Skip to content

Commit 252b4e1

Browse files
authored
feat: AWS Cloudwatch integration (#9)
* Adds the AWS Cloudwatch integration
1 parent 5ccff05 commit 252b4e1

10 files changed

Lines changed: 232 additions & 0 deletions

File tree

Gemfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ end
1818
gem "pg", "~> 1.3"
1919

2020
# integration dependencies
21+
gem "aws-sdk-sns", "~> 1.53"
2122
gem "deferred_request", "~> 1.0"
2223
gem "httparty", "~> 0.20.0"
2324
gem "sanitize", "~> 6.0"

Gemfile.lock

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,18 @@ GEM
7979
rake
8080
thor (>= 0.14.0)
8181
ast (2.4.2)
82+
aws-eventstream (1.2.0)
83+
aws-partitions (1.559.0)
84+
aws-sdk-core (3.127.0)
85+
aws-eventstream (~> 1, >= 1.0.2)
86+
aws-partitions (~> 1, >= 1.525.0)
87+
aws-sigv4 (~> 1.1)
88+
jmespath (~> 1.0)
89+
aws-sdk-sns (1.53.0)
90+
aws-sdk-core (~> 3, >= 3.127.0)
91+
aws-sigv4 (~> 1.1)
92+
aws-sigv4 (1.4.0)
93+
aws-eventstream (~> 1, >= 1.0.2)
8294
builder (3.2.4)
8395
concurrent-ruby (1.1.9)
8496
crack (0.4.5)
@@ -130,6 +142,7 @@ GEM
130142
io-wait (0.2.1)
131143
irb (1.4.1)
132144
reline (>= 0.3.0)
145+
jmespath (1.6.0)
133146
jwt (2.3.0)
134147
loofah (2.14.0)
135148
crass (~> 1.0.2)
@@ -264,6 +277,7 @@ PLATFORMS
264277

265278
DEPENDENCIES
266279
appraisal (~> 2.4)
280+
aws-sdk-sns (~> 1.53)
267281
debug (>= 1.0.0)
268282
deferred_request (~> 1.0)
269283
dotenv-rails (~> 2.7)
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
module PagerTree::Integrations
2+
class AwsCloudwatch::V3 < Integration
3+
OPTIONS = []
4+
store_accessor :options, *OPTIONS.map { |x| x[:key] }.map(&:to_s), prefix: "option"
5+
6+
# TODO: Does this integration support incoming requests?
7+
def adapter_supports_incoming?
8+
true
9+
end
10+
11+
def adapter_incoming_can_defer?
12+
true
13+
end
14+
15+
def adapter_should_block_incoming?
16+
!Aws::SNS::MessageVerifier.new.authentic?(adapter_incoming_request_params)
17+
end
18+
19+
def adapter_thirdparty_id
20+
_thirdparty_id
21+
end
22+
23+
def adapter_action
24+
if _is_create?
25+
:create
26+
elsif _is_resolve?
27+
:resolve
28+
else
29+
:other
30+
end
31+
end
32+
33+
# TODO: Implement your transform
34+
def adapter_process_create
35+
Alert.new(
36+
title: _title,
37+
description: _description,
38+
thirdparty_id: _thirdparty_id,
39+
dedup_keys: [_thirdparty_id],
40+
additional_data: _additional_datums
41+
)
42+
end
43+
44+
def adapter_process_other
45+
if _type == "SubscriptionConfirmation" || _type == "UnsubscribeConfirmation"
46+
url = adapter_incoming_request_params.dig("SubscribeURL")
47+
HTTParty.get(url) if url
48+
end
49+
end
50+
51+
private
52+
53+
def _type
54+
@_type ||= adapter_incoming_request_params.dig("Type")
55+
end
56+
57+
def _message
58+
@_message ||= adapter_incoming_request_params.dig("Message")
59+
end
60+
61+
def _json
62+
@_json ||= {} if _type != "Notification" || _message.blank?
63+
@_json ||= begin
64+
JSON.parse(_message).with_indifferent_access
65+
rescue
66+
{}
67+
end
68+
end
69+
70+
def _thirdparty_id
71+
[
72+
adapter_incoming_request_params.dig("TopicArn"),
73+
_json.dig("AlarmName")
74+
].join(":")
75+
end
76+
77+
def _is_create?
78+
_new_state == "ALARM" && (_old_state == "INSUFFICIENT_DATA" || _old_state == "OK")
79+
end
80+
81+
def _is_resolve?
82+
_new_state == "OK" && (_old_state == "INSUFFICIENT_DATA" || _old_state == "ALARM")
83+
end
84+
85+
def _new_state
86+
_json.dig("NewStateValue")
87+
end
88+
89+
def _old_state
90+
_json.dig("OldStateValue")
91+
end
92+
93+
def _title
94+
_json.dig("AlarmName")
95+
end
96+
97+
def _description
98+
_json.dig("NewStateReason")
99+
end
100+
101+
def _additional_datums
102+
[
103+
AdditionalDatum.new(format: "text", label: "AWS Account ID", value: _json.dig("AWSAccountId")),
104+
AdditionalDatum.new(format: "text", label: "Region", value: _json.dig("Region")),
105+
AdditionalDatum.new(format: "text", label: "Alarm ARN", value: _json.dig("AlarmArn")),
106+
AdditionalDatum.new(format: "datetime", label: "State Change Time", value: _json.dig("StateChangeTime"))
107+
]
108+
end
109+
end
110+
end

app/models/pager_tree/integrations/integration.rb

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,11 @@ def adapter_supports_incoming?
3434
false
3535
end
3636

37+
# a function to determine if we should block the incoming request (e.g. if the payload doesn't match a signature)
38+
def adapter_should_block_incoming?
39+
false
40+
end
41+
3742
# A unique identifier for this integration/alert
3843
def adapter_thirdparty_id
3944
ULID.generate

app/views/pager_tree/integrations/aws_cloudwatch/v3/_form_options.html.erb

Whitespace-only changes.

app/views/pager_tree/integrations/aws_cloudwatch/v3/_show_options.html.erb

Whitespace-only changes.

gemfiles/rails_7.gemfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ source "https://rubygems.org"
55
gem "sprockets-rails"
66
gem "appraisal", "~> 2.4"
77
gem "pg", "~> 1.3"
8+
gem "aws-sdk-sns", "~> 1.53"
89
gem "deferred_request", "~> 1.0"
910
gem "httparty", "~> 0.20.0"
1011
gem "sanitize", "~> 6.0"

gemfiles/rails_master.gemfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ source "https://rubygems.org"
55
gem "sprockets-rails"
66
gem "appraisal", "~> 2.4"
77
gem "pg", "~> 1.3"
8+
gem "aws-sdk-sns", "~> 1.53"
89
gem "deferred_request", "~> 1.0"
910
gem "httparty", "~> 0.20.0"
1011
gem "sanitize", "~> 6.0"

test/fixtures/pager_tree/integrations/integrations.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@ app_dynamics_v3:
88
type: "PagerTree::Integrations::AppDynamics::V3"
99
# options: no_options
1010

11+
aws_cloudwatch_v3:
12+
type: "PagerTree::Integrations::AwsCloudwatch::V3"
13+
# options: no_options
14+
1115
outgoing_webhook_v3:
1216
type: "PagerTree::Integrations::OutgoingWebhook::V3"
1317
options:
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
require "test_helper"
2+
3+
module PagerTree::Integrations
4+
class AwsCloudwatch::V3Test < ActiveSupport::TestCase
5+
include Integrateable
6+
7+
setup do
8+
@integration = pager_tree_integrations_integrations(:aws_cloudwatch_v3)
9+
10+
@create_request = {
11+
Type: "Notification",
12+
MessageId: "836fe1ec-79e0-56b8-827f-b66569e37e11",
13+
TopicArn: "arn:aws:sns:us-east-1:498849832712:update-cherwell-cmdb",
14+
Message: "{\r\n \"AlarmName\":\"Saffron-Octopus-RDS\",\r\n \"AlarmDescription\":null,\r\n \"AWSAccountId\":\"498849832712\",\r\n \"NewStateValue\":\"ALARM\",\r\n \"NewStateReason\":\"Threshold Crossed: 1 datapoint [2.1533759377604764 (20\/07\/20 21:07:00)] was greater than or equal to the threshold (0.0175).\",\r\n \"StateChangeTime\":\"2020-07-20T21:12:01.544+0000\",\r\n \"Region\":\"US East (N. Virginia)\",\r\n \"AlarmArn\":\"arn:aws:cloudwatch:us-east-1:498849832712:alarm:Saffron-Octopus-RDS\",\r\n \"OldStateValue\":\"INSUFFICIENT_DATA\",\r\n \"Trigger\":{\r\n \"MetricName\":\"CPUUtilization\",\r\n \"Namespace\":\"AWS\/RDS\",\r\n \"StatisticType\":\"Statistic\",\r\n \"Statistic\":\"AVERAGE\",\r\n \"Unit\":null,\r\n \"Dimensions\":[\r\n {\r\n \"value\":\"sm16lm1jrrjf0rk\",\r\n \"name\":\"DBInstanceIdentifier\"\r\n }\r\n ],\r\n \"Period\":300,\r\n \"EvaluationPeriods\":1,\r\n \"ComparisonOperator\":\"GreaterThanOrEqualToThreshold\",\r\n \"Threshold\":0.0175,\r\n \"TreatMissingData\":\"\",\r\n \"EvaluateLowSampleCountPercentile\":\"\"\r\n }\r\n}",
15+
Timestamp: "2020-07-15T14:08:03.824Z",
16+
SignatureVersion: "1",
17+
Signature: "JNdxahPfT0tVsX8+ZVPeA23M09UcCbIQ8uar5AZ4VqscGhzqpMcy4v00mluwr3eyJuFsogxhv1RprFIHU0ZH4bNRWxDpzdVnFIGVSnSBZDVi075ynf+oxagTLhSs7aa9Aar38RcQicaYBc6kHiCg5FHIwwU1OXeehVjHavFKC1ymSegaxtD2pUG4jST30gC2P55I+qyFItPOj+Ih8ZqRBXc3H989mwDKU0Qa54/lQ0cFMC8YwZcQzqwSoZQwIvsrCzLjNR7l2IIEq4pk9d2thq9C/tySFNlXd4/HP/Vd6I9wuP08c0nspmmWxQY1X7CQOvwKway7V9WmKVpku3avxQ==",
18+
SigningCertURL: "https://sns.us-east-1.amazonaws.com/SimpleNotificationService-a86cb10b4e1f29c941702d737128f7b6.pem",
19+
UnsubscribeURL: "https://sns.us-east-1.amazonaws.com/?Action=Unsubscribe&SubscriptionArn=arn:aws:sns:us-east-1:498849832712:update-cherwell-cmdb:e0cff011-7a6a-4425-9c0c-e812474debe5"
20+
}.with_indifferent_access
21+
22+
@resolve_request = {
23+
Type: "Notification",
24+
MessageId: "836fe1ec-79e0-56b8-827f-b66569e37e11",
25+
TopicArn: "arn:aws:sns:us-east-1:498849832712:update-cherwell-cmdb",
26+
Message: "{\r\n \"AlarmName\":\"Saffron-Octopus-RDS\",\r\n \"AlarmDescription\":null,\r\n \"AWSAccountId\":\"498849832712\",\r\n \"NewStateValue\":\"OK\",\r\n \"NewStateReason\":\"Threshold Crossed: 1 datapoint [2.1533759377604764 (20\/07\/20 21:07:00)] was greater than or equal to the threshold (0.0175).\",\r\n \"StateChangeTime\":\"2020-07-20T21:12:01.544+0000\",\r\n \"Region\":\"US East (N. Virginia)\",\r\n \"AlarmArn\":\"arn:aws:cloudwatch:us-east-1:498849832712:alarm:Saffron-Octopus-RDS\",\r\n \"OldStateValue\":\"INSUFFICIENT_DATA\",\r\n \"Trigger\":{\r\n \"MetricName\":\"CPUUtilization\",\r\n \"Namespace\":\"AWS\/RDS\",\r\n \"StatisticType\":\"Statistic\",\r\n \"Statistic\":\"AVERAGE\",\r\n \"Unit\":null,\r\n \"Dimensions\":[\r\n {\r\n \"value\":\"sm16lm1jrrjf0rk\",\r\n \"name\":\"DBInstanceIdentifier\"\r\n }\r\n ],\r\n \"Period\":300,\r\n \"EvaluationPeriods\":1,\r\n \"ComparisonOperator\":\"GreaterThanOrEqualToThreshold\",\r\n \"Threshold\":0.0175,\r\n \"TreatMissingData\":\"\",\r\n \"EvaluateLowSampleCountPercentile\":\"\"\r\n }\r\n}",
27+
Timestamp: "2020-07-15T14:08:03.824Z",
28+
SignatureVersion: "1",
29+
Signature: "JNdxahPfT0tVsX8+ZVPeA23M09UcCbIQ8uar5AZ4VqscGhzqpMcy4v00mluwr3eyJuFsogxhv1RprFIHU0ZH4bNRWxDpzdVnFIGVSnSBZDVi075ynf+oxagTLhSs7aa9Aar38RcQicaYBc6kHiCg5FHIwwU1OXeehVjHavFKC1ymSegaxtD2pUG4jST30gC2P55I+qyFItPOj+Ih8ZqRBXc3H989mwDKU0Qa54/lQ0cFMC8YwZcQzqwSoZQwIvsrCzLjNR7l2IIEq4pk9d2thq9C/tySFNlXd4/HP/Vd6I9wuP08c0nspmmWxQY1X7CQOvwKway7V9WmKVpku3avxQ==",
30+
SigningCertURL: "https://sns.us-east-1.amazonaws.com/SimpleNotificationService-a86cb10b4e1f29c941702d737128f7b6.pem",
31+
UnsubscribeURL: "https://sns.us-east-1.amazonaws.com/?Action=Unsubscribe&SubscriptionArn=arn:aws:sns:us-east-1:498849832712:update-cherwell-cmdb:e0cff011-7a6a-4425-9c0c-e812474debe5"
32+
}.with_indifferent_access
33+
34+
@other_request = {
35+
Type: "Notification",
36+
MessageId: "836fe1ec-79e0-56b8-827f-b66569e37e11",
37+
TopicArn: "arn:aws:sns:us-east-1:498849832712:update-cherwell-cmdb",
38+
Message: "{\r\n \"AlarmName\":\"Saffron-Octopus-RDS\",\r\n \"AlarmDescription\":null,\r\n \"AWSAccountId\":\"498849832712\",\r\n \"NewStateValue\":\"UNKNOW\",\r\n \"NewStateReason\":\"Threshold Crossed: 1 datapoint [2.1533759377604764 (20\/07\/20 21:07:00)] was greater than or equal to the threshold (0.0175).\",\r\n \"StateChangeTime\":\"2020-07-20T21:12:01.544+0000\",\r\n \"Region\":\"US East (N. Virginia)\",\r\n \"AlarmArn\":\"arn:aws:cloudwatch:us-east-1:498849832712:alarm:Saffron-Octopus-RDS\",\r\n \"OldStateValue\":\"INSUFFICIENT_DATA\",\r\n \"Trigger\":{\r\n \"MetricName\":\"CPUUtilization\",\r\n \"Namespace\":\"AWS\/RDS\",\r\n \"StatisticType\":\"Statistic\",\r\n \"Statistic\":\"AVERAGE\",\r\n \"Unit\":null,\r\n \"Dimensions\":[\r\n {\r\n \"value\":\"sm16lm1jrrjf0rk\",\r\n \"name\":\"DBInstanceIdentifier\"\r\n }\r\n ],\r\n \"Period\":300,\r\n \"EvaluationPeriods\":1,\r\n \"ComparisonOperator\":\"GreaterThanOrEqualToThreshold\",\r\n \"Threshold\":0.0175,\r\n \"TreatMissingData\":\"\",\r\n \"EvaluateLowSampleCountPercentile\":\"\"\r\n }\r\n}",
39+
Timestamp: "2020-07-15T14:08:03.824Z",
40+
SignatureVersion: "1",
41+
Signature: "JNdxahPfT0tVsX8+ZVPeA23M09UcCbIQ8uar5AZ4VqscGhzqpMcy4v00mluwr3eyJuFsogxhv1RprFIHU0ZH4bNRWxDpzdVnFIGVSnSBZDVi075ynf+oxagTLhSs7aa9Aar38RcQicaYBc6kHiCg5FHIwwU1OXeehVjHavFKC1ymSegaxtD2pUG4jST30gC2P55I+qyFItPOj+Ih8ZqRBXc3H989mwDKU0Qa54/lQ0cFMC8YwZcQzqwSoZQwIvsrCzLjNR7l2IIEq4pk9d2thq9C/tySFNlXd4/HP/Vd6I9wuP08c0nspmmWxQY1X7CQOvwKway7V9WmKVpku3avxQ==",
42+
SigningCertURL: "https://sns.us-east-1.amazonaws.com/SimpleNotificationService-a86cb10b4e1f29c941702d737128f7b6.pem",
43+
UnsubscribeURL: "https://sns.us-east-1.amazonaws.com/?Action=Unsubscribe&SubscriptionArn=arn:aws:sns:us-east-1:498849832712:update-cherwell-cmdb:e0cff011-7a6a-4425-9c0c-e812474debe5"
44+
}.with_indifferent_access
45+
end
46+
47+
test "sanity" do
48+
assert @integration.adapter_supports_incoming?
49+
assert @integration.adapter_incoming_can_defer?
50+
assert_not @integration.adapter_supports_outgoing?
51+
assert @integration.adapter_show_alerts?
52+
assert @integration.adapter_show_logs?
53+
assert_not @integration.adapter_show_outgoing_webhook_delivery?
54+
end
55+
56+
test "adapter_actions create" do
57+
@integration.adapter_incoming_request_params = @create_request
58+
assert_equal :create, @integration.adapter_action
59+
end
60+
61+
test "adapter_actions resolve" do
62+
@integration.adapter_incoming_request_params = @resolve_request
63+
assert_equal :resolve, @integration.adapter_action
64+
end
65+
66+
test "adapter_actions other" do
67+
@integration.adapter_incoming_request_params = @other_request
68+
assert_equal :other, @integration.adapter_action
69+
end
70+
71+
test "adapter_thirdparty_id" do
72+
@integration.adapter_incoming_request_params = @create_request
73+
assert_equal "arn:aws:sns:us-east-1:498849832712:update-cherwell-cmdb:Saffron-Octopus-RDS", @integration.adapter_thirdparty_id
74+
end
75+
76+
test "adapter_process_create" do
77+
@integration.adapter_incoming_request_params = @create_request
78+
79+
true_alert = Alert.new(
80+
title: "Saffron-Octopus-RDS",
81+
description: "Threshold Crossed: 1 datapoint [2.1533759377604764 (20/07/20 21:07:00)] was greater than or equal to the threshold (0.0175).",
82+
urgency: nil,
83+
thirdparty_id: "arn:aws:sns:us-east-1:498849832712:update-cherwell-cmdb:Saffron-Octopus-RDS",
84+
dedup_keys: ["arn:aws:sns:us-east-1:498849832712:update-cherwell-cmdb:Saffron-Octopus-RDS"],
85+
additional_data: [
86+
AdditionalDatum.new(format: "text", label: "AWS Account ID", value: "498849832712"),
87+
AdditionalDatum.new(format: "text", label: "Region", value: "US East (N. Virginia)"),
88+
AdditionalDatum.new(format: "text", label: "Alarm ARN", value: "arn:aws:cloudwatch:us-east-1:498849832712:alarm:Saffron-Octopus-RDS"),
89+
AdditionalDatum.new(format: "datetime", label: "State Change Time", value: "2020-07-20T21:12:01.544+0000")
90+
]
91+
)
92+
93+
assert_equal true_alert.to_json, @integration.adapter_process_create.to_json
94+
end
95+
end
96+
end

0 commit comments

Comments
 (0)