@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.
- package/dist/lib/app.d.ts +68 -1
- package/dist/lib/app.js +113 -4
- package/dist/lib/config/aws/__t17fixture.d.ts +1 -0
- package/dist/lib/config/aws/__t17fixture.js +3 -0
- package/dist/lib/config/aws/__t17fixtureType.d.ts +2 -0
- package/dist/lib/config/aws/__t17fixtureType.js +1 -0
- package/dist/lib/config/aws/alarmTopic.js +8 -4
- package/dist/lib/config/aws/cloudTrail.js +1 -1
- package/dist/lib/config/aws/disasterRecovery.js +11 -16
- package/dist/lib/config/aws/ecrDefaultImage.d.ts +0 -1
- package/dist/lib/config/aws/ecrDefaultImage.js +13 -23
- package/dist/lib/config/aws/identityCenter.d.ts +10 -3
- package/dist/lib/config/aws/identityCenter.js +101 -37
- package/dist/lib/config/aws/identityCenterGroupMembership.js +8 -2
- package/dist/lib/config/aws/identityCenterMembership.d.ts +11 -0
- package/dist/lib/config/aws/identityCenterMembership.js +61 -0
- package/dist/lib/config/aws/index.d.ts +1 -1
- package/dist/lib/config/aws/index.js +1 -1
- package/dist/lib/config/aws/ipam.js +6 -11
- package/dist/lib/config/aws/oidcConnector.js +5 -1
- package/dist/lib/config/aws/scpPreset.js +4 -1
- package/dist/lib/patterns/aws/_eslint_test_tmp/leak.d.ts +1 -0
- package/dist/lib/patterns/aws/_eslint_test_tmp/leak.js +4 -0
- package/dist/lib/patterns/aws/account.js +2 -4
- package/dist/lib/patterns/aws/apexDomainPattern.js +10 -10
- package/dist/lib/patterns/aws/bastionFactory.d.ts +10 -0
- package/dist/lib/patterns/aws/bastionFactory.js +29 -0
- package/dist/lib/patterns/aws/buildkite.d.ts +2 -2
- package/dist/lib/patterns/aws/buildkite.js +51 -97
- package/dist/lib/patterns/aws/cdn.js +1 -1
- package/dist/lib/patterns/aws/clickhouseDatabase.d.ts +173 -0
- package/dist/lib/patterns/aws/clickhouseDatabase.js +601 -0
- package/dist/lib/patterns/aws/compute.d.ts +4 -6
- package/dist/lib/patterns/aws/compute.js +7 -13
- package/dist/lib/patterns/aws/computeEcs.d.ts +93 -5
- package/dist/lib/patterns/aws/computeEcs.js +867 -37
- package/dist/lib/patterns/aws/computeEcsTypes.d.ts +528 -25
- package/dist/lib/patterns/aws/computeEcsTypes.js +10 -0
- package/dist/lib/patterns/aws/computeLambda.d.ts +0 -5
- package/dist/lib/patterns/aws/computeLambda.js +1 -2
- package/dist/lib/patterns/aws/database.d.ts +50 -8
- package/dist/lib/patterns/aws/database.js +183 -27
- package/dist/lib/patterns/aws/domain.js +6 -4
- package/dist/lib/patterns/aws/index.d.ts +1 -0
- package/dist/lib/patterns/aws/index.js +1 -0
- package/dist/lib/patterns/aws/interfaces/compute.d.ts +7 -1
- package/dist/lib/patterns/aws/interfaces/database.d.ts +187 -8
- package/dist/lib/patterns/aws/interfaces/database.js +17 -3
- package/dist/lib/patterns/aws/interfaces/index.d.ts +2 -1
- package/dist/lib/patterns/aws/interfaces/index.js +3 -1
- package/dist/lib/patterns/aws/interfaces/messaging.d.ts +7 -0
- package/dist/lib/patterns/aws/interfaces/migrationContributor.d.ts +47 -0
- package/dist/lib/patterns/aws/interfaces/migrationContributor.js +9 -0
- package/dist/lib/patterns/aws/messaging.d.ts +66 -10
- package/dist/lib/patterns/aws/messaging.js +115 -20
- package/dist/lib/patterns/aws/network.js +16 -7
- package/dist/lib/patterns/aws/organisation.d.ts +4 -0
- package/dist/lib/patterns/aws/organisation.js +22 -4
- package/dist/lib/patterns/aws/storage.d.ts +1 -2
- package/dist/lib/patterns/aws/storage.js +3 -2
- package/dist/lib/patterns/aws/vpcPeer.js +3 -1
- package/dist/lib/resources/aws/analytics/clickhouse.js +18 -9
- package/dist/lib/resources/aws/analytics/clickhouseAlarms.d.ts +24 -9
- package/dist/lib/resources/aws/analytics/clickhouseAlarms.js +61 -10
- package/dist/lib/resources/aws/analytics/clickhouseConstants.d.ts +3 -3
- package/dist/lib/resources/aws/analytics/clickhouseConstants.js +3 -3
- package/dist/lib/resources/aws/analytics/clickhouseTypes.d.ts +7 -1
- package/dist/lib/resources/aws/analytics/clickhouseUserData.d.ts +1 -1
- package/dist/lib/resources/aws/analytics/clickhouseUserData.js +53 -3
- package/dist/lib/resources/aws/base/awsStack.js +4 -2
- package/dist/lib/resources/aws/compute/__tmp__/regression-shape.d.ts +2 -0
- package/dist/lib/resources/aws/compute/__tmp__/regression-shape.js +11 -0
- package/dist/lib/resources/aws/compute/asgInlineLifecycleHook.d.ts +52 -0
- package/dist/lib/resources/aws/compute/asgInlineLifecycleHook.js +60 -0
- package/dist/lib/resources/aws/compute/blockDeviceVolume.d.ts +8 -0
- package/dist/lib/resources/aws/compute/blockDeviceVolume.js +10 -0
- package/dist/lib/resources/aws/compute/ec2.d.ts +132 -12
- package/dist/lib/resources/aws/compute/ec2.js +163 -23
- package/dist/lib/resources/aws/compute/ec2GracefulTerminationHandler.d.ts +41 -0
- package/dist/lib/resources/aws/compute/ec2GracefulTerminationHandler.js +194 -0
- package/dist/lib/resources/aws/compute/ec2GracefulTerminationLambda.source.cjs +458 -0
- package/dist/lib/resources/aws/compute/ecs.d.ts +27 -1
- package/dist/lib/resources/aws/compute/ecs.js +42 -2
- package/dist/lib/resources/aws/compute/ecsConstants.d.ts +9 -0
- package/dist/lib/resources/aws/compute/ecsConstants.js +16 -0
- package/dist/lib/resources/aws/compute/ecsImages.js +32 -20
- package/dist/lib/resources/aws/compute/ecsLifecycleHookMigration.d.ts +96 -0
- package/dist/lib/resources/aws/compute/ecsLifecycleHookMigration.js +113 -0
- package/dist/lib/resources/aws/compute/ecsNetworking.d.ts +2 -1
- package/dist/lib/resources/aws/compute/ecsNetworking.js +18 -6
- package/dist/lib/resources/aws/compute/ecsServiceFactory.d.ts +13 -4
- package/dist/lib/resources/aws/compute/ecsServiceFactory.js +155 -33
- package/dist/lib/resources/aws/compute/ecsTaskDefinition.d.ts +31 -1
- package/dist/lib/resources/aws/compute/ecsTaskDefinition.js +102 -6
- package/dist/lib/resources/aws/compute/ecsTypes.d.ts +173 -13
- package/dist/lib/resources/aws/compute/ecsValidation.d.ts +9 -0
- package/dist/lib/resources/aws/compute/ecsValidation.js +63 -0
- package/dist/lib/resources/aws/compute/index.d.ts +2 -0
- package/dist/lib/resources/aws/compute/index.js +2 -0
- package/dist/lib/resources/aws/compute/lambda.d.ts +7 -13
- package/dist/lib/resources/aws/compute/lambda.js +30 -38
- package/dist/lib/resources/aws/compute/lifecycleHookLambda.source.cjs +192 -0
- package/dist/lib/resources/aws/compute/persistentDataVolume.d.ts +104 -0
- package/dist/lib/resources/aws/compute/persistentDataVolume.js +245 -0
- package/dist/lib/resources/aws/compute/persistentDataVolumeLambda.source.cjs +398 -0
- package/dist/lib/resources/aws/compute/samApplication.d.ts +15 -0
- package/dist/lib/resources/aws/compute/samApplication.js +27 -0
- package/dist/lib/resources/aws/database/clickhouseConstants.d.ts +159 -0
- package/dist/lib/resources/aws/database/clickhouseConstants.js +181 -0
- package/dist/lib/resources/aws/database/clickhouseSchemas.d.ts +71 -0
- package/dist/lib/resources/aws/database/clickhouseSchemas.js +160 -0
- package/dist/lib/resources/aws/database/clickhouseSecurityGroup.d.ts +14 -0
- package/dist/lib/resources/aws/database/clickhouseSecurityGroup.js +23 -0
- package/dist/lib/resources/aws/database/clickhouseUserData.d.ts +69 -0
- package/dist/lib/resources/aws/database/clickhouseUserData.js +371 -0
- package/dist/lib/resources/aws/database/clickhouseXmlRenderer.d.ts +56 -0
- package/dist/lib/resources/aws/database/clickhouseXmlRenderer.js +112 -0
- package/dist/lib/resources/aws/database/rdsAurora.d.ts +8 -1
- package/dist/lib/resources/aws/database/rdsAurora.js +42 -32
- package/dist/lib/resources/aws/database/rdsAuroraGlobal.d.ts +15 -2
- package/dist/lib/resources/aws/database/rdsAuroraGlobal.js +39 -43
- package/dist/lib/resources/aws/database/rdsDefaults.d.ts +6 -0
- package/dist/lib/resources/aws/database/rdsDefaults.js +7 -1
- package/dist/lib/resources/aws/database/rdsHelpers.d.ts +3 -3
- package/dist/lib/resources/aws/database/rdsHelpers.js +1 -0
- package/dist/lib/resources/aws/database/rdsInstance.d.ts +8 -1
- package/dist/lib/resources/aws/database/rdsInstance.js +51 -34
- package/dist/lib/resources/aws/database/rdsProxyOutput.d.ts +1 -1
- package/dist/lib/resources/aws/database/rdsProxyOutput.js +1 -1
- package/dist/lib/resources/aws/iam/delegationRole.js +1 -1
- package/dist/lib/resources/aws/iam/identityCenter/groupMembership.d.ts +9 -0
- package/dist/lib/resources/aws/iam/identityCenter/groupMembership.js +12 -0
- package/dist/lib/resources/aws/iam/identityCenter/index.d.ts +1 -0
- package/dist/lib/resources/aws/iam/identityCenter/index.js +1 -0
- package/dist/lib/resources/aws/iam/identityCenter/permissionSet.d.ts +1 -0
- package/dist/lib/resources/aws/iam/identityCenter/permissionSet.js +1 -0
- package/dist/lib/resources/aws/logging/logGroup.d.ts +0 -8
- package/dist/lib/resources/aws/logging/logGroup.js +0 -11
- package/dist/lib/resources/aws/messaging/defaultEventBus.d.ts +7 -0
- package/dist/lib/resources/aws/messaging/defaultEventBus.js +21 -0
- package/dist/lib/resources/aws/messaging/eventBridgeRule.d.ts +96 -0
- package/dist/lib/resources/aws/messaging/eventBridgeRule.js +110 -0
- package/dist/lib/resources/aws/messaging/eventTargets.d.ts +84 -0
- package/dist/lib/resources/aws/messaging/eventTargets.js +152 -0
- package/dist/lib/resources/aws/messaging/eventbridge.d.ts +25 -2
- package/dist/lib/resources/aws/messaging/eventbridge.js +22 -10
- package/dist/lib/resources/aws/messaging/index.d.ts +5 -0
- package/dist/lib/resources/aws/messaging/index.js +2 -0
- package/dist/lib/resources/aws/messaging/schedule.d.ts +118 -0
- package/dist/lib/resources/aws/messaging/schedule.js +64 -0
- package/dist/lib/resources/aws/messaging/sns.d.ts +2 -1
- package/dist/lib/resources/aws/messaging/sqs.d.ts +2 -1
- package/dist/lib/resources/aws/messaging/subscription.d.ts +112 -0
- package/dist/lib/resources/aws/messaging/subscription.js +67 -0
- package/dist/lib/resources/aws/messaging/utils.d.ts +6 -0
- package/dist/lib/resources/aws/messaging/utils.js +10 -0
- package/dist/lib/resources/aws/monitoring/clickhouseAlarms.d.ts +60 -0
- package/dist/lib/resources/aws/monitoring/clickhouseAlarms.js +139 -0
- package/dist/lib/resources/aws/monitoring/index.d.ts +2 -0
- package/dist/lib/resources/aws/monitoring/index.js +2 -0
- package/dist/lib/resources/aws/monitoring/scheduleAlarms.d.ts +47 -0
- package/dist/lib/resources/aws/monitoring/scheduleAlarms.js +106 -0
- package/dist/lib/resources/aws/networking/crossAccountDelegationRecord.js +6 -4
- package/dist/lib/resources/aws/networking/crossAccountReturnRoutes.js +17 -13
- package/dist/lib/resources/aws/networking/dnsRecord/dnsRecordBase.js +7 -5
- package/dist/lib/resources/aws/networking/domainCertificate.d.ts +2 -2
- package/dist/lib/resources/aws/networking/domainCertificate.js +6 -4
- package/dist/lib/resources/aws/networking/hostedZone.js +6 -5
- package/dist/lib/resources/aws/networking/serviceDiscovery.d.ts +96 -0
- package/dist/lib/resources/aws/networking/serviceDiscovery.js +96 -0
- package/dist/lib/resources/aws/networking/vpc.d.ts +4 -1
- package/dist/lib/resources/aws/networking/vpc.js +4 -1
- package/dist/lib/resources/aws/networking/vpcPeeringConnection.js +21 -3
- package/dist/lib/resources/aws/organisation/costAllocationTagActivator.d.ts +16 -5
- package/dist/lib/resources/aws/organisation/costAllocationTagActivator.js +17 -3
- package/dist/lib/resources/aws/organisation/index.d.ts +1 -1
- package/dist/lib/resources/aws/organisation/organisationPolicy.d.ts +2 -0
- package/dist/lib/resources/aws/organisation/organisationPolicy.js +3 -2
- package/dist/lib/resources/aws/secrets/secret.d.ts +7 -0
- package/dist/lib/resources/aws/secrets/secret.js +4 -3
- package/dist/lib/resources/aws/storage/bucketDeployment.d.ts +16 -0
- package/dist/lib/resources/aws/storage/bucketDeployment.js +17 -0
- package/dist/lib/resources/aws/storage/ecr.js +5 -5
- package/dist/lib/resources/aws/storage/index.d.ts +1 -0
- package/dist/lib/resources/aws/storage/index.js +1 -0
- package/dist/lib/resources/aws/storage/s3.js +10 -3
- package/dist/lib/resources/aws/utilities/customResource.js +18 -9
- package/dist/lib/synth_dump.d.ts +1 -0
- package/dist/lib/synth_dump.js +42 -0
- package/dist/lib/utils/cdkContext.d.ts +2 -0
- package/dist/lib/utils/cdkContext.js +4 -2
- package/dist/lib/utils/connections.js +6 -0
- package/dist/lib/utils/connector.d.ts +12 -0
- package/dist/lib/utils/costAllocationTags.d.ts +9 -0
- package/dist/lib/utils/costAllocationTags.js +11 -1
- package/dist/lib/utils/databaseTypes.d.ts +14 -0
- package/dist/lib/utils/getConfig.d.ts +2 -0
- package/dist/lib/utils/getConfig.js +2 -0
- package/dist/lib/utils/index.d.ts +1 -0
- package/dist/lib/utils/index.js +1 -0
- package/dist/lib/utils/manifestWriter.d.ts +6 -89
- package/dist/lib/utils/manifestWriter.js +36 -23
- package/dist/lib/utils/migrationVersionResolvers.d.ts +2 -0
- package/dist/lib/utils/migrationVersionResolvers.js +2 -0
- package/dist/lib/utils/orgConfigParser.js +2 -1
- package/dist/lib/utils/resolveAlertsTopic.d.ts +14 -0
- package/dist/lib/utils/resolveAlertsTopic.js +30 -0
- package/dist/lib/utils/validationLogger.js +6 -3
- 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
|
|
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: {
|