Skip to content

Commit

Permalink
Merge pull request #186 from guardian/jw-scheduled-lambda
Browse files Browse the repository at this point in the history
Add pattern for scheduled lambdas
jacobwinch authored Jan 25, 2021

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
2 parents 1d33dc4 + 139d524 commit 73f2ddd
Showing 11 changed files with 1,082 additions and 1 deletion.
14 changes: 14 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -42,6 +42,7 @@
"dependencies": {
"@aws-cdk/assert": "1.86.0",
"@aws-cdk/aws-autoscaling": "1.86.0",
"@aws-cdk/aws-cloudwatch-actions": "1.86.0",
"@aws-cdk/aws-ec2": "1.86.0",
"@aws-cdk/aws-apigateway": "1.86.0",
"@aws-cdk/aws-elasticloadbalancing": "1.86.0",
273 changes: 273 additions & 0 deletions src/constructs/cloudwatch/__snapshots__/lambda-alarms.test.ts.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,273 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`The GuLambdaErrorPercentageAlarm pattern should create the correct alarm resource with minimal config 1`] = `
Object {
"Parameters": Object {
"Stack": Object {
"Default": "deploy",
"Description": "Name of this stack",
"Type": "String",
},
"Stage": Object {
"AllowedValues": Array [
"CODE",
"PROD",
],
"Default": "CODE",
"Description": "Stage name",
"Type": "String",
},
},
"Resources": Object {
"lambda8B5974B5": Object {
"DependsOn": Array [
"lambdaServiceRoleDefaultPolicyBF6FA5E7",
"lambdaServiceRole494E4CA6",
],
"Properties": Object {
"Code": Object {
"S3Bucket": "bucket1",
"S3Key": "folder/to/key",
},
"Handler": "handler.ts",
"MemorySize": 512,
"Role": Object {
"Fn::GetAtt": Array [
"lambdaServiceRole494E4CA6",
"Arn",
],
},
"Runtime": "nodejs12.x",
"Tags": Array [
Object {
"Key": "App",
"Value": "testing",
},
Object {
"Key": "Stack",
"Value": Object {
"Ref": "Stack",
},
},
Object {
"Key": "Stage",
"Value": Object {
"Ref": "Stage",
},
},
],
"Timeout": 30,
},
"Type": "AWS::Lambda::Function",
},
"lambdaServiceRole494E4CA6": Object {
"Properties": Object {
"AssumeRolePolicyDocument": Object {
"Statement": Array [
Object {
"Action": "sts:AssumeRole",
"Effect": "Allow",
"Principal": Object {
"Service": "lambda.amazonaws.com",
},
},
],
"Version": "2012-10-17",
},
"ManagedPolicyArns": Array [
Object {
"Fn::Join": Array [
"",
Array [
"arn:",
Object {
"Ref": "AWS::Partition",
},
":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole",
],
],
},
],
"Tags": Array [
Object {
"Key": "App",
"Value": "testing",
},
Object {
"Key": "Stack",
"Value": Object {
"Ref": "Stack",
},
},
Object {
"Key": "Stage",
"Value": Object {
"Ref": "Stage",
},
},
],
},
"Type": "AWS::IAM::Role",
},
"lambdaServiceRoleDefaultPolicyBF6FA5E7": Object {
"Properties": Object {
"PolicyDocument": Object {
"Statement": Array [
Object {
"Action": Array [
"s3:GetObject*",
"s3:GetBucket*",
"s3:List*",
],
"Effect": "Allow",
"Resource": Array [
Object {
"Fn::Join": Array [
"",
Array [
"arn:",
Object {
"Ref": "AWS::Partition",
},
":s3:::bucket1",
],
],
},
Object {
"Fn::Join": Array [
"",
Array [
"arn:",
Object {
"Ref": "AWS::Partition",
},
":s3:::bucket1/*",
],
],
},
],
},
],
"Version": "2012-10-17",
},
"PolicyName": "lambdaServiceRoleDefaultPolicyBF6FA5E7",
"Roles": Array [
Object {
"Ref": "lambdaServiceRole494E4CA6",
},
],
},
"Type": "AWS::IAM::Policy",
},
"mylambdafunction8D341B54": Object {
"Properties": Object {
"AlarmActions": Array [
Object {
"Fn::Join": Array [
"",
Array [
"arn:aws:sns:",
Object {
"Ref": "AWS::Region",
},
":",
Object {
"Ref": "AWS::AccountId",
},
":alerts-topic",
],
],
},
],
"AlarmDescription": Object {
"Fn::Join": Array [
"",
Array [
Object {
"Ref": "lambda8B5974B5",
},
" exceeded 80% error rate",
],
],
},
"AlarmName": Object {
"Fn::Join": Array [
"",
Array [
"High error % from ",
Object {
"Ref": "lambda8B5974B5",
},
" lambda in ",
Object {
"Ref": "Stage",
},
],
],
},
"ComparisonOperator": "GreaterThanThreshold",
"EvaluationPeriods": 1,
"Metrics": Array [
Object {
"Expression": "100*m1/m2",
"Id": "expr_1",
"Label": Object {
"Fn::Join": Array [
"",
Array [
"Error % of ",
Object {
"Ref": "lambda8B5974B5",
},
],
],
},
},
Object {
"Id": "m1",
"MetricStat": Object {
"Metric": Object {
"Dimensions": Array [
Object {
"Name": "FunctionName",
"Value": Object {
"Ref": "lambda8B5974B5",
},
},
],
"MetricName": "Errors",
"Namespace": "AWS/Lambda",
},
"Period": 300,
"Stat": "Sum",
},
"ReturnData": false,
},
Object {
"Id": "m2",
"MetricStat": Object {
"Metric": Object {
"Dimensions": Array [
Object {
"Name": "FunctionName",
"Value": Object {
"Ref": "lambda8B5974B5",
},
},
],
"MetricName": "Invocations",
"Namespace": "AWS/Lambda",
},
"Period": 300,
"Stat": "Sum",
},
"ReturnData": false,
},
],
"Threshold": 80,
},
"Type": "AWS::CloudWatch::Alarm",
},
},
}
`;
65 changes: 65 additions & 0 deletions src/constructs/cloudwatch/alarm.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import "@aws-cdk/assert/jest";
import { ComparisonOperator } from "@aws-cdk/aws-cloudwatch";
import { Runtime } from "@aws-cdk/aws-lambda";
import { simpleGuStackForTesting } from "../../../test/utils";
import { GuLambdaFunction } from "../lambda";
import { GuAlarm } from "./alarm";

