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

Commit

Permalink
Merge f2d644a into fe8593a
Browse files Browse the repository at this point in the history
  • Loading branch information
bulkan committed Jul 19, 2019
2 parents fe8593a + f2d644a commit d2cc90d
Show file tree
Hide file tree
Showing 3 changed files with 244 additions and 20 deletions.
61 changes: 61 additions & 0 deletions README.md
Expand Up @@ -70,6 +70,67 @@ functions:
comparisonOperator: GreaterThanOrEqualToThreshold
```

## Multiple topic definitions

You can define several topics for alarms. For example you want to have topics for critical alarms
reaching your pagerduty, and different topics for noncritical alarms, which just send you emails.

In each alarm definition you have to specify which topics you want to use. In following example
you get an email for each function error, pagerduty gets alarm only if there are more than 20
errors in 60s

```yaml
custom:
alerts:

topics:
critical:
ok:
topic: ${self:service}-${opt:stage}-critical-alerts-ok
notifications:
- protocol: https
endpoint: https://events.pagerduty.com/integration/.../enqueue
alarm:
topic: ${self:service}-${opt:stage}-critical-alerts-alarm
notifications:
- protocol: https
endpoint: https://events.pagerduty.com/integration/.../enqueue

nonCritical:
alarm:
topic: ${self:service}-${opt:stage}-nonCritical-alerts-alarm
notifications:
- protocol: email
endpoint: alarms@email.com

definitions: # these defaults are merged with your definitions
criticalFunctionErrors:
namespace: 'AWS/Lambda'
metric: Errors
threshold: 20
statistic: Sum
period: 60
evaluationPeriods: 10
comparisonOperator: GreaterThanOrEqualToThreshold
okActions:
- critical
alarmActions:
- critical
nonCriticalFunctionErrors:
namespace: 'AWS/Lambda'
metric: Errors
threshold: 1
statistic: Sum
period: 60
evaluationPeriods: 10
comparisonOperator: GreaterThanOrEqualToThreshold
alarmActions:
- nonCritical
alarms:
- criticalFunctionErrors
- nonCriticalFunctionErrors

