Repository: ACloudGuru/serverless-plugin-aws-alerts
Pull request #112 (2020-01-21 11:50:34)

Allow stack name to be omitted from alarm names
## What did you implement:

We have a use case that requires us to not include the stack name in the prefix.

## How did you implement it:

By templating it, we allow ourselves to remove it without breaking functionality for other cases.

## How can we verify it:

<!--
Add any applicable config, commands, screenshots or other resources
to make it easy for us to verify this works. The easier you make it for us
to review a PR, the faster we can review and merge it.

Examples:
* Screenshots - Showing the difference between your output and the master
* Other - Anything else that comes to mind to help us evaluate
-->


## Todos:

- [X] Write tests
- [x] Write documentation
- [X] Fix linting errors
- [x] Provide verification config/commands/resources




Patch:
diff --git a/README.md b/README.md
index 359780e..1dafea1 100644
--- a/README.md
+++ b/README.md
@@ -26,6 +26,7 @@ custom:
     dashboards: true
 
     nameTemplate: $[functionName]-$[metricName]-Alarm # Optionally - naming template for alarms, can be overwritten in definitions
+    prefixTemplate: $[stackName] # Optionally - override the alarm name prefix
 
     topics:
       ok: ${self:service}-${opt:stage}-alerts-ok
@@ -38,6 +39,7 @@ custom:
         description: 'My custom alarm'
         namespace: 'AWS/Lambda'
         nameTemplate: $[functionName]-Duration-IMPORTANT-Alarm # Optionally - naming template for the alarms, overwrites globally defined one
+        prefixTemplate: $[stackName] # Optionally - override the alarm name prefix, overwrites globally defined one
         metric: duration
         threshold: 200
         statistic: Average
