Basic stack and a decent readme

This commit is contained in:
Layla 2019-07-06 21:38:21 -04:00
parent 1cd6398be9
commit 2561d7569b
5 changed files with 356 additions and 26 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
config
*.zip

View File

@ -1 +1,39 @@
# TBD
# Billing Alerts Stack
![Billing Alerts Diagram](https://static.cloudsumu.com/billingalerts/diagram.png)
Uses SNS, Lambda, and CloudWatch to send billing alerts via:
- Email
- Text
- Discord
- Slack
Easy to launch and configure!
## Launching Stack
### Option 1: Launch via Link
Make sure you are logged into the AWS Console and have permissions then click:
[![Launch in AWS Console](https://static.cloudsumu.com/billingalerts/launch_button.png)](https://console.aws.amazon.com/cloudformation/home?region=us-east-1#/stacks/create/template?stackName=BillingAlerts&templateURL=https://sumu-billingalerts.s3.amazonaws.com/production/cloudformation/top.yaml)
Fill out the parameters and launch!
### Option 2: Manually input template into AWS Console
1. Download top template or copy URL for later.
*Development S3 URL:* [https://sumu-billingalerts.s3.amazonaws.com/develop/cloudformation/top.yaml](https://sumu-billingalerts.s3.amazonaws.com/production/cloudformation/top.yaml)
*Production S3 URL:* [https://sumu-billingalerts.s3.amazonaws.com/production/cloudformation/top.yaml](https://sumu-billingalerts.s3.amazonaws.com/production/cloudformation/top.yaml)
2. Go to [CloudFormation on the AWS Console](https://console.aws.amazon.com/cloudformation/home)
3. Click the "Create stack" button
4. Under "Prepare template" make sure that "Template is ready" is selected, it should be the default.
Then under "Template Source" either paste in the template URL or upload the downloaded template.
Then click "Next"
5. Fill out stack parameters then launch!

View File

@ -1,44 +1,262 @@
AWSTemplateFormatVersion: '2010-09-09'
Transform: 'AWS::Serverless-2016-10-31'
Description: 'Top level stack for Command Relay API resources'
AWSTemplateFormatVersion: 2010-09-09
Description: Stack that sends out billing alerts
Parameters:
cloudToolsBucket:
Type: String
Description: 'S3 Bucket containing Cloud Tools'
#------------------------
# Deployment Information
#------------------------
environment:
Type: String
Description: 'Environment'
Description: Name of the environment to use in naming.
Default: production
release:
Type: String
Description: 'Release'
Default: 'develop'
notificationDiscordId:
Description: Name of the release name of the stack version to use.
Default: production
AllowedValues: ['develop', 'production']
ConstraintDescription: "Must be a possible release version."
#---------------
# Alert Methods
#---------------
discordWebhook:
Type: String
Description: 'Username of discord user to notify'
Description: A webhook for notifications for Discord. (Leave empty for none)
Default: ""
slackWebhook:
Type: String
Description: An incoming webhook for notifications for Slack. (Leave empty for none)
Default: ""
notificationEmail:
Type: String
Description: 'Email of to send SNS notifications to'
Description: An email address to subscribe to the alerting topic. (Leave empty for none)
Default: ""
notificationPhone:
Type: String
Description: A mobile number to subscribe to the alerting topic. Formatted as '+1XXXXXXXXXX' (Leave empty for none)
Default: ""
#-------------------
# Alarm Information
#-------------------
lowPriorityAlert:
Type: Number
Description: Estimated monthly cost in USD to send a low priority alert.
Default: 10
MinValue: 1
mediumPriorityAlert:
Type: Number
Description: Estimated monthly cost in USD to send a normal alert.
Default: 15
MinValue: 2
highPriorityAlert:
Type: Number
Description: Estimated monthly cost in USD to send a low priority alert.
Default: 20
MinValue: 3
updateInterval:
Type: Number
Description: Time in seconds alarms are updated.
Default: 21600 # 6 hours
ConstraintDescription: Minimum alarm time is a minute.
MinValue: 60
Metadata:
AWS::CloudFormation::Interface:
ParameterGroups:
- Label:
default: "Deployment Information"
Parameters:
- environment
- release
- Label:
default: "Alert Methods"
Parameters:
- notificationEmail
- notificationPhone
- discordWebhook
- slackWebhook
- Label:
default: "Alarm Settings"
Parameters:
- lowPriorityAlert
- mediumPriorityAlert
- highPriorityAlert
ParameterLabels:
environment:
default: "Environment"
release:
default: "Release"
notificationEmail:
default: "Email"
notificationPhone:
default: "SMS Number"
discordWebhook:
default: "Discord Webhook"
slackWebhook:
default: "Slack Webhook"
lowPriorityAlert:
Default: "Cost for Low Priority Alert"
mediumPriorityAlert:
Default: "Cost for Medium Priority Alert"
highPriorityAlert:
Default: "Cost for High Priority Alert"
Conditions:
SubscribeEmail: !Not [!Equals [!Ref "notificationEmail", ""]]
SubscribePhone: !Not [!Equals [!Ref "notificationPhone", ""]]
Resources:
NotifyDiscord:
Type: 'AWS::Serverless::Function'
AlertSnsTopic:
Type: AWS::SNS::Topic
Properties:
TopicName: !Sub "BillingAlerts-${environment}"
EmailAlertSubscription:
Type: AWS::SNS::Subscription
Condition: SubscribeEmail
Properties:
Protocol: email
Endpoint: !Ref notificationEmail
TopicArn: !Ref AlertSnsTopic
PhoneAlertSubscription:
Type: AWS::SNS::Subscription
Condition: SubscribePhone
Properties:
Protocol: sms
Endpoint: !Ref notificationPhone
TopicArn: !Ref AlertSnsTopic
AlertExecutionerRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Principal:
Service:
- lambda.amazonaws.com
Action:
- sts:AssumeRole
Policies:
- PolicyName: LambdaLogging
PolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Action:
- logs:CreateLogGroup
- logs:CreateLogStream
- logs:PutLogEvents
Resource: "*"
- PolicyName: AlertSNS
PolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Action:
- sns:Publish
Resource: !Ref AlertSnsTopic
AlertHandler:
Type: AWS::Lambda::Function
Properties:
Handler: lambda_function.lambda_handler
Runtime: python3.6
# CodeUri:
# Bucket: !Ref CloudToolsBucket
# Key: !Ref TicketsArchive
FunctionName: !Sub "BillingBot-${Environment}-FnNotifyDiscord"
Description: 'Lambda receives API Gateway requests and generates tickets in FreshDesk.'
Code:
S3Bucket: "sumu-billingalerts"
S3Key: !Sub "${release}/lambda/alertHandler.zip"
FunctionName: !Sub "FnAlert-${environment}"
Description: Lambda receives CloudWatch events and alerts channels.
MemorySize: 128
Timeout: 10
#Role: !GetAtt CommandRelayIAM.Outputs.TicketsRoleArn
Role: !GetAtt AlertExecutionerRole.Arn
Environment:
Variables:
region: !Ref 'AWS::Region'
discordId: !Ref NotificationDiscordId
discordWebhook: !Ref discordWebhook
slackWebhook: !Ref slackWebhook
snsTopic: !Ref AlertSnsTopic
#CloudWatch CRON
#CloudWatch Billing Limit (3 Tiers (Low Priority, Medium Priority, High Priority))
CloudWatchReciever:
Type: AWS::SNS::Topic
Properties:
TopicName: !Sub "Billing-CloudWatch-${environment}"
CloudWatchAlertSubscription:
Type: AWS::SNS::Subscription
Properties:
Protocol: lambda
Endpoint: !GetAtt AlertHandler.Arn
TopicArn: !Ref CloudWatchReciever
SnsLambdaPermission:
Type: AWS::Lambda::Permission
Properties:
Action: lambda:InvokeFunction
Principal: sns.amazonaws.com
SourceArn: !Ref CloudWatchReciever
FunctionName: !GetAtt AlertHandler.Arn
LowPriorityAlarm:
Type: AWS::CloudWatch::Alarm
Properties:
ActionsEnabled: true
AlarmActions:
- !Ref CloudWatchReciever
ComparisonOperator: GreaterThanThreshold
Dimensions:
- Name: Currency
Value: USD
EvaluationPeriods: 1
MetricName: EstimatedCharges
Namespace: "AWS/Billing"
OKActions:
- !Ref CloudWatchReciever
Period: !Ref updateInterval
Statistic: Maximum
Threshold: !Ref lowPriorityAlert
MediumPriorityAlarm:
Type: AWS::CloudWatch::Alarm
Properties:
ActionsEnabled: true
AlarmActions:
- !Ref CloudWatchReciever
ComparisonOperator: GreaterThanThreshold
Dimensions:
- Name: Currency
Value: USD
EvaluationPeriods: 1
MetricName: EstimatedCharges
Namespace: "AWS/Billing"
OKActions:
- !Ref CloudWatchReciever
Period: !Ref updateInterval
Statistic: Maximum
Threshold: !Ref mediumPriorityAlert
HighPriorityAlarm:
Type: AWS::CloudWatch::Alarm
Properties:
ActionsEnabled: true
AlarmActions:
- !Ref CloudWatchReciever
ComparisonOperator: GreaterThanThreshold
Dimensions:
- Name: Currency
Value: USD
EvaluationPeriods: 1
MetricName: EstimatedCharges
Namespace: "AWS/Billing"
OKActions:
- !Ref CloudWatchReciever
Period: !Ref updateInterval
Statistic: Maximum
Threshold: !Ref highPriorityAlert

View File

@ -0,0 +1,70 @@
import json, requests, os, boto3
def lambda_handler(event, context):
if 'Records' in event:
for record in event['Records']:
if record['EventSource'] == "aws:sns":
if "Message" in record['Sns']:
message = json.loads(record['Sns']['Message'])
if "NewStateValue" in message:
print(message)
is_important = "HighPriority" in message["AlarmName"] and (message["NewStateValue"] != "OK")
send_message(message["NewStateReason"], "AWS Billing Alert", is_important)
else:
send_message(message, record['Sns']['Subject'])
#-------------------
# Messaging Methods
#-------------------
def send_discord_message(message, name = None, high_priority = False):
if high_priority:
send_discord_message("@everyone \n**HIGH PRIORITY:** " + message, name)
return
if "discordWebhook" in os.environ and os.environ["discordWebhook"] != "":
r = requests.post(os.environ['discordWebhook'],
data={'content': message, 'username': name})
print("Sent to Discord: " + message)
else:
print("Discord webhook not setup. Skipping...")
def send_sns_message(message, name = "Billing Alert", high_priority = False):
if high_priority:
send_sns_message(message, "HIGH PRIORITY: " + name)
return
if 'snsTopic' in os.environ and os.environ["snsTopic"] != "":
boto3.client('sns').publish(
TargetArn=os.environ['snsTopic'],
Message=json.dumps({'default':message,
'sms' : name +"\n---\n" + message}),
Subject=name,
MessageStructure='json'
)
print("Published to SNS: " + message)
else:
print("SNS not setup. Skipping...")
def send_slack_message(message, name = None, high_priority = False):
if high_priority:
send_slack_message("<!channel> \n*HIGH PRIORITY:* " + message, name)
return
if "slackWebhook" in os.environ and os.environ["slackWebhook"] != "":
r = requests.post(os.environ['slackWebhook'],
data=json.dumps({'text': message, 'username' : name}),
headers={'Content-Type' : 'application/json'})
print("Sent to Slack: " + message)
else:
print("Slack webhook not setup. Skipping...")
#Function sends message to every messaging method
def send_message(message, name = None, high_priority = False):
send_discord_message(message, name, high_priority)
send_slack_message(message, name, high_priority)
send_sns_message(message, name, high_priority)
# os.environ['snsTopic'] = "arn:aws:sns:us-east-1:959431236163:BillingDev"
# os.environ['slackWebhook'] = "https://hooks.slack.com/services/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"
# os.environ['discordWebhook'] = "https://discordapp.com/api/webhooks/597128213373648900/Ys4iB0MCeJiwne_KSJIE_Q8geZluKsFYzQQ02GJIMp2fGka1-tx47ZmH0aTxbGVz6fJ6"
# send_message("Test", "AWS Billing", True)

View File

@ -0,0 +1,2 @@
boto3>=1.9.57
requests>=2.21.0