Skip to content

Commit

Permalink
Refactor alarm modules and combine additional resources into compiled…
Browse files Browse the repository at this point in the history
…Template
  • Loading branch information
direnakkoc committed Mar 20, 2023
1 parent 4f40fce commit 3571dc4
Show file tree
Hide file tree
Showing 33 changed files with 273 additions and 314 deletions.
29 changes: 14 additions & 15 deletions core/alarms/alarms.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,8 @@ import createRuleAlarms from './eventbridge'
import createALBAlarms from './alb'
import createALBTargetAlarms from './alb-target-group'
import createAppSyncAlarms from './appsync'
import { type ResourceType } from './../cf-template'

export default function addAlarms (alarmProperties: AllAlarmsConfig, functionAlarmProperties: FunctionAlarmProperties, context: Context, compiledTemplate: Template, additionalResources: ResourceType = {}): void {
export default function addAlarms (alarmProperties: AllAlarmsConfig, functionAlarmProperties: FunctionAlarmProperties, context: Context, compiledTemplate: Template): void {
const {
Lambda: lambdaConfig,
ApiGateway: apiGwConfig,
Expand All @@ -37,18 +36,18 @@ export default function addAlarms (alarmProperties: AllAlarmsConfig, functionAla

const cascadedFunctionAlarmProperties = applyAlarmConfig(lambdaConfig, functionAlarmProperties)

if (alarmProperties.enabled != null) {
createLambdaAlarms(cascadedFunctionAlarmProperties, context, compiledTemplate, additionalResources)
apiGwConfig?.enabled != null && createApiGatewayAlarms(apiGwConfig, context, compiledTemplate, additionalResources)
sfConfig?.enabled != null && createStatesAlarms(sfConfig, context, compiledTemplate, additionalResources)
dynamoDbConfig?.enabled != null && createDynamoDbAlarms(dynamoDbConfig, context, compiledTemplate, additionalResources)
kinesisConfig?.enabled != null && createKinesisAlarms(kinesisConfig, context, compiledTemplate, additionalResources)
sqsConfig?.enabled != null && createSQSAlarms(sqsConfig, context, compiledTemplate, additionalResources)
ecsConfig?.enabled != null && createECSAlarms(ecsConfig, context, compiledTemplate, additionalResources)
snsConfig?.enabled != null && createSNSAlarms(snsConfig, context, compiledTemplate, additionalResources)
ruleConfig?.enabled != null && createRuleAlarms(ruleConfig, context, compiledTemplate, additionalResources)
albConfig?.enabled != null && createALBAlarms(albConfig, context, compiledTemplate, additionalResources)
albTargetConfig?.enabled != null && createALBTargetAlarms(albTargetConfig, context, compiledTemplate, additionalResources)
appSyncConfig?.enabled != null && createAppSyncAlarms(appSyncConfig, context, compiledTemplate, additionalResources)
if (alarmProperties.enabled ?? true) {
createLambdaAlarms(cascadedFunctionAlarmProperties, context, compiledTemplate)
apiGwConfig?.enabled != null && createApiGatewayAlarms(apiGwConfig, context, compiledTemplate)
sfConfig?.enabled != null && createStatesAlarms(sfConfig, context, compiledTemplate)
dynamoDbConfig?.enabled != null && createDynamoDbAlarms(dynamoDbConfig, context, compiledTemplate)
kinesisConfig?.enabled != null && createKinesisAlarms(kinesisConfig, context, compiledTemplate)
sqsConfig?.enabled != null && createSQSAlarms(sqsConfig, context, compiledTemplate)
ecsConfig?.enabled != null && createECSAlarms(ecsConfig, context, compiledTemplate)
snsConfig?.enabled != null && createSNSAlarms(snsConfig, context, compiledTemplate)
ruleConfig?.enabled != null && createRuleAlarms(ruleConfig, context, compiledTemplate)
albConfig?.enabled != null && createALBAlarms(albConfig, context, compiledTemplate)
albTargetConfig?.enabled != null && createALBTargetAlarms(albTargetConfig, context, compiledTemplate)
appSyncConfig?.enabled != null && createAppSyncAlarms(appSyncConfig, context, compiledTemplate)
}
}
31 changes: 12 additions & 19 deletions core/alarms/alb-target-group.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,12 @@ export interface AlbTargetAlarmProperties {
LambdaUserError: DefaultAlarmsProperties
}

export type AlbTargetAlarm = AlarmProperties & {
TargetGroupResourceName: string
LoadBalancerLogicalId: string
}

type TargetGroupMetrics = 'HTTPCode_Target_5XX_Count' | 'UnHealthyHostCount'

type TargetGroupLambdaMetrics = 'LambdaInternalError' | 'LambdaUserError'

function getResourceByName (resourceName: string, compiledTemplate, additionalResources = {}): Resource {
return compiledTemplate.Resources[resourceName] ?? additionalResources[resourceName]
function getResourceByName (resourceName: string, compiledTemplate): Resource {
return compiledTemplate.Resources[resourceName]
}

const executionMetrics: TargetGroupMetrics[] = [
Expand All @@ -48,10 +43,10 @@ const executionMetricsLambda: TargetGroupLambdaMetrics[] = [
* A CloudFormation template instance
* All Load Balancers CloudFormation logicalIDs
*/
export function findLoadBalancersForTargetGroup (targetGroupLogicalId: string, compiledTemplate: Template, additionalResources: ResourceType = {}): string[] {
export function findLoadBalancersForTargetGroup (targetGroupLogicalId: string, compiledTemplate: Template): string[] {
const allLoadBalancerLogicalIds: any = new Set()
const allListenerRules: ResourceType = {}
const listenerResources = getResourcesByType('AWS::ElasticLoadBalancingV2::Listener', compiledTemplate, additionalResources)
const listenerResources = getResourcesByType('AWS::ElasticLoadBalancingV2::Listener', compiledTemplate)

// First, find Listeners with _default actions_ referencing the target group
for (const listener of Object.values(listenerResources)) {
Expand All @@ -65,7 +60,7 @@ export function findLoadBalancersForTargetGroup (targetGroupLogicalId: string, c
}
}
}
const listenerRuleResources = getResourcesByType('AWS::ElasticLoadBalancingV2::ListenerRule', compiledTemplate, additionalResources)
const listenerRuleResources = getResourcesByType('AWS::ElasticLoadBalancingV2::ListenerRule', compiledTemplate)

// Second, find ListenerRules with actions referncing the target group, then follow to the rules' listeners
for (const [listenerRuleLogicalId, listenerRule] of Object.entries(listenerRuleResources)) {
Expand All @@ -80,7 +75,7 @@ export function findLoadBalancersForTargetGroup (targetGroupLogicalId: string, c

for (const listenerRule of Object.values(allListenerRules)) {
const listenerLogicalId = listenerRule.Properties?.ListenerArn.Ref
const listener = getResourceByName(listenerLogicalId, compiledTemplate, additionalResources)
const listener = getResourceByName(listenerLogicalId, compiledTemplate)
if (listener != null) {
const loadBalancerLogicalId = listener.Properties?.LoadBalancerArn?.Ref
if (loadBalancerLogicalId != null) {
Expand All @@ -96,12 +91,10 @@ function alarmProperty (targetGroupResourceName: string, metrics: string[], load
for (const loadBalancerLogicalId of loadBalancerLogicalIds) {
const config = albTargetAlarmProperties[metric]
if (config.enabled !== false) {
const threshold = config.Threshold
const albTargetAlarmProperties: AlbTargetAlarm = {
delete config.enabled
const albTargetAlarmProperties: AlarmProperties = {
AlarmName: `LoadBalancer${metric.replaceAll('_', '')}Alarm_${targetGroupResourceName}`,
AlarmDescription: `LoadBalancer ${metric} ${getStatisticName(config)} for ${targetGroupResourceName} breaches ${threshold}`,
TargetGroupResourceName: targetGroupResourceName,
LoadBalancerLogicalId: loadBalancerLogicalId,
AlarmDescription: `LoadBalancer ${metric} ${getStatisticName(config)} for ${targetGroupResourceName} breaches ${config.Threshold}`,
MetricName: metric,
Statistic: config.Statistic,
Namespace: 'AWS/ApplicationELB',
Expand All @@ -122,17 +115,17 @@ function alarmProperty (targetGroupResourceName: string, metrics: string[], load
/**
* The fully resolved alarm configuration
*/
export default function createALBTargetAlarms (albTargetAlarmProperties: AlbTargetAlarmProperties, context: Context, compiledTemplate: Template, additionalResources: ResourceType = {}): void {
export default function createALBTargetAlarms (albTargetAlarmProperties: AlbTargetAlarmProperties, context: Context, compiledTemplate: Template): void {
/**
* Add all required Application Load Balancer alarms for Target Group to the provided CloudFormation template
* based on the resources found within
*
* A CloudFormation template object
*/

const targetGroupResources = getResourcesByType('AWS::ElasticLoadBalancingV2::TargetGroup', compiledTemplate, additionalResources)
const targetGroupResources = getResourcesByType('AWS::ElasticLoadBalancingV2::TargetGroup', compiledTemplate)
for (const [targetGroupResourceName, targetGroupResource] of Object.entries(targetGroupResources)) {
const loadBalancerLogicalIds = findLoadBalancersForTargetGroup(targetGroupResourceName, compiledTemplate, additionalResources)
const loadBalancerLogicalIds = findLoadBalancersForTargetGroup(targetGroupResourceName, compiledTemplate)
alarmProperty(targetGroupResourceName, executionMetrics, loadBalancerLogicalIds, albTargetAlarmProperties, compiledTemplate, context)

if (targetGroupResource.Properties?.TargetType === 'lambda') {
Expand Down
24 changes: 12 additions & 12 deletions core/alarms/alb.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use strict'

import { addResource, getResourcesByType, type ResourceType } from '../cf-template'
import { addResource, getResourcesByType } from '../cf-template'
import { type Context, createAlarm, type DefaultAlarmsProperties } from './default-config-alarms'
import { getStatisticName } from './get-statistic-name'
import { makeResourceName } from './make-name'
Expand All @@ -15,10 +15,6 @@ export interface AlbAlarmProperties {

type AlbMetrics = 'HTTPCode_ELB_5XX_Count' | 'RejectedConnectionCount'

export type AlbAlarm = AlarmProperties & {
LoadBalancerResourceName: string
}

const executionMetrics: AlbMetrics[] = [
'HTTPCode_ELB_5XX_Count',
'RejectedConnectionCount'
Expand All @@ -27,24 +23,23 @@ const executionMetrics: AlbMetrics[] = [
/**
* albAlarmProperties The fully resolved alarm configuration
*/
export default function createALBAlarms (albAlarmProperties: AlbAlarmProperties, context: Context, compiledTemplate: Template, additionalResources: ResourceType = {}) {
export default function createALBAlarms (albAlarmProperties: AlbAlarmProperties, context: Context, compiledTemplate: Template) {
/**
* Add all required Application Load Balancer alarms for Application Load Balancer to the provided CloudFormation template
* based on the resources found within
*
* A CloudFormation template object
*/
const loadBalancerResources = getResourcesByType('AWS::ElasticLoadBalancingV2::LoadBalancer', compiledTemplate, additionalResources)
const loadBalancerResources = getResourcesByType('AWS::ElasticLoadBalancingV2::LoadBalancer', compiledTemplate)

for (const [loadBalancerResourceName] of Object.entries(loadBalancerResources)) {
for (const metric of executionMetrics) {
const config = albAlarmProperties[metric]
if (config.enabled !== false) {
const threshold = config.Threshold
const albAlarmProperties: AlbAlarm = {
delete config.enabled
const albAlarmProperties: AlarmProperties = {
AlarmName: `LoadBalancer${metric.replaceAll('_', '')}Alarm_${loadBalancerResourceName}`,
AlarmDescription: `LoadBalancer ${metric} ${getStatisticName(config)} for ${loadBalancerResourceName} breaches ${threshold}`,
LoadBalancerResourceName: loadBalancerResourceName,
AlarmDescription: `LoadBalancer ${metric} ${getStatisticName(config)} for ${loadBalancerResourceName} breaches ${config.Threshold}`,
MetricName: metric,
Namespace: 'AWS/ApplicationELB',
Dimensions: [
Expand All @@ -54,7 +49,12 @@ export default function createALBAlarms (albAlarmProperties: AlbAlarmProperties,
}
const resourceName = makeResourceName('LoadBalancer', loadBalancerResourceName, metric)
const resource = createAlarm(albAlarmProperties, context)
addResource(resourceName, resource, compiledTemplate)
addResource(resourceName, resource, compiledTemplate) // Suggested alternativre
// is to return the resource(s) rather than adding them to the template
// THey can be added to the template at a higher level
// return {
// [resourceName]: resource
// }
}
}
}
Expand Down
37 changes: 16 additions & 21 deletions core/alarms/api-gateway.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use strict'

import { getResourcesByType, addResource, type ResourceType } from '../cf-template'
import { getResourcesByType, addResource } from '../cf-template'
import { type Context, createAlarm, type DefaultAlarmsProperties } from './default-config-alarms'
import { getStatisticName } from './get-statistic-name'
import { makeResourceName } from './make-name'
Expand All @@ -15,12 +15,6 @@ export interface ApiGwAlarmProperties {
Latency: DefaultAlarmsProperties
}

export type ApiAlarm = AlarmProperties & {
ApiName: string
}

// type ApiMetrics = '5XXError' | '4XXError' | 'Latency'

/**
* Given a CloudFormation resource for an API Gateway REST API, derive CloudFormation syntax for
* the API name.
Expand Down Expand Up @@ -71,41 +65,42 @@ export function resolveRestApiNameForSub (restApiResource: Resource, restApiLogi
}
return name
}
type ApiMetrics = '5XXError' | '4XXError' | 'Latency'

const executionMetrics = {
'5XXError': 'Availability',
'4XXError': '4XXError',
Latency: 'Latency'
}
const executionMetrics: ApiMetrics[] = [
'5XXError',
'4XXError',
'Latency'
]

/**
* apiGwAlarmProperties The fully resolved alarm configuration
*/
export default function createApiGatewayAlarms (apiGwAlarmProperties: ApiGwAlarmProperties, context: Context, compiledTemplate: Template, additionalResources: ResourceType = {}): void {
export default function createApiGatewayAlarms (apiGwAlarmProperties: ApiGwAlarmProperties, context: Context, compiledTemplate: Template): void {
/**
* Add all required API Gateway alarms to the provided CloudFormation template
* based on the resources found within
*A CloudFormation template object
*/
const apiResources = getResourcesByType('AWS::ApiGateway::RestApi', compiledTemplate, additionalResources)
const apiResources = getResourcesByType('AWS::ApiGateway::RestApi', compiledTemplate)

for (const [apiResourceName, apiResource] of Object.entries(apiResources)) {
for (const [metric, type] of Object.entries(executionMetrics)) {
const config: DefaultAlarmsProperties = apiGwAlarmProperties[metric]
for (const metric of executionMetrics) {
const config = apiGwAlarmProperties[metric]
console.log(metric)
if (config.enabled !== false) {
delete config.enabled
const apiName = resolveRestApiNameAsCfn(apiResource, apiResourceName)
const apiNameForSub = resolveRestApiNameForSub(apiResource, apiResourceName)
const threshold = config.Threshold
const apiAlarmProperties: ApiAlarm = {
const apiAlarmProperties: AlarmProperties = {
AlarmName: `APIGW_${metric}_${apiNameForSub}`,
AlarmDescription: `API Gateway ${metric} ${getStatisticName(config)} for ${apiNameForSub} breaches ${threshold}`,
ApiName: apiName,
AlarmDescription: `API Gateway ${metric} ${getStatisticName(config)} for ${apiNameForSub} breaches ${config.Threshold}`,
MetricName: metric,
Namespace: 'AWS/ApiGateway',
Dimensions: [{ Name: 'ApiName', Value: apiName }],
...config
}
const resourceName = makeResourceName('Api', apiName, executionMetrics[type])
const resourceName = makeResourceName('Api', apiName, metric)
const resource = createAlarm(apiAlarmProperties, context)
addResource(resourceName, resource, compiledTemplate)
}
Expand Down
16 changes: 5 additions & 11 deletions core/alarms/appsync.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use strict'

import { getResourcesByType, addResource, type ResourceType } from '../cf-template'
import { getResourcesByType, addResource } from '../cf-template'
import { type Context, createAlarm, type DefaultAlarmsProperties } from './default-config-alarms'
import { getStatisticName } from './get-statistic-name'
import { makeResourceName } from './make-name'
Expand All @@ -13,37 +13,31 @@ export interface AppSyncAlarmProperties {
Latency: DefaultAlarmsProperties
}

export type AppSyncAlarm = AlarmProperties & {
AppSyncResourceName: string
}

type AppSyncMetrics = '5XXError' | 'Latency'

const executionMetrics: AppSyncMetrics[] = ['5XXError', 'Latency']

/**
* appSyncAlarmProperties The fully resolved alarm configuration
*/
export default function createAppSyncAlarms (appSyncAlarmProperties: AppSyncAlarmProperties, context: Context, compiledTemplate: Template, additionalResources: ResourceType = {}) {
export default function createAppSyncAlarms (appSyncAlarmProperties: AppSyncAlarmProperties, context: Context, compiledTemplate: Template) {
/**
* Add all required AppSync alarms to the provided CloudFormation template
* based on the AppSync resources found within
*
* A CloudFormation template object
*/
const appSyncResources = getResourcesByType('AWS::AppSync::GraphQLApi', compiledTemplate, additionalResources)
const appSyncResources = getResourcesByType('AWS::AppSync::GraphQLApi', compiledTemplate)

for (const [appSyncResourceName, appSyncResource] of Object.entries(appSyncResources)) {
for (const metric of executionMetrics) {
const config = appSyncAlarmProperties[metric]
if (config.enabled !== false) {
const graphQLName: string = appSyncResource.Properties?.Name
const threshold = config.Threshold
delete config.enabled
const appSyncAlarmProperties: AppSyncAlarm = {
const appSyncAlarmProperties: AlarmProperties = {
AlarmName: `AppSync${metric}Alarm_${graphQLName}`,
AlarmDescription: `AppSync ${metric} ${getStatisticName(config)} for ${graphQLName} breaches ${threshold}`,
AppSyncResourceName: appSyncResourceName,
AlarmDescription: `AppSync ${metric} ${getStatisticName(config)} for ${graphQLName} breaches ${config.Threshold}`,
MetricName: metric,
Namespace: 'AWS/AppSync',
Dimensions: [
Expand Down
8 changes: 5 additions & 3 deletions core/alarms/dynamodb.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use strict'

import { getResourcesByType, addResource, type ResourceType } from '../cf-template'
import { getResourcesByType, addResource } from '../cf-template'
import { type Context, createAlarm, type DefaultAlarmsProperties } from './default-config-alarms'
import { makeResourceName } from './make-name'
import { type AlarmProperties } from 'cloudform-types/types/cloudWatch/alarm'
Expand All @@ -24,19 +24,20 @@ const dynamoDbGsiMetrics: DynamoDbGsiMetrics[] = ['ReadThrottleEvents', 'WriteTh
/**
* dynamoDbAlarmProperties The fully resolved alarm configuration
*/
export default function createDynamoDbAlarms (dynamoDbAlarmProperties: DynamoDbAlarmProperties, context: Context, compiledTemplate: Template, additionalResources: ResourceType = {}) {
export default function createDynamoDbAlarms (dynamoDbAlarmProperties: DynamoDbAlarmProperties, context: Context, compiledTemplate: Template) {
/**
* Add all required DynamoDB alarms to the provided CloudFormation template
* based on the tables and their global secondary indices.
*
*/
const tableResources = getResourcesByType('AWS::DynamoDB::Table', compiledTemplate, additionalResources)
const tableResources = getResourcesByType('AWS::DynamoDB::Table', compiledTemplate)

for (const [tableResourceName, tableResource] of Object.entries(tableResources)) {
for (const metric of dynamoDbMetrics) {
const config = dynamoDbAlarmProperties[metric]
if (config.enabled !== false) {
const config = dynamoDbAlarmProperties[metric]
delete config.enabled
const dynamoDBAlarmProperties: AlarmProperties = {
AlarmName: `DDB_${metric}_${tableResourceName}`,
AlarmDescription: `DynamoDB ${config.Statistic} for ${tableResourceName} breaches ${config.Threshold}`,
Expand All @@ -54,6 +55,7 @@ export default function createDynamoDbAlarms (dynamoDbAlarmProperties: DynamoDbA
const config = dynamoDbAlarmProperties[metric]
for (const gsi of tableResource.Properties?.GlobalSecondaryIndexes ?? []) {
if (dynamoDbAlarmProperties.ReadThrottleEvents.enabled !== false && dynamoDbAlarmProperties.WriteThrottleEvents.enabled !== false) {
delete config.enabled
const gsiName: string = gsi.IndexName
const gsiIdentifierSub = `${tableResourceName}${gsiName}`
const dynamoDBAlarmProperties: AlarmProperties = {
Expand Down
Loading

0 comments on commit 3571dc4

Please sign in to comment.