@fjall/components-infrastructure 0.96.0 → 0.99.3

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 (209) hide show
  1. package/dist/lib/app.d.ts +68 -1
  2. package/dist/lib/app.js +113 -4
  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 +2 -4
  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 +173 -0
  32. package/dist/lib/patterns/aws/clickhouseDatabase.js +601 -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 +93 -5
  36. package/dist/lib/patterns/aws/computeEcs.js +867 -37
  37. package/dist/lib/patterns/aws/computeEcsTypes.d.ts +528 -25
  38. package/dist/lib/patterns/aws/computeEcsTypes.js +10 -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 +6 -4
  44. package/dist/lib/patterns/aws/index.d.ts +1 -0
  45. package/dist/lib/patterns/aws/index.js +1 -0
  46. package/dist/lib/patterns/aws/interfaces/compute.d.ts +7 -1
  47. package/dist/lib/patterns/aws/interfaces/database.d.ts +187 -8
  48. package/dist/lib/patterns/aws/interfaces/database.js +17 -3
  49. package/dist/lib/patterns/aws/interfaces/index.d.ts +2 -1
  50. package/dist/lib/patterns/aws/interfaces/index.js +3 -1
  51. package/dist/lib/patterns/aws/interfaces/messaging.d.ts +7 -0
  52. package/dist/lib/patterns/aws/interfaces/migrationContributor.d.ts +47 -0
  53. package/dist/lib/patterns/aws/interfaces/migrationContributor.js +9 -0
  54. package/dist/lib/patterns/aws/messaging.d.ts +66 -10
  55. package/dist/lib/patterns/aws/messaging.js +115 -20
  56. package/dist/lib/patterns/aws/network.js +16 -7
  57. package/dist/lib/patterns/aws/organisation.d.ts +4 -0
  58. package/dist/lib/patterns/aws/organisation.js +22 -4
  59. package/dist/lib/patterns/aws/storage.d.ts +1 -2
  60. package/dist/lib/patterns/aws/storage.js +3 -2
  61. package/dist/lib/patterns/aws/vpcPeer.js +3 -1
  62. package/dist/lib/resources/aws/analytics/clickhouse.js +18 -9
  63. package/dist/lib/resources/aws/analytics/clickhouseAlarms.d.ts +24 -9
  64. package/dist/lib/resources/aws/analytics/clickhouseAlarms.js +61 -10
  65. package/dist/lib/resources/aws/analytics/clickhouseConstants.d.ts +3 -3
  66. package/dist/lib/resources/aws/analytics/clickhouseConstants.js +3 -3
  67. package/dist/lib/resources/aws/analytics/clickhouseTypes.d.ts +7 -1
  68. package/dist/lib/resources/aws/analytics/clickhouseUserData.d.ts +1 -1
  69. package/dist/lib/resources/aws/analytics/clickhouseUserData.js +53 -3
  70. package/dist/lib/resources/aws/base/awsStack.js +4 -2
  71. package/dist/lib/resources/aws/compute/__tmp__/regression-shape.d.ts +2 -0
  72. package/dist/lib/resources/aws/compute/__tmp__/regression-shape.js +11 -0
  73. package/dist/lib/resources/aws/compute/asgInlineLifecycleHook.d.ts +52 -0
  74. package/dist/lib/resources/aws/compute/asgInlineLifecycleHook.js +60 -0
  75. package/dist/lib/resources/aws/compute/blockDeviceVolume.d.ts +8 -0
  76. package/dist/lib/resources/aws/compute/blockDeviceVolume.js +10 -0
  77. package/dist/lib/resources/aws/compute/ec2.d.ts +132 -12
  78. package/dist/lib/resources/aws/compute/ec2.js +163 -23
  79. package/dist/lib/resources/aws/compute/ec2GracefulTerminationHandler.d.ts +41 -0
  80. package/dist/lib/resources/aws/compute/ec2GracefulTerminationHandler.js +194 -0
  81. package/dist/lib/resources/aws/compute/ec2GracefulTerminationLambda.source.cjs +458 -0
  82. package/dist/lib/resources/aws/compute/ecs.d.ts +27 -1
  83. package/dist/lib/resources/aws/compute/ecs.js +42 -2
  84. package/dist/lib/resources/aws/compute/ecsConstants.d.ts +9 -0
  85. package/dist/lib/resources/aws/compute/ecsConstants.js +16 -0
  86. package/dist/lib/resources/aws/compute/ecsImages.js +32 -20
  87. package/dist/lib/resources/aws/compute/ecsLifecycleHookMigration.d.ts +96 -0
  88. package/dist/lib/resources/aws/compute/ecsLifecycleHookMigration.js +113 -0
  89. package/dist/lib/resources/aws/compute/ecsNetworking.d.ts +2 -1
  90. package/dist/lib/resources/aws/compute/ecsNetworking.js +18 -6
  91. package/dist/lib/resources/aws/compute/ecsServiceFactory.d.ts +13 -4
  92. package/dist/lib/resources/aws/compute/ecsServiceFactory.js +155 -33
  93. package/dist/lib/resources/aws/compute/ecsTaskDefinition.d.ts +31 -1
  94. package/dist/lib/resources/aws/compute/ecsTaskDefinition.js +102 -6
  95. package/dist/lib/resources/aws/compute/ecsTypes.d.ts +173 -13
  96. package/dist/lib/resources/aws/compute/ecsValidation.d.ts +9 -0
  97. package/dist/lib/resources/aws/compute/ecsValidation.js +63 -0
  98. package/dist/lib/resources/aws/compute/index.d.ts +2 -0
  99. package/dist/lib/resources/aws/compute/index.js +2 -0
  100. package/dist/lib/resources/aws/compute/lambda.d.ts +7 -13
  101. package/dist/lib/resources/aws/compute/lambda.js +30 -38
  102. package/dist/lib/resources/aws/compute/lifecycleHookLambda.source.cjs +192 -0
  103. package/dist/lib/resources/aws/compute/persistentDataVolume.d.ts +104 -0
  104. package/dist/lib/resources/aws/compute/persistentDataVolume.js +245 -0
  105. package/dist/lib/resources/aws/compute/persistentDataVolumeLambda.source.cjs +398 -0
  106. package/dist/lib/resources/aws/compute/samApplication.d.ts +15 -0
  107. package/dist/lib/resources/aws/compute/samApplication.js +27 -0
  108. package/dist/lib/resources/aws/database/clickhouseConstants.d.ts +159 -0
  109. package/dist/lib/resources/aws/database/clickhouseConstants.js +181 -0
  110. package/dist/lib/resources/aws/database/clickhouseSchemas.d.ts +71 -0
  111. package/dist/lib/resources/aws/database/clickhouseSchemas.js +160 -0
  112. package/dist/lib/resources/aws/database/clickhouseSecurityGroup.d.ts +14 -0
  113. package/dist/lib/resources/aws/database/clickhouseSecurityGroup.js +23 -0
  114. package/dist/lib/resources/aws/database/clickhouseUserData.d.ts +69 -0
  115. package/dist/lib/resources/aws/database/clickhouseUserData.js +371 -0
  116. package/dist/lib/resources/aws/database/clickhouseXmlRenderer.d.ts +56 -0
  117. package/dist/lib/resources/aws/database/clickhouseXmlRenderer.js +112 -0
  118. package/dist/lib/resources/aws/database/rdsAurora.d.ts +8 -1
  119. package/dist/lib/resources/aws/database/rdsAurora.js +42 -32
  120. package/dist/lib/resources/aws/database/rdsAuroraGlobal.d.ts +15 -2
  121. package/dist/lib/resources/aws/database/rdsAuroraGlobal.js +39 -43
  122. package/dist/lib/resources/aws/database/rdsDefaults.d.ts +6 -0
  123. package/dist/lib/resources/aws/database/rdsDefaults.js +7 -1
  124. package/dist/lib/resources/aws/database/rdsHelpers.d.ts +3 -3
  125. package/dist/lib/resources/aws/database/rdsHelpers.js +1 -0
  126. package/dist/lib/resources/aws/database/rdsInstance.d.ts +8 -1
  127. package/dist/lib/resources/aws/database/rdsInstance.js +51 -34
  128. package/dist/lib/resources/aws/database/rdsProxyOutput.d.ts +1 -1
  129. package/dist/lib/resources/aws/database/rdsProxyOutput.js +1 -1
  130. package/dist/lib/resources/aws/iam/delegationRole.js +1 -1
  131. package/dist/lib/resources/aws/iam/identityCenter/groupMembership.d.ts +9 -0
  132. package/dist/lib/resources/aws/iam/identityCenter/groupMembership.js +12 -0
  133. package/dist/lib/resources/aws/iam/identityCenter/index.d.ts +1 -0
  134. package/dist/lib/resources/aws/iam/identityCenter/index.js +1 -0
  135. package/dist/lib/resources/aws/iam/identityCenter/permissionSet.d.ts +1 -0
  136. package/dist/lib/resources/aws/iam/identityCenter/permissionSet.js +1 -0
  137. package/dist/lib/resources/aws/logging/logGroup.d.ts +0 -8
  138. package/dist/lib/resources/aws/logging/logGroup.js +0 -11
  139. package/dist/lib/resources/aws/messaging/defaultEventBus.d.ts +7 -0
  140. package/dist/lib/resources/aws/messaging/defaultEventBus.js +21 -0
  141. package/dist/lib/resources/aws/messaging/eventBridgeRule.d.ts +96 -0
  142. package/dist/lib/resources/aws/messaging/eventBridgeRule.js +110 -0
  143. package/dist/lib/resources/aws/messaging/eventTargets.d.ts +84 -0
  144. package/dist/lib/resources/aws/messaging/eventTargets.js +152 -0
  145. package/dist/lib/resources/aws/messaging/eventbridge.d.ts +25 -2
  146. package/dist/lib/resources/aws/messaging/eventbridge.js +22 -10
  147. package/dist/lib/resources/aws/messaging/index.d.ts +5 -0
  148. package/dist/lib/resources/aws/messaging/index.js +2 -0
  149. package/dist/lib/resources/aws/messaging/schedule.d.ts +118 -0
  150. package/dist/lib/resources/aws/messaging/schedule.js +64 -0
  151. package/dist/lib/resources/aws/messaging/sns.d.ts +2 -1
  152. package/dist/lib/resources/aws/messaging/sqs.d.ts +2 -1
  153. package/dist/lib/resources/aws/messaging/subscription.d.ts +112 -0
  154. package/dist/lib/resources/aws/messaging/subscription.js +67 -0
  155. package/dist/lib/resources/aws/messaging/utils.d.ts +6 -0
  156. package/dist/lib/resources/aws/messaging/utils.js +10 -0
  157. package/dist/lib/resources/aws/monitoring/clickhouseAlarms.d.ts +60 -0
  158. package/dist/lib/resources/aws/monitoring/clickhouseAlarms.js +139 -0
  159. package/dist/lib/resources/aws/monitoring/index.d.ts +2 -0
  160. package/dist/lib/resources/aws/monitoring/index.js +2 -0
  161. package/dist/lib/resources/aws/monitoring/scheduleAlarms.d.ts +47 -0
  162. package/dist/lib/resources/aws/monitoring/scheduleAlarms.js +106 -0
  163. package/dist/lib/resources/aws/networking/crossAccountDelegationRecord.js +6 -4
  164. package/dist/lib/resources/aws/networking/crossAccountReturnRoutes.js +17 -13
  165. package/dist/lib/resources/aws/networking/dnsRecord/dnsRecordBase.js +7 -5
  166. package/dist/lib/resources/aws/networking/domainCertificate.d.ts +2 -2
  167. package/dist/lib/resources/aws/networking/domainCertificate.js +6 -4
  168. package/dist/lib/resources/aws/networking/hostedZone.js +6 -5
  169. package/dist/lib/resources/aws/networking/serviceDiscovery.d.ts +96 -0
  170. package/dist/lib/resources/aws/networking/serviceDiscovery.js +96 -0
  171. package/dist/lib/resources/aws/networking/vpc.d.ts +4 -1
  172. package/dist/lib/resources/aws/networking/vpc.js +4 -1
  173. package/dist/lib/resources/aws/networking/vpcPeeringConnection.js +21 -3
  174. package/dist/lib/resources/aws/organisation/costAllocationTagActivator.d.ts +16 -5
  175. package/dist/lib/resources/aws/organisation/costAllocationTagActivator.js +17 -3
  176. package/dist/lib/resources/aws/organisation/index.d.ts +1 -1
  177. package/dist/lib/resources/aws/organisation/organisationPolicy.d.ts +2 -0
  178. package/dist/lib/resources/aws/organisation/organisationPolicy.js +3 -2
  179. package/dist/lib/resources/aws/secrets/secret.d.ts +7 -0
  180. package/dist/lib/resources/aws/secrets/secret.js +4 -3
  181. package/dist/lib/resources/aws/storage/bucketDeployment.d.ts +16 -0
  182. package/dist/lib/resources/aws/storage/bucketDeployment.js +17 -0
  183. package/dist/lib/resources/aws/storage/ecr.js +5 -5
  184. package/dist/lib/resources/aws/storage/index.d.ts +1 -0
  185. package/dist/lib/resources/aws/storage/index.js +1 -0
  186. package/dist/lib/resources/aws/storage/s3.js +10 -3
  187. package/dist/lib/resources/aws/utilities/customResource.js +18 -9
  188. package/dist/lib/synth_dump.d.ts +1 -0
  189. package/dist/lib/synth_dump.js +42 -0
  190. package/dist/lib/utils/cdkContext.d.ts +2 -0
  191. package/dist/lib/utils/cdkContext.js +4 -2
  192. package/dist/lib/utils/connections.js +6 -0
  193. package/dist/lib/utils/connector.d.ts +12 -0
  194. package/dist/lib/utils/costAllocationTags.d.ts +9 -0
  195. package/dist/lib/utils/costAllocationTags.js +11 -1
  196. package/dist/lib/utils/databaseTypes.d.ts +14 -0
  197. package/dist/lib/utils/getConfig.d.ts +2 -0
  198. package/dist/lib/utils/getConfig.js +2 -0
  199. package/dist/lib/utils/index.d.ts +1 -0
  200. package/dist/lib/utils/index.js +1 -0
  201. package/dist/lib/utils/manifestWriter.d.ts +6 -89
  202. package/dist/lib/utils/manifestWriter.js +36 -23
  203. package/dist/lib/utils/migrationVersionResolvers.d.ts +2 -0
  204. package/dist/lib/utils/migrationVersionResolvers.js +2 -0
  205. package/dist/lib/utils/orgConfigParser.js +2 -1
  206. package/dist/lib/utils/resolveAlertsTopic.d.ts +14 -0
  207. package/dist/lib/utils/resolveAlertsTopic.js +30 -0
  208. package/dist/lib/utils/validationLogger.js +6 -3
  209. package/package.json +22 -19
