-
Notifications
You must be signed in to change notification settings - Fork 15
/
api-gateway.ts
110 lines (102 loc) · 4.78 KB
/
api-gateway.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
import type { AlarmProperties } from 'cloudform-types/types/cloudWatch/alarm'
import type Resource from 'cloudform-types/types/resource'
import type Template from 'cloudform-types/types/template'
import { Fn } from 'cloudform'
import type { AlarmActionsConfig, CloudFormationResources, InputOutput, SlicWatchMergedConfig } from './alarm-types'
import { createAlarm, getStatisticName, makeAlarmLogicalId } from './alarm-utils'
import { getResourceAlarmConfigurationsByType } from '../cf-template'
export type SlicWatchApiGwAlarmsConfig<T extends InputOutput> = T & {
'5XXError': T
'4XXError': T
Latency: T
}
/**
* Given a CloudFormation resource for an API Gateway REST API, derive CloudFormation syntax for
* the API name.
* The API name can be provided as a `Name` property or in the OpenAPI specification as
* `Body.info.title`
*
* In either case, the name can be a string literal or use a CloudFormation intrinsic function
* (Sub, Ref or GetAtt)
*
* @param restApiResource CloudFormation resource for a REST API
* @param restApiLogicalId The logical ID for the resouce
*
* @returns CloudFormation syntax for the API name
*/
export function resolveRestApiNameAsCfn (restApiResource: Resource, restApiLogicalId: string) {
const apiName = restApiResource.Properties?.Name ?? restApiResource.Properties?.Body?.info?.title
if (apiName === undefined) {
throw new Error(`No API name specified for REST API ${restApiLogicalId}. Either Name or Body.info.title should be specified`)
}
return apiName
}
/**
* Given a CloudFormation resource for an API Gateway REST API, derive a string value or
* CloudFormation 'Fn::Sub' variable syntax for the cluster's name
*
* The API name can be provided as a `Name` property or in the OpenAPI specification as
* `Body.info.title`
*
* In either case, the name can be a string literal or use a CloudFormation intrinsic function
* (Sub, Ref or GetAtt)
*
* @param restApiResource CloudFormation resource for a REST API
* @param restApiLogicalId The logical ID for the resouce
*
* @returns Literal string or Sub variable syntax
*/
export function resolveRestApiNameForSub (restApiResource: Resource, restApiLogicalId: string): string {
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
const name = (restApiResource.Properties?.Name) || (restApiResource.Properties?.Body?.info?.title)
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
if (!name) {
throw new Error(`No API name specified for REST API ${restApiLogicalId}. Either Name or Body.info.title should be specified`)
}
if (name.GetAtt != null) {
return `\${${name.GetAtt[0]}.${name.GetAtt[1]}}`
} else if (name.Ref != null) {
return `\${${name.Ref}}`
} else if (name['Fn::Sub'] != null) {
return name['Fn::Sub']
}
return name
}
const executionMetrics = ['5XXError', '4XXError', 'Latency']
/**
* Add all required API Gateway REST API alarms to the provided CloudFormation template based on the resources found within
*
* @param apiGwAlarmsConfig The fully resolved alarm configuration
* @param alarmActionsConfig Notification configuration for alarm status change events
* @param compiledTemplate A CloudFormation template object
*
* @returns API Gateway-specific CloudFormation Alarm resources
*/
export default function createApiGatewayAlarms (
apiGwAlarmsConfig: SlicWatchApiGwAlarmsConfig<SlicWatchMergedConfig>, alarmActionsConfig: AlarmActionsConfig, compiledTemplate: Template
): CloudFormationResources {
const resources: CloudFormationResources = {}
const configuredResources = getResourceAlarmConfigurationsByType('AWS::ApiGateway::RestApi', compiledTemplate, apiGwAlarmsConfig)
for (const [apiLogicalId, apiResource] of Object.entries(configuredResources.resources)) {
for (const metric of executionMetrics) {
const mergedConfig: SlicWatchMergedConfig = configuredResources.alarmConfigurations[apiLogicalId][metric]
if (mergedConfig.enabled) {
const { enabled, ...rest } = mergedConfig
const apiName = resolveRestApiNameAsCfn(apiResource, apiLogicalId)
const apiNameForSub = resolveRestApiNameForSub(apiResource, apiLogicalId)
const apiAlarmProperties: AlarmProperties = {
AlarmName: Fn.Sub(`ApiGW_${metric}_${apiNameForSub}`, {}),
AlarmDescription: Fn.Sub(`API Gateway ${metric} ${getStatisticName(mergedConfig)} for ${apiNameForSub} breaches ${mergedConfig.Threshold}`, {}),
MetricName: metric,
Namespace: 'AWS/ApiGateway',
Dimensions: [{ Name: 'ApiName', Value: apiName }],
...rest
}
const alarmLogicalId = makeAlarmLogicalId('Api', apiNameForSub, metric)
const resource = createAlarm(apiAlarmProperties, alarmActionsConfig)
resources[alarmLogicalId] = resource
}
}
}
return resources
}