implemented http-monitoring example using AWS Cloudformation and AWS Cloudwatch Synthetics

Signed-off-by: Sam Alba <sam.alba@gmail.com>
This commit is contained in:
Sam Alba 2021-03-03 17:34:00 -08:00
parent 64ab495c82
commit 56bcf6366c
2 changed files with 298 additions and 0 deletions

View File

@ -0,0 +1,269 @@
package main
import (
"strings"
"regexp"
"encoding/json"
"dagger.io/aws"
"dagger.io/aws/cloudformation"
)
#Notification: {
protocol: string
endpoint: string
}
#Canary: {
name: =~"^[0-9a-z_-]{1,21}$"
slug: strings.Join(regexp.FindAll("[0-9a-zA-Z]*", name, -1), "")
url: string
expectedHTTPCode: *200 | int
timeoutInSeconds: *30 | int
intervalExpression: *"1 minute" | string
}
#HTTPMonitor: {
// For sending notifications
notifications: [...#Notification]
// Canaries (tests)
canaries: [...#Canary]
// Name of the Cloudformation stack
cfnStackName: string
// AWS Config
awsConfig: aws.#Config
cfnStack: cloudformation.#Stack & {
config: awsConfig
source: json.Marshal(#cfnTemplate)
stackName: cfnStackName
onFailure: "DO_NOTHING"
}
// Function handler
#lambdaHandler: {
url: string
expectedHTTPCode: int
script: #"""
var synthetics = require('Synthetics');
const log = require('SyntheticsLogger');
const pageLoadBlueprint = async function () {
// INSERT URL here
const URL = "\#(url)";
let page = await synthetics.getPage();
const response = await page.goto(URL, {waitUntil: 'domcontentloaded', timeout: 30000});
//Wait for page to render.
//Increase or decrease wait time based on endpoint being monitored.
await page.waitFor(15000);
// This will take a screenshot that will be included in test output artifacts
await synthetics.takeScreenshot('loaded', 'loaded');
let pageTitle = await page.title();
log.info('Page title: ' + pageTitle);
if (response.status() !== \#(expectedHTTPCode)) {
throw "Failed to load page!";
}
};
exports.handler = async () => {
return await pageLoadBlueprint();
};
"""#
}
#cfnTemplate: {
AWSTemplateFormatVersion: "2010-09-09"
Description: "CloudWatch Synthetics website monitoring"
Resources: {
Topic: {
Type: "AWS::SNS::Topic"
Properties: Subscription: [
for e in notifications {
Endpoint: e.endpoint
Protocol: e.protocol
},
]
}
TopicPolicy: {
Type: "AWS::SNS::TopicPolicy"
Properties: {
PolicyDocument: {
Id: "Id1"
Version: "2012-10-17"
Statement: [
{
Sid: "Sid1"
Effect: "Allow"
Principal: AWS: "*"
Action: "sns:Publish"
Resource: Ref: "Topic"
Condition: StringEquals: "AWS:SourceOwner": Ref: "AWS::AccountId"
},
]
}
Topics: [
{
Ref: "Topic"
},
]
}
}
CanaryBucket: {
Type: "AWS::S3::Bucket"
Properties: {}
}
CanaryRole: {
Type: "AWS::IAM::Role"
Properties: {
AssumeRolePolicyDocument: {
Version: "2012-10-17"
Statement: [
{
Effect: "Allow"
Principal: Service: "lambda.amazonaws.com"
Action: "sts:AssumeRole"
},
]
}
Policies: [
{
PolicyName: "execution"
PolicyDocument: {
Version: "2012-10-17"
Statement: [
{
Effect: "Allow"
Action: "s3:ListAllMyBuckets"
Resource: "*"
},
{
Effect: "Allow"
Action: "s3:PutObject"
Resource: "Fn::Sub": "${CanaryBucket.Arn}/*"
},
{
Effect: "Allow"
Action: "s3:GetBucketLocation"
Resource: "Fn::GetAtt": [
"CanaryBucket",
"Arn",
]
},
{
Effect: "Allow"
Action: "cloudwatch:PutMetricData"
Resource: "*"
Condition: StringEquals: "cloudwatch:namespace": "CloudWatchSynthetics"
},
]
}
},
]
}
}
CanaryLogGroup: {
Type: "AWS::Logs::LogGroup"
Properties: {
LogGroupName: "Fn::Sub": "/aws/lambda/cwsyn-\(cfnStackName)"
RetentionInDays: 14
}
}
CanaryPolicy: {
Type: "AWS::IAM::Policy"
Properties: {
PolicyDocument: Statement: [
{
Effect: "Allow"
Action: [
"logs:CreateLogStream",
"logs:PutLogEvents",
]
Resource: "Fn::GetAtt": [
"CanaryLogGroup",
"Arn",
]
},
]
PolicyName: "logs"
Roles: [
{
Ref: "CanaryRole"
},
]
}
}
for canary in canaries {
"Canary\(canary.slug)": {
Type: "AWS::Synthetics::Canary"
Properties: {
ArtifactS3Location: "Fn::Sub": "s3://${CanaryBucket}"
Code: {
#handler: #lambdaHandler & {
url: canary.url
expectedHTTPCode: canary.expectedHTTPCode
}
Handler: "index.handler"
Script: #handler.script
}
ExecutionRoleArn: "Fn::GetAtt": [
"CanaryRole",
"Arn",
]
FailureRetentionPeriod: 30
Name: canary.name
RunConfig: TimeoutInSeconds: canary.timeoutInSeconds
RuntimeVersion: "syn-1.0"
Schedule: {
DurationInSeconds: "0"
Expression: "rate(\(canary.intervalExpression))"
}
StartCanaryAfterCreation: true
SuccessRetentionPeriod: 30
}
}
"SuccessPercentAlarm\(canary.slug)": {
DependsOn: "TopicPolicy"
Type: "AWS::CloudWatch::Alarm"
Properties: {
AlarmActions: [
{
Ref: "Topic"
},
]
AlarmDescription: "Canary is failing."
ComparisonOperator: "LessThanThreshold"
Dimensions: [
{
Name: "CanaryName"
Value: Ref: "Canary\(canary.slug)"
},
]
EvaluationPeriods: 1
MetricName: "SuccessPercent"
Namespace: "CloudWatchSynthetics"
OKActions: [
{
Ref: "Topic"
},
]
Period: 300
Statistic: "Minimum"
Threshold: 90
TreatMissingData: "notBreaching"
}
}
}
}
Outputs: {
for canary in canaries {
"\(canary.slug)Canary": Value: Ref: "Canary\(canary.slug)"
"\(canary.slug)URL": Value: canary.url
}
NumberCanaries: Value: len(canaries)
}
}
}

View File

@ -0,0 +1,29 @@
package main
import (
"dagger.io/aws"
)
// Fill using:
// --input-string awsConfig.accessKey=XXX
// --input-string awsConfig.secretKey=XXX
awsConfig: aws.#Config & {
region: *"us-east-1" | string
}
monitor: #HTTPMonitor & {
notifications: [
#Notification & {
endpoint: "sam+test@blocklayerhq.com"
protocol: "email"
},
]
canaries: [
#Canary & {
name: "website-test"
url: "https://www.google.com/"
},
]
cfnStackName: "my-monitor"
"awsConfig": awsConfig
}