Skip to content

Commit

Permalink
feat: AWS Cloudwatch integration (#9)
Browse files Browse the repository at this point in the history
* Adds the AWS Cloudwatch integration
  • Loading branch information
armiiller authored Feb 28, 2022
1 parent 5ccff05 commit 252b4e1
Show file tree
Hide file tree
Showing 10 changed files with 232 additions and 0 deletions.
1 change: 1 addition & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ end
gem "pg", "~> 1.3"

# integration dependencies
gem "aws-sdk-sns", "~> 1.53"
gem "deferred_request", "~> 1.0"
gem "httparty", "~> 0.20.0"
gem "sanitize", "~> 6.0"
Expand Down
14 changes: 14 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,18 @@ GEM
rake
thor (>= 0.14.0)
ast (2.4.2)
aws-eventstream (1.2.0)
aws-partitions (1.559.0)
aws-sdk-core (3.127.0)
aws-eventstream (~> 1, >= 1.0.2)
aws-partitions (~> 1, >= 1.525.0)
aws-sigv4 (~> 1.1)
jmespath (~> 1.0)
aws-sdk-sns (1.53.0)
aws-sdk-core (~> 3, >= 3.127.0)
aws-sigv4 (~> 1.1)
aws-sigv4 (1.4.0)
aws-eventstream (~> 1, >= 1.0.2)
builder (3.2.4)
concurrent-ruby (1.1.9)
crack (0.4.5)
Expand Down Expand Up @@ -130,6 +142,7 @@ GEM
io-wait (0.2.1)
irb (1.4.1)
reline (>= 0.3.0)
jmespath (1.6.0)
jwt (2.3.0)
loofah (2.14.0)
crass (~> 1.0.2)
Expand Down Expand Up @@ -264,6 +277,7 @@ PLATFORMS

DEPENDENCIES
appraisal (~> 2.4)
aws-sdk-sns (~> 1.53)
debug (>= 1.0.0)
deferred_request (~> 1.0)
dotenv-rails (~> 2.7)
Expand Down
110 changes: 110 additions & 0 deletions app/models/pager_tree/integrations/aws_cloudwatch/v3.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
module PagerTree::Integrations
class AwsCloudwatch::V3 < Integration
OPTIONS = []
store_accessor :options, *OPTIONS.map { |x| x[:key] }.map(&:to_s), prefix: "option"

# TODO: Does this integration support incoming requests?
def adapter_supports_incoming?
true
end

def adapter_incoming_can_defer?
true
end

def adapter_should_block_incoming?
!Aws::SNS::MessageVerifier.new.authentic?(adapter_incoming_request_params)
end

def adapter_thirdparty_id
_thirdparty_id
end

def adapter_action
if _is_create?
:create
elsif _is_resolve?
:resolve
else
:other
end
end

# TODO: Implement your transform
def adapter_process_create
Alert.new(
title: _title,
description: _description,
thirdparty_id: _thirdparty_id,
dedup_keys: [_thirdparty_id],
additional_data: _additional_datums
)
end

def adapter_process_other
if _type == "SubscriptionConfirmation" || _type == "UnsubscribeConfirmation"
url = adapter_incoming_request_params.dig("SubscribeURL")
HTTParty.get(url) if url
end
end

private

def _type
@_type ||= adapter_incoming_request_params.dig("Type")
end

def _message
@_message ||= adapter_incoming_request_params.dig("Message")
end

def _json
@_json ||= {} if _type != "Notification" || _message.blank?
@_json ||= begin
JSON.parse(_message).with_indifferent_access
rescue
{}
end
end

def _thirdparty_id
[
adapter_incoming_request_params.dig("TopicArn"),
_json.dig("AlarmName")
].join(":")
end

def _is_create?
_new_state == "ALARM" && (_old_state == "INSUFFICIENT_DATA" || _old_state == "OK")
end

def _is_resolve?
_new_state == "OK" && (_old_state == "INSUFFICIENT_DATA" || _old_state == "ALARM")
end

def _new_state
_json.dig("NewStateValue")
end

def _old_state
_json.dig("OldStateValue")
end

def _title
_json.dig("AlarmName")
end

def _description
_json.dig("NewStateReason")
end

def _additional_datums
[
AdditionalDatum.new(format: "text", label: "AWS Account ID", value: _json.dig("AWSAccountId")),
AdditionalDatum.new(format: "text", label: "Region", value: _json.dig("Region")),
AdditionalDatum.new(format: "text", label: "Alarm ARN", value: _json.dig("AlarmArn")),
AdditionalDatum.new(format: "datetime", label: "State Change Time", value: _json.dig("StateChangeTime"))
]
end
end
end
5 changes: 5 additions & 0 deletions app/models/pager_tree/integrations/integration.rb
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,11 @@ def adapter_supports_incoming?
false
end

# a function to determine if we should block the incoming request (e.g. if the payload doesn't match a signature)
def adapter_should_block_incoming?
false
end

# A unique identifier for this integration/alert
def adapter_thirdparty_id
ULID.generate
Expand Down
Empty file.
Empty file.
1 change: 1 addition & 0 deletions gemfiles/rails_7.gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ source "https://rubygems.org"
gem "sprockets-rails"
gem "appraisal", "~> 2.4"
gem "pg", "~> 1.3"
gem "aws-sdk-sns", "~> 1.53"
gem "deferred_request", "~> 1.0"
gem "httparty", "~> 0.20.0"
gem "sanitize", "~> 6.0"
Expand Down
1 change: 1 addition & 0 deletions gemfiles/rails_master.gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ source "https://rubygems.org"
gem "sprockets-rails"
gem "appraisal", "~> 2.4"
gem "pg", "~> 1.3"
gem "aws-sdk-sns", "~> 1.53"
gem "deferred_request", "~> 1.0"
gem "httparty", "~> 0.20.0"
gem "sanitize", "~> 6.0"
Expand Down
4 changes: 4 additions & 0 deletions test/fixtures/pager_tree/integrations/integrations.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ app_dynamics_v3:
type: "PagerTree::Integrations::AppDynamics::V3"
# options: no_options

aws_cloudwatch_v3:
type: "PagerTree::Integrations::AwsCloudwatch::V3"
# options: no_options

outgoing_webhook_v3:
type: "PagerTree::Integrations::OutgoingWebhook::V3"
options:
Expand Down
96 changes: 96 additions & 0 deletions test/models/pager_tree/integrations/aws_cloudwatch/v3_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
require "test_helper"

module PagerTree::Integrations
class AwsCloudwatch::V3Test < ActiveSupport::TestCase
include Integrateable

setup do
@integration = pager_tree_integrations_integrations(:aws_cloudwatch_v3)

@create_request = {
Type: "Notification",
MessageId: "836fe1ec-79e0-56b8-827f-b66569e37e11",
TopicArn: "arn:aws:sns:us-east-1:498849832712:update-cherwell-cmdb",
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}",
Timestamp: "2020-07-15T14:08:03.824Z",
SignatureVersion: "1",
Signature: "JNdxahPfT0tVsX8+ZVPeA23M09UcCbIQ8uar5AZ4VqscGhzqpMcy4v00mluwr3eyJuFsogxhv1RprFIHU0ZH4bNRWxDpzdVnFIGVSnSBZDVi075ynf+oxagTLhSs7aa9Aar38RcQicaYBc6kHiCg5FHIwwU1OXeehVjHavFKC1ymSegaxtD2pUG4jST30gC2P55I+qyFItPOj+Ih8ZqRBXc3H989mwDKU0Qa54/lQ0cFMC8YwZcQzqwSoZQwIvsrCzLjNR7l2IIEq4pk9d2thq9C/tySFNlXd4/HP/Vd6I9wuP08c0nspmmWxQY1X7CQOvwKway7V9WmKVpku3avxQ==",
SigningCertURL: "https://sns.us-east-1.amazonaws.com/SimpleNotificationService-a86cb10b4e1f29c941702d737128f7b6.pem",
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"
}.with_indifferent_access

