Skip to content
This repository has been archived by the owner on Oct 25, 2023. It is now read-only.

Commit

Permalink
Support global alarms and sns alerts
Browse files Browse the repository at this point in the history
  • Loading branch information
johncmckim committed Nov 15, 2016
1 parent c230453 commit 04c271d
Show file tree
Hide file tree
Showing 6 changed files with 347 additions and 67 deletions.
19 changes: 13 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,18 @@ A Serverless plugin to easily add CloudWatch alarms to functions

## Usage

```
```yaml
service: your-service
...
provider:
name: aws
runtime: nodejs4.3

custom:
lambdaAlarms:
alerts:
topics:
ok: ${self:service}-${opt:stage}-alerts-ok
alarm: ${self:service}-${opt:stage}-alerts-alarm
insufficientData: ${self:service}-${opt:stage}-alerts-insufficientData
definitions: # these defaults are merged with your definitions
functionErrors:
period: 300 # override period
Expand All @@ -22,10 +29,10 @@ custom:
statistic: average
period: 300
global:
- throttles
- functionThrottles
- functionErrors
function:
- functionInvocations
- functionErrors
- functionDuration

plugins:
Expand All @@ -46,7 +53,7 @@ functions:
```

## Default definitions
```
```yaml
definitions:
functionInvocations:
namespace: 'AWS/Lambda'
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "serverless-plugin-aws-alerts",
"version": "0.0.2",
"version": "0.0.3",
"description": "",
"main": "src/index.js",
"repository": {
Expand Down
145 changes: 127 additions & 18 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,31 +2,29 @@

const _ = require('lodash');

const Naming = require('./naming');
const defaultDefinitions = require('./defaults/definitions');

class Plugin {
constructor(serverless, options) {
this.serverless = serverless;
this.options = options;
this.naming = new Naming();

this.hooks = {
'deploy:compileEvents': this.compileCloudWatchAlamrs.bind(this),
};
}

getConfig() {
return this.serverless.service.custom.lambdaAlarms;
return this.serverless.service.custom.alerts;
}

getDefinitions(config) {
return _.merge({}, defaultDefinitions, config.definitions)
}

getFunctionAlarms(functionObj, config, definitions) {
if (!config) throw new Error('Missing config argument');
if (!definitions) throw new Error('Missing definitions argument');

const alarms = _.union(config.function, functionObj.alarms);
getAlarms(alarms, definitions) {
return _.reduce(alarms, (result, alarm) => {
if (_.isString(alarm)) {
const definition = definitions[alarm];
Expand All @@ -46,7 +44,51 @@ class Plugin {
}, []);
}

getAlarmCloudFormation(definition) {
getGlobalAlarms(config, definitions) {
if (!config) throw new Error('Missing config argument');
if (!definitions) throw new Error('Missing definitions argument');

return this.getAlarms(config.global, definitions);
}

getFunctionAlarms(functionObj, config, definitions) {
if (!config) throw new Error('Missing config argument');
if (!definitions) throw new Error('Missing definitions argument');

const alarms = _.union(config.function, functionObj.alarms);
return this.getAlarms(alarms, definitions);
}

getAlarmCloudFormation(alertTopics, definition, functionRefs) {
if(!functionRefs || !functionRefs.length) {
return;
}

const okActions = [];
const alarmActions = [];
const insufficientDataActions = [];

if(alertTopics.ok) {
okActions.push({ Ref: alertTopics.ok })
}

if(alertTopics.alarm) {
alarmActions.push({ Ref: alertTopics.alarm })
}

if(alertTopics.insufficientData) {
insufficientDataActions.push({ Ref: alertTopics.insufficientData })
}

const dimensions = _.map(functionRefs, (ref) => {
return {
Name: `${ref}Name`,
Value: {
Ref: ref,
},
};
});

const properties = {
Namespace: definition.namespace,
MetricName: definition.metric,
Expand All @@ -55,38 +97,105 @@ class Plugin {
Period: definition.period,
EvaluationPeriods: definition.evaluationPeriods,
ComparisonOperator: definition.comparisonOperator,
//AlarmActions: [ ],
//OkActions: [ ],
OKActions: okActions,
AlarmActions: alarmActions,
InsufficientDataActions: insufficientDataActions,
Dimensions: dimensions,
};


return {
Type: 'AWS::CloudWatch::Alarm',
Properties: properties
};
}

compileCloudWatchAlamrs() {
const config = this.getConfig();
const definitions = this.getDefinitions(config);
getCfSnsTopic(topicName) {
return {
Type: "AWS::SNS::Topic",
Properties: {
TopicName: topicName,
}
};
}

compileAlertTopics(config) {
const alertTopics = {};

if(config.topics) {
Object.keys(config.topics).forEach((key) => {
const topicName = config.topics[key];
if(topicName) {
const cfRef = `AwsAlerts${_.upperFirst(key)}`
alertTopics[key] = cfRef;

this.addCfResources({
[cfRef]: this.getCfSnsTopic(topicName),
});
}
});
}

return alertTopics;
}

compileGlobalAlarms(config, definitions, alertTopics) {
const globalAlarms = this.getGlobalAlarms(config, definitions);
const functionRefs = this.serverless.service
.getAllFunctions()
.map(functionName => {
const normalizedName = this.naming.getNormalisedName(functionName)
return this.naming.getLambdaFunctionCFRef(normalizedName);
});

const alarmStatements = _.reduce(globalAlarms, (statements, alarm) => {
const key = this.naming.getAlarmCFRef(alarm.name, 'Global');
const cf = this.getAlarmCloudFormation(alertTopics, alarm, functionRefs);
statements[key] = cf;
return statements;
}, {});

this.addCfResources(alarmStatements);
}

compileFunctionAlarms(config, definitions, alertTopics) {
this.serverless.service.getAllFunctions().forEach((functionName) => {
const functionObj = this.serverless.service.getFunction(functionName);

const normalizedName = `${_.upperFirst(functionName.replace(/-/g, 'Dash').replace(/_/g, 'Underscore'))}`;

const normalizedName = this.naming.getNormalisedName(functionName)
const normalizedFunctionName = this.naming.getLambdaFunctionCFRef(normalizedName);

const alarms = this.getFunctionAlarms(functionObj, config, definitions);

const alarmStatements = _.reduce(alarms, (statements, alarm) => {
const key = `${normalizedName}${_.upperFirst(alarm.name)}Alarm`;
const cf = this.getAlarmCloudFormation(alarm);
const key = this.naming.getAlarmCFRef(alarm.name, normalizedName);
const cf = this.getAlarmCloudFormation(alertTopics, alarm, [
normalizedFunctionName
]);
statements[key] = cf;
return statements;
}, {});

_.merge(this.serverless.service.provider.compiledCloudFormationTemplate.Resources, alarmStatements);
this.addCfResources(alarmStatements);
});
}

compileCloudWatchAlamrs() {
const config = this.getConfig();
if(!config) {
// TODO warn no config
return;
}

const definitions = this.getDefinitions(config);
const alertTopics = this.compileAlertTopics(config);

this.compileGlobalAlarms(config, definitions, alertTopics);
this.compileFunctionAlarms(config, definitions, alertTopics);
}

addCfResources(resources) {
_.merge(this.serverless.service.provider.compiledCloudFormationTemplate.Resources, resources);
}
}

module.exports = Plugin;
20 changes: 20 additions & 0 deletions src/naming.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
'use strict'

const _ = require('lodash');

class Naming {
getNormalisedName(name) {
return `${_.upperFirst(name.replace(/-/g, 'Dash').replace(/_/g, 'Underscore'))}`;
}

getLambdaFunctionCFRef(normalizedName) {
return `${normalizedName}LambdaFunction`;
}

getAlarmCFRef(alarmName, prefix) {
const normalizedName = this.getNormalisedName(alarmName);
return `${prefix}${normalizedName}Alarm`;
}
}

module.exports = Naming;

0 comments on commit 04c271d

Please sign in to comment.