describe("The GuAlarm class", () => {
it("should create a CloudWatch alarm", () => {
const stack = simpleGuStackForTesting();
const lambda = new GuLambdaFunction(stack, "lambda", {
code: { bucket: "bucket1", key: "folder/to/key" },
handler: "handler.ts",
runtime: Runtime.NODEJS_12_X,
});
new GuAlarm(stack, "alarm", {
alarmName: `Alarm in ${stack.stage}`,
alarmDescription: "It's broken",
metric: lambda.metricErrors(),
comparisonOperator: ComparisonOperator.GREATER_THAN_OR_EQUAL_TO_THRESHOLD,
threshold: 1,
evaluationPeriods: 1,
snsTopicName: "alerts-topic",
});
expect(stack).toHaveResource("AWS::CloudWatch::Alarm");
});

it("should send alerts to the provided SNS Topic", () => {
const stack = simpleGuStackForTesting();
const lambda = new GuLambdaFunction(stack, "lambda", {
code: { bucket: "bucket1", key: "folder/to/key" },
handler: "handler.ts",
runtime: Runtime.NODEJS_12_X,
});
new GuAlarm(stack, "alarm", {
alarmName: `Alarm in ${stack.stage}`,
alarmDescription: "It's broken",
metric: lambda.metricErrors(),
comparisonOperator: ComparisonOperator.GREATER_THAN_OR_EQUAL_TO_THRESHOLD,
threshold: 1,
evaluationPeriods: 1,
snsTopicName: "alerts-topic",
});
expect(stack).toHaveResource("AWS::CloudWatch::Alarm", {
AlarmActions: [
{
"Fn::Join": [
"",
[
"arn:aws:sns:",
{
Ref: "AWS::Region",
},
":",
{
Ref: "AWS::AccountId",
},
":alerts-topic",
],
],
},
],
});
});
});
19 changes: 19 additions & 0 deletions src/constructs/cloudwatch/alarm.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import type { AlarmProps } from "@aws-cdk/aws-cloudwatch";
import { Alarm } from "@aws-cdk/aws-cloudwatch";
import { SnsAction } from "@aws-cdk/aws-cloudwatch-actions";
import type { ITopic } from "@aws-cdk/aws-sns";
import { Topic } from "@aws-cdk/aws-sns";
import type { GuStack } from "../core";

export interface GuAlarmProps extends AlarmProps {
snsTopicName: string;
}

export class GuAlarm extends Alarm {
constructor(scope: GuStack, id: string, props: GuAlarmProps) {
super(scope, id, props);
const topicArn: string = `arn:aws:sns:${scope.region}:${scope.account}:${props.snsTopicName}`;
const snsTopic: ITopic = Topic.fromTopicArn(scope, "sns-topic-for-alarm-notifications", topicArn);
this.addAlarmAction(new SnsAction(snsTopic));
}
}
Loading

0 comments on commit 73f2ddd

Please sign in to comment.