@fjall/components-infrastructure 0.95.0 → 0.99.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (234) hide show
  1. package/dist/lib/app.d.ts +90 -107
  2. package/dist/lib/app.js +149 -139
  3. package/dist/lib/config/aws/__t17fixture.d.ts +1 -0
  4. package/dist/lib/config/aws/__t17fixture.js +3 -0
  5. package/dist/lib/config/aws/__t17fixtureType.d.ts +2 -0
  6. package/dist/lib/config/aws/__t17fixtureType.js +1 -0
  7. package/dist/lib/config/aws/alarmTopic.js +8 -4
  8. package/dist/lib/config/aws/cloudTrail.js +1 -1
  9. package/dist/lib/config/aws/disasterRecovery.js +11 -16
  10. package/dist/lib/config/aws/ecrDefaultImage.d.ts +0 -1
  11. package/dist/lib/config/aws/ecrDefaultImage.js +13 -23
  12. package/dist/lib/config/aws/identityCenter.d.ts +10 -3
  13. package/dist/lib/config/aws/identityCenter.js +101 -37
  14. package/dist/lib/config/aws/identityCenterGroupMembership.js +8 -2
  15. package/dist/lib/config/aws/identityCenterMembership.d.ts +11 -0
  16. package/dist/lib/config/aws/identityCenterMembership.js +61 -0
  17. package/dist/lib/config/aws/index.d.ts +1 -1
  18. package/dist/lib/config/aws/index.js +1 -1
  19. package/dist/lib/config/aws/ipam.js +6 -11
  20. package/dist/lib/config/aws/oidcConnector.js +5 -1
  21. package/dist/lib/config/aws/scpPreset.js +4 -1
  22. package/dist/lib/patterns/aws/_eslint_test_tmp/leak.d.ts +1 -0
  23. package/dist/lib/patterns/aws/_eslint_test_tmp/leak.js +4 -0
  24. package/dist/lib/patterns/aws/account.js +7 -8
  25. package/dist/lib/patterns/aws/apexDomainPattern.js +10 -10
  26. package/dist/lib/patterns/aws/bastionFactory.d.ts +10 -0
  27. package/dist/lib/patterns/aws/bastionFactory.js +29 -0
  28. package/dist/lib/patterns/aws/buildkite.d.ts +2 -2
  29. package/dist/lib/patterns/aws/buildkite.js +51 -97
  30. package/dist/lib/patterns/aws/cdn.js +1 -1
  31. package/dist/lib/patterns/aws/clickhouseDatabase.d.ts +172 -0
  32. package/dist/lib/patterns/aws/clickhouseDatabase.js +600 -0
  33. package/dist/lib/patterns/aws/compute.d.ts +4 -6
  34. package/dist/lib/patterns/aws/compute.js +7 -13
  35. package/dist/lib/patterns/aws/computeEcs.d.ts +95 -396
  36. package/dist/lib/patterns/aws/computeEcs.js +880 -46
  37. package/dist/lib/patterns/aws/computeEcsTypes.d.ts +889 -0
  38. package/dist/lib/patterns/aws/computeEcsTypes.js +12 -0
  39. package/dist/lib/patterns/aws/computeLambda.d.ts +0 -5
  40. package/dist/lib/patterns/aws/computeLambda.js +1 -2
  41. package/dist/lib/patterns/aws/database.d.ts +50 -8
  42. package/dist/lib/patterns/aws/database.js +183 -27
  43. package/dist/lib/patterns/aws/domain.js +8 -7
  44. package/dist/lib/patterns/aws/index.d.ts +3 -0
  45. package/dist/lib/patterns/aws/index.js +3 -0
  46. package/dist/lib/patterns/aws/interfaces/compute.d.ts +13 -1
  47. package/dist/lib/patterns/aws/interfaces/connector.d.ts +1 -1
  48. package/dist/lib/patterns/aws/interfaces/connector.js +1 -1
  49. package/dist/lib/patterns/aws/interfaces/database.d.ts +187 -8
  50. package/dist/lib/patterns/aws/interfaces/database.js +17 -3
  51. package/dist/lib/patterns/aws/interfaces/index.d.ts +4 -2
  52. package/dist/lib/patterns/aws/interfaces/index.js +4 -2
  53. package/dist/lib/patterns/aws/interfaces/messaging.d.ts +7 -0
  54. package/dist/lib/patterns/aws/interfaces/migrationContributor.d.ts +47 -0
  55. package/dist/lib/patterns/aws/interfaces/migrationContributor.js +9 -0
  56. package/dist/lib/patterns/aws/interfaces/vpcPeer.d.ts +7 -0
  57. package/dist/lib/patterns/aws/interfaces/vpcPeer.js +1 -0
  58. package/dist/lib/patterns/aws/messaging.d.ts +66 -10
  59. package/dist/lib/patterns/aws/messaging.js +115 -20
  60. package/dist/lib/patterns/aws/network.js +16 -7
  61. package/dist/lib/patterns/aws/organisation.d.ts +4 -0
  62. package/dist/lib/patterns/aws/organisation.js +24 -5
  63. package/dist/lib/patterns/aws/storage.d.ts +1 -2
  64. package/dist/lib/patterns/aws/storage.js +3 -2
  65. package/dist/lib/patterns/aws/vpcPeer.d.ts +34 -0
  66. package/dist/lib/patterns/aws/vpcPeer.js +38 -0
  67. package/dist/lib/patterns/aws/vpcPeerAccepter.d.ts +29 -0
  68. package/dist/lib/patterns/aws/vpcPeerAccepter.js +196 -0
  69. package/dist/lib/resources/aws/analytics/clickhouse.js +25 -7
  70. package/dist/lib/resources/aws/analytics/clickhouseAlarms.d.ts +49 -0
  71. package/dist/lib/resources/aws/analytics/clickhouseAlarms.js +140 -0
  72. package/dist/lib/resources/aws/analytics/clickhouseConstants.d.ts +4 -4
  73. package/dist/lib/resources/aws/analytics/clickhouseConstants.js +6 -4
  74. package/dist/lib/resources/aws/analytics/clickhouseTypes.d.ts +12 -0
  75. package/dist/lib/resources/aws/analytics/clickhouseUserData.d.ts +1 -0
  76. package/dist/lib/resources/aws/analytics/clickhouseUserData.js +56 -5
  77. package/dist/lib/resources/aws/analytics/index.d.ts +2 -0
  78. package/dist/lib/resources/aws/analytics/index.js +1 -0
  79. package/dist/lib/resources/aws/base/awsStack.js +4 -2
  80. package/dist/lib/resources/aws/compute/__tmp__/regression-shape.d.ts +2 -0
  81. package/dist/lib/resources/aws/compute/__tmp__/regression-shape.js +11 -0
  82. package/dist/lib/resources/aws/compute/asgInlineLifecycleHook.d.ts +52 -0
  83. package/dist/lib/resources/aws/compute/asgInlineLifecycleHook.js +60 -0
  84. package/dist/lib/resources/aws/compute/blockDeviceVolume.d.ts +8 -0
  85. package/dist/lib/resources/aws/compute/blockDeviceVolume.js +10 -0
  86. package/dist/lib/resources/aws/compute/ec2.d.ts +132 -12
  87. package/dist/lib/resources/aws/compute/ec2.js +163 -23
  88. package/dist/lib/resources/aws/compute/ec2GracefulTerminationHandler.d.ts +41 -0
  89. package/dist/lib/resources/aws/compute/ec2GracefulTerminationHandler.js +194 -0
  90. package/dist/lib/resources/aws/compute/ec2GracefulTerminationLambda.source.cjs +458 -0
  91. package/dist/lib/resources/aws/compute/ecs.d.ts +27 -1
  92. package/dist/lib/resources/aws/compute/ecs.js +42 -2
  93. package/dist/lib/resources/aws/compute/ecsConstants.d.ts +9 -0
  94. package/dist/lib/resources/aws/compute/ecsConstants.js +16 -0
  95. package/dist/lib/resources/aws/compute/ecsImages.js +32 -20
  96. package/dist/lib/resources/aws/compute/ecsLifecycleHookMigration.d.ts +96 -0
  97. package/dist/lib/resources/aws/compute/ecsLifecycleHookMigration.js +113 -0
  98. package/dist/lib/resources/aws/compute/ecsNetworking.d.ts +2 -1
  99. package/dist/lib/resources/aws/compute/ecsNetworking.js +18 -6
  100. package/dist/lib/resources/aws/compute/ecsRemoteConnections.d.ts +38 -0
  101. package/dist/lib/resources/aws/compute/ecsRemoteConnections.js +80 -0
  102. package/dist/lib/resources/aws/compute/ecsServiceFactory.d.ts +13 -4
  103. package/dist/lib/resources/aws/compute/ecsServiceFactory.js +155 -33
  104. package/dist/lib/resources/aws/compute/ecsTaskDefinition.d.ts +31 -1
  105. package/dist/lib/resources/aws/compute/ecsTaskDefinition.js +110 -6
  106. package/dist/lib/resources/aws/compute/ecsTypes.d.ts +180 -13
  107. package/dist/lib/resources/aws/compute/ecsValidation.d.ts +9 -0
  108. package/dist/lib/resources/aws/compute/ecsValidation.js +63 -0
  109. package/dist/lib/resources/aws/compute/index.d.ts +2 -0
  110. package/dist/lib/resources/aws/compute/index.js +2 -0
  111. package/dist/lib/resources/aws/compute/lambda.d.ts +7 -13
  112. package/dist/lib/resources/aws/compute/lambda.js +30 -38
  113. package/dist/lib/resources/aws/compute/lifecycleHookLambda.source.cjs +192 -0
  114. package/dist/lib/resources/aws/compute/persistentDataVolume.d.ts +104 -0
  115. package/dist/lib/resources/aws/compute/persistentDataVolume.js +245 -0
  116. package/dist/lib/resources/aws/compute/persistentDataVolumeLambda.source.cjs +398 -0
  117. package/dist/lib/resources/aws/compute/samApplication.d.ts +15 -0
  118. package/dist/lib/resources/aws/compute/samApplication.js +27 -0
  119. package/dist/lib/resources/aws/database/clickhouseConstants.d.ts +159 -0
  120. package/dist/lib/resources/aws/database/clickhouseConstants.js +181 -0
  121. package/dist/lib/resources/aws/database/clickhouseSchemas.d.ts +71 -0
  122. package/dist/lib/resources/aws/database/clickhouseSchemas.js +157 -0
  123. package/dist/lib/resources/aws/database/clickhouseSecurityGroup.d.ts +14 -0
  124. package/dist/lib/resources/aws/database/clickhouseSecurityGroup.js +23 -0
  125. package/dist/lib/resources/aws/database/clickhouseUserData.d.ts +69 -0
  126. package/dist/lib/resources/aws/database/clickhouseUserData.js +371 -0
  127. package/dist/lib/resources/aws/database/clickhouseXmlRenderer.d.ts +56 -0
  128. package/dist/lib/resources/aws/database/clickhouseXmlRenderer.js +112 -0
  129. package/dist/lib/resources/aws/database/rdsAurora.d.ts +8 -1
  130. package/dist/lib/resources/aws/database/rdsAurora.js +42 -32
  131. package/dist/lib/resources/aws/database/rdsAuroraGlobal.d.ts +15 -2
  132. package/dist/lib/resources/aws/database/rdsAuroraGlobal.js +39 -43
  133. package/dist/lib/resources/aws/database/rdsDefaults.d.ts +6 -0
  134. package/dist/lib/resources/aws/database/rdsDefaults.js +7 -1
  135. package/dist/lib/resources/aws/database/rdsHelpers.d.ts +3 -3
  136. package/dist/lib/resources/aws/database/rdsHelpers.js +1 -0
  137. package/dist/lib/resources/aws/database/rdsInstance.d.ts +8 -1
  138. package/dist/lib/resources/aws/database/rdsInstance.js +51 -34
  139. package/dist/lib/resources/aws/database/rdsProxyOutput.d.ts +1 -1
  140. package/dist/lib/resources/aws/database/rdsProxyOutput.js +1 -1
  141. package/dist/lib/resources/aws/iam/delegationRole.js +12 -5
  142. package/dist/lib/resources/aws/iam/identityCenter/groupMembership.d.ts +9 -0
  143. package/dist/lib/resources/aws/iam/identityCenter/groupMembership.js +12 -0
  144. package/dist/lib/resources/aws/iam/identityCenter/index.d.ts +1 -0
  145. package/dist/lib/resources/aws/iam/identityCenter/index.js +1 -0
  146. package/dist/lib/resources/aws/iam/identityCenter/permissionSet.d.ts +1 -0
  147. package/dist/lib/resources/aws/iam/identityCenter/permissionSet.js +1 -0
  148. package/dist/lib/resources/aws/logging/logGroup.d.ts +0 -8
  149. package/dist/lib/resources/aws/logging/logGroup.js +0 -11
  150. package/dist/lib/resources/aws/messaging/defaultEventBus.d.ts +7 -0
  151. package/dist/lib/resources/aws/messaging/defaultEventBus.js +21 -0
  152. package/dist/lib/resources/aws/messaging/eventBridgeRule.d.ts +96 -0
  153. package/dist/lib/resources/aws/messaging/eventBridgeRule.js +110 -0
  154. package/dist/lib/resources/aws/messaging/eventTargets.d.ts +84 -0
  155. package/dist/lib/resources/aws/messaging/eventTargets.js +152 -0
  156. package/dist/lib/resources/aws/messaging/eventbridge.d.ts +25 -2
  157. package/dist/lib/resources/aws/messaging/eventbridge.js +22 -10
  158. package/dist/lib/resources/aws/messaging/index.d.ts +5 -0
  159. package/dist/lib/resources/aws/messaging/index.js +2 -0
  160. package/dist/lib/resources/aws/messaging/schedule.d.ts +118 -0
  161. package/dist/lib/resources/aws/messaging/schedule.js +64 -0
  162. package/dist/lib/resources/aws/messaging/sns.d.ts +2 -1
  163. package/dist/lib/resources/aws/messaging/sqs.d.ts +2 -1
  164. package/dist/lib/resources/aws/messaging/subscription.d.ts +112 -0
  165. package/dist/lib/resources/aws/messaging/subscription.js +67 -0
  166. package/dist/lib/resources/aws/messaging/utils.d.ts +6 -0
  167. package/dist/lib/resources/aws/messaging/utils.js +10 -0
  168. package/dist/lib/resources/aws/monitoring/clickhouseAlarms.d.ts +60 -0
  169. package/dist/lib/resources/aws/monitoring/clickhouseAlarms.js +139 -0
  170. package/dist/lib/resources/aws/monitoring/index.d.ts +2 -0
  171. package/dist/lib/resources/aws/monitoring/index.js +2 -0
  172. package/dist/lib/resources/aws/monitoring/scheduleAlarms.d.ts +47 -0
  173. package/dist/lib/resources/aws/monitoring/scheduleAlarms.js +106 -0
  174. package/dist/lib/resources/aws/networking/crossAccountDelegationRecord.js +6 -3
  175. package/dist/lib/resources/aws/networking/crossAccountReturnRoutes.d.ts +40 -0
  176. package/dist/lib/resources/aws/networking/crossAccountReturnRoutes.js +158 -0
  177. package/dist/lib/resources/aws/networking/dnsRecord/dnsRecordBase.js +7 -4
  178. package/dist/lib/resources/aws/networking/domainCertificate.d.ts +2 -2
  179. package/dist/lib/resources/aws/networking/domainCertificate.js +6 -3
  180. package/dist/lib/resources/aws/networking/hostedZone.js +6 -4
  181. package/dist/lib/resources/aws/networking/index.d.ts +3 -0
  182. package/dist/lib/resources/aws/networking/index.js +3 -0
  183. package/dist/lib/resources/aws/networking/serviceDiscovery.d.ts +96 -0
  184. package/dist/lib/resources/aws/networking/serviceDiscovery.js +96 -0
  185. package/dist/lib/resources/aws/networking/vpc.d.ts +4 -1
  186. package/dist/lib/resources/aws/networking/vpc.js +10 -3
  187. package/dist/lib/resources/aws/networking/vpcPeeringAccepterRole.d.ts +18 -0
  188. package/dist/lib/resources/aws/networking/vpcPeeringAccepterRole.js +61 -0
  189. package/dist/lib/resources/aws/networking/vpcPeeringConnection.d.ts +49 -0
  190. package/dist/lib/resources/aws/networking/vpcPeeringConnection.js +106 -0
  191. package/dist/lib/resources/aws/organisation/costAllocationTagActivator.d.ts +16 -5
  192. package/dist/lib/resources/aws/organisation/costAllocationTagActivator.js +17 -3
  193. package/dist/lib/resources/aws/organisation/index.d.ts +1 -1
  194. package/dist/lib/resources/aws/organisation/organisationPolicy.d.ts +2 -0
  195. package/dist/lib/resources/aws/organisation/organisationPolicy.js +3 -2
  196. package/dist/lib/resources/aws/secrets/secret.d.ts +7 -0
  197. package/dist/lib/resources/aws/secrets/secret.js +4 -3
  198. package/dist/lib/resources/aws/storage/bucketDeployment.d.ts +16 -0
  199. package/dist/lib/resources/aws/storage/bucketDeployment.js +17 -0
  200. package/dist/lib/resources/aws/storage/ecr.js +5 -5
  201. package/dist/lib/resources/aws/storage/index.d.ts +1 -0
  202. package/dist/lib/resources/aws/storage/index.js +1 -0
  203. package/dist/lib/resources/aws/storage/s3.js +10 -3
  204. package/dist/lib/resources/aws/utilities/customResource.js +18 -9
  205. package/dist/lib/synth_dump.d.ts +1 -0
  206. package/dist/lib/synth_dump.js +42 -0
  207. package/dist/lib/utils/bastionFactory.d.ts +10 -0
  208. package/dist/lib/utils/bastionFactory.js +29 -0
  209. package/dist/lib/utils/capitaliseString.d.ts +1 -1
  210. package/dist/lib/utils/capitaliseString.js +1 -1
  211. package/dist/lib/utils/cdkContext.d.ts +10 -0
  212. package/dist/lib/utils/cdkContext.js +13 -0
  213. package/dist/lib/utils/connections.d.ts +7 -1
  214. package/dist/lib/utils/connections.js +21 -0
  215. package/dist/lib/utils/connector.d.ts +30 -2
  216. package/dist/lib/utils/connector.js +6 -1
  217. package/dist/lib/utils/costAllocationTags.d.ts +15 -0
  218. package/dist/lib/utils/costAllocationTags.js +16 -0
  219. package/dist/lib/utils/databaseTypes.d.ts +14 -0
  220. package/dist/lib/utils/getConfig.d.ts +2 -0
  221. package/dist/lib/utils/getConfig.js +2 -0
  222. package/dist/lib/utils/index.d.ts +4 -0
  223. package/dist/lib/utils/index.js +4 -0
  224. package/dist/lib/utils/manifestWriter.d.ts +6 -89
  225. package/dist/lib/utils/manifestWriter.js +36 -23
  226. package/dist/lib/utils/migrationVersionResolvers.d.ts +2 -0
  227. package/dist/lib/utils/migrationVersionResolvers.js +2 -0
  228. package/dist/lib/utils/orgConfigParser.js +2 -1
  229. package/dist/lib/utils/resolveAlertsTopic.d.ts +14 -0
  230. package/dist/lib/utils/resolveAlertsTopic.js +30 -0
  231. package/dist/lib/utils/validationLogger.js +6 -3
  232. package/dist/lib/utils/vpcPeerInterface.d.ts +22 -0
  233. package/dist/lib/utils/vpcPeerInterface.js +1 -0
  234. package/package.json +22 -18
