@fjall/components-infrastructure 0.96.0 → 0.99.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +172 -0
- package/dist/lib/patterns/aws/clickhouseDatabase.js +600 -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 +157 -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
|
@@ -1,21 +1,37 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { EventBus as CdkEventBus } from "aws-cdk-lib/aws-events";
|
|
2
|
+
import { SQSQueue, SQS_LIMITS } from "../../resources/aws/messaging/sqs.js";
|
|
2
3
|
import { SNSTopic } from "../../resources/aws/messaging/sns.js";
|
|
3
4
|
import { EventBridgeBus } from "../../resources/aws/messaging/eventbridge.js";
|
|
5
|
+
import { Subscription } from "../../resources/aws/messaging/subscription.js";
|
|
4
6
|
import { FjallLogger } from "../../utils/validationLogger.js";
|
|
5
7
|
/**
|
|
6
8
|
* Validates SQS props and logs warnings for misconfigured options.
|
|
7
9
|
*/
|
|
8
10
|
function validateSQSProps(props) {
|
|
9
|
-
// Validate visibility timeout range (0-43200 seconds = 12 hours)
|
|
10
11
|
if (props.visibilityTimeout !== undefined &&
|
|
11
|
-
(props.visibilityTimeout <
|
|
12
|
-
|
|
12
|
+
(props.visibilityTimeout < SQS_LIMITS.VISIBILITY_TIMEOUT.MIN_SECONDS ||
|
|
13
|
+
props.visibilityTimeout > SQS_LIMITS.VISIBILITY_TIMEOUT.MAX_SECONDS)) {
|
|
14
|
+
throw new Error(`visibilityTimeout must be between ${SQS_LIMITS.VISIBILITY_TIMEOUT.MIN_SECONDS} and ${SQS_LIMITS.VISIBILITY_TIMEOUT.MAX_SECONDS} seconds (12 hours)`);
|
|
13
15
|
}
|
|
14
|
-
// Validate message retention period (60-1209600 seconds = 1 minute to 14 days)
|
|
15
16
|
if (props.messageRetentionPeriod !== undefined &&
|
|
16
|
-
(props.messageRetentionPeriod <
|
|
17
|
-
props.messageRetentionPeriod >
|
|
18
|
-
throw new Error(
|
|
17
|
+
(props.messageRetentionPeriod < SQS_LIMITS.MESSAGE_RETENTION.MIN_SECONDS ||
|
|
18
|
+
props.messageRetentionPeriod > SQS_LIMITS.MESSAGE_RETENTION.MAX_SECONDS)) {
|
|
19
|
+
throw new Error(`messageRetentionPeriod must be between ${SQS_LIMITS.MESSAGE_RETENTION.MIN_SECONDS} and ${SQS_LIMITS.MESSAGE_RETENTION.MAX_SECONDS} seconds (1 minute to 14 days)`);
|
|
20
|
+
}
|
|
21
|
+
if (props.deliveryDelay !== undefined &&
|
|
22
|
+
(props.deliveryDelay < SQS_LIMITS.DELAY.MIN_SECONDS ||
|
|
23
|
+
props.deliveryDelay > SQS_LIMITS.DELAY.MAX_SECONDS)) {
|
|
24
|
+
throw new Error(`deliveryDelay must be between ${SQS_LIMITS.DELAY.MIN_SECONDS} and ${SQS_LIMITS.DELAY.MAX_SECONDS} seconds (15 minutes)`);
|
|
25
|
+
}
|
|
26
|
+
if (props.receiveMessageWaitTime !== undefined &&
|
|
27
|
+
(props.receiveMessageWaitTime < SQS_LIMITS.RECEIVE_WAIT_TIME.MIN_SECONDS ||
|
|
28
|
+
props.receiveMessageWaitTime > SQS_LIMITS.RECEIVE_WAIT_TIME.MAX_SECONDS)) {
|
|
29
|
+
throw new Error(`receiveMessageWaitTime must be between ${SQS_LIMITS.RECEIVE_WAIT_TIME.MIN_SECONDS} and ${SQS_LIMITS.RECEIVE_WAIT_TIME.MAX_SECONDS} seconds (long-poll cap)`);
|
|
30
|
+
}
|
|
31
|
+
if (props.maxMessageSize !== undefined &&
|
|
32
|
+
(props.maxMessageSize < SQS_LIMITS.MESSAGE_SIZE.MIN_BYTES ||
|
|
33
|
+
props.maxMessageSize > SQS_LIMITS.MESSAGE_SIZE.MAX_BYTES)) {
|
|
34
|
+
throw new Error(`maxMessageSize must be between ${SQS_LIMITS.MESSAGE_SIZE.MIN_BYTES} and ${SQS_LIMITS.MESSAGE_SIZE.MAX_BYTES} bytes (256 KB)`);
|
|
19
35
|
}
|
|
20
36
|
// Warn about FIFO-specific options on standard queues
|
|
21
37
|
if (props.queueType !== "fifo") {
|
|
@@ -58,6 +74,7 @@ export class QueueMessaging extends SQSQueue {
|
|
|
58
74
|
/** The connector type for unified connection processing. */
|
|
59
75
|
connectorType = "queue";
|
|
60
76
|
constructor(scope, id, props) {
|
|
77
|
+
validateSQSProps(props);
|
|
61
78
|
const queueProps = {
|
|
62
79
|
queueType: props.queueType,
|
|
63
80
|
visibilityTimeout: props.visibilityTimeout,
|
|
@@ -120,14 +137,8 @@ export class TopicMessaging extends SNSTopic {
|
|
|
120
137
|
/** Type discriminator for runtime type narrowing. */
|
|
121
138
|
messagingType = "topic";
|
|
122
139
|
constructor(scope, id, props) {
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
displayName: props.displayName,
|
|
126
|
-
fifo: props.fifo,
|
|
127
|
-
contentBasedDeduplication: props.contentBasedDeduplication,
|
|
128
|
-
removalPolicy: props.removalPolicy
|
|
129
|
-
};
|
|
130
|
-
super(scope, id, topicProps);
|
|
140
|
+
validateTopicProps(props);
|
|
141
|
+
super(scope, id, props);
|
|
131
142
|
}
|
|
132
143
|
/**
|
|
133
144
|
* Grant publish permissions to the grantee.
|
|
@@ -151,12 +162,33 @@ export class TopicMessaging extends SNSTopic {
|
|
|
151
162
|
export class EventBusMessaging extends EventBridgeBus {
|
|
152
163
|
/** Type discriminator for runtime type narrowing. */
|
|
153
164
|
messagingType = "eventBus";
|
|
165
|
+
/**
|
|
166
|
+
* Application name threaded into Subscription default descriptions when the
|
|
167
|
+
* caller does not supply one. Captured at construction so `subscribe(...)`
|
|
168
|
+
* has the same naming context as `App.addSchedule(...)`.
|
|
169
|
+
*/
|
|
170
|
+
#appName;
|
|
171
|
+
/**
|
|
172
|
+
* Brand distinguishing app buses from the AWS-managed account+region default
|
|
173
|
+
* bus. Used by `subscribe(...)` to refuse `aws.*` source patterns on app
|
|
174
|
+
* buses (silent-no-op trap — AWS service events only fire on the default
|
|
175
|
+
* bus). Set by `fromAwsServiceBus(...)`; defaults to `false` for buses
|
|
176
|
+
* created via the regular constructor path or `fromEventBusArn(...)`.
|
|
177
|
+
*/
|
|
178
|
+
#isAwsServiceBus;
|
|
154
179
|
constructor(scope, id, props) {
|
|
155
180
|
const eventBusProps = {
|
|
156
181
|
eventBusName: props.eventBusName,
|
|
157
|
-
|
|
182
|
+
appName: props.appName,
|
|
183
|
+
description: props.description,
|
|
184
|
+
removalPolicy: props.removalPolicy,
|
|
185
|
+
importedBus: props.importedBus
|
|
158
186
|
};
|
|
159
187
|
super(scope, id, eventBusProps);
|
|
188
|
+
if (props.appName !== undefined) {
|
|
189
|
+
this.#appName = props.appName;
|
|
190
|
+
}
|
|
191
|
+
this.#isAwsServiceBus = props.awsServiceBus === true;
|
|
160
192
|
}
|
|
161
193
|
/**
|
|
162
194
|
* Grant put events permissions to the grantee.
|
|
@@ -165,6 +197,70 @@ export class EventBusMessaging extends EventBridgeBus {
|
|
|
165
197
|
grantPutEventsTo(grantee) {
|
|
166
198
|
return this.getEventBus().grantPutEventsTo(grantee);
|
|
167
199
|
}
|
|
200
|
+
/**
|
|
201
|
+
* Add a pub/sub subscription scoped to this bus. Threads the bus's
|
|
202
|
+
* `IEventBus` and the captured `appName` into the Subscription so the
|
|
203
|
+
* synthesised rule attaches to this bus (not the default AWS bus) and the
|
|
204
|
+
* default description picks up the bus's app context.
|
|
205
|
+
*
|
|
206
|
+
* Refuses `aws.*` source patterns when the wrapper is an app bus rather
|
|
207
|
+
* than the AWS-managed default bus. AWS service events fire only on the
|
|
208
|
+
* account+region default bus; subscribing them on a custom app bus
|
|
209
|
+
* synthesises and deploys cleanly but never invokes the target. Use
|
|
210
|
+
* `EventBusMessaging.fromAwsServiceBus(...)` to import the default bus
|
|
211
|
+
* for AWS service event traffic.
|
|
212
|
+
*/
|
|
213
|
+
subscribe(id, props) {
|
|
214
|
+
if (!this.#isAwsServiceBus) {
|
|
215
|
+
const sources = props.pattern.source ?? [];
|
|
216
|
+
const awsSources = sources.filter((s) => typeof s === "string" && s.startsWith("aws."));
|
|
217
|
+
if (awsSources.length > 0) {
|
|
218
|
+
throw new Error(`Subscription '${id}' uses AWS service source(s) [${awsSources.join(", ")}] on a custom app event bus. AWS service events fire only on the account+region default bus; subscribing them on a custom bus deploys cleanly but never invokes the target. Use EventBusMessaging.fromAwsServiceBus(scope, id, region, account) instead of app.getEventBus().`);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
return new Subscription(this, id, {
|
|
222
|
+
...props,
|
|
223
|
+
eventBus: this.getEventBus(),
|
|
224
|
+
appName: props.appName ?? this.#appName
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Wrap a bus imported from another account/region by ARN per D19. The
|
|
229
|
+
* returned handle supports `subscribe(...)` identically to created buses;
|
|
230
|
+
* rules are owned by the importing stack and attach to the imported bus's
|
|
231
|
+
* ARN. The base wrapper short-circuits resource creation when `importedBus`
|
|
232
|
+
* is set, so no `AWS::Events::EventBus` is synthesised and bus-level
|
|
233
|
+
* mutations (description, removalPolicy, CfnOutputs) are skipped.
|
|
234
|
+
*/
|
|
235
|
+
static fromEventBusArn(scope, id, arn) {
|
|
236
|
+
const importedBus = CdkEventBus.fromEventBusArn(scope, `${id}Bus`, arn);
|
|
237
|
+
return new EventBusMessaging(scope, id, {
|
|
238
|
+
type: "eventBus",
|
|
239
|
+
importedBus
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Wrap the AWS-managed account+region `default` event bus. This is the
|
|
244
|
+
* bus that receives `aws.*` service events emitted via CloudTrail
|
|
245
|
+
* (`aws.ecr` `CreateRepository`, `aws.ec2` state-change, etc.); custom
|
|
246
|
+
* Fjall app buses do not receive this traffic. The returned wrapper has
|
|
247
|
+
* the AWS-service brand set, so `subscribe(...)` accepts `aws.*` source
|
|
248
|
+
* patterns; subscribing app-source patterns is still allowed but
|
|
249
|
+
* unusual (the canonical app-source path is `app.getEventBus()`).
|
|
250
|
+
*
|
|
251
|
+
* @param region AWS region holding the default bus (typically the stack
|
|
252
|
+
* region — the default bus is per-account-per-region).
|
|
253
|
+
* @param account AWS account id holding the default bus.
|
|
254
|
+
*/
|
|
255
|
+
static fromAwsServiceBus(scope, id, region, account) {
|
|
256
|
+
const arn = `arn:aws:events:${region}:${account}:event-bus/default`;
|
|
257
|
+
const importedBus = CdkEventBus.fromEventBusArn(scope, `${id}Bus`, arn);
|
|
258
|
+
return new EventBusMessaging(scope, id, {
|
|
259
|
+
type: "eventBus",
|
|
260
|
+
importedBus,
|
|
261
|
+
awsServiceBus: true
|
|
262
|
+
});
|
|
263
|
+
}
|
|
168
264
|
}
|
|
169
265
|
/**
|
|
170
266
|
* Factory for creating messaging resources.
|
|
@@ -209,10 +305,8 @@ export class MessagingFactory {
|
|
|
209
305
|
return (_app, scope) => {
|
|
210
306
|
switch (props.type) {
|
|
211
307
|
case "queue":
|
|
212
|
-
validateSQSProps(props);
|
|
213
308
|
return new QueueMessaging(scope, id, props);
|
|
214
309
|
case "topic":
|
|
215
|
-
validateTopicProps(props);
|
|
216
310
|
return new TopicMessaging(scope, id, props);
|
|
217
311
|
case "eventBus":
|
|
218
312
|
return new EventBusMessaging(scope, id, props);
|
|
@@ -224,5 +318,6 @@ export class MessagingFactory {
|
|
|
224
318
|
};
|
|
225
319
|
}
|
|
226
320
|
}
|
|
227
|
-
//
|
|
321
|
+
// Canonical D18(b) re-export — pinned by subscription.test.ts integrity suite.
|
|
322
|
+
export { EventField, RuleTargetInput } from "aws-cdk-lib/aws-events";
|
|
228
323
|
export { isQueueMessaging, isTopicMessaging, isEventBusMessaging } from "./interfaces/messaging.js";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Construct } from "constructs";
|
|
2
|
-
import { Vpc } from "../../resources/aws/networking/vpc.js";
|
|
2
|
+
import { DEFAULT_MAX_AZS, Vpc } from "../../resources/aws/networking/vpc.js";
|
|
3
3
|
import { FjallLogger } from "../../utils/validationLogger.js";
|
|
4
4
|
function validateNetworkProps(props) {
|
|
5
5
|
if (props.maxAzs !== undefined && (props.maxAzs < 1 || props.maxAzs > 3)) {
|
|
@@ -8,7 +8,7 @@ function validateNetworkProps(props) {
|
|
|
8
8
|
if (typeof props.natGateways === "object" &&
|
|
9
9
|
props.natGateways !== null &&
|
|
10
10
|
props.natGateways.count !== undefined) {
|
|
11
|
-
const maxAzs = props.maxAzs ??
|
|
11
|
+
const maxAzs = props.maxAzs ?? DEFAULT_MAX_AZS;
|
|
12
12
|
if (props.natGateways.count > maxAzs) {
|
|
13
13
|
FjallLogger.warn(`'natGateways.count' (${props.natGateways.count}) exceeds maxAzs (${maxAzs}).`);
|
|
14
14
|
}
|
|
@@ -20,7 +20,6 @@ function validateNetworkProps(props) {
|
|
|
20
20
|
export class NetworkFactory {
|
|
21
21
|
static build(id, props) {
|
|
22
22
|
return (app, scope) => {
|
|
23
|
-
validateNetworkProps(props);
|
|
24
23
|
return new Network(scope, id, props, app);
|
|
25
24
|
};
|
|
26
25
|
}
|
|
@@ -29,15 +28,25 @@ export class Network extends Construct {
|
|
|
29
28
|
vpc;
|
|
30
29
|
constructor(scope, id, props, app) {
|
|
31
30
|
super(scope, id);
|
|
31
|
+
validateNetworkProps(props);
|
|
32
32
|
this.vpc = this.createVpc(id, props, app);
|
|
33
33
|
}
|
|
34
34
|
createVpc(id, props, app) {
|
|
35
35
|
const rawAccountId = app.node.tryGetContext("accountId");
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
|
|
36
|
+
const guardedCtxAccountId = typeof rawAccountId === "string" && rawAccountId !== ""
|
|
37
|
+
? rawAccountId
|
|
38
|
+
: undefined;
|
|
39
|
+
const envAccountId = process.env.CDK_DEFAULT_ACCOUNT;
|
|
40
|
+
const accountId = guardedCtxAccountId ??
|
|
41
|
+
(envAccountId !== undefined && envAccountId !== ""
|
|
42
|
+
? envAccountId
|
|
43
|
+
: undefined);
|
|
44
|
+
const envRegion = process.env.CDK_DEFAULT_REGION;
|
|
45
|
+
const region = envRegion !== undefined && envRegion !== "" ? envRegion : undefined;
|
|
39
46
|
const rawIpamPoolId = app.node.tryGetContext("ipamPoolId");
|
|
40
|
-
const ipv4IpamPoolId = typeof rawIpamPoolId === "string"
|
|
47
|
+
const ipv4IpamPoolId = typeof rawIpamPoolId === "string" && rawIpamPoolId !== ""
|
|
48
|
+
? rawIpamPoolId
|
|
49
|
+
: undefined;
|
|
41
50
|
const vpcProps = {
|
|
42
51
|
vpcName: props.vpcName,
|
|
43
52
|
accountId,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type Environment, Stack, type StackProps } from "aws-cdk-lib";
|
|
2
|
+
import { type CustomPermissionSets } from "../../config/aws/identityCenter.js";
|
|
2
3
|
import { ScpPreset } from "../../config/aws/scpPreset.js";
|
|
3
4
|
import type { ScpPresetProps } from "../../config/aws/scpPreset.js";
|
|
4
5
|
import { OrganisationResource } from "../../resources/aws/organisation/index.js";
|
|
@@ -32,11 +33,14 @@ export declare class Organisation extends Stack {
|
|
|
32
33
|
private accountRefs;
|
|
33
34
|
private accountMap;
|
|
34
35
|
private accountsConfig;
|
|
36
|
+
private identityCenter?;
|
|
35
37
|
constructor(id: string, props: OrganisationProps);
|
|
36
38
|
private createAccountReferences;
|
|
37
39
|
private setupOrganisationFeatures;
|
|
38
40
|
private setupCostAllocationTagActivator;
|
|
39
41
|
private setupIdentityCenter;
|
|
42
|
+
declarePermissionSets(customs: CustomPermissionSets): void;
|
|
43
|
+
assignGroupMembers(members: Record<string, string[]>): void;
|
|
40
44
|
getOrganisation(): OrganisationResource;
|
|
41
45
|
getAccounts(): Record<string, string>;
|
|
42
46
|
enableScps(props: ScpPresetProps): ScpPreset;
|
|
@@ -23,6 +23,7 @@ export class Organisation extends Stack {
|
|
|
23
23
|
accountRefs = [];
|
|
24
24
|
accountMap;
|
|
25
25
|
accountsConfig;
|
|
26
|
+
identityCenter;
|
|
26
27
|
constructor(id, props) {
|
|
27
28
|
const config = getConfig();
|
|
28
29
|
const env = props.env ?? {
|
|
@@ -42,8 +43,9 @@ export class Organisation extends Stack {
|
|
|
42
43
|
}
|
|
43
44
|
this.accountsConfig = props.accounts;
|
|
44
45
|
const orgId = this.node.tryGetContext(CDK_CONTEXT_KEYS.ORG_ID);
|
|
45
|
-
const rootId = this.node.tryGetContext(
|
|
46
|
-
const managementAccountId = this.node.tryGetContext(
|
|
46
|
+
const rootId = this.node.tryGetContext(CDK_CONTEXT_KEYS.ROOT_ID);
|
|
47
|
+
const managementAccountId = this.node.tryGetContext(CDK_CONTEXT_KEYS.MANAGEMENT_ACCOUNT_ID) ??
|
|
48
|
+
this.account;
|
|
47
49
|
if (!orgId || !rootId) {
|
|
48
50
|
throw new Error("orgId and rootId must be provided via CDK context. Run deployment through the Fjall CLI.");
|
|
49
51
|
}
|
|
@@ -76,7 +78,11 @@ export class Organisation extends Stack {
|
|
|
76
78
|
this.setupCostAllocationTagActivator();
|
|
77
79
|
}
|
|
78
80
|
setupCostAllocationTagActivator() {
|
|
79
|
-
new CostAllocationTagActivator(this, "CostAllocationTagActivator");
|
|
81
|
+
const activator = new CostAllocationTagActivator(this, "CostAllocationTagActivator");
|
|
82
|
+
App.getInstance().addSchedule("CostAllocationTagActivatorSchedule", {
|
|
83
|
+
schedule: "rate(24 hours)",
|
|
84
|
+
target: activator
|
|
85
|
+
});
|
|
80
86
|
}
|
|
81
87
|
setupIdentityCenter(identityCenter, managementAccountId) {
|
|
82
88
|
if (identityCenter) {
|
|
@@ -88,11 +94,23 @@ export class Organisation extends Stack {
|
|
|
88
94
|
ssoAccounts[name] = id;
|
|
89
95
|
}
|
|
90
96
|
}
|
|
91
|
-
new IdentityCenter(this, "IdentityCenter", {
|
|
97
|
+
this.identityCenter = new IdentityCenter(this, "IdentityCenter", {
|
|
92
98
|
accounts: ssoAccounts
|
|
93
99
|
});
|
|
94
100
|
}
|
|
95
101
|
}
|
|
102
|
+
declarePermissionSets(customs) {
|
|
103
|
+
if (this.identityCenter === undefined) {
|
|
104
|
+
throw new Error("Identity Center is not enabled. Pass identityCenter: true to OrganisationFactory.");
|
|
105
|
+
}
|
|
106
|
+
this.identityCenter.declarePermissionSets(customs);
|
|
107
|
+
}
|
|
108
|
+
assignGroupMembers(members) {
|
|
109
|
+
if (this.identityCenter === undefined) {
|
|
110
|
+
throw new Error("Identity Center is not enabled. Pass identityCenter: true to OrganisationFactory.");
|
|
111
|
+
}
|
|
112
|
+
this.identityCenter.assignGroupMembers(members);
|
|
113
|
+
}
|
|
96
114
|
getOrganisation() {
|
|
97
115
|
return this.org;
|
|
98
116
|
}
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import { Construct } from "constructs";
|
|
2
2
|
import { type IBucket, type EventType, type IBucketNotificationDestination, type NotificationKeyFilter } from "aws-cdk-lib/aws-s3";
|
|
3
|
-
import { BucketDeployment } from "aws-cdk-lib/aws-s3-deployment";
|
|
4
3
|
import { type IGrantable, type Grant } from "aws-cdk-lib/aws-iam";
|
|
5
4
|
import type App from "../../app.js";
|
|
6
|
-
import { type WebsiteHostingConfig } from "../../resources/aws/storage/index.js";
|
|
5
|
+
import { BucketDeployment, type WebsiteHostingConfig } from "../../resources/aws/storage/index.js";
|
|
7
6
|
import { type BackupTier } from "../../utils/backupTierMapping.js";
|
|
8
7
|
import { type IStorage } from "./interfaces/storage.js";
|
|
9
8
|
import { type IStorageConnector } from "./interfaces/connector.js";
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { Construct } from "constructs";
|
|
2
2
|
import { BucketEncryption, HttpMethods } from "aws-cdk-lib/aws-s3";
|
|
3
|
-
import {
|
|
3
|
+
import { Source, CacheControl } from "aws-cdk-lib/aws-s3-deployment";
|
|
4
4
|
import { Key } from "aws-cdk-lib/aws-kms";
|
|
5
5
|
import { CfnOutput, Duration, RemovalPolicy, Stack } from "aws-cdk-lib";
|
|
6
|
-
import { S3Bucket } from "../../resources/aws/storage/index.js";
|
|
6
|
+
import { BucketDeployment, S3Bucket } from "../../resources/aws/storage/index.js";
|
|
7
7
|
import { FjallLogger } from "../../utils/validationLogger.js";
|
|
8
8
|
export { isStorage } from "./interfaces/storage.js";
|
|
9
9
|
function toBucketEncryption(encryption) {
|
|
@@ -50,6 +50,7 @@ export class Storage extends Construct {
|
|
|
50
50
|
bucketDeployment;
|
|
51
51
|
constructor(scope, id, props) {
|
|
52
52
|
super(scope, id);
|
|
53
|
+
validateStorageProps(props);
|
|
53
54
|
const encryptionKey = props.kmsKeyArn
|
|
54
55
|
? Key.fromKeyArn(this, `${id}KmsKey`, props.kmsKeyArn)
|
|
55
56
|
: undefined;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { Stack } from "aws-cdk-lib";
|
|
1
2
|
import { StringListParameter, StringParameter } from "aws-cdk-lib/aws-ssm";
|
|
2
3
|
import { buildSsmPrefix, DEFAULT_ORG_ID, resolveOrgId } from "../../utils/cdkContext.js";
|
|
3
4
|
import { VpcPeeringConnection } from "../../resources/aws/networking/vpcPeeringConnection.js";
|
|
@@ -16,13 +17,14 @@ export class VpcPeerFactory {
|
|
|
16
17
|
const peerRouteTableIds = props.peerRouteTableIds ??
|
|
17
18
|
StringListParameter.valueForTypedListParameter(scope, `${ssmPrefix}/route-table-ids`);
|
|
18
19
|
const resolvedPeerOrgId = orgId !== DEFAULT_ORG_ID ? orgId : undefined;
|
|
20
|
+
const peerRegion = props.peerRegion ?? Stack.of(scope).region;
|
|
19
21
|
return new VpcPeeringConnection(scope, id, {
|
|
20
22
|
localVpc,
|
|
21
23
|
localVpcCidr: localVpc.vpcCidrBlock,
|
|
22
24
|
peerVpcId,
|
|
23
25
|
peerVpcCidr,
|
|
24
26
|
peerAccountId: props.peerAccountId,
|
|
25
|
-
peerRegion
|
|
27
|
+
peerRegion,
|
|
26
28
|
peerRoleArn,
|
|
27
29
|
peerRouteTableIds,
|
|
28
30
|
enableDnsResolution: props.enableDnsResolution,
|
|
@@ -5,7 +5,7 @@ import { InstanceType, SubnetType, Connections, Port, UserData } from "aws-cdk-l
|
|
|
5
5
|
import { AutoScalingGroup, Monitoring, BlockDeviceVolume, EbsDeviceVolumeType } from "aws-cdk-lib/aws-autoscaling";
|
|
6
6
|
import { Duration, Stack } from "aws-cdk-lib";
|
|
7
7
|
import { Construct } from "constructs";
|
|
8
|
-
import { RetentionDays } from "aws-cdk-lib/aws-logs";
|
|
8
|
+
import { LogGroup, RetentionDays } from "aws-cdk-lib/aws-logs";
|
|
9
9
|
import { S3Bucket } from "../storage/s3.js";
|
|
10
10
|
import { Secret } from "../secrets/secret.js";
|
|
11
11
|
import { vpcHasNatGateways } from "../../../utils/vpcUtils.js";
|
|
@@ -238,7 +238,10 @@ export default class ClickHouse extends Construct {
|
|
|
238
238
|
});
|
|
239
239
|
// 11. Scheduled weekly backup to S3
|
|
240
240
|
const backupDestUrl = `https://${backupBucket.bucketName}.s3.${Stack.of(this).region}.amazonaws.com/`;
|
|
241
|
-
const
|
|
241
|
+
const backupTaskLogGroup = new LogGroup(this, "ClickHouseBackupTaskLogGroup", {
|
|
242
|
+
retention: RetentionDays.TWO_WEEKS
|
|
243
|
+
});
|
|
244
|
+
new ScheduledEc2Task(this, "ClickHouseBackupTask", {
|
|
242
245
|
cluster,
|
|
243
246
|
schedule: Schedule.expression(BACKUP_SCHEDULE),
|
|
244
247
|
scheduledEc2TaskImageOptions: {
|
|
@@ -255,7 +258,7 @@ export default class ClickHouse extends Construct {
|
|
|
255
258
|
},
|
|
256
259
|
logDriver: LogDriver.awsLogs({
|
|
257
260
|
streamPrefix: "clickhouse-backup",
|
|
258
|
-
|
|
261
|
+
logGroup: backupTaskLogGroup
|
|
259
262
|
})
|
|
260
263
|
},
|
|
261
264
|
securityGroups: [securityGroup],
|
|
@@ -263,9 +266,11 @@ export default class ClickHouse extends Construct {
|
|
|
263
266
|
subnetType
|
|
264
267
|
}
|
|
265
268
|
});
|
|
266
|
-
//
|
|
267
|
-
|
|
268
|
-
//
|
|
269
|
+
// BACKUP DATABASE TO S3 runs inside the ClickHouse server process on the
|
|
270
|
+
// ASG instance, not the ephemeral backup task; the grant must therefore
|
|
271
|
+
// attach to the ASG instance role, not the task role.
|
|
272
|
+
backupBucket.grantReadWrite(asg.role);
|
|
273
|
+
// 12. Grant secret read to execution role
|
|
269
274
|
const executionRole = taskDefinition.executionRole;
|
|
270
275
|
if (!executionRole) {
|
|
271
276
|
throw new Error("ClickHouse task definition has no execution role — cannot grant secret access");
|
|
@@ -274,15 +279,19 @@ export default class ClickHouse extends Construct {
|
|
|
274
279
|
auditPasswordSecret.secret.grantRead(executionRole);
|
|
275
280
|
backupPasswordSecret.secret.grantRead(executionRole);
|
|
276
281
|
schemaPasswordSecret.secret.grantRead(executionRole);
|
|
277
|
-
// 14. CloudWatch alarms (CPU, memory, disk) — wired only when an SNS topic is supplied
|
|
278
282
|
if (props.alarmTopic) {
|
|
283
|
+
if (!props.webappLogGroup) {
|
|
284
|
+
throw new Error("ClickHouse: alarmTopic requires webappLogGroup so the stuck-merge metric filter can be wired.");
|
|
285
|
+
}
|
|
279
286
|
createClickHouseAlarms({
|
|
280
287
|
scope: this,
|
|
281
288
|
asg,
|
|
282
|
-
alarmTopic: props.alarmTopic
|
|
289
|
+
alarmTopic: props.alarmTopic,
|
|
290
|
+
webappLogGroup: props.webappLogGroup,
|
|
291
|
+
backupTaskLogGroup
|
|
283
292
|
});
|
|
284
293
|
}
|
|
285
|
-
//
|
|
294
|
+
// 13. Connections and outputs
|
|
286
295
|
this.connections = new Connections({
|
|
287
296
|
securityGroups: [securityGroup],
|
|
288
297
|
defaultPort: Port.tcp(CLICKHOUSE_HTTP_PORT)
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Alarm } from "aws-cdk-lib/aws-cloudwatch";
|
|
2
2
|
import type { AutoScalingGroup } from "aws-cdk-lib/aws-autoscaling";
|
|
3
3
|
import type { ITopic } from "aws-cdk-lib/aws-sns";
|
|
4
|
+
import type { ILogGroup } from "aws-cdk-lib/aws-logs";
|
|
4
5
|
import type { Construct } from "constructs";
|
|
5
6
|
export interface ClickHouseAlarmThresholds {
|
|
6
7
|
/** EC2 host CPU % over 5 min. Default 90. */
|
|
@@ -16,19 +17,33 @@ export interface ClickHouseAlarmsProps {
|
|
|
16
17
|
scope: Construct;
|
|
17
18
|
asg: AutoScalingGroup;
|
|
18
19
|
alarmTopic: ITopic;
|
|
20
|
+
/**
|
|
21
|
+
* Webapp log group. Required to wire the stuck-merge alarm — `client.ts`
|
|
22
|
+
* emits `serverLogger.warn("ClickHouse", "Stuck merge detected")` when
|
|
23
|
+
* `system.merges` shows a merge elapsed > 30 min.
|
|
24
|
+
*/
|
|
25
|
+
webappLogGroup: ILogGroup;
|
|
26
|
+
/**
|
|
27
|
+
* Backup-task log group. Required to wire the backup-failure alarm —
|
|
28
|
+
* `BACKUP DATABASE … TO S3(…)` emits `AccessDenied` / `S3Exception` lines
|
|
29
|
+
* when the IAM grant or bucket policy is misconfigured (silent before the
|
|
30
|
+
* alarm landed; the daily backup task exited non-zero with no signal).
|
|
31
|
+
*/
|
|
32
|
+
backupTaskLogGroup: ILogGroup;
|
|
19
33
|
config?: ClickHouseAlarmThresholds;
|
|
20
34
|
}
|
|
21
35
|
/**
|
|
22
36
|
* Single-node ClickHouse posture alarms. Covers host-level CPU + (optional)
|
|
23
|
-
* memory and disk via the CloudWatch Agent metric namespace `CWAgent
|
|
37
|
+
* memory and disk via the CloudWatch Agent metric namespace `CWAgent`, plus
|
|
38
|
+
* two log-driven alarms:
|
|
24
39
|
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
40
|
+
* - **Stuck merges** — `client.ts` polls `system.merges` every 5 min and logs
|
|
41
|
+
* `serverLogger.warn("ClickHouse", "Stuck merge detected")` when elapsed
|
|
42
|
+
* exceeds 30 min. The metric filter on the webapp log group emits a count
|
|
43
|
+
* metric per match; the alarm fires on Sum >= 1 over 5 min × 2 evaluations.
|
|
44
|
+
* - **Backup failures** — `AccessDenied` or `S3Exception` from the backup
|
|
45
|
+
* task's BACKUP DATABASE TO S3 statement. Closes the silent-failure mode
|
|
46
|
+
* that masked the original IAM-grant misconfiguration (see
|
|
47
|
+
* `designs/2026-04-27-clickhouse-backup-iam-role.md`).
|
|
33
48
|
*/
|
|
34
49
|
export declare function createClickHouseAlarms(props: ClickHouseAlarmsProps): Alarm[];
|
|
@@ -2,22 +2,25 @@ import { Duration } from "aws-cdk-lib";
|
|
|
2
2
|
import { Alarm, ComparisonOperator, TreatMissingData } from "aws-cdk-lib/aws-cloudwatch";
|
|
3
3
|
import { SnsAction } from "aws-cdk-lib/aws-cloudwatch-actions";
|
|
4
4
|
import { Metric } from "aws-cdk-lib/aws-cloudwatch";
|
|
5
|
+
import { FilterPattern, MetricFilter } from "aws-cdk-lib/aws-logs";
|
|
5
6
|
import { ALARM_DEFAULTS, registerAlarm, buildAlarmDescription } from "../monitoring/alarmDefaults.js";
|
|
7
|
+
const CLICKHOUSE_METRIC_NAMESPACE = "Fjall/ClickHouse";
|
|
6
8
|
/**
|
|
7
9
|
* Single-node ClickHouse posture alarms. Covers host-level CPU + (optional)
|
|
8
|
-
* memory and disk via the CloudWatch Agent metric namespace `CWAgent
|
|
10
|
+
* memory and disk via the CloudWatch Agent metric namespace `CWAgent`, plus
|
|
11
|
+
* two log-driven alarms:
|
|
9
12
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
13
|
+
* - **Stuck merges** — `client.ts` polls `system.merges` every 5 min and logs
|
|
14
|
+
* `serverLogger.warn("ClickHouse", "Stuck merge detected")` when elapsed
|
|
15
|
+
* exceeds 30 min. The metric filter on the webapp log group emits a count
|
|
16
|
+
* metric per match; the alarm fires on Sum >= 1 over 5 min × 2 evaluations.
|
|
17
|
+
* - **Backup failures** — `AccessDenied` or `S3Exception` from the backup
|
|
18
|
+
* task's BACKUP DATABASE TO S3 statement. Closes the silent-failure mode
|
|
19
|
+
* that masked the original IAM-grant misconfiguration (see
|
|
20
|
+
* `designs/2026-04-27-clickhouse-backup-iam-role.md`).
|
|
18
21
|
*/
|
|
19
22
|
export function createClickHouseAlarms(props) {
|
|
20
|
-
const { scope, asg, alarmTopic, config = {} } = props;
|
|
23
|
+
const { scope, asg, alarmTopic, webappLogGroup, backupTaskLogGroup, config = {} } = props;
|
|
21
24
|
const alarms = [];
|
|
22
25
|
const snsAction = new SnsAction(alarmTopic);
|
|
23
26
|
const asgName = asg.autoScalingGroupName;
|
|
@@ -85,5 +88,53 @@ export function createClickHouseAlarms(props) {
|
|
|
85
88
|
treatMissingData: TreatMissingData.NOT_BREACHING
|
|
86
89
|
});
|
|
87
90
|
registerAlarm(diskCriticalAlarm, snsAction, alarms);
|
|
91
|
+
const stuckMergeMetricName = "ClickHouseStuckMergeCount";
|
|
92
|
+
new MetricFilter(scope, "ClickHouseStuckMergeMetricFilter", {
|
|
93
|
+
logGroup: webappLogGroup,
|
|
94
|
+
metricNamespace: CLICKHOUSE_METRIC_NAMESPACE,
|
|
95
|
+
metricName: stuckMergeMetricName,
|
|
96
|
+
filterPattern: FilterPattern.literal('"Stuck merge detected"'),
|
|
97
|
+
metricValue: "1",
|
|
98
|
+
defaultValue: 0
|
|
99
|
+
});
|
|
100
|
+
const stuckMergeAlarm = new Alarm(scope, "ClickHouseStuckMergeAlarm", {
|
|
101
|
+
alarmDescription: buildAlarmDescription("ClickHouse merge stuck > 30 min — investigate parts pressure or replica health", undefined),
|
|
102
|
+
metric: new Metric({
|
|
103
|
+
namespace: CLICKHOUSE_METRIC_NAMESPACE,
|
|
104
|
+
metricName: stuckMergeMetricName,
|
|
105
|
+
period: Duration.minutes(5),
|
|
106
|
+
statistic: "Sum"
|
|
107
|
+
}),
|
|
108
|
+
threshold: 1,
|
|
109
|
+
evaluationPeriods: 2,
|
|
110
|
+
datapointsToAlarm: 2,
|
|
111
|
+
comparisonOperator: ComparisonOperator.GREATER_THAN_OR_EQUAL_TO_THRESHOLD,
|
|
112
|
+
treatMissingData: TreatMissingData.NOT_BREACHING
|
|
113
|
+
});
|
|
114
|
+
registerAlarm(stuckMergeAlarm, snsAction, alarms);
|
|
115
|
+
const backupFailureMetricName = "ClickHouseBackupFailureCount";
|
|
116
|
+
new MetricFilter(scope, "ClickHouseBackupFailureMetricFilter", {
|
|
117
|
+
logGroup: backupTaskLogGroup,
|
|
118
|
+
metricNamespace: CLICKHOUSE_METRIC_NAMESPACE,
|
|
119
|
+
metricName: backupFailureMetricName,
|
|
120
|
+
filterPattern: FilterPattern.anyTerm("AccessDenied", "S3Exception"),
|
|
121
|
+
metricValue: "1",
|
|
122
|
+
defaultValue: 0
|
|
123
|
+
});
|
|
124
|
+
const backupFailureAlarm = new Alarm(scope, "ClickHouseBackupFailureAlarm", {
|
|
125
|
+
alarmDescription: buildAlarmDescription("ClickHouse BACKUP TO S3 emitted AccessDenied/S3Exception — verify ASG instance role grant on backup bucket", undefined),
|
|
126
|
+
metric: new Metric({
|
|
127
|
+
namespace: CLICKHOUSE_METRIC_NAMESPACE,
|
|
128
|
+
metricName: backupFailureMetricName,
|
|
129
|
+
period: Duration.hours(1),
|
|
130
|
+
statistic: "Sum"
|
|
131
|
+
}),
|
|
132
|
+
threshold: 1,
|
|
133
|
+
evaluationPeriods: 1,
|
|
134
|
+
datapointsToAlarm: 1,
|
|
135
|
+
comparisonOperator: ComparisonOperator.GREATER_THAN_OR_EQUAL_TO_THRESHOLD,
|
|
136
|
+
treatMissingData: TreatMissingData.NOT_BREACHING
|
|
137
|
+
});
|
|
138
|
+
registerAlarm(backupFailureAlarm, snsAction, alarms);
|
|
88
139
|
return alarms;
|
|
89
140
|
}
|
|
@@ -64,10 +64,10 @@ export declare const OPTIMISE_MV_TABLES: readonly ["metrics_hourly_mv", "metrics
|
|
|
64
64
|
/** Resource allocation for the lightweight optimise task. */
|
|
65
65
|
export declare const OPTIMISE_TASK_MEMORY_MIB = 256;
|
|
66
66
|
export declare const OPTIMISE_TASK_CPU_UNITS = 256;
|
|
67
|
-
/** Automated backup schedule (
|
|
68
|
-
export declare const BACKUP_SCHEDULE = "cron(0 3
|
|
67
|
+
/** Automated backup schedule (daily 03:00 UTC — low-traffic window). */
|
|
68
|
+
export declare const BACKUP_SCHEDULE = "cron(0 3 * * ? *)";
|
|
69
69
|
/** Resource allocation for the backup task (lightweight — clickhouse-client only). */
|
|
70
70
|
export declare const BACKUP_TASK_MEMORY_MIB = 256;
|
|
71
71
|
export declare const BACKUP_TASK_CPU_UNITS = 256;
|
|
72
|
-
/** Backup object expiration: 14 days (retains
|
|
72
|
+
/** Backup object expiration: 14 days (retains 14 daily snapshots). */
|
|
73
73
|
export declare const BACKUP_RETENTION_DAYS = 14;
|
|
@@ -80,10 +80,10 @@ export const OPTIMISE_MV_TABLES = [
|
|
|
80
80
|
/** Resource allocation for the lightweight optimise task. */
|
|
81
81
|
export const OPTIMISE_TASK_MEMORY_MIB = 256;
|
|
82
82
|
export const OPTIMISE_TASK_CPU_UNITS = 256;
|
|
83
|
-
/** Automated backup schedule (
|
|
84
|
-
export const BACKUP_SCHEDULE = "cron(0 3
|
|
83
|
+
/** Automated backup schedule (daily 03:00 UTC — low-traffic window). */
|
|
84
|
+
export const BACKUP_SCHEDULE = "cron(0 3 * * ? *)";
|
|
85
85
|
/** Resource allocation for the backup task (lightweight — clickhouse-client only). */
|
|
86
86
|
export const BACKUP_TASK_MEMORY_MIB = 256;
|
|
87
87
|
export const BACKUP_TASK_CPU_UNITS = 256;
|
|
88
|
-
/** Backup object expiration: 14 days (retains
|
|
88
|
+
/** Backup object expiration: 14 days (retains 14 daily snapshots). */
|
|
89
89
|
export const BACKUP_RETENTION_DAYS = 14;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { IVpc, ISecurityGroup } from "aws-cdk-lib/aws-ec2";
|
|
2
|
+
import type { ILogGroup } from "aws-cdk-lib/aws-logs";
|
|
2
3
|
import type { IBucket } from "aws-cdk-lib/aws-s3";
|
|
3
4
|
import type { ISecret } from "aws-cdk-lib/aws-secretsmanager";
|
|
4
5
|
import type { ITopic } from "aws-cdk-lib/aws-sns";
|
|
@@ -23,10 +24,15 @@ export interface ClickHouseProps {
|
|
|
23
24
|
*/
|
|
24
25
|
r2Config?: ClickHouseR2Config;
|
|
25
26
|
/**
|
|
26
|
-
* SNS topic for CloudWatch alarms (CPU, memory, disk).
|
|
27
|
+
* SNS topic for CloudWatch alarms (CPU, memory, disk, stuck merges).
|
|
27
28
|
* If omitted, posture alarms are not created.
|
|
28
29
|
*/
|
|
29
30
|
alarmTopic?: ITopic;
|
|
31
|
+
/**
|
|
32
|
+
* Webapp log group, required when `alarmTopic` is set so the stuck-merge
|
|
33
|
+
* metric filter can read the structured warning emitted by `client.ts`.
|
|
34
|
+
*/
|
|
35
|
+
webappLogGroup?: ILogGroup;
|
|
30
36
|
}
|
|
31
37
|
/** Cloudflare R2 configuration for tiered storage and backups. */
|
|
32
38
|
export interface ClickHouseR2Config {
|