@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,398 @@
1
+ /**
2
+ * PersistentDataVolume LAUNCHING handler. Inlined into a Lambda at synth
3
+ * time by persistentDataVolume.ts. Triggered by an SQS queue fed by an
4
+ * EventBridge rule subscribed to the ASG EC2_INSTANCE_LAUNCHING lifecycle
5
+ * event on the default bus.
6
+ *
7
+ * Wire format: the SQS message body is the EventBridge event envelope
8
+ * `{ version, "detail-type", source, account, time, region, resources,
9
+ * detail: { LifecycleActionToken, AutoScalingGroupName,
10
+ * LifecycleHookName, EC2InstanceId, LifecycleTransition,
11
+ * NotificationMetadata, ... } }`.
12
+ * The lifecycle payload lives at `body.detail`. Records whose envelope does
13
+ * not match (wrong source, missing detail, non-Lifecycle Action detail-type)
14
+ * are logged and skipped — they shouldn't reach this queue but a defensive
15
+ * guard is cheap and avoids crashing the loop on stray test events.
16
+ *
17
+ * Behaviour:
18
+ * 1. Unwrap the EventBridge envelope; reject non-lifecycle records.
19
+ * 2. DescribeVolumes filtered by THREE tags: `tag:fjall:Lifecycle ==
20
+ * data-volume` AND `tag:fjall:OwnerLogicalId == OWNER_LOGICAL_ID` AND
21
+ * `tag:fjall:StackId == STACK_ID`. The StackId filter discriminates
22
+ * this deploy's volume from any prior-iteration orphans (CREATE_ROLLBACK
23
+ * failures, aborted destroys) that share the OwnerLogicalId but carry
24
+ * a stale StackId. No match → ABANDON so the ASG kills the new instance
25
+ * and retries; multiple matches → ABANDON and surface to the
26
+ * Lambda-Errors alarm (a tag-uniqueness invariant violation; silently
27
+ * picking one risks attaching the wrong data and requires operator
28
+ * investigation).
29
+ * 3. AttachVolume(Device: DEVICE_NAME, InstanceId, VolumeId).
30
+ * 4. Poll DescribeVolumes until State === "in-use" AND the matching
31
+ * Attachments[].State === "attached", deadline 90s. Timeout → ABANDON.
32
+ * 5. CompleteLifecycleAction(CONTINUE) on success.
33
+ *
34
+ * The Lambda is paired with the TERMINATING-side DetachVolume branch in
35
+ * ec2GracefulTerminationLambda.source.cjs. The two Lambdas form a closed
36
+ * re-attach contract: TERMINATING detaches the tagged volume from the
37
+ * outgoing instance, LAUNCHING (this file) re-attaches it to the incoming
38
+ * instance.
39
+ *
40
+ * CommonJS rather than ESM because Code.fromInline lands the source as
41
+ * `index.js`, which Lambda treats as CommonJS by default.
42
+ */
43
+ const {
44
+ EC2Client,
45
+ DescribeVolumesCommand,
46
+ AttachVolumeCommand
47
+ } = require("@aws-sdk/client-ec2");
48
+ const {
49
+ AutoScalingClient,
50
+ CompleteLifecycleActionCommand
51
+ } = require("@aws-sdk/client-auto-scaling");
52
+
53
+ const ATTACH_POLL_INTERVAL_MS = 5_000;
54
+ // 90s attach deadline. AWS volume attach is usually < 30s; the bootstrap
55
+ // script waits 60s for /dev/xvdf so the hook MUST complete inside that.
56
+ const ATTACH_DEADLINE_MS = 90_000;
57
+ const TAG_OWNER_LOGICAL_ID = "fjall:OwnerLogicalId";
58
+ const TAG_LIFECYCLE = "fjall:Lifecycle";
59
+ const TAG_LIFECYCLE_VALUE = "data-volume";
60
+ const TAG_STACK_ID = "fjall:StackId";
61
+
62
+ function errMessage(err) {
63
+ return err && err.message ? err.message : String(err);
64
+ }
65
+
66
+ let _ec2;
67
+ let _asg;
68
+ function getEc2() {
69
+ if (!_ec2) _ec2 = new EC2Client({});
70
+ return _ec2;
71
+ }
72
+ function getAsg() {
73
+ if (!_asg) _asg = new AutoScalingClient({});
74
+ return _asg;
75
+ }
76
+
77
+ function parseLifecycleMessage(record) {
78
+ const envelope = JSON.parse(record.body);
79
+ const detail = envelope && envelope.detail;
80
+ const source = envelope && envelope.source;
81
+ const detailType = envelope && envelope["detail-type"];
82
+ const valid =
83
+ detail !== undefined &&
84
+ detail !== null &&
85
+ typeof detail === "object" &&
86
+ source === "aws.autoscaling" &&
87
+ typeof detailType === "string" &&
88
+ detailType.endsWith("Lifecycle Action");
89
+ if (!valid) {
90
+ return { valid: false };
91
+ }
92
+ return {
93
+ valid: true,
94
+ instanceId: detail.EC2InstanceId,
95
+ asgName: detail.AutoScalingGroupName,
96
+ actionToken: detail.LifecycleActionToken,
97
+ hookName: detail.LifecycleHookName,
98
+ isTest: false
99
+ };
100
+ }
101
+
102
+ function readEnv(env, name) {
103
+ const value = env[name];
104
+ if (typeof value !== "string" || value === "") return undefined;
105
+ return value;
106
+ }
107
+
108
+ async function findOwnedVolume(ec2Client, ownerLogicalId, stackId) {
109
+ const result = await ec2Client.send(
110
+ new DescribeVolumesCommand({
111
+ Filters: [
112
+ { Name: `tag:${TAG_OWNER_LOGICAL_ID}`, Values: [ownerLogicalId] },
113
+ { Name: `tag:${TAG_LIFECYCLE}`, Values: [TAG_LIFECYCLE_VALUE] },
114
+ { Name: `tag:${TAG_STACK_ID}`, Values: [stackId] }
115
+ ]
116
+ })
117
+ );
118
+ const volumes = result.Volumes || [];
119
+ return volumes;
120
+ }
121
+
122
+ async function pollUntilAttached(
123
+ ec2Client,
124
+ volumeId,
125
+ instanceId,
126
+ sleepFn,
127
+ nowFn
128
+ ) {
129
+ const deadline = nowFn() + ATTACH_DEADLINE_MS;
130
+ while (nowFn() < deadline) {
131
+ const desc = await ec2Client.send(
132
+ new DescribeVolumesCommand({ VolumeIds: [volumeId] })
133
+ );
134
+ const volume =
135
+ desc.Volumes && desc.Volumes[0] ? desc.Volumes[0] : undefined;
136
+ if (volume) {
137
+ const attachment = (volume.Attachments || []).find(
138
+ (a) => a.InstanceId === instanceId
139
+ );
140
+ if (volume.State === "in-use" && attachment?.State === "attached") {
141
+ return true;
142
+ }
143
+ }
144
+ await sleepFn(ATTACH_POLL_INTERVAL_MS);
145
+ }
146
+ return false;
147
+ }
148
+
149
+ async function completeLifecycle(
150
+ asgClient,
151
+ asgName,
152
+ hookName,
153
+ actionToken,
154
+ result
155
+ ) {
156
+ await asgClient.send(
157
+ new CompleteLifecycleActionCommand({
158
+ AutoScalingGroupName: asgName,
159
+ LifecycleHookName: hookName,
160
+ LifecycleActionToken: actionToken,
161
+ LifecycleActionResult: result
162
+ })
163
+ );
164
+ }
165
+
166
+ async function processRecord(record, deps) {
167
+ const ec2Client = (deps && deps.ec2Client) || getEc2();
168
+ const asgClient = (deps && deps.asgClient) || getAsg();
169
+ const sleepFn =
170
+ (deps && deps.sleep) ||
171
+ ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
172
+ const nowFn = (deps && deps.now) || (() => Date.now());
173
+ const env = (deps && deps.env) || process.env;
174
+ const log =
175
+ (deps && deps.log) ||
176
+ ((msg, fields) => {
177
+ console.log(JSON.stringify({ message: msg, ...(fields || {}) }));
178
+ });
179
+
180
+ const parsed = parseLifecycleMessage(record);
181
+ if (!parsed.valid) {
182
+ log("Record is not an aws.autoscaling Lifecycle Action; skipping", {
183
+ bodyPreview: record.body ? String(record.body).slice(0, 200) : undefined
184
+ });
185
+ return;
186
+ }
187
+ const { instanceId, asgName, actionToken, hookName } = parsed;
188
+
189
+ const ownerLogicalId = readEnv(env, "OWNER_LOGICAL_ID");
190
+ const stackId = readEnv(env, "STACK_ID");
191
+ const deviceName = readEnv(env, "DEVICE_NAME");
192
+ if (
193
+ ownerLogicalId === undefined ||
194
+ stackId === undefined ||
195
+ deviceName === undefined
196
+ ) {
197
+ log("OWNER_LOGICAL_ID, STACK_ID or DEVICE_NAME unset; abandoning", {
198
+ asgName,
199
+ instanceId
200
+ });
201
+ await completeLifecycle(
202
+ asgClient,
203
+ asgName,
204
+ hookName,
205
+ actionToken,
206
+ "ABANDON"
207
+ );
208
+ return;
209
+ }
210
+
211
+ let volumes;
212
+ try {
213
+ volumes = await findOwnedVolume(ec2Client, ownerLogicalId, stackId);
214
+ } catch (err) {
215
+ log("DescribeVolumes failed; abandoning", {
216
+ asgName,
217
+ instanceId,
218
+ error: errMessage(err)
219
+ });
220
+ await completeLifecycle(
221
+ asgClient,
222
+ asgName,
223
+ hookName,
224
+ actionToken,
225
+ "ABANDON"
226
+ );
227
+ return;
228
+ }
229
+
230
+ if (volumes.length === 0) {
231
+ log("No tagged data volume found; abandoning launch", {
232
+ ownerLogicalId,
233
+ instanceId
234
+ });
235
+ await completeLifecycle(
236
+ asgClient,
237
+ asgName,
238
+ hookName,
239
+ actionToken,
240
+ "ABANDON"
241
+ );
242
+ return;
243
+ }
244
+
245
+ if (volumes.length > 1) {
246
+ log(
247
+ "Multiple tagged data volumes found; abandoning launch — tag-uniqueness invariant violated",
248
+ {
249
+ ownerLogicalId,
250
+ volumeIds: volumes.map((v) => v.VolumeId)
251
+ }
252
+ );
253
+ await completeLifecycle(
254
+ asgClient,
255
+ asgName,
256
+ hookName,
257
+ actionToken,
258
+ "ABANDON"
259
+ );
260
+ return;
261
+ }
262
+
263
+ const volume = volumes[0];
264
+ const volumeId = volume.VolumeId;
265
+
266
+ // Already attached to the launching instance (e.g. previous Lambda invocation
267
+ // succeeded before the hook timeout fired and ASG re-fired the message).
268
+ const existingAttachment = (volume.Attachments || []).find(
269
+ (a) => a.InstanceId === instanceId && a.State === "attached"
270
+ );
271
+ if (existingAttachment !== undefined) {
272
+ log("Volume already attached to launching instance; completing CONTINUE", {
273
+ volumeId,
274
+ instanceId
275
+ });
276
+ await completeLifecycle(
277
+ asgClient,
278
+ asgName,
279
+ hookName,
280
+ actionToken,
281
+ "CONTINUE"
282
+ );
283
+ return;
284
+ }
285
+
286
+ try {
287
+ await ec2Client.send(
288
+ new AttachVolumeCommand({
289
+ VolumeId: volumeId,
290
+ InstanceId: instanceId,
291
+ Device: deviceName
292
+ })
293
+ );
294
+ } catch (err) {
295
+ log("AttachVolume failed; abandoning", {
296
+ volumeId,
297
+ instanceId,
298
+ error: errMessage(err)
299
+ });
300
+ await completeLifecycle(
301
+ asgClient,
302
+ asgName,
303
+ hookName,
304
+ actionToken,
305
+ "ABANDON"
306
+ );
307
+ return;
308
+ }
309
+
310
+ const attached = await pollUntilAttached(
311
+ ec2Client,
312
+ volumeId,
313
+ instanceId,
314
+ sleepFn,
315
+ nowFn
316
+ );
317
+ if (!attached) {
318
+ log(
319
+ "Volume attach did not reach in-use/attached within deadline; abandoning",
320
+ {
321
+ volumeId,
322
+ instanceId,
323
+ deadlineMs: ATTACH_DEADLINE_MS
324
+ }
325
+ );
326
+ await completeLifecycle(
327
+ asgClient,
328
+ asgName,
329
+ hookName,
330
+ actionToken,
331
+ "ABANDON"
332
+ );
333
+ return;
334
+ }
335
+
336
+ log("Volume attached successfully; completing CONTINUE", {
337
+ volumeId,
338
+ instanceId
339
+ });
340
+ await completeLifecycle(
341
+ asgClient,
342
+ asgName,
343
+ hookName,
344
+ actionToken,
345
+ "CONTINUE"
346
+ );
347
+ }
348
+
349
+ exports.handler = async (event) => {
350
+ const records = (event && event.Records) || [];
351
+ for (const record of records) {
352
+ try {
353
+ await processRecord(record);
354
+ } catch (err) {
355
+ console.error(
356
+ JSON.stringify({
357
+ message:
358
+ "PersistentDataVolume LAUNCHING handler crashed; attempting ABANDON",
359
+ error: errMessage(err)
360
+ })
361
+ );
362
+ try {
363
+ const parsed = parseLifecycleMessage(record);
364
+ if (!parsed.valid) continue;
365
+ await completeLifecycle(
366
+ getAsg(),
367
+ parsed.asgName,
368
+ parsed.hookName,
369
+ parsed.actionToken,
370
+ "ABANDON"
371
+ );
372
+ } catch (innerErr) {
373
+ console.error(
374
+ JSON.stringify({
375
+ message: "CompleteLifecycleAction also failed",
376
+ error: errMessage(innerErr)
377
+ })
378
+ );
379
+ throw err;
380
+ }
381
+ }
382
+ }
383
+ };
384
+
385
+ exports._internals = {
386
+ parseLifecycleMessage,
387
+ readEnv,
388
+ findOwnedVolume,
389
+ pollUntilAttached,
390
+ completeLifecycle,
391
+ processRecord,
392
+ ATTACH_POLL_INTERVAL_MS,
393
+ ATTACH_DEADLINE_MS,
394
+ TAG_OWNER_LOGICAL_ID,
395
+ TAG_LIFECYCLE,
396
+ TAG_LIFECYCLE_VALUE,
397
+ TAG_STACK_ID
398
+ };
@@ -0,0 +1,15 @@
1
+ import { Construct } from "constructs";
2
+ export interface SamApplicationProps {
3
+ readonly applicationId: string;
4
+ readonly semanticVersion: string;
5
+ readonly parameters: Record<string, string>;
6
+ readonly description?: string;
7
+ readonly costAllocationService: string;
8
+ readonly costAllocationDomain: string;
9
+ readonly costAllocationEnvironment?: string;
10
+ }
11
+ export declare class SamApplication extends Construct {
12
+ private readonly application;
13
+ readonly description: string;
14
+ constructor(scope: Construct, id: string, props: SamApplicationProps);
15
+ }
@@ -0,0 +1,27 @@
1
+ import { Tags } from "aws-cdk-lib";
2
+ import { CfnApplication } from "aws-cdk-lib/aws-sam";
3
+ import { Construct } from "constructs";
4
+ import { applyCostAllocationTags } from "../../../utils/costAllocationTags.js";
5
+ export class SamApplication extends Construct {
6
+ application;
7
+ description;
8
+ constructor(scope, id, props) {
9
+ super(scope, id);
10
+ this.description =
11
+ props.description ??
12
+ `Fjall-managed SAR application: ${props.applicationId}`;
13
+ this.application = new CfnApplication(this, "Application", {
14
+ location: {
15
+ applicationId: props.applicationId,
16
+ semanticVersion: props.semanticVersion
17
+ },
18
+ parameters: props.parameters
19
+ });
20
+ Tags.of(this).add("fjall:description", this.description);
21
+ applyCostAllocationTags(this, {
22
+ service: props.costAllocationService,
23
+ domain: props.costAllocationDomain,
24
+ environment: props.costAllocationEnvironment
25
+ });
26
+ }
27
+ }
@@ -0,0 +1,159 @@
1
+ /** Database name created at ClickHouse bootstrap; consumed by BACKUP DATABASE,
2
+ * OPTIMIZE TABLE, and the DatabaseName CfnOutput so all four sites share one source. */
3
+ export declare const CLICKHOUSE_DATABASE_NAME = "analytics";
4
+ /** Default EC2 instance type for ClickHouse (Graviton3 — fixed-spec cores).
5
+ *
6
+ * `m7g.medium` (1 vCPU sustained, 4 GiB) chosen over `t4g.medium` (2 vCPU
7
+ * burst, 4 GiB) because CH's steady-state merge/scan/aggregation profile is
8
+ * not bursty — burst credits run out under the per-5min metrics ingest plus
9
+ * the OPTIMIZE FINAL sidecar, leaving the box throttled at baseline (10–20%
10
+ * of 2 vCPU) for the rest of the hour. Graviton3 is also ~25% faster on
11
+ * columnar scans than Graviton2.
12
+ *
13
+ * Settings are tuned for 1 vCPU sustained (max_threads=1 across all profiles,
14
+ * max_concurrent_queries=4) — see clickhouseUserData.ts. Bumping to a larger
15
+ * instance MUST be paired with raising those thread/concurrency caps.
16
+ *
17
+ * Next size up: `m7g.large` (2 vCPU, 8 GiB) for sustained workloads, or
18
+ * `r7g.medium` (1 vCPU, 8 GiB) when memory-bound. Set via
19
+ * `clickhouseInstanceType` CDK context or the `instanceType` prop, no code
20
+ * change needed (see clickhouseDatabase.ts:162). */
21
+ export declare const DEFAULT_CLICKHOUSE_INSTANCE_TYPE = "m7g.medium";
22
+ /** ClickHouse container image. Explicit `docker.io/` prefix is required so
23
+ * string-form consumers in `ecsImages.ts#getContainerImage()` route through
24
+ * `ContainerImage.fromRegistry(...)` instead of the ECR-repo fallback (which
25
+ * would otherwise append a service-tag suffix and produce a double-colon
26
+ * malformed image reference).
27
+ *
28
+ * Pin the full patch (not the floating `26.3`) so prod and local containers
29
+ * always run the same digest. The floating tag bit us once when CH 26.3.10
30
+ * tightened config.xml validation overnight while local Docker caches still
31
+ * held the prior patch — keep this pin in lockstep with
32
+ * `webapp/docker-compose.yaml`.
33
+ *
34
+ * Default Ubuntu (glibc) variant chosen over `-alpine` (musl): musl's
35
+ * threading/NPTL implementation is materially slower under CH's per-merge
36
+ * / per-query-stage thread fan-out, and musl's 128 KB default thread stack
37
+ * has a recurring history of stack-overflow incidents on deep query plans.
38
+ * Image size is ~250 MB larger but irrelevant on a single-instance EC2 ASG
39
+ * that pulls once per launch. Upstream CH CI runs its full perf + stress
40
+ * matrix on the Ubuntu build; Alpine is community-tier coverage. */
41
+ export declare const CLICKHOUSE_IMAGE = "docker.io/clickhouse/clickhouse-server:26.3.10.60";
42
+ /** EBS volume configuration. */
43
+ export declare const CLICKHOUSE_EBS_VOLUME_SIZE_GB = 80;
44
+ export declare const CLICKHOUSE_EBS_IOPS = 3000;
45
+ export declare const CLICKHOUSE_EBS_THROUGHPUT_MBPS = 125;
46
+ /** ECS task resource allocation. m7g.medium ships with 4 GB; reserve 1 GB
47
+ * for the host (kernel + ECS agent + cloudwatch agent + IMDS) and give
48
+ * ClickHouse the remaining 3 GB. Stays right when bumping to m7g.large
49
+ * (8 GB) — raise this value in lockstep, or fall back to deriving from
50
+ * the instance type if we ever support multiple sizes. */
51
+ export declare const CLICKHOUSE_TASK_MEMORY_MIB = 3072;
52
+ /** ClickHouse ports. */
53
+ export declare const CLICKHOUSE_HTTP_PORT = 8123;
54
+ export declare const CLICKHOUSE_NATIVE_PORT = 9000;
55
+ export declare const CLICKHOUSE_PROMETHEUS_PORT = 9363;
56
+ /** EBS device name for the data volume (must match user data script). */
57
+ export declare const CLICKHOUSE_EBS_DEVICE_NAME = "/dev/xvdf";
58
+ /** EBS mount path on the EC2 host. */
59
+ export declare const CLICKHOUSE_DATA_MOUNT_PATH = "/mnt/clickhouse-data";
60
+ /** Secrets Manager path prefix. */
61
+ export declare const CLICKHOUSE_SECRETS_PREFIX = "fjall/clickhouse";
62
+ /** Canonical Secrets Manager secret name for a ClickHouse user.
63
+ * Two sites compute this name and they MUST match:
64
+ * (a) the secret mint inside `createClickHouseUserSecret(...)` in
65
+ * `clickhouseDatabase.ts` — calls this helper directly;
66
+ * (b) the SSM live-reload script that fetches the admin password via
67
+ * `aws secretsmanager get-secret-value --secret-id` — also calls
68
+ * this helper directly.
69
+ * Drift between the two would silently break the live-reload path post-
70
+ * deploy on a users.d S3 change (no compile or test signal). */
71
+ export declare function clickHouseUserSecretName(userName: string): string;
72
+ /** ECS-injected env var name carrying a user's plaintext password.
73
+ * Canonical source is `@fjall/util/migration § userPasswordEnvName` — re-exported
74
+ * here so the construct-side import chain stays unchanged while the runner
75
+ * (`webapp/scripts/migration-runner.mjs`) and any future consumer share a single
76
+ * declaration. Coupled values per `.claude/rules/code-quality.md § "Coupled values:
77
+ * shared source at 2 occurrences"`. */
78
+ export { userPasswordEnvName } from "@fjall/util/migration";
79
+ /** Env var name carrying the SHA-256 of a user's plaintext password.
80
+ * The container entrypoint wrapper derives this name from the
81
+ * `USER_<NAME>_PASSWORD` env var injected by ECS `secretsImport`, then
82
+ * resolves `<password_sha256_hex from_env="..."/>` directives in
83
+ * `users.d/fjall.xml` against it at server start. */
84
+ export declare function userSha256EnvName(userName: string): string;
85
+ /** Canonical bash pipeline that hashes a ClickHouse user password.
86
+ * Invoked from the container entrypoint wrapper
87
+ * (`buildClickHouseEntrypointWrapper`) to derive `USER_<NAME>_SHA256` from
88
+ * the ECS-injected plaintext at server start.
89
+ * @param plaintextShellRef A quoted bash reference to the plaintext
90
+ * password (e.g. `"\${!var}"` for the wrapper's indirect deref). */
91
+ export declare function clickHousePasswordSha256Snippet(plaintextShellRef: string): string;
92
+ /** Tag identifying ClickHouse server ASG instances.
93
+ * Three sites carry this pair and they MUST match: (a) the `tags:` map on
94
+ * the ClickHouse EC2 instance (threaded through to ASG instance tags),
95
+ * (b) the SSM `Targets: [{ Key: "tag:<key>", Values: ["<value>"] }]` filter
96
+ * on the live-reload `AwsCustomResource`, and (c) the synth snapshot tests
97
+ * pinning the relationship. Drift between (a) and (b) silently routes the
98
+ * reload command to zero instances — the `InvocationDoesNotExist` failure
99
+ * is already suppressed by `ignoreErrorCodesMatching`, so the misroute
100
+ * emits no signal. */
101
+ export declare const CLICKHOUSE_SERVER_ROLE_TAG: {
102
+ readonly key: "ClickHouseRole";
103
+ readonly value: "server";
104
+ };
105
+ /** Shared secret generation options (all ClickHouse users share the same policy). */
106
+ export declare const CLICKHOUSE_SECRET_OPTIONS: {
107
+ readonly excludePunctuation: true;
108
+ readonly passwordLength: 32;
109
+ };
110
+ /** Health check configuration. */
111
+ export declare const CLICKHOUSE_HEALTH_CHECK: {
112
+ readonly INTERVAL_SECONDS: 30;
113
+ readonly TIMEOUT_SECONDS: 5;
114
+ readonly RETRIES: 3;
115
+ readonly START_PERIOD_SECONDS: 120;
116
+ };
117
+ /** Container stop timeout in seconds. ECS waits this long after SIGTERM
118
+ * before sending SIGKILL. ClickHouse needs the longer-than-default window
119
+ * (CDK default: 30s) to flush in-progress merges, finish writing parts,
120
+ * and close the data directory cleanly — premature SIGKILL on a 1 vCPU
121
+ * m7g.medium under merge load risks `max_suspicious_broken_parts` on the
122
+ * next start. ECS upper bound is 120s. */
123
+ export declare const CLICKHOUSE_STOP_TIMEOUT_SECONDS = 90;
124
+ /** OPTIMIZE TABLE FINAL schedule.
125
+ * RMT tables carry min_age_to_force_merge_seconds=600 so the engine already merges
126
+ * old parts within 10 min; this task is a safety net for MVs (no engine-level setting)
127
+ * and for ReplacingMergeTree dedup under skewed write patterns. 6 hours is sufficient. */
128
+ export declare const OPTIMISE_FINAL_SCHEDULE = "rate(6 hours)";
129
+ /** Tables requiring periodic OPTIMIZE FINAL (ReplacingMergeTree only).
130
+ * Keep in sync with REPLACING_MERGE_TREE_TABLES in
131
+ * webapp/app/.server/lib/clickhouse/tenantQuery/tableConstants.ts (auto-FINAL). */
132
+ export declare const REPLACING_MERGE_TREE_TABLES: readonly ["application_metrics", "cost_records", "log_fingerprints", "insights", "asset_inventory"];
133
+ /** Subdirectory on the EBS volume for server config files (must match CDK volume mount). */
134
+ export declare const CLICKHOUSE_CONFIG_SUBDIR = "server-config.d";
135
+ /** Subdirectory on the EBS volume for users config files (must match CDK volume mount). */
136
+ export declare const CLICKHOUSE_USERS_SUBDIR = "server-users.d";
137
+ /** Cloud Map service name (resolves to `clickhouse.<appName>.local` via the per-app namespace registered by `App.getInstance().registerService(...)`). */
138
+ export declare const CLICKHOUSE_CLOUDMAP_SERVICE_NAME = "clickhouse";
139
+ /** ECS container name for the ClickHouse server task. Threaded through both
140
+ * the `addContainer` call site and the SSM reload script (which uses
141
+ * `docker ps --filter "label=com.amazonaws.ecs.container-name=<name>"` to
142
+ * locate the running container). Drift between the two sites turns the
143
+ * live-reload path into a silent no-op without any compile/test signal,
144
+ * so this lives as a single source of truth. */
145
+ export declare const CLICKHOUSE_SERVER_CONTAINER_NAME = "clickhouse";
146
+ /** Materialised views that benefit from periodic OPTIMIZE to reduce part count at read time.
147
+ * These are not ReplacingMergeTree (no dedup needed) but un-merged parts force
148
+ * read-time aggregation which degrades query performance. */
149
+ export declare const OPTIMISE_MV_TABLES: readonly ["metrics_hourly_mv", "metrics_daily_mv", "response_time_quantiles_hourly_mv", "deployment_duration_quantiles_daily_mv", "log_severity_hourly_mv", "compliance_score_daily_mv", "ai_usage_daily_mv", "finding_daily_aggregate", "insight_pattern_dismissals"];
150
+ /** Resource allocation for the lightweight optimise task. */
151
+ export declare const OPTIMISE_TASK_MEMORY_MIB = 256;
152
+ export declare const OPTIMISE_TASK_CPU_UNITS = 256;
153
+ /** Automated backup schedule (daily 03:00 UTC — low-traffic window). */
154
+ export declare const BACKUP_SCHEDULE = "cron(0 3 * * ? *)";
155
+ /** Resource allocation for the backup task (lightweight — clickhouse-client only). */
156
+ export declare const BACKUP_TASK_MEMORY_MIB = 256;
157
+ export declare const BACKUP_TASK_CPU_UNITS = 256;
158
+ /** Backup object expiration: 14 days (retains 14 daily snapshots). */
159
+ export declare const BACKUP_RETENTION_DAYS = 14;