```
## SNS Topics

If topic name is specified, plugin assumes that topic does not exist and will create it. To use existing topics, specify ARNs or use Fn::ImportValue to use a topic exported with CloudFormation.
Expand Down
74 changes: 54 additions & 20 deletions src/index.js
Expand Up @@ -90,7 +90,22 @@ class AlertsPlugin {
insufficientDataActions.push(alertTopics.insufficientData);
}


if (definition.okActions) {
definition.okActions.map( alertTopic => {okActions.push(alertTopics[alertTopic].ok)});
}

if (definition.alarmActions) {
definition.alarmActions.map( alertTopic => {alarmActions.push(alertTopics[alertTopic].alarm)});
}

if (definition.insufficientDataActions) {
definition.insufficientDataActions.map( alertTopic => {insufficientDataActions.push(alertTopics[alertTopic].insufficientData)});
}


const stackName = this.awsProvider.naming.getStackName();

const namespace = definition.pattern ?
stackName :
definition.namespace;
Expand Down Expand Up @@ -157,31 +172,50 @@ class AlertsPlugin {
};
}

_addAlertTopic(key, topics, alertTopics, customAlarmName) {
const topicConfig = topics[key];
const isTopicConfigAnObject = _.isObject(topicConfig);
const isTopicConfigAnImport = isTopicConfigAnObject && topicConfig['Fn::ImportValue'];

const topic = isTopicConfigAnObject ? topicConfig.topic : topicConfig;
const notifications = isTopicConfigAnObject ? topicConfig.notifications : [];

if (topic) {
if (isTopicConfigAnImport || topic.indexOf('arn:') === 0) {
alertTopics[key] = topic;
} else {
const cfRef = `AwsAlerts${customAlarmName ? _.upperFirst(customAlarmName) : ''}${_.upperFirst(key)}`;
if (customAlarmName) {
if (!alertTopics[customAlarmName]) {
alertTopics[customAlarmName] = {}
}
alertTopics[customAlarmName][key] = {
Ref: cfRef
};
} else {
alertTopics[key] = {
Ref: cfRef
};
}

this.addCfResources({
[cfRef]: this.getSnsTopicCloudFormation(topic, notifications),
});
}
}
}

compileAlertTopics(config) {
const alertTopics = {};

if (config.topics) {
Object.keys(config.topics).forEach((key) => {
const topicConfig = config.topics[key];
const isTopicConfigAnObject = _.isObject(topicConfig);
const isTopicConfigAnImport = isTopicConfigAnObject && topicConfig['Fn::ImportValue'];

const topic = isTopicConfigAnObject && !isTopicConfigAnImport ? topicConfig.topic : topicConfig;
const notifications = isTopicConfigAnObject && !isTopicConfigAnImport ? topicConfig.notifications : [];

if (topic) {
if (isTopicConfigAnImport || topic.indexOf('arn:') === 0) {
alertTopics[key] = topic;
} else {
const cfRef = `AwsAlerts${_.upperFirst(key)}`;
alertTopics[key] = {
Ref: cfRef
};

this.addCfResources({
[cfRef]: this.getSnsTopicCloudFormation(topic, notifications),
});
}
if (['ok', 'alarm', 'insufficientData'].indexOf(key) !== -1) {
this._addAlertTopic(key, config.topics, alertTopics)
} else {
Object.keys(config.topics[key]).forEach((subkey) => {
this._addAlertTopic(subkey, config.topics[key], alertTopics, key)
})
}
});
}
Expand Down
129 changes: 129 additions & 0 deletions src/index.test.js
Expand Up @@ -447,6 +447,74 @@ describe('#index', function () {
}
});
});

it('should create SNS topic with nested definitions', () => {
const plugin = pluginFactory({
topics: {
critical: {
ok: 'critical-ok-topic',
alert: 'critical-alert-topic',
insufficientData: 'critical-insufficientData-topic'
},
nonCritical: {
alarm: 'nonCritical-alarm-topic'
}
}
});

const config = plugin.getConfig();
const topics = plugin.compileAlertTopics(config);

expect(topics).toEqual({
critical: {
ok: {
Ref: `AwsAlertsCriticalOk`
},
alert: {
Ref: `AwsAlertsCriticalAlert`
},
insufficientData: {
Ref: `AwsAlertsCriticalInsufficientData`
}
},
nonCritical: {
alarm: {
Ref: `AwsAlertsNonCriticalAlarm`
}
}
});

expect(plugin.serverless.service.provider.compiledCloudFormationTemplate.Resources).toEqual({
'AwsAlertsCriticalOk': {
Type: 'AWS::SNS::Topic',
Properties: {
TopicName: 'critical-ok-topic',
Subscription: [],
}
},
'AwsAlertsCriticalAlert': {
Type: 'AWS::SNS::Topic',
Properties: {
TopicName: 'critical-alert-topic',
Subscription: [],
}
},
'AwsAlertsCriticalInsufficientData': {
Type: 'AWS::SNS::Topic',
Properties: {
TopicName: 'critical-insufficientData-topic',
Subscription: [],
}
},
'AwsAlertsNonCriticalAlarm': {
Type: 'AWS::SNS::Topic',
Properties: {
TopicName: 'nonCritical-alarm-topic',
Subscription: [],
}
}
});
});
});

describe('#compileAlarms', () => {
Expand Down Expand Up @@ -795,6 +863,67 @@ describe('#index', function () {
});
});


it('should add nested actions - create topics', () => {
const alertTopics = {
critical: {
ok: 'critical-ok-topic',
alarm: 'critical-alarm-topic',
insufficientData: 'critical-insufficientData-topic',
},
nonCritical: {
ok: 'nonCritical-ok-topic',
alarm: 'nonCritical-alarm-topic',
insufficientData: 'nonCritical-insufficientData-topic',
}
};

const definition = {
description: 'An error alarm',
namespace: 'AWS/Lambda',
metric: 'Errors',
threshold: 1,
statistic: 'Maximum',
period: 300,
evaluationPeriods: 1,
comparisonOperator: 'GreaterThanOrEqualToThreshold',
treatMissingData: 'breaching',
okActions: ['critical', 'nonCritical'],
alarmActions: ['critical', 'nonCritical'],
insufficientDataActions: ['critical', 'nonCritical']
};

const functionName = 'func-name';
const functionRef = 'func-ref';

const cf = plugin.getAlarmCloudFormation(alertTopics, definition, functionName, functionRef);

expect(cf).toEqual({
Type: 'AWS::CloudWatch::Alarm',
Properties: {
AlarmDescription: definition.description,
Namespace: definition.namespace,
MetricName: definition.metric,
Threshold: definition.threshold,
Statistic: definition.statistic,
Period: definition.period,
EvaluationPeriods: definition.evaluationPeriods,
ComparisonOperator: definition.comparisonOperator,
OKActions: ['critical-ok-topic', 'nonCritical-ok-topic'],
AlarmActions: ['critical-alarm-topic', 'nonCritical-alarm-topic'],
InsufficientDataActions: ['critical-insufficientData-topic', 'nonCritical-insufficientData-topic'],
Dimensions: [{
Name: 'FunctionName',
Value: {
Ref: functionRef,
}
}],
TreatMissingData: 'breaching',
}
});
});


it('should use the CloudFormation value ExtendedStatistic for p values', () => {
const alertTopics = {
ok: 'ok-topic',
Expand Down

0 comments on commit d2cc90d

Please sign in to comment.