@fjall/components-infrastructure 0.100.0 → 1.1.0
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.
- package/dist/lib/lambda-assets/cert-generator/asset/index.js +17948 -0
- package/dist/lib/lambda-assets/cert-generator/asset/package.json +4 -0
- package/dist/lib/patterns/aws/clickhouseDatabase.d.ts +49 -1
- package/dist/lib/patterns/aws/clickhouseDatabase.js +137 -20
- package/dist/lib/patterns/aws/clickhouseTls/index.d.ts +1 -0
- package/dist/lib/patterns/aws/clickhouseTls/index.js +1 -0
- package/dist/lib/patterns/aws/clickhouseTls/types.d.ts +48 -0
- package/dist/lib/patterns/aws/computeEcs.d.ts +13 -1
- package/dist/lib/patterns/aws/computeEcs.js +88 -8
- package/dist/lib/patterns/aws/interfaces/database.d.ts +32 -1
- package/dist/lib/patterns/aws/interfaces/database.js +1 -1
- package/dist/lib/resources/aws/database/clickhouseConstants.d.ts +21 -0
- package/dist/lib/resources/aws/database/clickhouseConstants.js +21 -0
- package/dist/lib/resources/aws/database/clickhouseSecurityGroup.d.ts +2 -0
- package/dist/lib/resources/aws/database/clickhouseSecurityGroup.js +2 -0
- package/dist/lib/resources/aws/database/clickhouseUserData.d.ts +21 -0
- package/dist/lib/resources/aws/database/clickhouseUserData.js +48 -3
- package/dist/lib/resources/aws/database/clickhouseXmlRenderer.d.ts +1 -1
- package/dist/lib/resources/aws/database/clickhouseXmlRenderer.js +1 -1
- package/dist/lib/resources/aws/secrets/index.d.ts +2 -0
- package/dist/lib/resources/aws/secrets/index.js +2 -0
- package/dist/lib/resources/aws/secrets/tlsCaSecret.d.ts +13 -0
- package/dist/lib/resources/aws/secrets/tlsCaSecret.js +15 -0
- package/dist/lib/resources/aws/secrets/tlsServerSecret.d.ts +15 -0
- package/dist/lib/resources/aws/secrets/tlsServerSecret.js +17 -0
- package/dist/lib/resources/aws/utilities/index.d.ts +1 -0
- package/dist/lib/resources/aws/utilities/index.js +1 -0
- package/dist/lib/resources/aws/utilities/tlsCertGenerator.d.ts +33 -0
- package/dist/lib/resources/aws/utilities/tlsCertGenerator.js +67 -0
- package/package.json +7 -5
- package/dist/lib/config/aws/__t17fixture.js +0 -3
- package/dist/lib/config/aws/__t17fixtureType.d.ts +0 -2
- package/dist/lib/config/aws/__t17fixtureType.js +0 -1
- package/dist/lib/config/aws/eventBus.d.ts +0 -7
- package/dist/lib/config/aws/eventBus.js +0 -21
- package/dist/lib/config/aws/identityCenterGroupMembership.d.ts +0 -10
- package/dist/lib/config/aws/identityCenterGroupMembership.js +0 -102
- package/dist/lib/config/aws/securityBaseline.d.ts +0 -15
- package/dist/lib/config/aws/securityBaseline.js +0 -27
- package/dist/lib/patterns/aws/_eslint_test_tmp/leak.d.ts +0 -1
- package/dist/lib/patterns/aws/_eslint_test_tmp/leak.js +0 -4
- package/dist/lib/patterns/aws/managedIdentityCenter.d.ts +0 -4
- package/dist/lib/patterns/aws/managedIdentityCenter.js +0 -19
- package/dist/lib/patterns/aws/subdomainHostedZone.d.ts +0 -9
- package/dist/lib/patterns/aws/subdomainHostedZone.js +0 -34
- package/dist/lib/resources/aws/analytics/clickhouse.d.ts +0 -15
- package/dist/lib/resources/aws/analytics/clickhouse.js +0 -310
- package/dist/lib/resources/aws/analytics/clickhouseAlarms.d.ts +0 -49
- package/dist/lib/resources/aws/analytics/clickhouseAlarms.js +0 -140
- package/dist/lib/resources/aws/analytics/clickhouseConstants.d.ts +0 -73
- package/dist/lib/resources/aws/analytics/clickhouseConstants.js +0 -89
- package/dist/lib/resources/aws/analytics/clickhouseSecurityGroup.d.ts +0 -13
- package/dist/lib/resources/aws/analytics/clickhouseSecurityGroup.js +0 -28
- package/dist/lib/resources/aws/analytics/clickhouseTypes.d.ts +0 -59
- package/dist/lib/resources/aws/analytics/clickhouseTypes.js +0 -1
- package/dist/lib/resources/aws/analytics/clickhouseUserData.d.ts +0 -6
- package/dist/lib/resources/aws/analytics/clickhouseUserData.js +0 -299
- package/dist/lib/resources/aws/analytics/index.d.ts +0 -4
- package/dist/lib/resources/aws/analytics/index.js +0 -2
- package/dist/lib/resources/aws/compute/__tmp__/regression-shape.d.ts +0 -2
- package/dist/lib/resources/aws/compute/__tmp__/regression-shape.js +0 -11
- package/dist/lib/resources/aws/messaging/defaultEventBus.d.ts +0 -7
- package/dist/lib/resources/aws/messaging/defaultEventBus.js +0 -21
- package/dist/lib/resources/aws/networking/domain.d.ts +0 -13
- package/dist/lib/resources/aws/networking/domain.js +0 -100
- package/dist/lib/synth_dump.d.ts +0 -1
- package/dist/lib/synth_dump.js +0 -42
- package/dist/lib/utils/bastionFactory.d.ts +0 -10
- package/dist/lib/utils/bastionFactory.js +0 -29
- package/dist/lib/utils/constructMap.d.ts +0 -33
- package/dist/lib/utils/constructMap.js +0 -154
- package/dist/lib/utils/dnsRecords.d.ts +0 -4
- package/dist/lib/utils/dnsRecords.js +0 -104
- /package/dist/lib/{config/aws/__t17fixture.d.ts → patterns/aws/clickhouseTls/types.js} +0 -0
|
@@ -4,7 +4,9 @@ import { type IBucket } from "aws-cdk-lib/aws-s3";
|
|
|
4
4
|
import { Construct } from "constructs";
|
|
5
5
|
import { Secret } from "../../resources/aws/secrets/secret.js";
|
|
6
6
|
import { type ClickHouseSchemaAdmin, type ProfileSpec } from "../../resources/aws/database/clickhouseSchemas.js";
|
|
7
|
-
import { type
|
|
7
|
+
import { type ISecret } from "aws-cdk-lib/aws-secretsmanager";
|
|
8
|
+
import type { ClickHouseTlsOptions } from "./clickhouseTls/index.js";
|
|
9
|
+
import { type ClickHouseMigrationsConfig, type IClickHouseDatabase } from "./interfaces/database.js";
|
|
8
10
|
import { type ISecurityGroupConnector } from "./interfaces/connector.js";
|
|
9
11
|
import { type MigrationContributions } from "./interfaces/migrationContributor.js";
|
|
10
12
|
/**
|
|
@@ -95,6 +97,24 @@ export interface ClickHouseDatabaseProps {
|
|
|
95
97
|
* non-snake_case names.
|
|
96
98
|
*/
|
|
97
99
|
profiles?: Record<string, ProfileSpec>;
|
|
100
|
+
/**
|
|
101
|
+
* Schema-version gate. When set, every container of every service whose
|
|
102
|
+
* `connections:` includes this database receives `EXPECTED_CH_SCHEMA_VERSION`
|
|
103
|
+
* baked into its env at synth, unless the service sets `schemaGate: false`.
|
|
104
|
+
* The default resolver picks the lexicographically-latest `*.sql` filename
|
|
105
|
+
* under `dir` (skipping `*.dev.sql`), matching what `runSqlMigrations`
|
|
106
|
+
* records in `_schema_migrations.ch_version`.
|
|
107
|
+
*/
|
|
108
|
+
migrations?: ClickHouseMigrationsConfig;
|
|
109
|
+
/**
|
|
110
|
+
* TLS configuration. Default: `{ mode: "self-signed" }` — mints an ECDSA
|
|
111
|
+
* P-256 CA + leaf cert at deploy via a Lambda custom resource, stores both
|
|
112
|
+
* PEMs in Secrets Manager, and the EC2 user-data materialises them at boot
|
|
113
|
+
* for the CH server to read. Set `mode: "imported"` to bring your own CA;
|
|
114
|
+
* `mode: "none"` (acknowledgement literal required) opts out of TLS.
|
|
115
|
+
* See ./clickhouseTls/types.ts.
|
|
116
|
+
*/
|
|
117
|
+
tls?: ClickHouseTlsOptions;
|
|
98
118
|
}
|
|
99
119
|
/**
|
|
100
120
|
* ClickHouse analytics database wrapper implementing IClickHouseDatabase.
|
|
@@ -112,6 +132,25 @@ export declare class ClickHouseDatabase extends Construct implements IClickHouse
|
|
|
112
132
|
readonly additionalTcpPorts: number[];
|
|
113
133
|
readonly id: string;
|
|
114
134
|
readonly connections: Connections;
|
|
135
|
+
/**
|
|
136
|
+
* CA cert secret. Self-signed: minted by `TlsCertGenerator`. Imported: the
|
|
137
|
+
* user-supplied ISecret. `undefined` when `tls.mode === "none"`.
|
|
138
|
+
* SecretString shape: raw PEM (single cert, no JSON wrapping).
|
|
139
|
+
*/
|
|
140
|
+
readonly tlsCaSecret: ISecret | undefined;
|
|
141
|
+
/**
|
|
142
|
+
* Server cert + key secret. Self-signed: minted by `TlsCertGenerator`.
|
|
143
|
+
* Imported: the user-supplied ISecret. `undefined` when `tls.mode === "none"`.
|
|
144
|
+
* SecretString shape: JSON `{ "cert": "<leaf-pem>", "key": "<key-pem>" }`.
|
|
145
|
+
*/
|
|
146
|
+
readonly tlsServerSecret: ISecret | undefined;
|
|
147
|
+
/**
|
|
148
|
+
* SHA-256 of the CA cert PEM. CFN token in self-signed mode (from the cert
|
|
149
|
+
* generator custom resource Data attribute). Plain string in imported mode
|
|
150
|
+
* when supplied. `undefined` when `tls.mode === "none"` or when imported
|
|
151
|
+
* without a digest. Surface on consumers as a task-replacement trigger.
|
|
152
|
+
*/
|
|
153
|
+
readonly caCertSha256: string | undefined;
|
|
115
154
|
constructor(scope: Construct, id: string, props: ClickHouseDatabaseProps);
|
|
116
155
|
/**
|
|
117
156
|
* Returns the Fjall `Secret` wrapper for the named user. Throws with a
|
|
@@ -129,11 +168,20 @@ export declare class ClickHouseDatabase extends Construct implements IClickHouse
|
|
|
129
168
|
getHttpPort(): number;
|
|
130
169
|
getNativePort(): number;
|
|
131
170
|
getHostEndpoint(): string;
|
|
171
|
+
/**
|
|
172
|
+
* Canonical URL for the HTTP(S) interface. Scheme is `https` when TLS is
|
|
173
|
+
* active (default), `http` only when `tls: { mode: "none", … }` is set.
|
|
174
|
+
* Port matches: HTTPS (8443) under TLS, HTTP (8123) otherwise.
|
|
175
|
+
*/
|
|
176
|
+
getUrl(): string;
|
|
177
|
+
/** @deprecated Alias for {@link getUrl}. Removed in a follow-up landing. */
|
|
132
178
|
getHttpUrl(): string;
|
|
133
179
|
getNativeUrl(): string;
|
|
134
180
|
getDatabaseName(): string;
|
|
135
181
|
getBackupBucket(): IBucket;
|
|
136
182
|
getColdTierBucket(): IBucket | undefined;
|
|
183
|
+
getMigrationsConfig(): ClickHouseMigrationsConfig | undefined;
|
|
184
|
+
getExpectedSchemaVersion(): string | undefined;
|
|
137
185
|
grantConnect(grantee: IConnectable): void;
|
|
138
186
|
/**
|
|
139
187
|
* Migration contributions for a task connecting to this ClickHouse cluster
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { CLICKHOUSE_MANAGED_USERS_ENV } from "@fjall/util/migration";
|
|
1
|
+
import { CLICKHOUSE_MANAGED_USERS_ENV, pickLatestClickHouseMigration } from "@fjall/util/migration";
|
|
2
2
|
import { Annotations, CfnOutput, Duration, Fn, Stack } from "aws-cdk-lib";
|
|
3
3
|
import { Connections, Port, UserData } from "aws-cdk-lib/aws-ec2";
|
|
4
4
|
import { Monitoring } from "aws-cdk-lib/aws-autoscaling";
|
|
@@ -20,7 +20,8 @@ import { buildClickHouseEntrypointWrapper, buildClickHouseUserData, generateUser
|
|
|
20
20
|
import { toPascalCase } from "../../utils/capitaliseString.js";
|
|
21
21
|
import { ClickHouseSchemaAdminSchema, ManagedPasswordNameSchema, ProfileSpecSchema, ClickHouseDefaultProfiles, PROFILE_NAME_PATTERN } from "../../resources/aws/database/clickhouseSchemas.js";
|
|
22
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";
|
|
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_HTTPS_PORT, CLICKHOUSE_NATIVE_PORT, CLICKHOUSE_TCP_SECURE_PORT, CLICKHOUSE_TLS_CERT_MOUNT_PATH, 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 { TlsCertGenerator } from "../../resources/aws/utilities/tlsCertGenerator.js";
|
|
24
25
|
import { EcsCompute } from "./computeEcs.js";
|
|
25
26
|
/**
|
|
26
27
|
* Resolve the ECS desired task count for the ClickHouse service.
|
|
@@ -81,14 +82,36 @@ function createClickHouseUserSecret(scope, name) {
|
|
|
81
82
|
export class ClickHouseDatabase extends Construct {
|
|
82
83
|
databaseType = "ClickHouse";
|
|
83
84
|
connectorType = "relational";
|
|
84
|
-
additionalTcpPorts
|
|
85
|
+
additionalTcpPorts;
|
|
85
86
|
id;
|
|
86
87
|
connections;
|
|
88
|
+
/**
|
|
89
|
+
* CA cert secret. Self-signed: minted by `TlsCertGenerator`. Imported: the
|
|
90
|
+
* user-supplied ISecret. `undefined` when `tls.mode === "none"`.
|
|
91
|
+
* SecretString shape: raw PEM (single cert, no JSON wrapping).
|
|
92
|
+
*/
|
|
93
|
+
tlsCaSecret;
|
|
94
|
+
/**
|
|
95
|
+
* Server cert + key secret. Self-signed: minted by `TlsCertGenerator`.
|
|
96
|
+
* Imported: the user-supplied ISecret. `undefined` when `tls.mode === "none"`.
|
|
97
|
+
* SecretString shape: JSON `{ "cert": "<leaf-pem>", "key": "<key-pem>" }`.
|
|
98
|
+
*/
|
|
99
|
+
tlsServerSecret;
|
|
100
|
+
/**
|
|
101
|
+
* SHA-256 of the CA cert PEM. CFN token in self-signed mode (from the cert
|
|
102
|
+
* generator custom resource Data attribute). Plain string in imported mode
|
|
103
|
+
* when supplied. `undefined` when `tls.mode === "none"` or when imported
|
|
104
|
+
* without a digest. Surface on consumers as a task-replacement trigger.
|
|
105
|
+
*/
|
|
106
|
+
caCertSha256;
|
|
107
|
+
#httpPort;
|
|
108
|
+
#nativePort;
|
|
87
109
|
#users;
|
|
88
110
|
#schemaAdmin;
|
|
89
111
|
#managedPasswordNames;
|
|
90
112
|
#backupBucket;
|
|
91
113
|
#coldTierBucket;
|
|
114
|
+
#migrationsConfig;
|
|
92
115
|
constructor(scope, id, props) {
|
|
93
116
|
super(scope, id);
|
|
94
117
|
this.id = id;
|
|
@@ -156,7 +179,46 @@ export class ClickHouseDatabase extends Construct {
|
|
|
156
179
|
(props.backupRetentionDays < 1 || props.backupRetentionDays > 3650)) {
|
|
157
180
|
throw new Error(`ClickHouseDatabase: backupRetentionDays must be between 1 and 3650; got ${props.backupRetentionDays}.`);
|
|
158
181
|
}
|
|
159
|
-
const
|
|
182
|
+
const tlsOptions = props.tls ?? {
|
|
183
|
+
mode: "self-signed"
|
|
184
|
+
};
|
|
185
|
+
const tlsActive = tlsOptions.mode !== "none";
|
|
186
|
+
const httpPort = tlsActive ? CLICKHOUSE_HTTPS_PORT : CLICKHOUSE_HTTP_PORT;
|
|
187
|
+
const nativePort = tlsActive
|
|
188
|
+
? CLICKHOUSE_TCP_SECURE_PORT
|
|
189
|
+
: CLICKHOUSE_NATIVE_PORT;
|
|
190
|
+
let tlsCaSecret;
|
|
191
|
+
let tlsServerSecret;
|
|
192
|
+
let caCertSha256;
|
|
193
|
+
if (tlsOptions.mode === "self-signed") {
|
|
194
|
+
const namespaceName = App.getInstance().getNamespace().namespaceName;
|
|
195
|
+
const hostname = `${CLICKHOUSE_CLOUDMAP_SERVICE_NAME}.${namespaceName}`;
|
|
196
|
+
const certGen = new TlsCertGenerator(this, "TlsCertGenerator", {
|
|
197
|
+
appName: App.getInstance().getName(),
|
|
198
|
+
hostname,
|
|
199
|
+
...(tlsOptions.caValidityYears !== undefined && {
|
|
200
|
+
caValidityYears: tlsOptions.caValidityYears
|
|
201
|
+
}),
|
|
202
|
+
...(tlsOptions.leafValidityYears !== undefined && {
|
|
203
|
+
leafValidityYears: tlsOptions.leafValidityYears
|
|
204
|
+
})
|
|
205
|
+
});
|
|
206
|
+
tlsCaSecret = certGen.caSecret.secret;
|
|
207
|
+
tlsServerSecret = certGen.serverSecret.secret;
|
|
208
|
+
caCertSha256 = certGen.caCertSha256;
|
|
209
|
+
}
|
|
210
|
+
else if (tlsOptions.mode === "imported") {
|
|
211
|
+
tlsCaSecret = tlsOptions.caCertSecret;
|
|
212
|
+
tlsServerSecret = tlsOptions.serverCertSecret;
|
|
213
|
+
caCertSha256 = tlsOptions.caCertSha256;
|
|
214
|
+
}
|
|
215
|
+
this.tlsCaSecret = tlsCaSecret;
|
|
216
|
+
this.tlsServerSecret = tlsServerSecret;
|
|
217
|
+
this.caCertSha256 = caCertSha256;
|
|
218
|
+
this.additionalTcpPorts = [nativePort];
|
|
219
|
+
this.#httpPort = httpPort;
|
|
220
|
+
this.#nativePort = nativePort;
|
|
221
|
+
const securityGroup = createClickHouseSecurityGroup(this, vpc, nativePort);
|
|
160
222
|
const userSecrets = new Map();
|
|
161
223
|
for (const name of allUserNames) {
|
|
162
224
|
userSecrets.set(name, createClickHouseUserSecret(this, name));
|
|
@@ -185,6 +247,7 @@ export class ClickHouseDatabase extends Construct {
|
|
|
185
247
|
: undefined;
|
|
186
248
|
this.#backupBucket = backupBucket;
|
|
187
249
|
this.#coldTierBucket = coldTierBucket;
|
|
250
|
+
this.#migrationsConfig = props.migrations;
|
|
188
251
|
const backupTaskLogGroup = backupEnabled
|
|
189
252
|
? new LogGroup(this, "ClickHouseBackupTaskLogGroup", {
|
|
190
253
|
retention: RetentionDays.TWO_WEEKS
|
|
@@ -198,6 +261,13 @@ export class ClickHouseDatabase extends Construct {
|
|
|
198
261
|
bucketName: coldTierBucket.bucketName,
|
|
199
262
|
region: Stack.of(this).region
|
|
200
263
|
}
|
|
264
|
+
}),
|
|
265
|
+
tlsActive,
|
|
266
|
+
...(tlsActive &&
|
|
267
|
+
tlsCaSecret !== undefined &&
|
|
268
|
+
tlsServerSecret !== undefined && {
|
|
269
|
+
caSecretArn: tlsCaSecret.secretArn,
|
|
270
|
+
serverSecretArn: tlsServerSecret.secretArn
|
|
201
271
|
})
|
|
202
272
|
}));
|
|
203
273
|
// Source.data wraps the content in a zip; the default `extract: true`
|
|
@@ -237,9 +307,10 @@ export class ClickHouseDatabase extends Construct {
|
|
|
237
307
|
"--host",
|
|
238
308
|
clickHouseHost,
|
|
239
309
|
"--port",
|
|
240
|
-
String(
|
|
310
|
+
String(nativePort),
|
|
241
311
|
"--user",
|
|
242
312
|
schemaAdmin.name,
|
|
313
|
+
...(tlsActive ? ["--secure", "--accept-invalid-certificate"] : []),
|
|
243
314
|
"--query",
|
|
244
315
|
`${optimiseQuery};`
|
|
245
316
|
],
|
|
@@ -261,7 +332,7 @@ export class ClickHouseDatabase extends Construct {
|
|
|
261
332
|
"sh",
|
|
262
333
|
"-c",
|
|
263
334
|
// 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 ${
|
|
335
|
+
`STAMP=$(date +%Y%m%d-%H%M%S) && clickhouse-client --host ${clickHouseHost} --port ${nativePort} --user ${schemaAdmin.name}${tlsActive ? " --secure --accept-invalid-certificate" : ""} --query "BACKUP DATABASE ${CLICKHOUSE_DATABASE_NAME} TO S3('${backupDestUrl}weekly-$STAMP/')"`
|
|
265
336
|
],
|
|
266
337
|
secrets: {
|
|
267
338
|
CLICKHOUSE_PASSWORD: EcsSecret.fromSecretsManager(adminSecret.secret, "password")
|
|
@@ -329,12 +400,12 @@ export class ClickHouseDatabase extends Construct {
|
|
|
329
400
|
secretsImport: clickHouseContainerSecrets,
|
|
330
401
|
portMappings: [
|
|
331
402
|
{
|
|
332
|
-
containerPort:
|
|
333
|
-
hostPort:
|
|
403
|
+
containerPort: httpPort,
|
|
404
|
+
hostPort: httpPort
|
|
334
405
|
},
|
|
335
406
|
{
|
|
336
|
-
containerPort:
|
|
337
|
-
hostPort:
|
|
407
|
+
containerPort: nativePort,
|
|
408
|
+
hostPort: nativePort
|
|
338
409
|
},
|
|
339
410
|
{
|
|
340
411
|
containerPort: CLICKHOUSE_PROMETHEUS_PORT,
|
|
@@ -358,12 +429,24 @@ export class ClickHouseDatabase extends Construct {
|
|
|
358
429
|
hostSourcePath: `${CLICKHOUSE_DATA_MOUNT_PATH}/${CLICKHOUSE_USERS_SUBDIR}`,
|
|
359
430
|
mountPath: "/etc/clickhouse-server/users.d",
|
|
360
431
|
readOnly: true
|
|
361
|
-
}
|
|
432
|
+
},
|
|
433
|
+
...(tlsActive
|
|
434
|
+
? [
|
|
435
|
+
{
|
|
436
|
+
name: "clickhouse-certs",
|
|
437
|
+
hostSourcePath: `${CLICKHOUSE_DATA_MOUNT_PATH}/server-certs`,
|
|
438
|
+
mountPath: CLICKHOUSE_TLS_CERT_MOUNT_PATH,
|
|
439
|
+
readOnly: true
|
|
440
|
+
}
|
|
441
|
+
]
|
|
442
|
+
: [])
|
|
362
443
|
],
|
|
363
444
|
healthCheck: {
|
|
364
445
|
command: [
|
|
365
446
|
"CMD-SHELL",
|
|
366
|
-
|
|
447
|
+
tlsActive
|
|
448
|
+
? `wget -q --no-check-certificate -O /dev/null https://127.0.0.1:${httpPort}/ping || exit 1`
|
|
449
|
+
: `wget -q -O /dev/null http://127.0.0.1:${httpPort}/ping || exit 1`
|
|
367
450
|
],
|
|
368
451
|
interval: CLICKHOUSE_HEALTH_CHECK.INTERVAL_SECONDS,
|
|
369
452
|
timeout: CLICKHOUSE_HEALTH_CHECK.TIMEOUT_SECONDS,
|
|
@@ -388,6 +471,10 @@ export class ClickHouseDatabase extends Construct {
|
|
|
388
471
|
instanceRole.addManagedPolicy(ManagedPolicy.fromAwsManagedPolicyName("AmazonSSMManagedInstanceCore"));
|
|
389
472
|
backupBucket.grantRead(instanceRole, "config/*");
|
|
390
473
|
adminSecret.secret.grantRead(instanceRole);
|
|
474
|
+
if (tlsCaSecret !== undefined)
|
|
475
|
+
tlsCaSecret.grantRead(instanceRole);
|
|
476
|
+
if (tlsServerSecret !== undefined)
|
|
477
|
+
tlsServerSecret.grantRead(instanceRole);
|
|
391
478
|
const adminSecretName = clickHouseUserSecretName(schemaAdmin.name);
|
|
392
479
|
// Password via CLICKHOUSE_CLIENT_PASSWORD env, not --password on argv
|
|
393
480
|
// (argv → /proc/<pid>/cmdline). `jq -r .password` on an empty pipeline
|
|
@@ -402,7 +489,7 @@ export class ClickHouseDatabase extends Construct {
|
|
|
402
489
|
`ADMIN_PASSWORD=$(aws secretsmanager get-secret-value --secret-id "${adminSecretName}" --query SecretString --output text | jq -r .password)`,
|
|
403
490
|
`if [ -z "$ADMIN_PASSWORD" ] || [ "$ADMIN_PASSWORD" = "null" ]; then echo "fjall:reload:status=failed reason=password-fetch" >&2; exit 1; fi`,
|
|
404
491
|
`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
|
|
492
|
+
`docker exec -i -e CLICKHOUSE_CLIENT_PASSWORD="$ADMIN_PASSWORD" "$CONTAINER" clickhouse-client${tlsActive ? " --secure --accept-invalid-certificate" : ""} --port ${nativePort} --user ${schemaAdmin.name} -q "SYSTEM RELOAD USERS"`
|
|
406
493
|
].join("\n");
|
|
407
494
|
const region = Stack.of(this).region;
|
|
408
495
|
const account = Stack.of(this).account;
|
|
@@ -454,15 +541,21 @@ export class ClickHouseDatabase extends Construct {
|
|
|
454
541
|
reloadCustomResource.node.addDependency(usersConfigDeployment);
|
|
455
542
|
this.connections = new Connections({
|
|
456
543
|
securityGroups: [securityGroup],
|
|
457
|
-
defaultPort: Port.tcp(
|
|
544
|
+
defaultPort: Port.tcp(httpPort)
|
|
458
545
|
});
|
|
459
546
|
const declareOutput = (name, value) => {
|
|
460
547
|
const output = new CfnOutput(this, name, { value });
|
|
461
548
|
output.overrideLogicalId(name);
|
|
462
549
|
};
|
|
463
550
|
declareOutput("Endpoint", clickHouseHost);
|
|
464
|
-
declareOutput("HttpPort", String(
|
|
465
|
-
declareOutput("NativePort", String(
|
|
551
|
+
declareOutput("HttpPort", String(httpPort));
|
|
552
|
+
declareOutput("NativePort", String(nativePort));
|
|
553
|
+
if (tlsActive) {
|
|
554
|
+
declareOutput("TlsActive", "true");
|
|
555
|
+
}
|
|
556
|
+
if (caCertSha256 !== undefined) {
|
|
557
|
+
declareOutput("CaCertSha256", caCertSha256);
|
|
558
|
+
}
|
|
466
559
|
declareOutput("DatabaseName", CLICKHOUSE_DATABASE_NAME);
|
|
467
560
|
for (const [name, secret] of userSecrets) {
|
|
468
561
|
declareOutput(`${toPascalCase(name)}SecretArn`, secret.secret.secretArn);
|
|
@@ -495,19 +588,29 @@ export class ClickHouseDatabase extends Construct {
|
|
|
495
588
|
return this.#users.get(name);
|
|
496
589
|
}
|
|
497
590
|
getHttpPort() {
|
|
498
|
-
return
|
|
591
|
+
return this.#httpPort;
|
|
499
592
|
}
|
|
500
593
|
getNativePort() {
|
|
501
|
-
return
|
|
594
|
+
return this.#nativePort;
|
|
502
595
|
}
|
|
503
596
|
getHostEndpoint() {
|
|
504
597
|
return `${CLICKHOUSE_CLOUDMAP_SERVICE_NAME}.${App.getInstance().getNamespace().namespaceName}`;
|
|
505
598
|
}
|
|
599
|
+
/**
|
|
600
|
+
* Canonical URL for the HTTP(S) interface. Scheme is `https` when TLS is
|
|
601
|
+
* active (default), `http` only when `tls: { mode: "none", … }` is set.
|
|
602
|
+
* Port matches: HTTPS (8443) under TLS, HTTP (8123) otherwise.
|
|
603
|
+
*/
|
|
604
|
+
getUrl() {
|
|
605
|
+
const scheme = this.tlsCaSecret !== undefined ? "https" : "http";
|
|
606
|
+
return `${scheme}://${this.getHostEndpoint()}:${this.#httpPort}`;
|
|
607
|
+
}
|
|
608
|
+
/** @deprecated Alias for {@link getUrl}. Removed in a follow-up landing. */
|
|
506
609
|
getHttpUrl() {
|
|
507
|
-
return
|
|
610
|
+
return this.getUrl();
|
|
508
611
|
}
|
|
509
612
|
getNativeUrl() {
|
|
510
|
-
return `tcp://${this.getHostEndpoint()}:${this
|
|
613
|
+
return `tcp://${this.getHostEndpoint()}:${this.#nativePort}`;
|
|
511
614
|
}
|
|
512
615
|
getDatabaseName() {
|
|
513
616
|
return CLICKHOUSE_DATABASE_NAME;
|
|
@@ -518,6 +621,20 @@ export class ClickHouseDatabase extends Construct {
|
|
|
518
621
|
getColdTierBucket() {
|
|
519
622
|
return this.#coldTierBucket;
|
|
520
623
|
}
|
|
624
|
+
getMigrationsConfig() {
|
|
625
|
+
return this.#migrationsConfig;
|
|
626
|
+
}
|
|
627
|
+
getExpectedSchemaVersion() {
|
|
628
|
+
const config = this.#migrationsConfig;
|
|
629
|
+
if (config === undefined)
|
|
630
|
+
return undefined;
|
|
631
|
+
const resolver = config.versionResolver ?? pickLatestClickHouseMigration;
|
|
632
|
+
const version = resolver(config.dir);
|
|
633
|
+
if (version.trim() === "") {
|
|
634
|
+
throw new Error(`ClickHouseDatabase '${this.id}': migrations.versionResolver returned an empty string for dir '${config.dir}' — refusing to inject an empty EXPECTED_CH_SCHEMA_VERSION.`);
|
|
635
|
+
}
|
|
636
|
+
return version;
|
|
637
|
+
}
|
|
521
638
|
grantConnect(grantee) {
|
|
522
639
|
this.connections.allowDefaultPortFrom(grantee);
|
|
523
640
|
this.connections.allowFrom(grantee, Port.tcp(CLICKHOUSE_NATIVE_PORT));
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./types.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./types.js";
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { ISecret } from "aws-cdk-lib/aws-secretsmanager";
|
|
2
|
+
/**
|
|
3
|
+
* Self-signed mode (default). The construct mints an ECDSA P-256 CA + leaf
|
|
4
|
+
* cert at stack deploy via a custom-resource Lambda. Bumping
|
|
5
|
+
* `caValidityYears` / `leafValidityYears` triggers re-issuance on the next
|
|
6
|
+
* deploy.
|
|
7
|
+
*/
|
|
8
|
+
export interface ClickHouseSelfSignedTls {
|
|
9
|
+
readonly mode: "self-signed";
|
|
10
|
+
readonly caValidityYears?: number;
|
|
11
|
+
readonly leafValidityYears?: number;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Imported mode. Caller owns the CA + server-cert secrets in Secrets
|
|
15
|
+
* Manager. `serverCertSecret`'s SecretString MUST be JSON of the shape
|
|
16
|
+
* `{ "cert": "<pem>", "key": "<pem>" }`. `caCertSha256` is optional — when
|
|
17
|
+
* supplied the construct surfaces it as a CFN env-var token so consumers
|
|
18
|
+
* can wire it into containers for rotation-triggers-task-replacement
|
|
19
|
+
* parity with self-signed mode. Omit it and rotation requires manual task
|
|
20
|
+
* restart (documented in `aiDocs/patterns/clickhouse-tls-pattern.md § "Imported mode"`).
|
|
21
|
+
*/
|
|
22
|
+
export interface ClickHouseImportedTls {
|
|
23
|
+
readonly mode: "imported";
|
|
24
|
+
readonly caCertSecret: ISecret;
|
|
25
|
+
readonly serverCertSecret: ISecret;
|
|
26
|
+
readonly caCertSha256?: string;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Plaintext escape hatch. Disables TLS entirely. The acknowledgement is a
|
|
30
|
+
* literal-string type so misuse fails at compile time — passing any other
|
|
31
|
+
* string fails `tsc --noEmit`.
|
|
32
|
+
*/
|
|
33
|
+
export interface ClickHouseNoTls {
|
|
34
|
+
readonly mode: "none";
|
|
35
|
+
readonly acknowledgement: "I understand plaintext-only ClickHouse fails SOC2 DP5";
|
|
36
|
+
}
|
|
37
|
+
export type ClickHouseTlsOptions = ClickHouseSelfSignedTls | ClickHouseImportedTls | ClickHouseNoTls;
|
|
38
|
+
/**
|
|
39
|
+
* The resolved TLS surface a `ClickHouseDatabase` carries after dispatch.
|
|
40
|
+
* Every field is `undefined` in `mode: "none"`. `certGeneratorFunction`
|
|
41
|
+
* is `undefined` in `mode: "imported"`. `caCertSha256` is `undefined` in
|
|
42
|
+
* `mode: "none"` AND in `mode: "imported"` unless the caller supplied it.
|
|
43
|
+
*/
|
|
44
|
+
export interface ClickHouseTlsResolved {
|
|
45
|
+
readonly caSecret: ISecret | undefined;
|
|
46
|
+
readonly serverSecret: ISecret | undefined;
|
|
47
|
+
readonly caCertSha256: string | undefined;
|
|
48
|
+
}
|
|
@@ -48,7 +48,7 @@ export declare function expandMigrationsSugar(service: EcsServiceConfig, userCon
|
|
|
48
48
|
* themselves and the resolved value differs.
|
|
49
49
|
* @internal Exported for testing only
|
|
50
50
|
*/
|
|
51
|
-
export declare function buildContainerConfigs(service: EcsServiceConfig, schemaVersionEnv?: Record<string, string>, annotationsScope?: Construct): EcsClusterProps["services"][number]["containers"];
|
|
51
|
+
export declare function buildContainerConfigs(service: EcsServiceConfig, schemaVersionEnv?: Record<string, string>, annotationsScope?: Construct, chSchemaVersionEnv?: Record<string, string>): EcsClusterProps["services"][number]["containers"];
|
|
52
52
|
/**
|
|
53
53
|
* Resolved scaling configuration for an ECS service.
|
|
54
54
|
* @internal Exported for testing only
|
|
@@ -92,6 +92,18 @@ export declare class EcsCompute extends Construct implements IEcsCompute {
|
|
|
92
92
|
* hardcoding one.
|
|
93
93
|
*/
|
|
94
94
|
private resolveSchemaVersionEnv;
|
|
95
|
+
/**
|
|
96
|
+
* ClickHouse mirror of `resolveSchemaVersionEnv`. Returns the
|
|
97
|
+
* `EXPECTED_CH_SCHEMA_VERSION` env entry, or `undefined` when the service is
|
|
98
|
+
* not gated.
|
|
99
|
+
*
|
|
100
|
+
* - `schemaGate: false` → returns `undefined` (auditable opt-out — same flag
|
|
101
|
+
* covers BOTH PG and CH gates)
|
|
102
|
+
* - No migrated CH in connections → returns `undefined`
|
|
103
|
+
* - Exactly one migrated CH → returns `{ EXPECTED_CH_SCHEMA_VERSION }`
|
|
104
|
+
* - Two or more migrated CHs → throws via `resolveClickHouseDatabaseForService`
|
|
105
|
+
*/
|
|
106
|
+
private resolveClickHouseSchemaVersionEnv;
|
|
95
107
|
private materialiseScheduledTasks;
|
|
96
108
|
private buildScheduledTaskDefinition;
|
|
97
109
|
/**
|
|
@@ -7,7 +7,7 @@ import { Secret } from "aws-cdk-lib/aws-secretsmanager";
|
|
|
7
7
|
import { StringParameter } from "aws-cdk-lib/aws-ssm";
|
|
8
8
|
import { Construct } from "constructs";
|
|
9
9
|
import { resolveAlertsTopic } from "../../utils/resolveAlertsTopic.js";
|
|
10
|
-
import { EXPECTED_SCHEMA_VERSION_ENV, EXPECTED_SCHEMA_VERSION_TOOL_ENV, isDatabase, isRelationalDatabase } from "./interfaces/database.js";
|
|
10
|
+
import { EXPECTED_SCHEMA_VERSION_ENV, EXPECTED_SCHEMA_VERSION_TOOL_ENV, EXPECTED_CH_SCHEMA_VERSION_ENV, isClickHouseDatabase, isDatabase, isRelationalDatabase } from "./interfaces/database.js";
|
|
11
11
|
import { isConnectionConfig } from "../../utils/connector.js";
|
|
12
12
|
import { isMigrationContributor } from "./interfaces/migrationContributor.js";
|
|
13
13
|
import App from "../../app.js";
|
|
@@ -274,6 +274,37 @@ function resolveMigrationDatabaseForService(service) {
|
|
|
274
274
|
}
|
|
275
275
|
return matches[0];
|
|
276
276
|
}
|
|
277
|
+
/**
|
|
278
|
+
* ClickHouse mirror of `resolveMigrationDatabaseForService`. Two-CH-per-service
|
|
279
|
+
* is rejected for the same reason: the gate can only inject one
|
|
280
|
+
* `EXPECTED_CH_SCHEMA_VERSION`.
|
|
281
|
+
*/
|
|
282
|
+
function resolveClickHouseDatabaseForService(service) {
|
|
283
|
+
const connections = service.connections;
|
|
284
|
+
if (connections === undefined || connections.length === 0)
|
|
285
|
+
return undefined;
|
|
286
|
+
const matches = [];
|
|
287
|
+
for (const spec of connections) {
|
|
288
|
+
const resource = isConnectionConfig(spec) ? spec.resource : spec;
|
|
289
|
+
if (!isDatabase(resource))
|
|
290
|
+
continue;
|
|
291
|
+
if (!isClickHouseDatabase(resource))
|
|
292
|
+
continue;
|
|
293
|
+
if (resource.getMigrationsConfig() !== undefined)
|
|
294
|
+
matches.push(resource);
|
|
295
|
+
}
|
|
296
|
+
if (matches.length === 0)
|
|
297
|
+
return undefined;
|
|
298
|
+
if (matches.length > 1) {
|
|
299
|
+
const names = matches.map((d) => d.node.id).join(", ");
|
|
300
|
+
throw new Error(`Service '${service.name}': connections include two or more ClickHouse ` +
|
|
301
|
+
`databases declaring \`migrations:\` (${names}). The schema-version ` +
|
|
302
|
+
`gate cannot inject ${EXPECTED_CH_SCHEMA_VERSION_ENV} unambiguously. ` +
|
|
303
|
+
`Split the service so each consumes a single migrated database, or ` +
|
|
304
|
+
`set \`schemaGate: false\` and inject the env manually.`);
|
|
305
|
+
}
|
|
306
|
+
return matches[0];
|
|
307
|
+
}
|
|
277
308
|
function getResourceLabel(resource) {
|
|
278
309
|
if (resource !== null &&
|
|
279
310
|
typeof resource === "object" &&
|
|
@@ -436,7 +467,7 @@ function mergeContributionsIntoSeparateTaskDef(separateTaskDef, contributions) {
|
|
|
436
467
|
* themselves and the resolved value differs.
|
|
437
468
|
* @internal Exported for testing only
|
|
438
469
|
*/
|
|
439
|
-
export function buildContainerConfigs(service, schemaVersionEnv, annotationsScope) {
|
|
470
|
+
export function buildContainerConfigs(service, schemaVersionEnv, annotationsScope, chSchemaVersionEnv) {
|
|
440
471
|
const userContainers = service.containers && service.containers.length > 0
|
|
441
472
|
? service.containers
|
|
442
473
|
: undefined;
|
|
@@ -464,19 +495,43 @@ export function buildContainerConfigs(service, schemaVersionEnv, annotationsScop
|
|
|
464
495
|
};
|
|
465
496
|
}
|
|
466
497
|
const merged = { ...(authored ?? {}) };
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
498
|
+
if (schemaVersionEnv[EXPECTED_SCHEMA_VERSION_ENV] !== undefined) {
|
|
499
|
+
merged[EXPECTED_SCHEMA_VERSION_ENV] =
|
|
500
|
+
schemaVersionEnv[EXPECTED_SCHEMA_VERSION_ENV];
|
|
501
|
+
}
|
|
502
|
+
if (schemaVersionEnv[EXPECTED_SCHEMA_VERSION_TOOL_ENV] !== undefined) {
|
|
503
|
+
merged[EXPECTED_SCHEMA_VERSION_TOOL_ENV] =
|
|
504
|
+
schemaVersionEnv[EXPECTED_SCHEMA_VERSION_TOOL_ENV];
|
|
470
505
|
}
|
|
471
506
|
return merged;
|
|
472
507
|
};
|
|
508
|
+
const mergeChSchemaEnv = (authored) => {
|
|
509
|
+
if (chSchemaVersionEnv === undefined)
|
|
510
|
+
return authored;
|
|
511
|
+
const authoredValue = authored?.[EXPECTED_CH_SCHEMA_VERSION_ENV];
|
|
512
|
+
const resolvedValue = chSchemaVersionEnv[EXPECTED_CH_SCHEMA_VERSION_ENV];
|
|
513
|
+
if (authoredValue !== undefined) {
|
|
514
|
+
if (authoredValue !== resolvedValue && annotationsScope !== undefined) {
|
|
515
|
+
Annotations.of(annotationsScope).addWarning(`Service '${service.name}': author-set ${EXPECTED_CH_SCHEMA_VERSION_ENV}` +
|
|
516
|
+
`='${authoredValue}' overrides the value resolved from the connected ` +
|
|
517
|
+
`ClickHouse database's \`migrations:\` config ('${resolvedValue}'). The ` +
|
|
518
|
+
`author value wins; set \`schemaGate: false\` to silence this warning.`);
|
|
519
|
+
}
|
|
520
|
+
return authored;
|
|
521
|
+
}
|
|
522
|
+
return {
|
|
523
|
+
...(authored ?? {}),
|
|
524
|
+
[EXPECTED_CH_SCHEMA_VERSION_ENV]: resolvedValue
|
|
525
|
+
};
|
|
526
|
+
};
|
|
527
|
+
const mergeAllSchemaEnv = (authored) => mergeChSchemaEnv(mergeSchemaEnv(authored));
|
|
473
528
|
if (expanded) {
|
|
474
529
|
return expanded.map((c, index) => {
|
|
475
530
|
return {
|
|
476
531
|
name: c.name || `${service.name}Container${index > 0 ? index : ""}`,
|
|
477
532
|
image: c.image,
|
|
478
533
|
port: c.port,
|
|
479
|
-
environment:
|
|
534
|
+
environment: mergeAllSchemaEnv(c.environment),
|
|
480
535
|
secrets: c.secrets,
|
|
481
536
|
secretsImport: c.secretsImport,
|
|
482
537
|
command: c.command,
|
|
@@ -490,7 +545,7 @@ export function buildContainerConfigs(service, schemaVersionEnv, annotationsScop
|
|
|
490
545
|
};
|
|
491
546
|
});
|
|
492
547
|
}
|
|
493
|
-
const fallbackEnv =
|
|
548
|
+
const fallbackEnv = mergeAllSchemaEnv(undefined);
|
|
494
549
|
return [
|
|
495
550
|
{
|
|
496
551
|
name: `${service.name}Container`,
|
|
@@ -562,7 +617,8 @@ export class EcsCompute extends Construct {
|
|
|
562
617
|
this.appName = props.appName;
|
|
563
618
|
const services = props.services.map((service) => {
|
|
564
619
|
const schemaVersionEnv = this.resolveSchemaVersionEnv(service);
|
|
565
|
-
const
|
|
620
|
+
const chSchemaVersionEnv = this.resolveClickHouseSchemaVersionEnv(service);
|
|
621
|
+
const containers = buildContainerConfigs(service, schemaVersionEnv, this, chSchemaVersionEnv);
|
|
566
622
|
const { scalingType, minCapacity, maxCapacity } = resolveScalingConfig(service.scaling);
|
|
567
623
|
const cloudMapService = service.serviceDiscovery !== undefined
|
|
568
624
|
? App.getInstance().registerService({
|
|
@@ -670,6 +726,30 @@ export class EcsCompute extends Construct {
|
|
|
670
726
|
[EXPECTED_SCHEMA_VERSION_TOOL_ENV]: config.tool
|
|
671
727
|
};
|
|
672
728
|
}
|
|
729
|
+
/**
|
|
730
|
+
* ClickHouse mirror of `resolveSchemaVersionEnv`. Returns the
|
|
731
|
+
* `EXPECTED_CH_SCHEMA_VERSION` env entry, or `undefined` when the service is
|
|
732
|
+
* not gated.
|
|
733
|
+
*
|
|
734
|
+
* - `schemaGate: false` → returns `undefined` (auditable opt-out — same flag
|
|
735
|
+
* covers BOTH PG and CH gates)
|
|
736
|
+
* - No migrated CH in connections → returns `undefined`
|
|
737
|
+
* - Exactly one migrated CH → returns `{ EXPECTED_CH_SCHEMA_VERSION }`
|
|
738
|
+
* - Two or more migrated CHs → throws via `resolveClickHouseDatabaseForService`
|
|
739
|
+
*/
|
|
740
|
+
resolveClickHouseSchemaVersionEnv(service) {
|
|
741
|
+
if (service.schemaGate === false)
|
|
742
|
+
return undefined;
|
|
743
|
+
const db = resolveClickHouseDatabaseForService(service);
|
|
744
|
+
if (db === undefined)
|
|
745
|
+
return undefined;
|
|
746
|
+
const version = db.getExpectedSchemaVersion();
|
|
747
|
+
if (version === undefined)
|
|
748
|
+
return undefined;
|
|
749
|
+
return {
|
|
750
|
+
[EXPECTED_CH_SCHEMA_VERSION_ENV]: version
|
|
751
|
+
};
|
|
752
|
+
}
|
|
673
753
|
materialiseScheduledTasks(id, props) {
|
|
674
754
|
const scheduledTasks = props.cluster?.scheduledTasks;
|
|
675
755
|
if (!scheduledTasks || scheduledTasks.length === 0)
|