@@ -0,0 +1,139 @@
1
+ import { Duration } from "aws-cdk-lib";
2
+ import { Alarm, ComparisonOperator, TreatMissingData } from "aws-cdk-lib/aws-cloudwatch";
3
+ import { SnsAction } from "aws-cdk-lib/aws-cloudwatch-actions";
4
+ import { Metric } from "aws-cdk-lib/aws-cloudwatch";
5
+ import { FilterPattern, MetricFilter } from "aws-cdk-lib/aws-logs";
6
+ import { ALARM_DEFAULTS, registerAlarm, buildAlarmDescription } from "./alarmDefaults.js";
7
+ const CLICKHOUSE_METRIC_NAMESPACE = "Fjall/ClickHouse";
8
+ /**
9
+ * Single-node ClickHouse posture alarms. Covers host-level CPU + (optional)
10
+ * memory and disk via the CloudWatch Agent metric namespace `CWAgent`, plus
11
+ * two log-driven alarms:
12
+ *
13
+ * - **Stuck merges** — `client.ts` polls `system.merges` every 5 min and logs
14
+ * `serverLogger.warn("ClickHouse", "Stuck merge detected")` when elapsed
15
+ * exceeds 30 min. The metric filter on the webapp log group emits a count
16
+ * metric per match; the alarm fires on Sum >= 1 over 5 min × 2 evaluations.
17
+ * - **Backup failures** — `AccessDenied` or `S3Exception` from the backup
18
+ * task's BACKUP DATABASE TO S3 statement. Closes the silent-failure mode
19
+ * that masked the original IAM-grant misconfiguration (see
20
+ * `designs/2026-04-27-clickhouse-backup-iam-role.md`).
21
+ */
22
+ export function createClickHouseAlarms(props) {
23
+ const { scope, instanceRole, asgName, alarmTopic, webappLogGroup, backupTaskLogGroup, config = {} } = props;
24
+ const alarms = [];
25
+ const snsAction = new SnsAction(alarmTopic);
26
+ const cpuAlarm = new Alarm(scope, "ClickHouseCpuAlarm", {
27
+ alarmDescription: buildAlarmDescription("ClickHouse host CPU utilisation exceeds threshold", undefined),
28
+ metric: new Metric({
29
+ namespace: "AWS/EC2",
30
+ metricName: "CPUUtilization",
31
+ dimensionsMap: { AutoScalingGroupName: asgName },
32
+ period: ALARM_DEFAULTS.EVALUATION_PERIOD,
33
+ statistic: "Average"
34
+ }),
35
+ threshold: config.cpuThreshold ?? 90,
36
+ evaluationPeriods: 3,
37
+ datapointsToAlarm: 2,
38
+ comparisonOperator: ComparisonOperator.GREATER_THAN_THRESHOLD,
39
+ treatMissingData: TreatMissingData.NOT_BREACHING
40
+ });
41
+ registerAlarm(cpuAlarm, snsAction, alarms);
42
+ const memoryAlarm = new Alarm(scope, "ClickHouseMemoryAlarm", {
43
+ alarmDescription: buildAlarmDescription("ClickHouse host memory utilisation exceeds threshold (CWAgent)", undefined),
44
+ metric: new Metric({
45
+ namespace: "CWAgent",
46
+ metricName: "mem_used_percent",
47
+ dimensionsMap: { AutoScalingGroupName: asgName },
48
+ period: ALARM_DEFAULTS.EVALUATION_PERIOD,
49
+ statistic: "Average"
50
+ }),
51
+ threshold: config.memoryThreshold ?? 80,
52
+ evaluationPeriods: 3,
53
+ datapointsToAlarm: 2,
54
+ comparisonOperator: ComparisonOperator.GREATER_THAN_THRESHOLD,
55
+ treatMissingData: TreatMissingData.NOT_BREACHING
56
+ });
57
+ registerAlarm(memoryAlarm, snsAction, alarms);
58
+ const diskWarnAlarm = new Alarm(scope, "ClickHouseDiskWarnAlarm", {
59
+ alarmDescription: buildAlarmDescription("ClickHouse data volume above 70% used — plan growth response", undefined),
60
+ metric: new Metric({
61
+ namespace: "CWAgent",
62
+ metricName: "disk_used_percent",
63
+ dimensionsMap: { AutoScalingGroupName: asgName },
64
+ period: Duration.minutes(15),
65
+ statistic: "Average"
66
+ }),
67
+ threshold: config.diskWarnThreshold ?? 70,
68
+ evaluationPeriods: 2,
69
+ datapointsToAlarm: 2,
70
+ comparisonOperator: ComparisonOperator.GREATER_THAN_THRESHOLD,
71
+ treatMissingData: TreatMissingData.NOT_BREACHING
72
+ });
73
+ registerAlarm(diskWarnAlarm, snsAction, alarms);
74
+ const diskCriticalAlarm = new Alarm(scope, "ClickHouseDiskCriticalAlarm", {
75
+ alarmDescription: buildAlarmDescription("ClickHouse data volume above 85% used — imminent insert failures", undefined),
76
+ metric: new Metric({
77
+ namespace: "CWAgent",
78
+ metricName: "disk_used_percent",
79
+ dimensionsMap: { AutoScalingGroupName: asgName },
80
+ period: Duration.minutes(5),
81
+ statistic: "Average"
82
+ }),
83
+ threshold: config.diskCriticalThreshold ?? 85,
84
+ evaluationPeriods: 2,
85
+ datapointsToAlarm: 2,
86
+ comparisonOperator: ComparisonOperator.GREATER_THAN_THRESHOLD,
87
+ treatMissingData: TreatMissingData.NOT_BREACHING
88
+ });
89
+ registerAlarm(diskCriticalAlarm, snsAction, alarms);
90
+ const stuckMergeMetricName = "ClickHouseStuckMergeCount";
91
+ new MetricFilter(scope, "ClickHouseStuckMergeMetricFilter", {
92
+ logGroup: webappLogGroup,
93
+ metricNamespace: CLICKHOUSE_METRIC_NAMESPACE,
94
+ metricName: stuckMergeMetricName,
95
+ filterPattern: FilterPattern.literal('"Stuck merge detected"'),
96
+ metricValue: "1",
97
+ defaultValue: 0
98
+ });
99
+ const stuckMergeAlarm = new Alarm(scope, "ClickHouseStuckMergeAlarm", {
100
+ alarmDescription: buildAlarmDescription("ClickHouse merge stuck > 30 min — investigate parts pressure or replica health", undefined),
101
+ metric: new Metric({
102
+ namespace: CLICKHOUSE_METRIC_NAMESPACE,
103
+ metricName: stuckMergeMetricName,
104
+ period: Duration.minutes(5),
105
+ statistic: "Sum"
106
+ }),
107
+ threshold: 1,
108
+ evaluationPeriods: 2,
109
+ datapointsToAlarm: 2,
110
+ comparisonOperator: ComparisonOperator.GREATER_THAN_OR_EQUAL_TO_THRESHOLD,
111
+ treatMissingData: TreatMissingData.NOT_BREACHING
112
+ });
113
+ registerAlarm(stuckMergeAlarm, snsAction, alarms);
114
+ const backupFailureMetricName = "ClickHouseBackupFailureCount";
115
+ new MetricFilter(scope, "ClickHouseBackupFailureMetricFilter", {
116
+ logGroup: backupTaskLogGroup,
117
+ metricNamespace: CLICKHOUSE_METRIC_NAMESPACE,
118
+ metricName: backupFailureMetricName,
119
+ filterPattern: FilterPattern.anyTerm("AccessDenied", "S3Exception"),
120
+ metricValue: "1",
121
+ defaultValue: 0
122
+ });
123
+ const backupFailureAlarm = new Alarm(scope, "ClickHouseBackupFailureAlarm", {
124
+ alarmDescription: buildAlarmDescription(`ClickHouse BACKUP TO S3 emitted AccessDenied/S3Exception — verify instance role '${instanceRole.roleName}' grant on backup bucket`, undefined),
125
+ metric: new Metric({
126
+ namespace: CLICKHOUSE_METRIC_NAMESPACE,
127
+ metricName: backupFailureMetricName,
128
+ period: Duration.hours(1),
129
+ statistic: "Sum"
130
+ }),
131
+ threshold: 1,
132
+ evaluationPeriods: 1,
133
+ datapointsToAlarm: 1,
134
+ comparisonOperator: ComparisonOperator.GREATER_THAN_OR_EQUAL_TO_THRESHOLD,
135
+ treatMissingData: TreatMissingData.NOT_BREACHING
136
+ });
137
+ registerAlarm(backupFailureAlarm, snsAction, alarms);
138
+ return alarms;
139
+ }
@@ -2,3 +2,5 @@ export { ALARM_DEFAULTS, APPLICATION_ID_TAG_KEY, registerAlarm, tagAlarmsWithApp
2
2
  export { createEcsServiceAlarms, type EcsServiceAlarmThresholds, type EcsServiceAlarmsProps } from "./ecsAlarms.js";
3
3
  export { createRdsAlarms, type RdsAlarmThresholds, type RdsAlarmsProps } from "./rdsAlarms.js";
4
4
  export { createLambdaAlarms, type LambdaAlarmThresholds, type LambdaAlarmsProps } from "./lambdaAlarms.js";
5
+ export { createScheduleAlarms, type ScheduleAlarmThresholds, type CreateScheduleAlarmsProps } from "./scheduleAlarms.js";
6
+ export { createClickHouseAlarms, type ClickHouseAlarmThresholds, type ClickHouseAlarmsProps } from "./clickhouseAlarms.js";
@@ -2,3 +2,5 @@ export { ALARM_DEFAULTS, APPLICATION_ID_TAG_KEY, registerAlarm, tagAlarmsWithApp
2
2
  export { createEcsServiceAlarms } from "./ecsAlarms.js";
3
3
  export { createRdsAlarms } from "./rdsAlarms.js";
4
4
  export { createLambdaAlarms } from "./lambdaAlarms.js";
5
+ export { createScheduleAlarms } from "./scheduleAlarms.js";
6
+ export { createClickHouseAlarms } from "./clickhouseAlarms.js";
@@ -0,0 +1,47 @@
1
+ import { Alarm } from "aws-cdk-lib/aws-cloudwatch";
2
+ import type { IRule } from "aws-cdk-lib/aws-events";
3
+ import type { ITopic } from "aws-cdk-lib/aws-sns";
4
+ import type { Construct } from "constructs";
5
+ /**
6
+ * Tunable thresholds for the EventBridge schedule alarms. Lives here (NOT in
7
+ * `messaging/schedule.ts`) so the consumer module — `eventBridgeRule.ts` /
8
+ * `Schedule` — can re-export it without forming a circular import.
9
+ */
10
+ export interface ScheduleAlarmThresholds {
11
+ failedInvocationsThreshold?: number;
12
+ throttledRulesThreshold?: number;
13
+ dlqFailureThreshold?: number;
14
+ /**
15
+ * Multiplier applied to `expectedTicksPerHour` to derive the missed-tick
16
+ * floor. Default 0.8 — fires when observed matches drop below 80% of the
17
+ * expected cadence (silent disablement detector).
18
+ */
19
+ matchedEventsLowFloorRatio?: number;
20
+ }
21
+ export interface CreateScheduleAlarmsProps {
22
+ scope: Construct;
23
+ ruleName: string;
24
+ rule: IRule;
25
+ kind: "schedule" | "subscription";
26
+ /** Populated by `parseRateExpression(...)` — undefined for cron schedules and subscriptions. */
27
+ expectedTicksPerHour?: number;
28
+ config?: ScheduleAlarmThresholds;
29
+ alarmTopic: ITopic;
30
+ applicationId?: string;
31
+ dlqEnabled: boolean;
32
+ }
33
+ /**
34
+ * Provisions D13 alarms for an EventBridge rule. Returns the created alarms
35
+ * (each wired to the SNS topic for both ALARM and OK transitions and tagged
36
+ * with the application id for webhook routing).
37
+ *
38
+ * Alarm coverage:
39
+ * - `FailedInvocations` / `ThrottledRules` always fire.
40
+ * - `InvocationsFailedToBeSentToDlq` fires only when `dlqEnabled === true`.
41
+ * - `MatchedEvents` (missed-tick detector) fires only when
42
+ * `kind === "schedule"` AND `expectedTicksPerHour !== undefined` —
43
+ * i.e. rate expressions, not cron, not subscriptions. Uses
44
+ * `LessThanThreshold` + `treatMissingData: BREACHING` so a fully silent
45
+ * rule trips the alarm.
46
+ */
47
+ export declare function createScheduleAlarms(props: CreateScheduleAlarmsProps): Alarm[];
@@ -0,0 +1,106 @@
1
+ import { Duration } from "aws-cdk-lib";
2
+ import { Alarm, ComparisonOperator, TreatMissingData, Metric } from "aws-cdk-lib/aws-cloudwatch";
3
+ import { SnsAction } from "aws-cdk-lib/aws-cloudwatch-actions";
4
+ import { ALARM_DEFAULTS, registerAlarm, tagAlarmsWithApplicationId, buildAlarmDescription } from "./alarmDefaults.js";
5
+ const DEFAULT_THRESHOLDS = {
6
+ FAILED_INVOCATIONS: 1,
7
+ THROTTLED_RULES: 1,
8
+ DLQ_FAILURE: 1,
9
+ MATCHED_EVENTS_LOW_FLOOR_RATIO: 0.8
10
+ };
11
+ /**
12
+ * Provisions D13 alarms for an EventBridge rule. Returns the created alarms
13
+ * (each wired to the SNS topic for both ALARM and OK transitions and tagged
14
+ * with the application id for webhook routing).
15
+ *
16
+ * Alarm coverage:
17
+ * - `FailedInvocations` / `ThrottledRules` always fire.
18
+ * - `InvocationsFailedToBeSentToDlq` fires only when `dlqEnabled === true`.
19
+ * - `MatchedEvents` (missed-tick detector) fires only when
20
+ * `kind === "schedule"` AND `expectedTicksPerHour !== undefined` —
21
+ * i.e. rate expressions, not cron, not subscriptions. Uses
22
+ * `LessThanThreshold` + `treatMissingData: BREACHING` so a fully silent
23
+ * rule trips the alarm.
24
+ */
25
+ export function createScheduleAlarms(props) {
26
+ const { scope, ruleName, rule, kind, expectedTicksPerHour, config = {}, alarmTopic, applicationId, dlqEnabled } = props;
27
+ const alarms = [];
28
+ const snsAction = new SnsAction(alarmTopic);
29
+ const failedInvocationsThreshold = config.failedInvocationsThreshold ?? DEFAULT_THRESHOLDS.FAILED_INVOCATIONS;
30
+ const throttledRulesThreshold = config.throttledRulesThreshold ?? DEFAULT_THRESHOLDS.THROTTLED_RULES;
31
+ const dlqFailureThreshold = config.dlqFailureThreshold ?? DEFAULT_THRESHOLDS.DLQ_FAILURE;
32
+ const matchedEventsLowFloorRatio = config.matchedEventsLowFloorRatio ??
33
+ DEFAULT_THRESHOLDS.MATCHED_EVENTS_LOW_FLOOR_RATIO;
34
+ const ruleDimensions = { RuleName: rule.ruleName };
35
+ const failedInvocationsAlarm = new Alarm(scope, `${ruleName}FailedInvocationsAlarm`, {
36
+ alarmDescription: buildAlarmDescription(`EventBridge rule ${ruleName} FailedInvocations exceeds ${failedInvocationsThreshold}`, applicationId),
37
+ metric: new Metric({
38
+ namespace: "AWS/Events",
39
+ metricName: "FailedInvocations",
40
+ dimensionsMap: ruleDimensions,
41
+ period: ALARM_DEFAULTS.EVALUATION_PERIOD,
42
+ statistic: "Sum"
43
+ }),
44
+ threshold: failedInvocationsThreshold,
45
+ evaluationPeriods: 2,
46
+ datapointsToAlarm: 2,
47
+ comparisonOperator: ComparisonOperator.GREATER_THAN_THRESHOLD,
48
+ treatMissingData: TreatMissingData.NOT_BREACHING
49
+ });
50
+ registerAlarm(failedInvocationsAlarm, snsAction, alarms);
51
+ const throttledRulesAlarm = new Alarm(scope, `${ruleName}ThrottledRulesAlarm`, {
52
+ alarmDescription: buildAlarmDescription(`EventBridge rule ${ruleName} ThrottledRules exceeds ${throttledRulesThreshold}`, applicationId),
53
+ metric: new Metric({
54
+ namespace: "AWS/Events",
55
+ metricName: "ThrottledRules",
56
+ dimensionsMap: ruleDimensions,
57
+ period: ALARM_DEFAULTS.EVALUATION_PERIOD,
58
+ statistic: "Sum"
59
+ }),
60
+ threshold: throttledRulesThreshold,
61
+ evaluationPeriods: 2,
62
+ datapointsToAlarm: 2,
63
+ comparisonOperator: ComparisonOperator.GREATER_THAN_THRESHOLD,
64
+ treatMissingData: TreatMissingData.NOT_BREACHING
65
+ });
66
+ registerAlarm(throttledRulesAlarm, snsAction, alarms);
67
+ if (dlqEnabled) {
68
+ const dlqFailureAlarm = new Alarm(scope, `${ruleName}DlqFailureAlarm`, {
69
+ alarmDescription: buildAlarmDescription(`EventBridge rule ${ruleName} InvocationsFailedToBeSentToDlq exceeds ${dlqFailureThreshold}`, applicationId),
70
+ metric: new Metric({
71
+ namespace: "AWS/Events",
72
+ metricName: "InvocationsFailedToBeSentToDlq",
73
+ dimensionsMap: ruleDimensions,
74
+ period: Duration.minutes(5),
75
+ statistic: "Sum"
76
+ }),
77
+ threshold: dlqFailureThreshold,
78
+ evaluationPeriods: 1,
79
+ datapointsToAlarm: 1,
80
+ comparisonOperator: ComparisonOperator.GREATER_THAN_THRESHOLD,
81
+ treatMissingData: TreatMissingData.NOT_BREACHING
82
+ });
83
+ registerAlarm(dlqFailureAlarm, snsAction, alarms);
84
+ }
85
+ if (kind === "schedule" && expectedTicksPerHour !== undefined) {
86
+ const floor = Math.max(1, Math.floor(expectedTicksPerHour * matchedEventsLowFloorRatio));
87
+ const matchedEventsAlarm = new Alarm(scope, `${ruleName}MatchedEventsAlarm`, {
88
+ alarmDescription: buildAlarmDescription(`EventBridge rule ${ruleName} expected >=${floor} ticks/hour, observed <=${Math.round(matchedEventsLowFloorRatio * 100)}% threshold = silent disablement detector`, applicationId),
89
+ metric: new Metric({
90
+ namespace: "AWS/Events",
91
+ metricName: "MatchedEvents",
92
+ dimensionsMap: ruleDimensions,
93
+ period: Duration.hours(1),
94
+ statistic: "Sum"
95
+ }),
96
+ threshold: floor,
97
+ evaluationPeriods: 1,
98
+ datapointsToAlarm: 1,
99
+ comparisonOperator: ComparisonOperator.LESS_THAN_THRESHOLD,
100
+ treatMissingData: TreatMissingData.BREACHING
101
+ });
102
+ registerAlarm(matchedEventsAlarm, snsAction, alarms);
103
+ }
104
+ tagAlarmsWithApplicationId(alarms, applicationId);
105
+ return alarms;
106
+ }
@@ -1,6 +1,7 @@
1
1
  import { Construct } from "constructs";
2
2
  import { Tags } from "aws-cdk-lib";
3
3
  import { CrossAccountZoneDelegationRecord as CdkCrossAccountZoneDelegationRecord } from "aws-cdk-lib/aws-route53";
4
+ import { applyCostAllocationTags } from "../../../utils/costAllocationTags.js";
4
5
  // CDK's CrossAccountZoneDelegationRecord is a custom-resource Lambda: user-level tags
5
6
  // on the record itself may not reach AWS. We tag the wrapping Fjall Construct for
6
7
  // assertion purposes; create-path tags still propagate to child resources (Lambda,
@@ -19,8 +20,10 @@ export class CrossAccountDelegationRecord extends Construct {
19
20
  parentHostedZoneName: props.parentHostedZoneName
20
21
  });
21
22
  Tags.of(this).add("fjall:description", this.description);
22
- Tags.of(this).add("fjall:costAllocation:environment", props.costAllocationEnvironment ?? "management");
23
- Tags.of(this).add("fjall:costAllocation:service", "crossAccountDelegation");
24
- Tags.of(this).add("fjall:costAllocation:domain", props.costAllocationDomain ?? props.delegatedZoneName);
23
+ applyCostAllocationTags(this, {
24
+ service: "crossAccountDelegation",
25
+ domain: props.costAllocationDomain ?? props.delegatedZoneName,
26
+ environment: props.costAllocationEnvironment
27
+ });
25
28
  }