@@ -0,0 +1,601 @@
1
+ import { CLICKHOUSE_MANAGED_USERS_ENV } from "@fjall/util/migration";
2
+ import { Annotations, CfnOutput, Duration, Fn, Stack } from "aws-cdk-lib";
3
+ import { Connections, Port, UserData } from "aws-cdk-lib/aws-ec2";
4
+ import { Monitoring } from "aws-cdk-lib/aws-autoscaling";
5
+ import { ContainerImage, EcsOptimizedImage, NetworkMode, Secret as EcsSecret } from "aws-cdk-lib/aws-ecs";
6
+ import { ManagedPolicy, PolicyStatement } from "aws-cdk-lib/aws-iam";
7
+ import { Source } from "aws-cdk-lib/aws-s3-deployment";
8
+ import { BucketDeployment } from "../../resources/aws/storage/bucketDeployment.js";
9
+ import { AwsCustomResourcePolicy, PhysicalResourceId } from "aws-cdk-lib/custom-resources";
10
+ import { AwsCustomResource } from "../../resources/aws/utilities/awsCustomResource.js";
11
+ import { RetentionDays } from "aws-cdk-lib/aws-logs";
12
+ import { Construct } from "constructs";
13
+ import { z } from "zod";
14
+ import App from "../../app.js";
15
+ import { S3Bucket } from "../../resources/aws/storage/s3.js";
16
+ import { Secret } from "../../resources/aws/secrets/secret.js";
17
+ import { LogGroup } from "../../resources/aws/logging/logGroup.js";
18
+ import { createClickHouseSecurityGroup } from "../../resources/aws/database/clickhouseSecurityGroup.js";
19
+ import { buildClickHouseEntrypointWrapper, buildClickHouseUserData, generateUsersConfigXml } from "../../resources/aws/database/clickhouseUserData.js";
20
+ import { toPascalCase } from "../../utils/capitaliseString.js";
21
+ import { ClickHouseSchemaAdminSchema, ManagedPasswordNameSchema, ProfileSpecSchema, ClickHouseDefaultProfiles, PROFILE_NAME_PATTERN } from "../../resources/aws/database/clickhouseSchemas.js";
22
+ import { inferAmiHardwareType } from "../../resources/aws/compute/ecsConstants.js";
23
+ import { CLICKHOUSE_DATABASE_NAME, DEFAULT_CLICKHOUSE_INSTANCE_TYPE, CLICKHOUSE_IMAGE, CLICKHOUSE_EBS_VOLUME_SIZE_GB, CLICKHOUSE_EBS_IOPS, CLICKHOUSE_EBS_THROUGHPUT_MBPS, CLICKHOUSE_TASK_MEMORY_MIB, CLICKHOUSE_HTTP_PORT, CLICKHOUSE_NATIVE_PORT, CLICKHOUSE_PROMETHEUS_PORT, CLICKHOUSE_DATA_MOUNT_PATH, CLICKHOUSE_SECRET_OPTIONS, CLICKHOUSE_SERVER_ROLE_TAG, clickHouseUserSecretName, CLICKHOUSE_HEALTH_CHECK, CLICKHOUSE_STOP_TIMEOUT_SECONDS, CLICKHOUSE_EBS_DEVICE_NAME, CLICKHOUSE_CONFIG_SUBDIR, CLICKHOUSE_USERS_SUBDIR, userPasswordEnvName, OPTIMISE_FINAL_SCHEDULE, REPLACING_MERGE_TREE_TABLES, OPTIMISE_MV_TABLES, CLICKHOUSE_CLOUDMAP_SERVICE_NAME, CLICKHOUSE_SERVER_CONTAINER_NAME, OPTIMISE_TASK_MEMORY_MIB, OPTIMISE_TASK_CPU_UNITS, BACKUP_SCHEDULE, BACKUP_TASK_MEMORY_MIB, BACKUP_TASK_CPU_UNITS, BACKUP_RETENTION_DAYS } from "../../resources/aws/database/clickhouseConstants.js";
24
+ import { EcsCompute } from "./computeEcs.js";
25
+ /**
26
+ * Resolve the ECS desired task count for the ClickHouse service.
27
+ *
28
+ * Precedence: CDK context (`clickhouseDesiredCount`) > prop > default 1.
29
+ *
30
+ * Context values arrive as strings (`--context clickhouseDesiredCount=0`) or
31
+ * numbers (when set in `cdk.json`); both shapes parse here. Empty-string and
32
+ * non-finite values fall through to the prop / default — closes the
33
+ * `??`-empty-string trap (CDK consumers can legitimately set `--context X=`
34
+ * to clear a stale value).
35
+ */
36
+ function resolveClickHouseDesiredCount(contextValue, propValue) {
37
+ if (typeof contextValue === "number" && Number.isFinite(contextValue)) {
38
+ return contextValue;
39
+ }
40
+ if (typeof contextValue === "string" && contextValue !== "") {
41
+ const parsed = parseInt(contextValue, 10);
42
+ if (Number.isFinite(parsed))
43
+ return parsed;
44
+ }
45
+ if (propValue !== undefined)
46
+ return propValue;
47
+ return 1;
48
+ }
49
+ /**
50
+ * Narrow `T | undefined` to `T`. Used at the 3 sites where a `Map.get(...)` /
51
+ * `Array.find(...)` result is structurally undefined-able but invariants
52
+ * earlier in the constructor guarantee a value. The thrown message names the
53
+ * invariant so the rare violation is debuggable.
54
+ */
55
+ function expectDefined(value, invariant) {
56
+ if (value === undefined) {
57
+ throw new Error(`ClickHouseDatabase: unreachable — ${invariant}`);
58
+ }
59
+ return value;
60
+ }
61
+ function createClickHouseUserSecret(scope, name) {
62
+ return new Secret(scope, `ClickHouseUser${toPascalCase(name)}`, {
63
+ secretName: clickHouseUserSecretName(name),
64
+ description: `ClickHouse ${name} user password`,
65
+ generateSecretString: {
66
+ ...CLICKHOUSE_SECRET_OPTIONS,
67
+ secretStringTemplate: JSON.stringify({ username: name }),
68
+ generateStringKey: "password"
69
+ }
70
+ });
71
+ }
72
+ /**
73
+ * ClickHouse analytics database wrapper implementing IClickHouseDatabase.
74
+ *
75
+ * Composes `EcsCompute` directly per Phase 5e D7 — one EC2-pinned service
76
+ * (single-instance, AWS_VPC mode, Cloud Map registered) plus two scheduled
77
+ * tasks (optimise + backup). Per-role secrets, dual S3 buckets, and 10
78
+ * CfnOutputs are owned by `ClickHouseDatabase` itself; every taggable
79
+ * resource routes through its Fjall wrapper (AC45d-g).
80
+ */
81
+ export class ClickHouseDatabase extends Construct {
82
+ databaseType = "ClickHouse";
83
+ connectorType = "relational";
84
+ additionalTcpPorts = [CLICKHOUSE_NATIVE_PORT];
85
+ id;
86
+ connections;
87
+ #users;
88
+ #schemaAdmin;
89
+ #managedPasswordNames;
90
+ #backupBucket;
91
+ #coldTierBucket;
92
+ constructor(scope, id, props) {
93
+ super(scope, id);
94
+ this.id = id;
95
+ if (!props.vpc) {
96
+ throw new Error("VPC is required for ClickHouse database");
97
+ }
98
+ const vpc = props.vpc;
99
+ const schemaAdminParse = ClickHouseSchemaAdminSchema.safeParse(props.schemaAdmin);
100
+ if (!schemaAdminParse.success) {
101
+ throw new Error(`ClickHouseDatabase: invalid schemaAdmin: prop — ${schemaAdminParse.error.message}`);
102
+ }
103
+ const schemaAdmin = schemaAdminParse.data;
104
+ const managedPasswordsParse = z
105
+ .array(ManagedPasswordNameSchema)
106
+ .default(() => [])
107
+ .safeParse(props.managedPasswords);
108
+ if (!managedPasswordsParse.success) {
109
+ throw new Error(`ClickHouseDatabase: invalid managedPasswords: prop — ${managedPasswordsParse.error.message}`);
110
+ }
111
+ const managedPasswords = managedPasswordsParse.data;
112
+ const profiles = props.profiles ?? ClickHouseDefaultProfiles;
113
+ const ProfilesRecordSchema = z.record(z
114
+ .string()
115
+ .regex(PROFILE_NAME_PATTERN, "Profile name must be lowercase snake_case"), ProfileSpecSchema);
116
+ const profilesParse = ProfilesRecordSchema.safeParse(profiles);
117
+ if (!profilesParse.success) {
118
+ throw new Error(`ClickHouseDatabase: invalid profiles: prop — ${profilesParse.error.message}`);
119
+ }
120
+ if (!(schemaAdmin.profile in profiles)) {
121
+ throw new Error(`ClickHouseDatabase: schemaAdmin '${schemaAdmin.name}' references unknown profile '${schemaAdmin.profile}'. ` +
122
+ `Known profiles: ${Object.keys(profiles).join(", ")}`);
123
+ }
124
+ const seenNames = new Set([schemaAdmin.name]);
125
+ for (const name of managedPasswords) {
126
+ if (seenNames.has(name)) {
127
+ throw new Error(`ClickHouseDatabase: duplicate user name '${name}' — collides with schemaAdmin or another managedPasswords entry`);
128
+ }
129
+ seenNames.add(name);
130
+ }
131
+ const allUserNames = [
132
+ schemaAdmin.name,
133
+ ...managedPasswords
134
+ ];
135
+ const contextValue = this.node.tryGetContext("clickhouseInstanceType");
136
+ const contextInstanceType = typeof contextValue === "string" && contextValue !== ""
137
+ ? contextValue
138
+ : undefined;
139
+ const propInstanceType = props.instanceType !== undefined && props.instanceType !== ""
140
+ ? props.instanceType
141
+ : undefined;
142
+ const instanceType = contextInstanceType ??
143
+ propInstanceType ??
144
+ DEFAULT_CLICKHOUSE_INSTANCE_TYPE;
145
+ const desiredCount = resolveClickHouseDesiredCount(this.node.tryGetContext("clickhouseDesiredCount"), props.desiredCount);
146
+ const optimiseEnabled = props.optimiseSchedule !== false;
147
+ const backupEnabled = props.backupSchedule !== false;
148
+ const optimiseSchedule = typeof props.optimiseSchedule === "string"
149
+ ? props.optimiseSchedule
150
+ : OPTIMISE_FINAL_SCHEDULE;
151
+ const backupSchedule = typeof props.backupSchedule === "string"
152
+ ? props.backupSchedule
153
+ : BACKUP_SCHEDULE;
154
+ const backupRetentionDays = props.backupRetentionDays ?? BACKUP_RETENTION_DAYS;
155
+ if (props.backupRetentionDays !== undefined &&
156
+ (props.backupRetentionDays < 1 || props.backupRetentionDays > 3650)) {
157
+ throw new Error(`ClickHouseDatabase: backupRetentionDays must be between 1 and 3650; got ${props.backupRetentionDays}.`);
158
+ }
159
+ const securityGroup = createClickHouseSecurityGroup(this, vpc, CLICKHOUSE_NATIVE_PORT);
160
+ const userSecrets = new Map();
161
+ for (const name of allUserNames) {
162
+ userSecrets.set(name, createClickHouseUserSecret(this, name));
163
+ }
164
+ this.#users = userSecrets;
165
+ this.#schemaAdmin = schemaAdmin;
166
+ this.#managedPasswordNames = managedPasswords;
167
+ const backupBucket = props.backupBucket ??
168
+ new S3Bucket(this, "ClickHouseBackupBucket", {
169
+ versioned: true,
170
+ lifecycleRules: [
171
+ {
172
+ enabled: true,
173
+ prefix: "backup/",
174
+ expiration: Duration.days(backupRetentionDays),
175
+ noncurrentVersionExpiration: Duration.days(backupRetentionDays)
176
+ }
177
+ ]
178
+ });
179
+ const coldTierEnabled = props.coldTier !== false;
180
+ const coldTierBucket = coldTierEnabled
181
+ ? (props.coldTierBucket ??
182
+ new S3Bucket(this, "ClickHouseColdTierBucket", {
183
+ versioned: false
184
+ }))
185
+ : undefined;
186
+ this.#backupBucket = backupBucket;
187
+ this.#coldTierBucket = coldTierBucket;
188
+ const backupTaskLogGroup = backupEnabled
189
+ ? new LogGroup(this, "ClickHouseBackupTaskLogGroup", {
190
+ retention: RetentionDays.TWO_WEEKS
191
+ })
192
+ : undefined;
193
+ const userData = UserData.custom(buildClickHouseUserData({
194
+ backupBucketName: backupBucket.bucketName,
195
+ backupBucketRegion: Stack.of(this).region,
196
+ ...(coldTierBucket !== undefined && {
197
+ coldTier: {
198
+ bucketName: coldTierBucket.bucketName,
199
+ region: Stack.of(this).region
200
+ }
201
+ })
202
+ }));
203
+ // Source.data wraps the content in a zip; the default `extract: true`
204
+ // unpacks it at the destination so the key is `config/users.d.xml`
205
+ // (with `extract: false` it would be `config/<hash>.zip` instead).
206
+ const usersConfigXml = generateUsersConfigXml({
207
+ schemaAdmin,
208
+ profiles,
209
+ vpcCidr: vpc.vpcCidrBlock
210
+ });
211
+ const usersConfigDeployment = new BucketDeployment(this, "ClickHouseUsersConfigDeployment", {
212
+ sources: [Source.data("users.d.xml", usersConfigXml)],
213
+ destinationBucket: backupBucket,
214
+ destinationKeyPrefix: "config",
215
+ prune: false,
216
+ retainOnDelete: true
217
+ });
218
+ const amiHardwareType = inferAmiHardwareType(instanceType);
219
+ const dataAz = Stack.of(this).availabilityZones[0];
220
+ const clickHouseHost = this.getHostEndpoint();
221
+ const backupDestUrl = `https://${backupBucket.bucketName}.s3.${Stack.of(this).region}.amazonaws.com/backup/`;
222
+ const optimiseQuery = [
223
+ ...REPLACING_MERGE_TREE_TABLES.map((table) => `OPTIMIZE TABLE ${CLICKHOUSE_DATABASE_NAME}.${table} FINAL`),
224
+ ...OPTIMISE_MV_TABLES.map((table) => `OPTIMIZE TABLE ${CLICKHOUSE_DATABASE_NAME}.${table}`)
225
+ ].join("; ");
226
+ const adminSecret = expectDefined(userSecrets.get(schemaAdmin.name), `schemaAdmin '${schemaAdmin.name}' secret not minted.`);
227
+ const scheduledTasks = [];
228
+ if (optimiseEnabled) {
229
+ scheduledTasks.push({
230
+ name: "ClickHouseOptimiseTask",
231
+ schedule: optimiseSchedule,
232
+ image: ContainerImage.fromRegistry(CLICKHOUSE_IMAGE),
233
+ cpu: OPTIMISE_TASK_CPU_UNITS,
234
+ memoryLimitMiB: OPTIMISE_TASK_MEMORY_MIB,
235
+ command: [
236
+ "clickhouse-client",
237
+ "--host",
238
+ clickHouseHost,
239
+ "--port",
240
+ String(CLICKHOUSE_NATIVE_PORT),
241
+ "--user",
242
+ schemaAdmin.name,
243
+ "--query",
244
+ `${optimiseQuery};`
245
+ ],
246
+ secrets: {
247
+ CLICKHOUSE_PASSWORD: EcsSecret.fromSecretsManager(adminSecret.secret, "password")
248
+ },
249
+ logRetention: RetentionDays.ONE_WEEK,
250
+ securityGroups: [securityGroup]
251
+ });
252
+ }
253
+ if (backupEnabled && backupTaskLogGroup !== undefined) {
254
+ scheduledTasks.push({
255
+ name: "ClickHouseBackupTask",
256
+ schedule: backupSchedule,
257
+ image: ContainerImage.fromRegistry(CLICKHOUSE_IMAGE),
258
+ cpu: BACKUP_TASK_CPU_UNITS,
259
+ memoryLimitMiB: BACKUP_TASK_MEMORY_MIB,
260
+ command: [
261
+ "sh",
262
+ "-c",
263
+ // Password via CLICKHOUSE_PASSWORD env, not --password on argv (argv → /proc/<pid>/cmdline).
264
+ `STAMP=$(date +%Y%m%d-%H%M%S) && clickhouse-client --host ${clickHouseHost} --port ${CLICKHOUSE_NATIVE_PORT} --user ${schemaAdmin.name} --query "BACKUP DATABASE ${CLICKHOUSE_DATABASE_NAME} TO S3('${backupDestUrl}weekly-$STAMP/')"`
265
+ ],
266
+ secrets: {
267
+ CLICKHOUSE_PASSWORD: EcsSecret.fromSecretsManager(adminSecret.secret, "password")
268
+ },
269
+ logGroup: backupTaskLogGroup,
270
+ securityGroups: [securityGroup]
271
+ });
272
+ }
273
+ const clickHouseContainerSecrets = {};
274
+ for (const name of allUserNames) {
275
+ const userSecret = expectDefined(userSecrets.get(name), `user '${name}' secret not minted.`);
276
+ clickHouseContainerSecrets[userPasswordEnvName(name)] =
277
+ userSecret.getImport("password");
278
+ }
279
+ const ecsCompute = new EcsCompute(this, "Compute", {
280
+ type: "ecs",
281
+ vpc,
282
+ cluster: {
283
+ loadBalancer: false,
284
+ securityGroup,
285
+ scheduledTasks
286
+ },
287
+ services: [
288
+ {
289
+ name: "ClickHouseService",
290
+ capacityProvider: "EC2",
291
+ desiredCount,
292
+ serviceDiscovery: {
293
+ name: CLICKHOUSE_CLOUDMAP_SERVICE_NAME,
294
+ // getaddrinfo clients require A; AWS rejects A under HOST/BRIDGE → AWS_VPC.
295
+ dnsRecordType: "A"
296
+ },
297
+ networkMode: NetworkMode.AWS_VPC,
298
+ // Omit → CDK auto-creates a service SG that no consumer ingress targets.
299
+ securityGroups: [securityGroup],
300
+ deployment: { minHealthyPercent: 0, maxHealthyPercent: 100 },
301
+ ec2Config: {
302
+ instanceType,
303
+ machineImage: EcsOptimizedImage.amazonLinux2023(amiHardwareType),
304
+ instanceMonitoring: Monitoring.BASIC,
305
+ availabilityZones: [dataAz],
306
+ persistentDataVolume: {
307
+ sizeGb: CLICKHOUSE_EBS_VOLUME_SIZE_GB,
308
+ deviceName: CLICKHOUSE_EBS_DEVICE_NAME,
309
+ availabilityZone: dataAz,
310
+ iops: CLICKHOUSE_EBS_IOPS,
311
+ throughputMbps: CLICKHOUSE_EBS_THROUGHPUT_MBPS
312
+ },
313
+ userData,
314
+ desiredCapacity: 1,
315
+ minCapacity: 1,
316
+ maxCapacity: 1,
317
+ memoryLimitMiB: CLICKHOUSE_TASK_MEMORY_MIB,
318
+ tags: {
319
+ [CLICKHOUSE_SERVER_ROLE_TAG.key]: CLICKHOUSE_SERVER_ROLE_TAG.value
320
+ }
321
+ },
322
+ containers: [
323
+ {
324
+ name: CLICKHOUSE_SERVER_CONTAINER_NAME,
325
+ image: CLICKHOUSE_IMAGE,
326
+ stopTimeout: CLICKHOUSE_STOP_TIMEOUT_SECONDS,
327
+ entryPoint: ["/bin/bash", "-c"],
328
+ command: [buildClickHouseEntrypointWrapper()],
329
+ secretsImport: clickHouseContainerSecrets,
330
+ portMappings: [
331
+ {
332
+ containerPort: CLICKHOUSE_HTTP_PORT,
333
+ hostPort: CLICKHOUSE_HTTP_PORT
334
+ },
335
+ {
336
+ containerPort: CLICKHOUSE_NATIVE_PORT,
337
+ hostPort: CLICKHOUSE_NATIVE_PORT
338
+ },
339
+ {
340
+ containerPort: CLICKHOUSE_PROMETHEUS_PORT,
341
+ hostPort: CLICKHOUSE_PROMETHEUS_PORT
342
+ }
343
+ ],
344
+ volumes: [
345
+ {
346
+ name: "clickhouse-data",
347
+ hostSourcePath: CLICKHOUSE_DATA_MOUNT_PATH,
348
+ mountPath: "/var/lib/clickhouse"
349
+ },
350
+ {
351
+ name: "clickhouse-config",
352
+ hostSourcePath: `${CLICKHOUSE_DATA_MOUNT_PATH}/${CLICKHOUSE_CONFIG_SUBDIR}`,
353
+ mountPath: "/etc/clickhouse-server/config.d",
354
+ readOnly: true
355
+ },
356
+ {
357
+ name: "clickhouse-users",
358
+ hostSourcePath: `${CLICKHOUSE_DATA_MOUNT_PATH}/${CLICKHOUSE_USERS_SUBDIR}`,
359
+ mountPath: "/etc/clickhouse-server/users.d",
360
+ readOnly: true
361
+ }
362
+ ],
363
+ healthCheck: {
364
+ command: [
365
+ "CMD-SHELL",
366
+ `wget -q -O /dev/null http://127.0.0.1:${CLICKHOUSE_HTTP_PORT}/ping || exit 1`
367
+ ],
368
+ interval: CLICKHOUSE_HEALTH_CHECK.INTERVAL_SECONDS,
369
+ timeout: CLICKHOUSE_HEALTH_CHECK.TIMEOUT_SECONDS,
370
+ retries: CLICKHOUSE_HEALTH_CHECK.RETRIES,
371
+ startPeriod: CLICKHOUSE_HEALTH_CHECK.START_PERIOD_SECONDS
372
+ }
373
+ }
374
+ ]
375
+ }
376
+ ]
377
+ });
378
+ const clickHouseTaskDef = ecsCompute.getTaskDefinition("ClickHouseService");
379
+ if (!clickHouseTaskDef) {
380
+ throw new Error("ClickHouseDatabase: EcsCompute did not expose a ClickHouseService task definition — expected the service to be registered.");
381
+ }
382
+ backupBucket.grantReadWrite(clickHouseTaskDef.taskRole);
383
+ coldTierBucket?.grantReadWrite(clickHouseTaskDef.taskRole);
384
+ const instanceRole = ecsCompute.getInstanceRole();
385
+ if (instanceRole === undefined) {
386
+ throw new Error("ClickHouseDatabase: EcsCompute did not expose an instance role — expected an EC2-backed capacity provider.");
387
+ }
388
+ instanceRole.addManagedPolicy(ManagedPolicy.fromAwsManagedPolicyName("AmazonSSMManagedInstanceCore"));
389
+ backupBucket.grantRead(instanceRole, "config/*");
390
+ adminSecret.secret.grantRead(instanceRole);
391
+ const adminSecretName = clickHouseUserSecretName(schemaAdmin.name);
392
+ // Password via CLICKHOUSE_CLIENT_PASSWORD env, not --password on argv
393
+ // (argv → /proc/<pid>/cmdline). `jq -r .password` on an empty pipeline
394
+ // emits literal "null" exit 0 — `pipefail` + null/empty guard close
395
+ // the silent-auth-fail trap. Validate password BEFORE the atomic mv
396
+ // so a fetch failure leaves the OLD on-disk users.d intact.
397
+ const reloadScript = [
398
+ `set -euo pipefail`,
399
+ `aws s3 cp "s3://${backupBucket.bucketName}/config/users.d.xml" /var/lib/clickhouse/users.d/fjall.xml.new`,
400
+ `CONTAINER=$(docker ps -q --filter "label=com.amazonaws.ecs.container-name=${CLICKHOUSE_SERVER_CONTAINER_NAME}" | head -n1)`,
401
+ `if [ -z "$CONTAINER" ]; then mv /var/lib/clickhouse/users.d/fjall.xml.new /var/lib/clickhouse/users.d/fjall.xml; echo "fjall:reload:status=skipped reason=no-container" >&2; exit 0; fi`,
402
+ `ADMIN_PASSWORD=$(aws secretsmanager get-secret-value --secret-id "${adminSecretName}" --query SecretString --output text | jq -r .password)`,
403
+ `if [ -z "$ADMIN_PASSWORD" ] || [ "$ADMIN_PASSWORD" = "null" ]; then echo "fjall:reload:status=failed reason=password-fetch" >&2; exit 1; fi`,
404
+ `mv /var/lib/clickhouse/users.d/fjall.xml.new /var/lib/clickhouse/users.d/fjall.xml`,
405
+ `docker exec -i -e CLICKHOUSE_CLIENT_PASSWORD="$ADMIN_PASSWORD" "$CONTAINER" clickhouse-client --port 9000 --user ${schemaAdmin.name} -q "SYSTEM RELOAD USERS"`
406
+ ].join("\n");
407
+ const region = Stack.of(this).region;
408
+ const account = Stack.of(this).account;
409
+ const reloadParameters = {
410
+ DocumentName: "AWS-RunShellScript",
411
+ Targets: [
412
+ {
413
+ Key: `tag:${CLICKHOUSE_SERVER_ROLE_TAG.key}`,
414
+ Values: [CLICKHOUSE_SERVER_ROLE_TAG.value]
415
+ }
416
+ ],
417
+ Parameters: { commands: [reloadScript] },
418
+ CloudWatchOutputConfig: { CloudWatchOutputEnabled: false }
419
+ };
420
+ const reloadPhysicalResourceId = PhysicalResourceId.of(`${usersConfigDeployment.deployedBucket.bucketName}/${Fn.select(0, usersConfigDeployment.objectKeys)}`);
421
+ const reloadCustomResource = new AwsCustomResource(this, "ClickHouseUsersReload", {
422
+ onCreate: {
423
+ service: "SSM",
424
+ action: "sendCommand",
425
+ parameters: reloadParameters,
426
+ physicalResourceId: reloadPhysicalResourceId,
427
+ ignoreErrorCodesMatching: "InvalidInstanceId|InvocationDoesNotExist"
428
+ },
429
+ onUpdate: {
430
+ service: "SSM",
431
+ action: "sendCommand",
432
+ parameters: reloadParameters,
433
+ physicalResourceId: reloadPhysicalResourceId,
434
+ ignoreErrorCodesMatching: "InvalidInstanceId|InvocationDoesNotExist"
435
+ },
436
+ policy: AwsCustomResourcePolicy.fromStatements([
437
+ new PolicyStatement({
438
+ actions: ["ssm:SendCommand"],
439
+ resources: [`arn:aws:ssm:${region}::document/AWS-RunShellScript`]
440
+ }),
441
+ // Keep this statement separate — merging into the document statement
442
+ // would drop the tag condition (IAM conditions are per-statement).
443
+ new PolicyStatement({
444
+ actions: ["ssm:SendCommand"],
445
+ resources: [`arn:aws:ec2:${region}:${account}:instance/*`],
446
+ conditions: {
447
+ StringEquals: {
448
+ [`aws:ResourceTag/${CLICKHOUSE_SERVER_ROLE_TAG.key}`]: CLICKHOUSE_SERVER_ROLE_TAG.value
449
+ }
450
+ }
451
+ })
452
+ ])
453
+ });
454
+ reloadCustomResource.node.addDependency(usersConfigDeployment);
455
+ this.connections = new Connections({
456
+ securityGroups: [securityGroup],
457
+ defaultPort: Port.tcp(CLICKHOUSE_HTTP_PORT)
458
+ });
459
+ const declareOutput = (name, value) => {
460
+ const output = new CfnOutput(this, name, { value });
461
+ output.overrideLogicalId(name);
462
+ };
463
+ declareOutput("Endpoint", clickHouseHost);
464
+ declareOutput("HttpPort", String(CLICKHOUSE_HTTP_PORT));
465
+ declareOutput("NativePort", String(CLICKHOUSE_NATIVE_PORT));
466
+ declareOutput("DatabaseName", CLICKHOUSE_DATABASE_NAME);
467
+ for (const [name, secret] of userSecrets) {
468
+ declareOutput(`${toPascalCase(name)}SecretArn`, secret.secret.secretArn);
469
+ }
470
+ declareOutput("BackupBucketName", backupBucket.bucketName);
471
+ if (coldTierBucket !== undefined) {
472
+ declareOutput("ColdTierBucketName", coldTierBucket.bucketName);
473
+ }
474
+ }
475
+ /**
476
+ * Returns the Fjall `Secret` wrapper for the named user. Throws with a
477
+ * `Known users:` list if the name is not present in the construct's
478
+ * `schemaAdmin:` / `managedPasswords:` props.
479
+ */
480
+ getUser(name) {
481
+ const secret = this.#users.get(name);
482
+ if (secret === undefined) {
483
+ throw new Error(`ClickHouseDatabase: unknown user '${name}'. ` +
484
+ `Known users: ${Array.from(this.#users.keys()).join(", ")}`);
485
+ }
486
+ return secret;
487
+ }
488
+ /**
489
+ * Non-throwing sibling of `getUser`. Returns the Fjall `Secret` wrapper for
490
+ * the named user, or `undefined` when no such user is declared. Used by
491
+ * `getMigrationContributions()` to gracefully skip credential wiring when the
492
+ * caller did not declare a `migrationUser:` on the database.
493
+ */
494
+ tryGetUser(name) {
495
+ return this.#users.get(name);
496
+ }
497
+ getHttpPort() {
498
+ return CLICKHOUSE_HTTP_PORT;
499
+ }
500
+ getNativePort() {
501
+ return CLICKHOUSE_NATIVE_PORT;
502
+ }
503
+ getHostEndpoint() {
504
+ return `${CLICKHOUSE_CLOUDMAP_SERVICE_NAME}.${App.getInstance().getNamespace().namespaceName}`;
505
+ }
506
+ getHttpUrl() {
507
+ return `http://${this.getHostEndpoint()}:${this.getHttpPort()}`;
508
+ }
509
+ getNativeUrl() {
510
+ return `tcp://${this.getHostEndpoint()}:${this.getNativePort()}`;
511
+ }
512
+ getDatabaseName() {
513
+ return CLICKHOUSE_DATABASE_NAME;
514
+ }
515
+ getBackupBucket() {
516
+ return this.#backupBucket;
517
+ }
518
+ getColdTierBucket() {
519
+ return this.#coldTierBucket;
520
+ }
521
+ grantConnect(grantee) {
522
+ this.connections.allowDefaultPortFrom(grantee);
523
+ this.connections.allowFrom(grantee, Port.tcp(CLICKHOUSE_NATIVE_PORT));
524
+ }
525
+ /**
526
+ * Migration contributions for a task connecting to this ClickHouse cluster
527
+ * via `service.connections:`. Contributes env (`CLICKHOUSE_URL`,
528
+ * `CLICKHOUSE_DATABASE`, plus the managed-user manifest under
529
+ * `CLICKHOUSE_MANAGED_USERS`) and egress to the cluster's HTTP port.
530
+ *
531
+ * **Schema admin.** The bootstrap-privileged user's password is imported as
532
+ * `SCHEMA_ADMIN_PASSWORD` so customer SQL migrations can authenticate. The
533
+ * schema admin is defined in `users.d/fjall.xml` (users_xml storage =
534
+ * read-only); its password syncs via XML reload on EC2 boot or via the
535
+ * Custom::AWS SYSTEM RELOAD USERS resource on stack updates — NOT via the
536
+ * migration helper. The admin is therefore NOT included in the manifest.
537
+ *
538
+ * **Managed-password workload users.** Every entry in `managedPasswords` is
539
+ * added to the manifest (name only — profile binding is customer SQL's
540
+ * concern) AND its `password` secret is imported as `USER_<NAME>_PASSWORD`
541
+ * so the migration container boots with each plaintext already in
542
+ * `process.env`. The helper reads the env vars directly — no runtime
543
+ * `secretsmanager:GetSecretValue` SDK call is needed; ECS executionRole
544
+ * carries the IAM grant via the standard `secretsImport` framework path.
545
+ */
546
+ getMigrationContributions() {
547
+ const environment = {
548
+ CLICKHOUSE_URL: this.getHttpUrl(),
549
+ CLICKHOUSE_DATABASE: this.getDatabaseName()
550
+ };
551
+ const secretsImport = {};
552
+ const adminSecret = this.getUser(this.#schemaAdmin.name);
553
+ secretsImport.SCHEMA_ADMIN_PASSWORD = adminSecret.getImport("password");
554
+ for (const name of this.#managedPasswordNames) {
555
+ const userSecret = this.getUser(name);
556
+ secretsImport[userPasswordEnvName(name)] =
557
+ userSecret.getImport("password");
558
+ }
559
+ environment[CLICKHOUSE_MANAGED_USERS_ENV] = JSON.stringify([
560
+ ...this.#managedPasswordNames
561
+ ]);
562
+ const primarySg = this.connections.securityGroups[0];
563
+ const egress = [];
564
+ if (primarySg !== undefined) {
565
+ egress.push({
566
+ peer: primarySg,
567
+ port: Port.tcp(this.getHttpPort()),
568
+ description: "Migration runner to ClickHouse HTTP for DDL apply"
569
+ });
570
+ }
571
+ else {
572
+ Annotations.of(this).addWarning("ClickHouseDatabase: no primary security group exposed; migration " +
573
+ "runner egress not auto-wired. Provide egress manually in " +
574
+ "separateTaskDef if needed.");
575
+ }
576
+ return { environment, secretsImport, egress };
577
+ }
578
+ /**
579
+ * Wires a task definition to connect to ClickHouse as `opts.user` (if
580
+ * supplied). Adds `CLICKHOUSE_URL` / `CLICKHOUSE_DATABASE` env vars to the
581
+ * task's default container; when `opts.user` is set, also adds
582
+ * `CLICKHOUSE_USER` (plain env) and `CLICKHOUSE_PASSWORD` (Secrets
583
+ * Manager-backed env var resolved from the user's secret). Throws if the
584
+ * user is unknown. Callers separately call `grantConnect(service)` on the
585
+ * resulting ECS service for the network grant — `TaskDefinition` itself
586
+ * isn't `IConnectable`.
587
+ */
588
+ connectFromTask(task, opts) {
589
+ const container = task.defaultContainer;
590
+ if (container === undefined) {
591
+ throw new Error("ClickHouseDatabase.connectFromTask: task has no default container");
592
+ }
593
+ container.addEnvironment("CLICKHOUSE_URL", this.getHttpUrl());
594
+ container.addEnvironment("CLICKHOUSE_DATABASE", CLICKHOUSE_DATABASE_NAME);
595
+ if (opts?.user !== undefined) {
596
+ const userSecret = this.getUser(opts.user);
597
+ container.addEnvironment("CLICKHOUSE_USER", opts.user);
598
+ container.addSecret("CLICKHOUSE_PASSWORD", EcsSecret.fromSecretsManager(userSecret.secret, "password"));
599
+ }
600
+ }
601
+ }
@@ -1,12 +1,12 @@
1
1
  import { Runtime } from "aws-cdk-lib/aws-lambda";
2
2
  import { type Construct } from "constructs";
3
- import { type IEcsCompute, type ILambdaCompute, type IEc2Compute, type AnyCompute, isCompute, isEcsCompute, isLambdaCompute, isEc2Compute } from "./interfaces/compute.js";
3
+ import { type ComputeType, type IEcsCompute, type ILambdaCompute, type IEc2Compute, type AnyCompute, isCompute, isEcsCompute, isLambdaCompute, isEc2Compute } from "./interfaces/compute.js";
4
4
  import type App from "../../app.js";
5
- import { EcsCompute, type EcsComputeProps, type EcsServiceConfig, type EcsContainerConfig, type EcsScalingConfig, type EcsClusterConfig, type EcsRoutingConfig, type EcsCapacityProviderConfig, ECS_CAPACITY_PROVIDER_CONFIG, getEcsCapacityProviderConfig, ScalingType, type EcsCapacityProvider, type Ec2CapacityConfig, validateEcsProps, buildContainerConfigs, type ResolvedScalingConfig, resolveScalingConfig } from "./computeEcs.js";
5
+ import { EcsCompute, type EcsComputeProps, type EcsServiceConfig, type EcsContainerConfig, type EcsScalingConfig, type EcsClusterConfig, type EcsRoutingConfig, type EcsCapacityProviderConfig, type ContainerDependency, type EcsMigrationsConfig, ECS_CAPACITY_PROVIDER_CONFIG, getEcsCapacityProviderConfig, ScalingType, type EcsCapacityProvider, type Ec2CapacityConfig, validateEcsProps, buildContainerConfigs, expandMigrationsSugar, type ResolvedScalingConfig, resolveScalingConfig } from "./computeEcs.js";
6
6
  import { LambdaCompute, type LambdaComputeProps, type ContainerLambdaProps, type CodeLambdaProps, type FunctionUrlConfig, type ResolvedLambdaDeployment, resolveLambdaDeployment, Architecture, HttpMethod, InvokeMode, type FunctionUrlCorsOptions } from "./computeLambda.js";
7
7
  import { Ec2Compute, type Ec2ComputeProps, type SshConfig } from "./computeEc2.js";
8
- export { EcsCompute, type EcsComputeProps, type EcsServiceConfig, type EcsContainerConfig, type EcsScalingConfig, type EcsClusterConfig, type EcsRoutingConfig, type EcsCapacityProviderConfig, ECS_CAPACITY_PROVIDER_CONFIG, getEcsCapacityProviderConfig, ScalingType, type EcsCapacityProvider, type Ec2CapacityConfig, validateEcsProps, buildContainerConfigs, type ResolvedScalingConfig, resolveScalingConfig, LambdaCompute, type LambdaComputeProps, type ContainerLambdaProps, type CodeLambdaProps, type FunctionUrlConfig, type ResolvedLambdaDeployment, resolveLambdaDeployment, Architecture, HttpMethod, InvokeMode, type FunctionUrlCorsOptions, Ec2Compute, type Ec2ComputeProps, type SshConfig };
9
- export type ComputeType = "ecs" | "ec2" | "lambda";
8
+ export { EcsCompute, type EcsComputeProps, type EcsServiceConfig, type EcsContainerConfig, type EcsScalingConfig, type EcsClusterConfig, type EcsRoutingConfig, type EcsCapacityProviderConfig, ECS_CAPACITY_PROVIDER_CONFIG, getEcsCapacityProviderConfig, ScalingType, type EcsCapacityProvider, type Ec2CapacityConfig, type ContainerDependency, type EcsMigrationsConfig, validateEcsProps, buildContainerConfigs, expandMigrationsSugar, type ResolvedScalingConfig, resolveScalingConfig, LambdaCompute, type LambdaComputeProps, type ContainerLambdaProps, type CodeLambdaProps, type FunctionUrlConfig, type ResolvedLambdaDeployment, resolveLambdaDeployment, Architecture, HttpMethod, InvokeMode, type FunctionUrlCorsOptions, Ec2Compute, type Ec2ComputeProps, type SshConfig };
9
+ export type { ComputeType } from "./interfaces/compute.js";
10
10
  /**
11
11
  * Configuration defaults for each compute type.
12
12
  */
@@ -33,9 +33,7 @@ export declare const COMPUTE_DEFAULTS: {
33
33
  readonly MAX_CAPACITY: 1;
34
34
  };
35
35
  readonly ECS: {
36
- /** AWS sample image used when no ECR repository is provided */
37
36
  readonly FALLBACK_IMAGE: "amazon/amazon-ecs-sample";
38
- /** Default tag for ECR images */
39
37
  readonly IMAGE_TAG: "latest";
40
38
  };
41
39
  readonly LAMBDA: {