diff --git a/src/index.js b/src/index.js
index 0f3ef49..c2bcb8a 100644
--- a/src/index.js
+++ b/src/index.js
@@ -140,6 +140,7 @@ class AlertsPlugin {
     if (definition.nameTemplate) {
       alarm.Properties.AlarmName = this.naming.getAlarmName({
         template: definition.nameTemplate,
+        prefixTemplate: definition.prefixTemplate,
         functionLogicalId: functionRef,
         metricName: definition.metric,
         metricId,
@@ -274,7 +275,7 @@ class AlertsPlugin {
       const normalizedFunctionName = this.providerNaming.getLambdaLogicalId(functionName);
 
       const functionAlarms = this.getFunctionAlarms(functionObj, config, definitions);
-      const alarms = globalAlarms.concat(functionAlarms).map(alarm => _.assign({ nameTemplate: config.nameTemplate }, alarm));
+      const alarms = globalAlarms.concat(functionAlarms).map(alarm => _.assign({ nameTemplate: config.nameTemplate, prefixTemplate: config.prefixTemplate }, alarm));
 
       const alarmStatements = alarms.reduce((statements, alarm) => {
         const key = this.naming.getAlarmCloudFormationRef(alarm.name, functionName);
diff --git a/src/naming.js b/src/naming.js
index e00922f..809b631 100644
--- a/src/naming.js
+++ b/src/naming.js
@@ -47,7 +47,15 @@ class Naming {
       .replace('$[metricName]', options.metricName)
       .replace('$[metricId]', options.metricId);
 
-    return `${options.stackName}-${interpolatedTemplate}`;
+    const prefixTemplate = typeof options.prefixTemplate !== 'undefined'
+      ? options.prefixTemplate
+      : '$[stackName]';
+    const interpolatedPrefix = prefixTemplate
+      .replace('$[stackName]', options.stackName);
+
+    return interpolatedPrefix
+      ? `${interpolatedPrefix}-${interpolatedTemplate}`
+      : interpolatedTemplate;
   }
 }
 


Test patch:
diff --git a/src/index.test.js b/src/index.test.js
index 39b4003..24177d5 100644
--- a/src/index.test.js
+++ b/src/index.test.js
@@ -671,6 +671,48 @@ describe('#index', function () {
       });
     });
 
+    it('should use globally defined prefixTemplate when it`s not provided in definitions', function() {
+      let config = {
+        nameTemplate: '$[functionName]-global',
+        prefixTemplate: 'notTheStackName',
+        function: ['functionErrors']
+      };
+
+      const plugin = pluginFactory(config);
+
+      config = plugin.getConfig();
+      const definitions = plugin.getDefinitions(config);
+      const alertTopics = plugin.compileAlertTopics(config);
+
+      plugin.compileAlarms(config, definitions, alertTopics);
+      expect(plugin.serverless.service.provider.compiledCloudFormationTemplate.Resources).toEqual({
+        FooFunctionErrorsAlarm: {
+          Type: 'AWS::CloudWatch::Alarm',
+          Properties: {
+            AlarmName: 'notTheStackName-foo-global',
+            Namespace: 'AWS/Lambda',
+            MetricName: 'Errors',
+            Threshold: 1,
+            Statistic: 'Sum',
+            Period: 60,
+            EvaluationPeriods: 1,
+            DatapointsToAlarm: 1,
+            ComparisonOperator: 'GreaterThanOrEqualToThreshold',
+            AlarmActions: [],
+            OKActions: [],
+            InsufficientDataActions: [],
+            Dimensions: [{
+              Name: 'FunctionName',
+              Value: {
+                Ref: 'FooLambdaFunction'
+              },
+            }],
+            TreatMissingData: 'missing',
+          }
+        }
+      });
+    });
+
     it('should overwrite globally defined nameTemplate using definitions', function() {
       let config = {
         nameTemplate: '$[functionName]-global',
@@ -716,6 +758,54 @@ describe('#index', function () {
         }
       });
     });
+
+    it('should overwrite globally defined prefixTemplate using definitions', function() {
+      let config = {
+        nameTemplate: '$[functionName]-global',
+        prefixTemplate: 'notTheStackName',
+        definitions: {
+          functionErrors: {
+            nameTemplate: '$[functionName]-local',
+            prefixTemplate: 'somethingCompletelyCustom'
+          }
+        },
+        function: ['functionErrors']
+      };
+
+      const plugin = pluginFactory(config);
+
+      config = plugin.getConfig();
+      const definitions = plugin.getDefinitions(config);
+      const alertTopics = plugin.compileAlertTopics(config);
+
+      plugin.compileAlarms(config, definitions, alertTopics);
+      expect(plugin.serverless.service.provider.compiledCloudFormationTemplate.Resources).toEqual({
+        FooFunctionErrorsAlarm: {
+          Type: 'AWS::CloudWatch::Alarm',
+          Properties: {
+            AlarmName: 'somethingCompletelyCustom-foo-local',
+            Namespace: 'AWS/Lambda',
+            MetricName: 'Errors',
+            Threshold: 1,
+            Statistic: 'Sum',
+            Period: 60,
+            EvaluationPeriods: 1,
+            DatapointsToAlarm: 1,
+            ComparisonOperator: 'GreaterThanOrEqualToThreshold',
+            AlarmActions: [],
+            OKActions: [],
+            InsufficientDataActions: [],
+            Dimensions: [{
+              Name: 'FunctionName',
+              Value: {
+                Ref: 'FooLambdaFunction'
+              },
+            }],
+            TreatMissingData: 'missing',
+          }
+        }
+      });
+    });
   });
 
   describe('#compileCloudWatchAlarms', () => {
diff --git a/src/naming.test.js b/src/naming.test.js
index 195ae6e..8c6588a 100644
--- a/src/naming.test.js
+++ b/src/naming.test.js
@@ -70,5 +70,35 @@ describe('#naming', function () {
 
       expect(actual).toEqual(expected);
     });
+
+    it('should interpolate alarm prefix', () => {
+      const template = '$[functionName]-$[functionId]-$[metricName]-$[metricId]';
+      const prefixTemplate = 'notTheStackName';
+      const functionName = 'function';
+      const functionLogicalId = 'functionId';
+      const metricName = 'metric';
+      const metricId = 'metricId';
+      const stackName = 'fooservice-dev';
+
+      const expected = `notTheStackName-${functionName}-${functionLogicalId}-${metricName}-${metricId}`;
+      const actual = naming.getAlarmName({ template, prefixTemplate, functionName, functionLogicalId, metricName, metricId, stackName });
+
+      expect(actual).toEqual(expected);
+    });
+
+    it('should interpolate an empty alarm prefix', () => {
+      const template = '$[functionName]-$[functionId]-$[metricName]-$[metricId]';
+      const prefixTemplate = '';
+      const functionName = 'function';
+      const functionLogicalId = 'functionId';
+      const metricName = 'metric';
+      const metricId = 'metricId';
+      const stackName = 'fooservice-dev';
+
+      const expected = `${functionName}-${functionLogicalId}-${metricName}-${metricId}`;
+      const actual = naming.getAlarmName({ template, prefixTemplate, functionName, functionLogicalId, metricName, metricId, stackName });
+
+      expect(actual).toEqual(expected);
+    });
   });
 });