26
29
  }
@@ -0,0 +1,40 @@
1
+ import { Construct } from "constructs";
2
+ import { CustomResource } from "../utilities/customResource.js";
3
+ /**
4
+ * Properties for {@link CrossAccountReturnRoutes}.
5
+ *
6
+ * The route-table IDs are passed as a comma-joined string because CloudFormation
7
+ * custom-resource properties are flat strings. Inside the handler (which runs
8
+ * after CFN token resolution) the string is split back into an array.
9
+ */
10
+ export interface CrossAccountReturnRoutesProps {
11
+ /** ARN of the accepter-account role the Lambda assumes to make EC2 calls. */
12
+ readonly peerRoleArn: string;
13
+ /** Region of the accepter VPC (so the EC2 client targets the correct region). */
14
+ readonly peerRegion: string;
15
+ /** Route-table IDs in the accepter VPC that should receive return routes. */
16
+ readonly routeTableIds: string[];
17
+ /** Local VPC CIDR — destination of the return routes in the accepter's tables. */
18
+ readonly localVpcCidr: string;
19
+ /** The peering connection ID (the target of the return routes). */
20
+ readonly peeringConnectionId: string;
21
+ /** Whether to also set `AccepterPeeringConnectionOptions.AllowDnsResolutionFromRemoteVpc`. */
22
+ readonly enableDns?: boolean;
23
+ }
24
+ /**
25
+ * Cross-account return-route manager for a VPC peering connection.
26
+ *
27
+ * Uses the shared {@link CustomResource} wrapper with an inline Lambda handler.
28
+ * The Lambda's execution role is local to the requester account — it holds only
29
+ * `sts:AssumeRole` on the accepter role. All EC2 calls happen under the assumed
30
+ * credentials in the accepter account.
31
+ *
32
+ * Create paths add `CreateRouteCommand` entries per route-table ID; delete
33
+ * paths reverse them via `DeleteRouteCommand`. When `enableDns` is true the
34
+ * handler additionally calls `ModifyVpcPeeringConnectionOptionsCommand` on the
35
+ * accepter side so DNS resolution works bidirectionally.
36
+ */
37
+ export declare class CrossAccountReturnRoutes extends Construct {
38
+ readonly customResource: CustomResource;
39
+ constructor(scope: Construct, id: string, props: CrossAccountReturnRoutesProps);
40
+ }
@@ -0,0 +1,158 @@
1
+ import { Construct } from "constructs";
2
+ import { Duration, Fn } from "aws-cdk-lib";
3
+ import { Runtime } from "aws-cdk-lib/aws-lambda";
4
+ import { Effect, PolicyStatement } from "aws-cdk-lib/aws-iam";
5
+ import { CustomResource } from "../utilities/customResource.js";
6
+ /**
7
+ * Cross-account return-route manager for a VPC peering connection.
8
+ *
9
+ * Uses the shared {@link CustomResource} wrapper with an inline Lambda handler.
10
+ * The Lambda's execution role is local to the requester account — it holds only
11
+ * `sts:AssumeRole` on the accepter role. All EC2 calls happen under the assumed
12
+ * credentials in the accepter account.
13
+ *
14
+ * Create paths add `CreateRouteCommand` entries per route-table ID; delete
15
+ * paths reverse them via `DeleteRouteCommand`. When `enableDns` is true the
16
+ * handler additionally calls `ModifyVpcPeeringConnectionOptionsCommand` on the
17
+ * accepter side so DNS resolution works bidirectionally.
18
+ */
19
+ export class CrossAccountReturnRoutes extends Construct {
20
+ customResource;
21
+ constructor(scope, id, props) {
22
+ super(scope, id);
23
+ this.customResource = new CustomResource(this, "Handler", {
24
+ runtime: Runtime.NODEJS_22_X,
25
+ timeout: Duration.minutes(5),
26
+ lambdaDescription: `${id} cross-account return-route manager`,
27
+ roleDescription: `${id} return-route Lambda execution role`,
28
+ inlinePolicy: [
29
+ new PolicyStatement({
30
+ effect: Effect.ALLOW,
31
+ actions: ["sts:AssumeRole"],
32
+ resources: [props.peerRoleArn]
33
+ })
34
+ ],
35
+ properties: {
36
+ PeerRoleArn: props.peerRoleArn,
37
+ PeerRegion: props.peerRegion,
38
+ RouteTableIds: Fn.join(",", props.routeTableIds),
39
+ LocalVpcCidr: props.localVpcCidr,
40
+ PeeringConnectionId: props.peeringConnectionId,
41
+ EnableDns: props.enableDns === false ? "false" : "true"
42
+ },
43
+ inlineCode: HANDLER_SOURCE
44
+ });
45
+ }
46
+ }
47
+ // Handler source. Inline because the CustomResource utility expects a string;
48
+ // extracting to its own file would require switching to a bundled asset.
49
+ const HANDLER_SOURCE = `
50
+ const { STSClient, AssumeRoleCommand } = require("@aws-sdk/client-sts");
51
+ const {
52
+ EC2Client,
53
+ CreateRouteCommand,
54
+ DeleteRouteCommand,
55
+ ModifyVpcPeeringConnectionOptionsCommand
56
+ } = require("@aws-sdk/client-ec2");
57
+
58
+ async function buildAccepterClient(props) {
59
+ const sts = new STSClient({});
60
+ const assumed = await sts.send(new AssumeRoleCommand({
61
+ RoleArn: props.PeerRoleArn,
62
+ RoleSessionName: "fjall-vpc-peer-return-routes"
63
+ }));
64
+ const creds = assumed.Credentials;
65
+ if (!creds || !creds.AccessKeyId || !creds.SecretAccessKey || !creds.SessionToken) {
66
+ throw new Error("AssumeRole returned no credentials");
67
+ }
68
+ return new EC2Client({
69
+ region: props.PeerRegion,
70
+ credentials: {
71
+ accessKeyId: creds.AccessKeyId,
72
+ secretAccessKey: creds.SecretAccessKey,
73
+ sessionToken: creds.SessionToken
74
+ }
75
+ });
76
+ }
77
+
78
+ function parseRouteTableIds(value) {
79
+ if (!value) return [];
80
+ return value.split(",").map((s) => s.trim()).filter(Boolean);
81
+ }
82
+
83
+ exports.handler = async (event) => {
84
+ const props = event.ResourceProperties || {};
85
+ const physicalResourceId = event.PhysicalResourceId || event.LogicalResourceId || "return-routes";
86
+ const routeTableIds = parseRouteTableIds(props.RouteTableIds);
87
+ const ec2 = await buildAccepterClient(props);
88
+
89
+ if (event.RequestType === "Delete") {
90
+ for (const routeTableId of routeTableIds) {
91
+ try {
92
+ await ec2.send(new DeleteRouteCommand({
93
+ RouteTableId: routeTableId,
94
+ DestinationCidrBlock: props.LocalVpcCidr
95
+ }));
96
+ } catch (err) {
97
+ const name = err && err.name;
98
+ if (name === "InvalidRoute.NotFound" || name === "InvalidRouteTableID.NotFound") {
99
+ continue;
100
+ }
101
+ throw err;
102
+ }
103
+ }
104
+ return { PhysicalResourceId: physicalResourceId };
105
+ }
106
+
107
+ if (event.RequestType === "Update") {
108
+ const oldProps = event.OldResourceProperties || {};
109
+ const oldRouteTableIds = parseRouteTableIds(oldProps.RouteTableIds);
110
+ const oldCidr = oldProps.LocalVpcCidr || props.LocalVpcCidr;
111
+ const cidrChanged = oldCidr !== props.LocalVpcCidr;
112
+ const currentSet = new Set(routeTableIds);
113
+ // CIDR change: stale routes persist on every kept table unless deleted by old CIDR first.
114
+ const idsToDeleteOldCidr = cidrChanged
115
+ ? oldRouteTableIds
116
+ : oldRouteTableIds.filter((id) => !currentSet.has(id));
117
+ for (const routeTableId of idsToDeleteOldCidr) {
118
+ try {
119
+ await ec2.send(new DeleteRouteCommand({
120
+ RouteTableId: routeTableId,
121
+ DestinationCidrBlock: oldCidr
122
+ }));
123
+ } catch (err) {
124
+ const name = err && err.name;
125
+ if (name === "InvalidRoute.NotFound" || name === "InvalidRouteTableID.NotFound") {
126
+ continue;
127
+ }
128
+ throw err;
129
+ }
130
+ }
131
+ }
132
+
133
+ for (const routeTableId of routeTableIds) {
134
+ try {
135
+ await ec2.send(new CreateRouteCommand({
136
+ RouteTableId: routeTableId,
137
+ DestinationCidrBlock: props.LocalVpcCidr,
138
+ VpcPeeringConnectionId: props.PeeringConnectionId
139
+ }));
140
+ } catch (err) {
141
+ const name = err && err.name;
142
+ if (name !== "RouteAlreadyExists") {
143
+ throw err;
144
+ }
145
+ }
146
+ }
147
+
148
+ // Always sent so a flip from true to false actually disables DNS — guarding on === "true" leaves it enabled forever.
149
+ await ec2.send(new ModifyVpcPeeringConnectionOptionsCommand({
150
+ VpcPeeringConnectionId: props.PeeringConnectionId,
151
+ AccepterPeeringConnectionOptions: {
152
+ AllowDnsResolutionFromRemoteVpc: props.EnableDns === "true"
153
+ }
154
+ }));
155
+
156
+ return { PhysicalResourceId: physicalResourceId };
157
+ };
158
+ `.trim();
@@ -1,5 +1,6 @@
1
- import { Duration, Tags } from "aws-cdk-lib";
1
+ import { Duration } from "aws-cdk-lib";
2
2
  import { DNS_APEX } from "@fjall/util";