@resolve_request = {
Type: "Notification",
MessageId: "836fe1ec-79e0-56b8-827f-b66569e37e11",
TopicArn: "arn:aws:sns:us-east-1:498849832712:update-cherwell-cmdb",
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}",
Timestamp: "2020-07-15T14:08:03.824Z",
SignatureVersion: "1",
Signature: "JNdxahPfT0tVsX8+ZVPeA23M09UcCbIQ8uar5AZ4VqscGhzqpMcy4v00mluwr3eyJuFsogxhv1RprFIHU0ZH4bNRWxDpzdVnFIGVSnSBZDVi075ynf+oxagTLhSs7aa9Aar38RcQicaYBc6kHiCg5FHIwwU1OXeehVjHavFKC1ymSegaxtD2pUG4jST30gC2P55I+qyFItPOj+Ih8ZqRBXc3H989mwDKU0Qa54/lQ0cFMC8YwZcQzqwSoZQwIvsrCzLjNR7l2IIEq4pk9d2thq9C/tySFNlXd4/HP/Vd6I9wuP08c0nspmmWxQY1X7CQOvwKway7V9WmKVpku3avxQ==",
SigningCertURL: "https://sns.us-east-1.amazonaws.com/SimpleNotificationService-a86cb10b4e1f29c941702d737128f7b6.pem",
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"
}.with_indifferent_access

