From 75546635208c0e5c7ba025bcd1678fe47e91fc7f Mon Sep 17 00:00:00 2001 From: LironEr Date: Sat, 2 Dec 2023 17:27:13 +0200 Subject: [PATCH] feat: add suffixTemplate property to add suffix to alarm name (#3) --- .../{release.yml => release-please.yml} | 2 +- README.md | 2 + src/__tests__/ServerlessAwsAlarms.test.ts | 9 ++- src/consts/naming.ts | 1 + src/types.ts | 1 + src/utils/__tests__/config.test.ts | 11 +++- src/utils/__tests__/naming.test.ts | 57 ++++++++++++++++++- src/utils/alarms.ts | 1 + src/utils/config.ts | 9 ++- src/utils/naming.ts | 8 ++- tests/integration/integration.test.ts | 4 +- tests/integration/serverless/basic.yml | 5 ++ tests/utils/alarms.ts | 2 +- 13 files changed, 97 insertions(+), 15 deletions(-) rename .github/workflows/{release.yml => release-please.yml} (94%) diff --git a/.github/workflows/release.yml b/.github/workflows/release-please.yml similarity index 94% rename from .github/workflows/release.yml rename to .github/workflows/release-please.yml index a5cf2f4..9f24959 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release-please.yml @@ -1,4 +1,4 @@ -name: Release +name: release-please on: push: diff --git a/README.md b/README.md index 29ac4a5..18a02cc 100644 --- a/README.md +++ b/README.md @@ -30,9 +30,11 @@ custom: critical: # optional - create more actions ok: !Ref MyTeamCriticalAlertsTopic # you can also use !Ref and other CloudFormation functions alarm: !ImportValue 'my-team-alerts' + defaults: nameTemplate: $[functionName]-$[metricName]-alarm # Optional - naming template for alarms, can be overwritten in definitions prefixTemplate: $[stackName] # Optional - override the alarm name prefix + suffixTemplate: alarm # Optional - override the alarm name suffix definitions: # these defaults are merged with your definitions lambdaErrors: diff --git a/src/__tests__/ServerlessAwsAlarms.test.ts b/src/__tests__/ServerlessAwsAlarms.test.ts index 4236431..6552a04 100644 --- a/src/__tests__/ServerlessAwsAlarms.test.ts +++ b/src/__tests__/ServerlessAwsAlarms.test.ts @@ -1,6 +1,6 @@ import { MockLogging, MockServerless } from '@tests/utils/MockServerless'; import { PluginConfig } from '../types'; -import { DEFAULT_NAME_TEMPLATE, DEFAULT_PREFIX_TEMPLATE } from '../consts/naming'; +import { DEFAULT_NAME_TEMPLATE, DEFAULT_PREFIX_TEMPLATE, DEFAULT_SUFFIX_TEMPLATE } from '../consts/naming'; import { DEFAULT_ALARM_DEFINITIONS } from '../consts/definitions'; import { ServerlessAwsAlarms } from '../ServerlessAwsAlarms'; import { TEST_ACCOUNT_NUM } from '@tests/consts'; @@ -38,6 +38,7 @@ describe('ServerlessAwsAlarms', () => { enabled: true, prefixTemplate: DEFAULT_PREFIX_TEMPLATE, nameTemplate: DEFAULT_NAME_TEMPLATE, + suffixTemplate: DEFAULT_SUFFIX_TEMPLATE, }, definitions: DEFAULT_ALARM_DEFINITIONS, }); @@ -56,6 +57,7 @@ describe('ServerlessAwsAlarms', () => { enabled: true, prefixTemplate: DEFAULT_PREFIX_TEMPLATE, nameTemplate: DEFAULT_NAME_TEMPLATE, + suffixTemplate: DEFAULT_SUFFIX_TEMPLATE, }, definitions: DEFAULT_ALARM_DEFINITIONS, }); @@ -65,7 +67,7 @@ describe('ServerlessAwsAlarms', () => { const serverless = new MockServerless(); serverless.service.custom.awsAlarms = { defaults: { - prefixTemplate: '', + suffixTemplate: 'warning', }, } as PluginConfig; @@ -76,8 +78,9 @@ describe('ServerlessAwsAlarms', () => { expect(plugin.config).toEqual({ defaults: { enabled: true, - prefixTemplate: '', + prefixTemplate: DEFAULT_PREFIX_TEMPLATE, nameTemplate: DEFAULT_NAME_TEMPLATE, + suffixTemplate: 'warning', }, definitions: DEFAULT_ALARM_DEFINITIONS, }); diff --git a/src/consts/naming.ts b/src/consts/naming.ts index c1f3d34..cb1c932 100644 --- a/src/consts/naming.ts +++ b/src/consts/naming.ts @@ -1,3 +1,4 @@ export const DEFAULT_NAME_TEMPLATE = '$[lambdaName]-$[definitionName]'; export const DEFAULT_PREFIX_TEMPLATE = ''; +export const DEFAULT_SUFFIX_TEMPLATE = ''; export const DEFAULT_METRIC_FILTER_NAME_TEMPLATE = '$[lambdaName]$[definitionName]AlarmFilter'; diff --git a/src/types.ts b/src/types.ts index 54986f9..86b5af9 100644 --- a/src/types.ts +++ b/src/types.ts @@ -13,6 +13,7 @@ export interface AlarmDefinition { enabled?: boolean; prefixTemplate?: string; nameTemplate?: string; + suffixTemplate?: string; description?: string; namespace?: string; metric?: string; diff --git a/src/utils/__tests__/config.test.ts b/src/utils/__tests__/config.test.ts index 160b382..b673f0e 100644 --- a/src/utils/__tests__/config.test.ts +++ b/src/utils/__tests__/config.test.ts @@ -1,7 +1,7 @@ import { ZodError } from 'zod'; import { validateConfig } from '../config'; import { PluginConfig } from '../../types'; -import { DEFAULT_NAME_TEMPLATE, DEFAULT_PREFIX_TEMPLATE } from '../../consts/naming'; +import { DEFAULT_NAME_TEMPLATE, DEFAULT_PREFIX_TEMPLATE, DEFAULT_SUFFIX_TEMPLATE } from '../../consts/naming'; describe('config utils', () => { describe('validateConfig', () => { @@ -15,6 +15,7 @@ describe('config utils', () => { enabled: true, prefixTemplate: DEFAULT_PREFIX_TEMPLATE, nameTemplate: DEFAULT_NAME_TEMPLATE, + suffixTemplate: DEFAULT_SUFFIX_TEMPLATE, }, }); }); @@ -25,6 +26,7 @@ describe('config utils', () => { enabled: true, prefixTemplate: DEFAULT_PREFIX_TEMPLATE, nameTemplate: DEFAULT_NAME_TEMPLATE, + suffixTemplate: DEFAULT_SUFFIX_TEMPLATE, }, }); }); @@ -32,14 +34,17 @@ describe('config utils', () => { test('override some defaults', () => { const input: PluginConfig = { defaults: { - prefixTemplate: '', + suffixTemplate: 'critical', + treatMissingData: 'notBreaching', }, }; expect(validateConfig(input)).toEqual({ defaults: { enabled: true, - prefixTemplate: '', + prefixTemplate: DEFAULT_PREFIX_TEMPLATE, nameTemplate: DEFAULT_NAME_TEMPLATE, + suffixTemplate: 'critical', + treatMissingData: 'notBreaching', }, }); }); diff --git a/src/utils/__tests__/naming.test.ts b/src/utils/__tests__/naming.test.ts index 8a046ff..fe2d87e 100644 --- a/src/utils/__tests__/naming.test.ts +++ b/src/utils/__tests__/naming.test.ts @@ -1,4 +1,10 @@ -import { replacePlaceholders, getNamespaceForMetricFilter, getMetricNameForMetricFilter } from '../naming'; +import { + replacePlaceholders, + getAlarmName, + getNamespaceForMetricFilter, + getMetricNameForMetricFilter, + GetAlarmNameOptions, +} from '../naming'; describe('naming utils', () => { describe('replacePlaceholders', () => { @@ -29,6 +35,55 @@ describe('naming utils', () => { }); }); + describe('getAlarmName', () => { + const placeholders: GetAlarmNameOptions['placeholders'] = { + stackName: 'stackName', + lambdaName: 'lambdaName', + lambdaId: 'lambdaId', + lambdaLogicalId: 'lambdaLogicalId', + metricName: 'metricName', + definitionName: 'definitionName', + }; + + test('only with nameTemplate', () => { + expect(getAlarmName({ nameTemplate: '$[lambdaName]-$[definitionName]-alarm', placeholders })).toEqual( + 'lambdaName-definitionName-alarm', + ); + }); + + test('nameTemplate with prefix', () => { + expect( + getAlarmName({ + nameTemplate: '$[definitionName]-alarm', + prefixTemplate: '$[lambdaName]', + suffixTemplate: '', + placeholders, + }), + ).toEqual('lambdaName-definitionName-alarm'); + }); + + test('nameTemplate with suffix', () => { + expect( + getAlarmName({ + nameTemplate: '$[lambdaName]-$[definitionName]', + suffixTemplate: '$[stackName]-warning', + placeholders, + }), + ).toEqual('lambdaName-definitionName-stackName-warning'); + }); + + test('nameTemplate with prefix and suffix', () => { + expect( + getAlarmName({ + nameTemplate: '$[lambdaName]-$[definitionName]', + prefixTemplate: '$[stackName]', + suffixTemplate: 'warning', + placeholders, + }), + ).toEqual('stackName-lambdaName-definitionName-warning'); + }); + }); + describe('getNamespaceForMetricFilter', () => { test('with namespace', () => { expect(getNamespaceForMetricFilter('stackName', 'namespace')).toEqual('namespace'); diff --git a/src/utils/alarms.ts b/src/utils/alarms.ts index 44bf72a..1dcc15d 100644 --- a/src/utils/alarms.ts +++ b/src/utils/alarms.ts @@ -53,6 +53,7 @@ export function generateCloudFormationResourcesForDefinition( AlarmName: getAlarmName({ nameTemplate: alarm.nameTemplate, prefixTemplate: alarm.prefixTemplate, + suffixTemplate: alarm.suffixTemplate, placeholders: { definitionName: alarmDefinitionName, lambdaId, diff --git a/src/utils/config.ts b/src/utils/config.ts index b764c97..6d755a6 100644 --- a/src/utils/config.ts +++ b/src/utils/config.ts @@ -1,6 +1,11 @@ import { z } from 'zod'; import { AlarmMetricFilterDefinition, AlarmActions, AlarmDefinition, PluginConfig } from '../types'; -import { DEFAULT_NAME_TEMPLATE, DEFAULT_PREFIX_TEMPLATE, DEFAULT_METRIC_FILTER_NAME_TEMPLATE } from '../consts/naming'; +import { + DEFAULT_NAME_TEMPLATE, + DEFAULT_PREFIX_TEMPLATE, + DEFAULT_SUFFIX_TEMPLATE, + DEFAULT_METRIC_FILTER_NAME_TEMPLATE, +} from '../consts/naming'; const alarmMetricFilterDefSchema = z.object({ pattern: z.string(), @@ -13,6 +18,7 @@ const alarmDefSchema = z.object({ enabled: z.boolean().default(true), prefixTemplate: z.optional(z.string()), nameTemplate: z.optional(z.string()), + suffixTemplate: z.optional(z.string()), description: z.optional(z.string().max(1024)), namespace: z.optional(z.string()), metric: z.optional(z.string()), @@ -55,6 +61,7 @@ const alarmActionsSchema = z.object({ const defaultsDefsSchema = alarmDefSchema.extend({ prefixTemplate: z.optional(z.string()).default(DEFAULT_PREFIX_TEMPLATE), nameTemplate: z.optional(z.string()).default(DEFAULT_NAME_TEMPLATE), + suffixTemplate: z.optional(z.string()).default(DEFAULT_SUFFIX_TEMPLATE), }) satisfies z.ZodType; const configSchema = z.object({ diff --git a/src/utils/naming.ts b/src/utils/naming.ts index a885577..bf9df10 100644 --- a/src/utils/naming.ts +++ b/src/utils/naming.ts @@ -2,18 +2,20 @@ import { DEFAULT_NAME_TEMPLATE, DEFAULT_METRIC_FILTER_NAME_TEMPLATE } from '../c type BasePlaceholders = 'stackName' | 'lambdaName' | 'lambdaId' | 'lambdaLogicalId' | 'metricName' | 'definitionName'; -interface GetAlarmNameOptions { +export interface GetAlarmNameOptions { nameTemplate?: string; prefixTemplate?: string; + suffixTemplate?: string; placeholders: Record; } -export function getAlarmName({ nameTemplate, prefixTemplate, placeholders }: GetAlarmNameOptions) { +export function getAlarmName({ nameTemplate, prefixTemplate, suffixTemplate, placeholders }: GetAlarmNameOptions) { // nameTemplate shouldn't be undefined here, but anyway we have a default value just in case const alarmName = replacePlaceholders(nameTemplate || DEFAULT_NAME_TEMPLATE, placeholders); const prefix = prefixTemplate ? replacePlaceholders(prefixTemplate, placeholders) : undefined; + const suffix = suffixTemplate ? replacePlaceholders(suffixTemplate, placeholders) : undefined; - return prefix ? `${prefix}-${alarmName}` : alarmName; + return [prefix, alarmName, suffix].filter((x) => !!x).join('-'); } interface GetMetricFilterNameOptions { diff --git a/tests/integration/integration.test.ts b/tests/integration/integration.test.ts index 9eae4d6..7dc8bbb 100644 --- a/tests/integration/integration.test.ts +++ b/tests/integration/integration.test.ts @@ -55,7 +55,7 @@ describe('integration tests', () => { // verify alarm props - assertAlarm(alarms, `${serviceName}-some-func-lambdaErrors`, { + assertAlarm(alarms, `${serviceName}-some-func-lambdaErrors-warning`, { Namespace: 'AWS/Lambda', MetricName: 'Errors', ComparisonOperator: 'GreaterThanOrEqualToThreshold', @@ -72,7 +72,7 @@ describe('integration tests', () => { ], }); - assertAlarm(alarms, `${serviceName}-some-func2-lambdaErrors`, { + assertAlarm(alarms, `${serviceName}-some-func2-lambdaErrors-warning`, { Namespace: 'AWS/Lambda', MetricName: 'Errors', ComparisonOperator: 'GreaterThanOrEqualToThreshold', diff --git a/tests/integration/serverless/basic.yml b/tests/integration/serverless/basic.yml index 31cdb25..7ea8c7e 100644 --- a/tests/integration/serverless/basic.yml +++ b/tests/integration/serverless/basic.yml @@ -27,6 +27,10 @@ custom: ok: 'arn:aws:sns:${self:provider.region}:${aws:accountId}:my-team-alerts-ok' alarm: 'arn:aws:sns:${self:provider.region}:${aws:accountId}:my-team-alerts-alarm' insufficientData: 'arn:aws:sns:${self:provider.region}:${aws:accountId}:my-team-alerts-insufficientData' + + defaults: + suffixTemplate: 'warning' + definitions: lambdaErrors: enabled: true @@ -55,6 +59,7 @@ functions: handler: handlers.someFunc alarms: criticalLambdaErrors: + suffixTemplate: '' namespace: 'AWS/Lambda' metric: 'Errors' comparisonOperator: 'GreaterThanOrEqualToThreshold' diff --git a/tests/utils/alarms.ts b/tests/utils/alarms.ts index 135ecae..77aa92f 100644 --- a/tests/utils/alarms.ts +++ b/tests/utils/alarms.ts @@ -18,7 +18,7 @@ export function assertAlarm(alarms: MetricAlarm[], alarmName: string, expected: const alarm = alarms.find((a) => a.AlarmName === alarmName); if (!alarm) { - throw new Error(`Alarm ${alarmName} not found`); + throw new Error(`Alarm ${alarmName} not found in ${JSON.stringify(alarms.map((a) => a.AlarmName))}`); } expect(alarm).toMatchObject(expected);