3
+ import { applyCostAllocationTags } from "../../../../utils/costAllocationTags.js";
3
4
  export const DEFAULT_DNS_TTL_SECONDS = 300;
4
5
  export function resolveFqdn(zoneName, recordName) {
5
6
  return recordName === DNS_APEX ? zoneName : `${recordName}.${zoneName}`;
@@ -11,7 +12,9 @@ export function defaultDnsComment(recordType, fqdn) {
11
12
  return `Fjall-managed ${recordType} record for ${fqdn}`;
12
13
  }
13
14
  export function applyDnsRecordTags(construct, props) {
14
- Tags.of(construct).add("fjall:costAllocation:environment", props.costAllocationEnvironment ?? "management");
15
- Tags.of(construct).add("fjall:costAllocation:service", "dnsRecord");
16
- Tags.of(construct).add("fjall:costAllocation:domain", props.costAllocationDomain ?? props.zoneName);
15
+ applyCostAllocationTags(construct, {
16
+ service: "dnsRecord",
17
+ domain: props.costAllocationDomain ?? props.zoneName,
18
+ environment: props.costAllocationEnvironment
19
+ });
17
20
  }
@@ -1,5 +1,5 @@
1
1
  import { Construct } from "constructs";
2
- import { Certificate } from "aws-cdk-lib/aws-certificatemanager";
2
+ import { type ICertificate } from "aws-cdk-lib/aws-certificatemanager";
3
3
  import { type IHostedZone } from "aws-cdk-lib/aws-route53";
4
4
  export interface DomainCertificateProps {
5
5
  readonly domainName: string;
@@ -11,7 +11,7 @@ export interface DomainCertificateProps {
11
11
  readonly costAllocationDomain?: string;
12
12
  }
13
13
  export declare class DomainCertificate extends Construct {
14
- readonly certificate: Certificate;
14
+ readonly certificate: ICertificate;
15
15
  readonly certificateArn: string;
16
16
  readonly description: string;
17
17
  constructor(scope: Construct, id: string, props: DomainCertificateProps);
@@ -3,6 +3,7 @@ import { CfnOutput, Tags } from "aws-cdk-lib";
3
3
  import { Certificate, CertificateValidation } from "aws-cdk-lib/aws-certificatemanager";
4
4
  import { getDomainExportNames } from "@fjall/util";
5
5
  import { toPascalCase } from "../../../utils/capitaliseString.js";
6
+ import { applyCostAllocationTags } from "../../../utils/costAllocationTags.js";
6
7
  export class DomainCertificate extends Construct {
7
8
  certificate;
8
9
  certificateArn;
@@ -20,9 +21,11 @@ export class DomainCertificate extends Construct {
20
21
  });
21
22
  this.certificateArn = this.certificate.certificateArn;
22
23
  Tags.of(this).add("fjall:description", this.description);
23
- Tags.of(this).add("fjall:costAllocation:environment", props.costAllocationEnvironment ?? "management");
24
- Tags.of(this).add("fjall:costAllocation:service", "certificate");
25
- Tags.of(this).add("fjall:costAllocation:domain", props.costAllocationDomain ?? props.domainName);
24
+ applyCostAllocationTags(this, {
25
+ service: "certificate",
26
+ domain: props.costAllocationDomain ?? props.domainName,
27
+ environment: props.costAllocationEnvironment
28
+ });
26
29
  const safeKey = toPascalCase(props.domainName.split(".").join(""));
27
30
  const exports = getDomainExportNames(props.domainName);
28
31
  new CfnOutput(this, `${safeKey}CertificateArn`, {
@@ -4,6 +4,7 @@ import { HostedZone as AWSHostedZone } from "aws-cdk-lib/aws-route53";
4
4
  import { getDomainExportNames } from "@fjall/util";
5
5
  import { toPascalCase, getSafeZoneName } from "../../../utils/capitaliseString.js";
6
6
  import { DelegationRole } from "../iam/delegationRole.js";
7
+ import { applyCostAllocationTags } from "../../../utils/costAllocationTags.js";
7
8
  export class HostedZoneFactory {
8
9
  static import(stack, hostedZoneId, zoneName, opts) {
9
10
  const safeZone = toPascalCase(getSafeZoneName(zoneName));
@@ -70,10 +71,11 @@ export class HostedZone extends Construct {
70
71
  this.isImported = true;
71
72
  Tags.of(this).add("fjall:description", this.description);
72
73
  }
73
- // Cost allocation tags applied uniformly on both create and import paths.
74
- Tags.of(this).add("fjall:costAllocation:environment", props.costAllocationEnvironment ?? "management");
75
- Tags.of(this).add("fjall:costAllocation:service", "hostedZone");
76
- Tags.of(this).add("fjall:costAllocation:domain", props.costAllocationDomain ?? props.zoneName);
74
+ applyCostAllocationTags(this, {
75
+ service: "hostedZone",
76
+ domain: props.costAllocationDomain ?? props.zoneName,
77
+ environment: props.costAllocationEnvironment
78
+ });
77
79
  new CfnOutput(this, `${safeZone}HostedZoneId`, {
78
80
  key: `${safeZone}HostedZoneId`,
79
81
  value: this.hostedZoneId,
@@ -1,4 +1,5 @@
1
1
  export * from "./crossAccountDelegationRecord.js";
2
+ export * from "./crossAccountReturnRoutes.js";
2
3
  export * from "./dnsRecord/index.js";
3
4
  export * from "./domainCertificate.js";
4
5
  export * from "./hostedZone.js";
@@ -6,3 +7,5 @@ export * from "./ipam.js";
6
7
  export * from "./ipamPool.js";
7
8
  export * from "./securityGroup.js";
8
9
  export * from "./vpc.js";
10
+ export * from "./vpcPeeringAccepterRole.js";
11
+ export * from "./vpcPeeringConnection.js";
@@ -1,4 +1,5 @@
1
1
  export * from "./crossAccountDelegationRecord.js";
2
+ export * from "./crossAccountReturnRoutes.js";
2
3
  export * from "./dnsRecord/index.js";
3
4
  export * from "./domainCertificate.js";
4
5
  export * from "./hostedZone.js";
@@ -6,3 +7,5 @@ export * from "./ipam.js";
6
7
  export * from "./ipamPool.js";
7
8
  export * from "./securityGroup.js";
8
9
  export * from "./vpc.js";
10
+ export * from "./vpcPeeringAccepterRole.js";
11
+ export * from "./vpcPeeringConnection.js";