@other_request = {
Type: "Notification",
MessageId: "836fe1ec-79e0-56b8-827f-b66569e37e11",
TopicArn: "arn:aws:sns:us-east-1:498849832712:update-cherwell-cmdb",
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}",
Timestamp: "2020-07-15T14:08:03.824Z",
SignatureVersion: "1",
Signature: "JNdxahPfT0tVsX8+ZVPeA23M09UcCbIQ8uar5AZ4VqscGhzqpMcy4v00mluwr3eyJuFsogxhv1RprFIHU0ZH4bNRWxDpzdVnFIGVSnSBZDVi075ynf+oxagTLhSs7aa9Aar38RcQicaYBc6kHiCg5FHIwwU1OXeehVjHavFKC1ymSegaxtD2pUG4jST30gC2P55I+qyFItPOj+Ih8ZqRBXc3H989mwDKU0Qa54/lQ0cFMC8YwZcQzqwSoZQwIvsrCzLjNR7l2IIEq4pk9d2thq9C/tySFNlXd4/HP/Vd6I9wuP08c0nspmmWxQY1X7CQOvwKway7V9WmKVpku3avxQ==",
SigningCertURL: "https://sns.us-east-1.amazonaws.com/SimpleNotificationService-a86cb10b4e1f29c941702d737128f7b6.pem",
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"
}.with_indifferent_access
end

test "sanity" do
assert @integration.adapter_supports_incoming?
assert @integration.adapter_incoming_can_defer?
assert_not @integration.adapter_supports_outgoing?
assert @integration.adapter_show_alerts?
assert @integration.adapter_show_logs?
assert_not @integration.adapter_show_outgoing_webhook_delivery?
end

test "adapter_actions create" do
@integration.adapter_incoming_request_params = @create_request
assert_equal :create, @integration.adapter_action
end

test "adapter_actions resolve" do
@integration.adapter_incoming_request_params = @resolve_request
assert_equal :resolve, @integration.adapter_action
end

test "adapter_actions other" do
@integration.adapter_incoming_request_params = @other_request
assert_equal :other, @integration.adapter_action
end

test "adapter_thirdparty_id" do
@integration.adapter_incoming_request_params = @create_request
assert_equal "arn:aws:sns:us-east-1:498849832712:update-cherwell-cmdb:Saffron-Octopus-RDS", @integration.adapter_thirdparty_id
end

test "adapter_process_create" do
@integration.adapter_incoming_request_params = @create_request

true_alert = Alert.new(
title: "Saffron-Octopus-RDS",
description: "Threshold Crossed: 1 datapoint [2.1533759377604764 (20/07/20 21:07:00)] was greater than or equal to the threshold (0.0175).",
urgency: nil,
thirdparty_id: "arn:aws:sns:us-east-1:498849832712:update-cherwell-cmdb:Saffron-Octopus-RDS",
dedup_keys: ["arn:aws:sns:us-east-1:498849832712:update-cherwell-cmdb:Saffron-Octopus-RDS"],
additional_data: [
AdditionalDatum.new(format: "text", label: "AWS Account ID", value: "498849832712"),
AdditionalDatum.new(format: "text", label: "Region", value: "US East (N. Virginia)"),
AdditionalDatum.new(format: "text", label: "Alarm ARN", value: "arn:aws:cloudwatch:us-east-1:498849832712:alarm:Saffron-Octopus-RDS"),
AdditionalDatum.new(format: "datetime", label: "State Change Time", value: "2020-07-20T21:12:01.544+0000")
]
)

assert_equal true_alert.to_json, @integration.adapter_process_create.to_json
end
end
end

0 comments on commit 252b4e1

Please sign in to comment.