@fjall/components-infrastructure 3.7.0 → 3.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/lib/config/aws/alarmTopic.d.ts +17 -1
- package/dist/lib/config/aws/alarmTopic.js +8 -1
- package/dist/lib/patterns/aws/account.d.ts +8 -0
- package/dist/lib/patterns/aws/account.js +5 -1
- package/dist/lib/patterns/aws/database.js +4 -1
- package/dist/lib/patterns/aws/devSubstrate.js +10 -3
- package/dist/lib/patterns/aws/devSubstrate.waker.source.cjs +5 -1
- package/dist/lib/patterns/aws/messaging.d.ts +12 -0
- package/dist/lib/patterns/aws/messaging.js +6 -1
- package/dist/lib/resources/aws/compute/persistentDataVolume.d.ts +2 -1
- package/dist/lib/resources/aws/compute/persistentDataVolume.js +2 -1
- package/dist/lib/resources/aws/messaging/eventBridgeRule.js +28 -8
- package/dist/lib/resources/aws/messaging/sqs.d.ts +16 -0
- package/dist/lib/resources/aws/messaging/sqs.js +11 -0
- package/dist/lib/resources/aws/monitoring/alarmDefaults.d.ts +3 -0
- package/dist/lib/resources/aws/monitoring/alarmDefaults.js +2 -1
- package/dist/lib/resources/aws/monitoring/index.d.ts +1 -0
- package/dist/lib/resources/aws/monitoring/index.js +1 -0
- package/dist/lib/resources/aws/monitoring/scheduleAlarms.d.ts +6 -0
- package/dist/lib/resources/aws/monitoring/sqsAlarms.d.ts +23 -0
- package/dist/lib/resources/aws/monitoring/sqsAlarms.js +29 -0
- package/dist/lib/resources/aws/storage/ecr.d.ts +4 -1
- package/dist/lib/resources/aws/storage/ecr.js +1 -1
- package/package.json +4 -4
|
@@ -8,8 +8,24 @@ import { Construct } from "constructs";
|
|
|
8
8
|
* producer.
|
|
9
9
|
*/
|
|
10
10
|
export declare const SHARED_ALARM_TOPIC_EXPORT_NAME = "SharedAlarmTopicArn";
|
|
11
|
+
export interface SharedAlarmTopicProps {
|
|
12
|
+
/**
|
|
13
|
+
* Delivery endpoints for alarm notifications. Without at least one
|
|
14
|
+
* subscription the topic receives every ALARM/OK transition and drops it —
|
|
15
|
+
* alarms exist but page nobody.
|
|
16
|
+
*
|
|
17
|
+
* `emails` require the recipient to click SNS's confirmation email before
|
|
18
|
+
* delivery starts. `httpsEndpoints` must speak the SNS webhook protocol
|
|
19
|
+
* (SubscriptionConfirmation handshake); the Fjall webapp ingest at
|
|
20
|
+
* `/api/internal/alarm-webhook` does.
|
|
21
|
+
*/
|
|
22
|
+
subscriptions?: {
|
|
23
|
+
emails?: string[];
|
|
24
|
+
httpsEndpoints?: string[];
|
|
25
|
+
};
|
|
26
|
+
}
|
|
11
27
|
export declare class SharedAlarmTopic extends Construct {
|
|
12
28
|
readonly topic: ITopic;
|
|
13
29
|
readonly topicArn: CfnOutput;
|
|
14
|
-
constructor(scope: Construct, id: string);
|
|
30
|
+
constructor(scope: Construct, id: string, props?: SharedAlarmTopicProps);
|
|
15
31
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { CfnOutput } from "aws-cdk-lib";
|
|
2
|
+
import { EmailSubscription, UrlSubscription } from "aws-cdk-lib/aws-sns-subscriptions";
|
|
2
3
|
import { Construct } from "constructs";
|
|
3
4
|
import { SNSTopic } from "../../resources/aws/messaging/sns.js";
|
|
4
5
|
/**
|
|
@@ -11,12 +12,18 @@ export const SHARED_ALARM_TOPIC_EXPORT_NAME = "SharedAlarmTopicArn";
|
|
|
11
12
|
export class SharedAlarmTopic extends Construct {
|
|
12
13
|
topic;
|
|
13
14
|
topicArn;
|
|
14
|
-
constructor(scope, id) {
|
|
15
|
+
constructor(scope, id, props) {
|
|
15
16
|
super(scope, id);
|
|
16
17
|
const wrapped = new SNSTopic(this, "AlarmNotifications", {
|
|
17
18
|
displayName: "Fjall CloudWatch Alarm Notifications"
|
|
18
19
|
});
|
|
19
20
|
this.topic = wrapped.getTopic();
|
|
21
|
+
for (const email of props?.subscriptions?.emails ?? []) {
|
|
22
|
+
this.topic.addSubscription(new EmailSubscription(email));
|
|
23
|
+
}
|
|
24
|
+
for (const endpoint of props?.subscriptions?.httpsEndpoints ?? []) {
|
|
25
|
+
this.topic.addSubscription(new UrlSubscription(endpoint));
|
|
26
|
+
}
|
|
20
27
|
// SharedAlarmTopicArn is a well-known cross-stack export consumed via
|
|
21
28
|
// Fn.importValue (see resolveAlertsTopic + its tests). Do not delete
|
|
22
29
|
// thinking it duplicates the wrapper's AlarmNotificationsTopicArn —
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Stack, type StackProps } from "aws-cdk-lib";
|
|
2
|
+
import { type SharedAlarmTopicProps } from "../../config/aws/index.js";
|
|
2
3
|
import { type Construct } from "constructs";
|
|
3
4
|
import { ConfigRulePreset } from "../../config/aws/configRulePreset.js";
|
|
4
5
|
import { GuardDutyDetector } from "../../config/aws/guardDutyDetector.js";
|
|
@@ -25,6 +26,13 @@ export interface AccountProps extends StackProps {
|
|
|
25
26
|
* persisted anywhere the deploy flow could read it).
|
|
26
27
|
*/
|
|
27
28
|
securityTier?: GovernancePreset;
|
|
29
|
+
/**
|
|
30
|
+
* Delivery endpoints for the account's shared alarm topic. Undefined ⇒
|
|
31
|
+
* the topic synthesises with no subscribers and every alarm transition is
|
|
32
|
+
* dropped. Sourced from the generated `account/infrastructure.ts`, same as
|
|
33
|
+
* `securityTier`.
|
|
34
|
+
*/
|
|
35
|
+
alarmSubscriptions?: SharedAlarmTopicProps["subscriptions"];
|
|
28
36
|
}
|
|
29
37
|
export declare class Account extends Stack {
|
|
30
38
|
readonly organisationType: OrganisationType;
|
|
@@ -48,7 +48,11 @@ export class Account extends Stack {
|
|
|
48
48
|
exportName: "AccountId",
|
|
49
49
|
description: "AWS Account ID for this account"
|
|
50
50
|
});
|
|
51
|
-
new SharedAlarmTopic(this, "AlarmTopic"
|
|
51
|
+
new SharedAlarmTopic(this, "AlarmTopic", {
|
|
52
|
+
...(props.alarmSubscriptions !== undefined && {
|
|
53
|
+
subscriptions: props.alarmSubscriptions
|
|
54
|
+
})
|
|
55
|
+
});
|
|
52
56
|
const isStandaloneAccount = this.constructor === Account;
|
|
53
57
|
const ipamPoolId = this.node.tryGetContext("ipamPoolId");
|
|
54
58
|
if (isStandaloneAccount && ipamPoolId && this.resolvedRegion) {
|
|
@@ -427,7 +427,10 @@ export class RelationalDatabase extends Construct {
|
|
|
427
427
|
databaseInsights: props.databaseInsights,
|
|
428
428
|
deletionProtection: props.deletionProtection,
|
|
429
429
|
snapshotIdentifier: props.snapshotIdentifier,
|
|
430
|
-
snapshotUsername: props.snapshotUsername
|
|
430
|
+
snapshotUsername: props.snapshotUsername,
|
|
431
|
+
alertsTopic: this.resolveAlertsTopic(props.alertsTopic),
|
|
432
|
+
alarms: props.alarms,
|
|
433
|
+
applicationId: props.applicationId
|
|
431
434
|
});
|
|
432
435
|
this.connections = this.database.connections;
|
|
433
436
|
this.addDatabaseOutputs(props);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { CfnOutput, Duration, Stack, Tags } from "aws-cdk-lib";
|
|
2
2
|
import { Port } from "aws-cdk-lib/aws-ec2";
|
|
3
3
|
import { Cluster } from "aws-cdk-lib/aws-ecs";
|
|
4
|
-
import { Repository } from "aws-cdk-lib/aws-ecr";
|
|
4
|
+
import { Repository, TagMutability } from "aws-cdk-lib/aws-ecr";
|
|
5
5
|
import { Effect, PolicyStatement } from "aws-cdk-lib/aws-iam";
|
|
6
6
|
import { Code, Runtime } from "aws-cdk-lib/aws-lambda";
|
|
7
7
|
import { PublicHostedZone } from "aws-cdk-lib/aws-route53";
|
|
@@ -238,10 +238,17 @@ export class DevSubstrate extends Construct {
|
|
|
238
238
|
// getDefaultContainerRegistry's `${stackPrefix}Ecr`.
|
|
239
239
|
const slotEcrId = `${toPascalCase(this.app.getName())}${DEV_SUBSTRATE_SLOT_ECR_ID}`;
|
|
240
240
|
const slotEcrRepositoryName = `${devFenceName(props.appKebab)}-slots`;
|
|
241
|
-
// Adopt-mode is an import only — no CFN resource, so it carries no fence tags
|
|
241
|
+
// Adopt-mode is an import only — no CFN resource, so it carries no fence tags
|
|
242
|
+
// (nor mutability convergence — a pre-BUG-22 adopted repo keeps IMMUTABLE
|
|
243
|
+
// until flipped out-of-band).
|
|
242
244
|
const slotRepository = props.adoptSlotEcr === true
|
|
243
245
|
? Repository.fromRepositoryName(this, slotEcrId, slotEcrRepositoryName)
|
|
244
|
-
: new Ecr(this, slotEcrId, {
|
|
246
|
+
: new Ecr(this, slotEcrId, {
|
|
247
|
+
repositoryName: slotEcrRepositoryName,
|
|
248
|
+
// Every `fjall dev up` repushes the branch tag; IMMUTABLE breaks the
|
|
249
|
+
// first same-branch recreate (BUG-22).
|
|
250
|
+
tagMutability: TagMutability.MUTABLE
|
|
251
|
+
});
|
|
245
252
|
return {
|
|
246
253
|
vpc,
|
|
247
254
|
slotSecurityGroup,
|
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
3
|
// Dev-substrate slot waker, invoked as an ALB Lambda target. Milestone
|
|
4
|
-
// placeholder
|
|
4
|
+
// placeholder - the real wake-token check + ECS/Aurora resume land in 5.4.
|
|
5
5
|
// ALB Lambda-target responses MUST carry all five fields; a missing
|
|
6
6
|
// statusDescription or isBase64Encoded 502s at the ALB.
|
|
7
|
+
// ASCII-ONLY FILE: this source ships inline as the template ZipFile, and
|
|
8
|
+
// CloudFormation GetTemplate lossily transcodes non-ASCII to "?" - a single
|
|
9
|
+
// non-ASCII byte here wedges recreate/forget surgery on the Compute stack
|
|
10
|
+
// (the flip proof reports Code as directly modified). Pinned by test.
|
|
7
11
|
exports.handler = async () => ({
|
|
8
12
|
statusCode: 200,
|
|
9
13
|
statusDescription: "200 OK",
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import { type Construct } from "constructs";
|
|
2
2
|
import { type IGrantable, type Grant } from "aws-cdk-lib/aws-iam";
|
|
3
3
|
import { type IEventBus } from "aws-cdk-lib/aws-events";
|
|
4
|
+
import type { ITopic } from "aws-cdk-lib/aws-sns";
|
|
4
5
|
import type App from "../../app.js";
|
|
5
6
|
import { SQSQueue } from "../../resources/aws/messaging/sqs.js";
|
|
6
7
|
import { SNSTopic, type SNSTopicProps } from "../../resources/aws/messaging/sns.js";
|
|
7
8
|
import { EventBridgeBus } from "../../resources/aws/messaging/eventbridge.js";
|
|
8
9
|
import { type RemovalPolicyString } from "../../resources/aws/messaging/utils.js";
|
|
9
10
|
import { Subscription, type SubscriptionProps } from "../../resources/aws/messaging/subscription.js";
|
|
11
|
+
import { type SqsAlarmThresholds } from "../../resources/aws/monitoring/index.js";
|
|
10
12
|
import { type IQueueMessaging, type ITopicMessaging, type IEventBusMessaging, type AnyMessaging } from "./interfaces/messaging.js";
|
|
11
13
|
import { type IQueueConnector } from "./interfaces/connector.js";
|
|
12
14
|
/**
|
|
@@ -31,6 +33,16 @@ export interface IQueueProps {
|
|
|
31
33
|
fifoThroughputLimit?: "perQueue" | "perMessageGroupId";
|
|
32
34
|
deduplicationScope?: "queue" | "messageGroup";
|
|
33
35
|
removalPolicy?: RemovalPolicyString;
|
|
36
|
+
/**
|
|
37
|
+
* SNS topic for the DLQ-depth alarm. Accepts ITopic, ARN string, or
|
|
38
|
+
* "import:ExportName" (production apps use "import:SharedAlarmTopicArn").
|
|
39
|
+
* Only queues with `deadLetterQueue.enabled` gain the alarm.
|
|
40
|
+
*/
|
|
41
|
+
alertsTopic?: ITopic | string;
|
|
42
|
+
/** Alarm thresholds. false to disable, undefined for defaults, object to override. */
|
|
43
|
+
alarms?: SqsAlarmThresholds | false;
|
|
44
|
+
/** Application ID for alarm tagging. */
|
|
45
|
+
applicationId?: string;
|
|
34
46
|
}
|
|
35
47
|
/**
|
|
36
48
|
* Topic messaging props with type discriminator. Extends SNSTopicProps so the
|
|
@@ -4,6 +4,7 @@ import { SNSTopic } from "../../resources/aws/messaging/sns.js";
|
|
|
4
4
|
import { EventBridgeBus } from "../../resources/aws/messaging/eventbridge.js";
|
|
5
5
|
import { Subscription } from "../../resources/aws/messaging/subscription.js";
|
|
6
6
|
import { FjallLogger } from "../../utils/validationLogger.js";
|
|
7
|
+
import { resolveAlertsTopic } from "../../utils/resolveAlertsTopic.js";
|
|
7
8
|
/**
|
|
8
9
|
* Validates SQS props and logs warnings for misconfigured options.
|
|
9
10
|
*/
|
|
@@ -88,7 +89,11 @@ export class QueueMessaging extends SQSQueue {
|
|
|
88
89
|
contentBasedDeduplication: props.contentBasedDeduplication,
|
|
89
90
|
fifoThroughputLimit: props.fifoThroughputLimit,
|
|
90
91
|
deduplicationScope: props.deduplicationScope,
|
|
91
|
-
removalPolicy: props.removalPolicy
|
|
92
|
+
removalPolicy: props.removalPolicy,
|
|
93
|
+
// Resolved in the parent scope — `this` does not exist before super().
|
|
94
|
+
alertsTopic: resolveAlertsTopic(scope, `${id}AlertsTopic`, props.alertsTopic),
|
|
95
|
+
alarms: props.alarms,
|
|
96
|
+
applicationId: props.applicationId
|
|
92
97
|
};
|
|
93
98
|
super(scope, id, queueProps);
|
|
94
99
|
}
|
|
@@ -39,7 +39,8 @@ export interface PersistentDataVolumeProps {
|
|
|
39
39
|
/**
|
|
40
40
|
* SNS topic `attachFailureAlarm` notifies. Omitted, the alarm still exists
|
|
41
41
|
* but fires no action — pass the stack's alerts topic or the failure is
|
|
42
|
-
* only visible to someone watching the CloudWatch console.
|
|
42
|
+
* only visible to someone watching the CloudWatch console. Also wires the
|
|
43
|
+
* re-attach queue's DLQ-depth alarm (created only when the topic is set).
|
|
43
44
|
*/
|
|
44
45
|
alarmTopic?: ITopic;
|
|
45
46
|
}
|
|
@@ -110,7 +110,8 @@ export class PersistentDataVolume extends Construct {
|
|
|
110
110
|
// Transient volume-attach signals — no durable state (the EBS volume
|
|
111
111
|
// itself is SNAPSHOT above). Pinned DESTROY (now also the SQSQueue wrapper
|
|
112
112
|
// default) so a replacing deploy reclaims this queue + DLQ, not orphans.
|
|
113
|
-
removalPolicy: "DESTROY"
|
|
113
|
+
removalPolicy: "DESTROY",
|
|
114
|
+
alertsTopic: props.alarmTopic
|
|
114
115
|
});
|
|
115
116
|
const sourcePath = path.resolve(__dirname, LAUNCHING_LAMBDA_SOURCE_FILE);
|
|
116
117
|
const source = readFileSync(sourcePath, "utf-8");
|
|
@@ -5,6 +5,7 @@ import { ServicePrincipal } from "aws-cdk-lib/aws-iam";
|
|
|
5
5
|
import { SQSQueue } from "./sqs.js";
|
|
6
6
|
import { resolveTarget } from "./eventTargets.js";
|
|
7
7
|
import { createScheduleAlarms } from "../monitoring/scheduleAlarms.js";
|
|
8
|
+
import { createSqsDlqAlarms } from "../monitoring/sqsAlarms.js";
|
|
8
9
|
/**
|
|
9
10
|
* Build the default description for a Schedule.
|
|
10
11
|
*/
|
|
@@ -31,7 +32,8 @@ export class EventBridgeRule extends Construct {
|
|
|
31
32
|
constructor(scope, id, props) {
|
|
32
33
|
super(scope, id);
|
|
33
34
|
this.id = id;
|
|
34
|
-
const
|
|
35
|
+
const dlqResolution = this.#resolveOrProvisionDlq(id, props.deadLetterQueue);
|
|
36
|
+
const dlq = dlqResolution?.queue;
|
|
35
37
|
if (dlq !== undefined)
|
|
36
38
|
this.deadLetterQueue = dlq;
|
|
37
39
|
grantKmsDecryptToEventBridge(dlq);
|
|
@@ -53,20 +55,24 @@ export class EventBridgeRule extends Construct {
|
|
|
53
55
|
crossStackScope: props.crossStackScope,
|
|
54
56
|
targets: [targetAdapter]
|
|
55
57
|
});
|
|
56
|
-
provisionAlarms(this, id, props, this.#rule,
|
|
58
|
+
provisionAlarms(this, id, props, this.#rule, dlqResolution);
|
|
57
59
|
}
|
|
58
60
|
#resolveOrProvisionDlq(id, prop) {
|
|
59
61
|
if (prop === undefined)
|
|
60
62
|
return undefined;
|
|
61
63
|
if (prop instanceof SQSQueue)
|
|
62
|
-
return prop;
|
|
64
|
+
return { queue: prop, provisioned: false };
|
|
63
65
|
if (prop.enabled === false)
|
|
64
66
|
return undefined;
|
|
65
|
-
if ("queue" in prop && prop.queue !== undefined)
|
|
66
|
-
return prop.queue;
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
67
|
+
if ("queue" in prop && prop.queue !== undefined) {
|
|
68
|
+
return { queue: prop.queue, provisioned: false };
|
|
69
|
+
}
|
|
70
|
+
return {
|
|
71
|
+
queue: new SQSQueue(this, `${id}Dlq`, {
|
|
72
|
+
queueType: "standard"
|
|
73
|
+
}),
|
|
74
|
+
provisioned: true
|
|
75
|
+
};
|
|
70
76
|
}
|
|
71
77
|
getRule() {
|
|
72
78
|
return this.#rule;
|
|
@@ -107,4 +113,18 @@ function provisionAlarms(scope, id, props, rule, dlq) {
|
|
|
107
113
|
applicationId: props.applicationId,
|
|
108
114
|
dlqEnabled: dlq !== undefined
|
|
109
115
|
});
|
|
116
|
+
// Depth alarm only on the rule-provisioned DLQ — a BYO/shared queue would
|
|
117
|
+
// collect one duplicate alarm per referencing rule; its owner alarms it.
|
|
118
|
+
if (dlq !== undefined && dlq.provisioned) {
|
|
119
|
+
createSqsDlqAlarms({
|
|
120
|
+
scope,
|
|
121
|
+
queueName: id,
|
|
122
|
+
deadLetterQueue: dlq.queue.getQueue(),
|
|
123
|
+
config: props.alarmConfig?.dlqDepthThreshold !== undefined
|
|
124
|
+
? { dlqDepthThreshold: props.alarmConfig.dlqDepthThreshold }
|
|
125
|
+
: {},
|
|
126
|
+
alarmTopic: props.alertsTopic,
|
|
127
|
+
applicationId: props.applicationId
|
|
128
|
+
});
|
|
129
|
+
}
|
|
110
130
|
}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { Construct } from "constructs";
|
|
2
2
|
import { type IQueue } from "aws-cdk-lib/aws-sqs";
|
|
3
3
|
import { type IGrantable, type Grant } from "aws-cdk-lib/aws-iam";
|
|
4
|
+
import type { ITopic } from "aws-cdk-lib/aws-sns";
|
|
4
5
|
import { type RemovalPolicyString } from "./utils.js";
|
|
6
|
+
import { type SqsAlarmThresholds } from "../monitoring/index.js";
|
|
5
7
|
/**
|
|
6
8
|
* AWS SQS service limits and defaults.
|
|
7
9
|
* @see https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-quotas.html
|
|
@@ -67,6 +69,20 @@ export interface SQSQueueProps {
|
|
|
67
69
|
* irreplaceable and must survive stack deletion.
|
|
68
70
|
*/
|
|
69
71
|
removalPolicy?: RemovalPolicyString;
|
|
72
|
+
/**
|
|
73
|
+
* SNS topic for the DLQ-depth alarm. When set (and `alarms` is not false),
|
|
74
|
+
* the auto-created dead-letter queue gains an alarm that fires on any
|
|
75
|
+
* visible message — a dead-lettered message means a consumer exhausted its
|
|
76
|
+
* receive budget, and an unwatched DLQ ages messages out silently after
|
|
77
|
+
* 14 days. BYO DLQs (`deadLetterQueue.queue`) are not alarmed here: the
|
|
78
|
+
* queue's owner wires its alarms, and a shared DLQ would otherwise collect
|
|
79
|
+
* one duplicate alarm per referencing queue.
|
|
80
|
+
*/
|
|
81
|
+
alertsTopic?: ITopic;
|
|
82
|
+
/** Alarm thresholds. false to disable, undefined for defaults, object to override. */
|
|
83
|
+
alarms?: SqsAlarmThresholds | false;
|
|
84
|
+
/** Application ID for alarm tagging. */
|
|
85
|
+
applicationId?: string;
|
|
70
86
|
}
|
|
71
87
|
export declare class SQSQueue extends Construct {
|
|
72
88
|
readonly id: string;
|
|
@@ -3,6 +3,7 @@ import { CfnOutput, Duration } from "aws-cdk-lib";
|
|
|
3
3
|
import { toPascalCase } from "../../../utils/capitaliseString.js";
|
|
4
4
|
import { Queue, QueueEncryption, FifoThroughputLimit, DeduplicationScope } from "aws-cdk-lib/aws-sqs";
|
|
5
5
|
import { toRemovalPolicy } from "./utils.js";
|
|
6
|
+
import { createSqsDlqAlarms } from "../monitoring/index.js";
|
|
6
7
|
/**
|
|
7
8
|
* AWS SQS service limits and defaults.
|
|
8
9
|
* @see https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-quotas.html
|
|
@@ -181,6 +182,16 @@ export class SQSQueue extends Construct {
|
|
|
181
182
|
value: this.queue.queueName,
|
|
182
183
|
description: `SQS queue name for ${id}`
|
|
183
184
|
});
|
|
185
|
+
if (props.alertsTopic && props.alarms !== false && this.dlq !== undefined) {
|
|
186
|
+
createSqsDlqAlarms({
|
|
187
|
+
scope: this,
|
|
188
|
+
queueName: id,
|
|
189
|
+
deadLetterQueue: this.dlq,
|
|
190
|
+
config: typeof props.alarms === "object" ? props.alarms : {},
|
|
191
|
+
alarmTopic: props.alertsTopic,
|
|
192
|
+
applicationId: props.applicationId
|
|
193
|
+
});
|
|
194
|
+
}
|
|
184
195
|
}
|
|
185
196
|
/**
|
|
186
197
|
* Get the SQS queue URL.
|
|
@@ -27,6 +27,9 @@ export declare const ALARM_DEFAULTS: {
|
|
|
27
27
|
readonly ERROR_RATE: 5;
|
|
28
28
|
readonly DURATION_PERCENT: 80;
|
|
29
29
|
};
|
|
30
|
+
readonly SQS: {
|
|
31
|
+
readonly DLQ_VISIBLE_MESSAGES: 0;
|
|
32
|
+
};
|
|
30
33
|
};
|
|
31
34
|
/** Build alarm description, appending the application ID tag for webhook extraction. */
|
|
32
35
|
export declare function buildAlarmDescription(baseDescription: string, applicationId: string | undefined): string;
|
|
@@ -10,7 +10,8 @@ export const ALARM_DEFAULTS = {
|
|
|
10
10
|
ECS: { CPU: 80, MEMORY: 80, RUNNING_TASKS_MIN: 1 },
|
|
11
11
|
ALB: { HTTP_5XX_PERCENT: 5, P99_RESPONSE_TIME_MS: 3000 },
|
|
12
12
|
RDS: { CPU: 80, FREE_STORAGE_GIB: 5, CONNECTIONS: 50 },
|
|
13
|
-
LAMBDA: { ERROR_RATE: 5, DURATION_PERCENT: 80 }
|
|
13
|
+
LAMBDA: { ERROR_RATE: 5, DURATION_PERCENT: 80 },
|
|
14
|
+
SQS: { DLQ_VISIBLE_MESSAGES: 0 }
|
|
14
15
|
};
|
|
15
16
|
/** Build alarm description, appending the application ID tag for webhook extraction. */
|
|
16
17
|
export function buildAlarmDescription(baseDescription, applicationId) {
|
|
@@ -3,6 +3,7 @@ export { createEcsServiceAlarms, type EcsServiceAlarmThresholds, type EcsService
|
|
|
3
3
|
export { createRdsAlarms, type RdsAlarmThresholds, type RdsAlarmsProps } from "./rdsAlarms.js";
|
|
4
4
|
export { createLambdaAlarms, type LambdaAlarmThresholds, type LambdaAlarmsProps } from "./lambdaAlarms.js";
|
|
5
5
|
export { createScheduleAlarms, type ScheduleAlarmThresholds, type CreateScheduleAlarmsProps } from "./scheduleAlarms.js";
|
|
6
|
+
export { createSqsDlqAlarms, type SqsAlarmThresholds, type SqsDlqAlarmsProps } from "./sqsAlarms.js";
|
|
6
7
|
export { createClickHouseAlarms, type ClickHouseAlarmThresholds, type ClickHouseAlarmsProps } from "./clickhouseAlarms.js";
|
|
7
8
|
export { createBuildkiteAlarms, type BuildkiteAlarmsProps } from "./buildkiteAlarms.js";
|
|
8
9
|
export { createLogPatternAlarms, type LogPatternAlarmSpec, type LogPatternAlarmsProps } from "./logPatternAlarms.js";
|
|
@@ -3,6 +3,7 @@ export { createEcsServiceAlarms } from "./ecsAlarms.js";
|
|
|
3
3
|
export { createRdsAlarms } from "./rdsAlarms.js";
|
|
4
4
|
export { createLambdaAlarms } from "./lambdaAlarms.js";
|
|
5
5
|
export { createScheduleAlarms } from "./scheduleAlarms.js";
|
|
6
|
+
export { createSqsDlqAlarms } from "./sqsAlarms.js";
|
|
6
7
|
export { createClickHouseAlarms } from "./clickhouseAlarms.js";
|
|
7
8
|
export { createBuildkiteAlarms } from "./buildkiteAlarms.js";
|
|
8
9
|
export { createLogPatternAlarms } from "./logPatternAlarms.js";
|
|
@@ -17,6 +17,12 @@ export interface ScheduleAlarmThresholds {
|
|
|
17
17
|
* expected cadence (silent disablement detector).
|
|
18
18
|
*/
|
|
19
19
|
matchedEventsLowFloorRatio?: number;
|
|
20
|
+
/**
|
|
21
|
+
* Visible-message count above which the rule's DLQ-depth alarm fires
|
|
22
|
+
* (`createSqsDlqAlarms`, wired by `eventBridgeRule.ts` when a DLQ exists).
|
|
23
|
+
* Default 0 — any dead-lettered event pages.
|
|
24
|
+
*/
|
|
25
|
+
dlqDepthThreshold?: number;
|
|
20
26
|
}
|
|
21
27
|
export interface CreateScheduleAlarmsProps {
|
|
22
28
|
scope: Construct;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { Alarm } from "aws-cdk-lib/aws-cloudwatch";
|
|
2
|
+
import type { IQueue } from "aws-cdk-lib/aws-sqs";
|
|
3
|
+
import type { ITopic } from "aws-cdk-lib/aws-sns";
|
|
4
|
+
import type { Construct } from "constructs";
|
|
5
|
+
export interface SqsAlarmThresholds {
|
|
6
|
+
/** Visible-message count above which the DLQ-depth alarm fires. Default 0 — any dead-lettered message pages. */
|
|
7
|
+
dlqDepthThreshold?: number;
|
|
8
|
+
}
|
|
9
|
+
export interface SqsDlqAlarmsProps {
|
|
10
|
+
scope: Construct;
|
|
11
|
+
queueName: string;
|
|
12
|
+
deadLetterQueue: IQueue;
|
|
13
|
+
config: SqsAlarmThresholds;
|
|
14
|
+
alarmTopic: ITopic;
|
|
15
|
+
applicationId?: string;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* DLQ-depth alarm: fires when the dead-letter queue holds any visible
|
|
19
|
+
* message. A dead-lettered message is a single-event failure (a poison
|
|
20
|
+
* message has exhausted its receive budget), so this uses the 1/1
|
|
21
|
+
* single-event evaluation shape rather than the utilisation 3/2 shape.
|
|
22
|
+
*/
|
|
23
|
+
export declare function createSqsDlqAlarms(props: SqsDlqAlarmsProps): Alarm[];
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { Alarm, ComparisonOperator, TreatMissingData } from "aws-cdk-lib/aws-cloudwatch";
|
|
2
|
+
import { SnsAction } from "aws-cdk-lib/aws-cloudwatch-actions";
|
|
3
|
+
import { ALARM_DEFAULTS, registerAlarm, tagAlarmsWithApplicationId, buildAlarmDescription } from "./alarmDefaults.js";
|
|
4
|
+
/**
|
|
5
|
+
* DLQ-depth alarm: fires when the dead-letter queue holds any visible
|
|
6
|
+
* message. A dead-lettered message is a single-event failure (a poison
|
|
7
|
+
* message has exhausted its receive budget), so this uses the 1/1
|
|
8
|
+
* single-event evaluation shape rather than the utilisation 3/2 shape.
|
|
9
|
+
*/
|
|
10
|
+
export function createSqsDlqAlarms(props) {
|
|
11
|
+
const { scope, queueName, deadLetterQueue, config, alarmTopic, applicationId } = props;
|
|
12
|
+
const alarms = [];
|
|
13
|
+
const snsAction = new SnsAction(alarmTopic);
|
|
14
|
+
const dlqDepthAlarm = new Alarm(scope, `${queueName}DlqDepthAlarm`, {
|
|
15
|
+
alarmDescription: buildAlarmDescription(`Dead-letter queue for ${queueName} contains messages`, applicationId),
|
|
16
|
+
metric: deadLetterQueue.metricApproximateNumberOfMessagesVisible({
|
|
17
|
+
period: ALARM_DEFAULTS.EVALUATION_PERIOD,
|
|
18
|
+
statistic: "Maximum"
|
|
19
|
+
}),
|
|
20
|
+
threshold: config.dlqDepthThreshold ?? ALARM_DEFAULTS.SQS.DLQ_VISIBLE_MESSAGES,
|
|
21
|
+
evaluationPeriods: 1,
|
|
22
|
+
datapointsToAlarm: 1,
|
|
23
|
+
comparisonOperator: ComparisonOperator.GREATER_THAN_THRESHOLD,
|
|
24
|
+
treatMissingData: TreatMissingData.NOT_BREACHING
|
|
25
|
+
});
|
|
26
|
+
registerAlarm(dlqDepthAlarm, snsAction, alarms);
|
|
27
|
+
tagAlarmsWithApplicationId(alarms, applicationId);
|
|
28
|
+
return alarms;
|
|
29
|
+
}
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import { type Construct } from "constructs";
|
|
2
|
-
import { Repository, type RepositoryProps } from "aws-cdk-lib/aws-ecr";
|
|
2
|
+
import { Repository, type RepositoryProps, TagMutability } from "aws-cdk-lib/aws-ecr";
|
|
3
3
|
import type App from "../../../app.js";
|
|
4
4
|
import { type StackBuilder } from "../base/awsStack.js";
|
|
5
5
|
interface EcrProps {
|
|
6
6
|
repositoryName?: string;
|
|
7
|
+
/** Default IMMUTABLE (supply-chain guard). Dev-slot repos MUST pass MUTABLE —
|
|
8
|
+
* every `fjall dev up` repushes the branch tag (BUG-22). */
|
|
9
|
+
tagMutability?: TagMutability;
|
|
7
10
|
}
|
|
8
11
|
export declare class EcrFactory {
|
|
9
12
|
static build(id: string, props?: EcrProps): (app: App, scope: Construct) => Ecr;
|
|
@@ -29,7 +29,7 @@ export class Ecr extends Repository {
|
|
|
29
29
|
return {
|
|
30
30
|
...(props?.repositoryName && { repositoryName: props.repositoryName }),
|
|
31
31
|
imageScanOnPush: true,
|
|
32
|
-
imageTagMutability: TagMutability.IMMUTABLE,
|
|
32
|
+
imageTagMutability: props?.tagMutability ?? TagMutability.IMMUTABLE,
|
|
33
33
|
emptyOnDelete: false,
|
|
34
34
|
removalPolicy: RemovalPolicy.RETAIN
|
|
35
35
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fjall/components-infrastructure",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.8.0",
|
|
4
4
|
"license": "SEE LICENSE IN LICENSE",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -67,8 +67,8 @@
|
|
|
67
67
|
},
|
|
68
68
|
"dependencies": {
|
|
69
69
|
"@aws-sdk/client-organizations": "^3.1038.0",
|
|
70
|
-
"@fjall/generator": "^3.
|
|
71
|
-
"@fjall/util": "^3.
|
|
70
|
+
"@fjall/generator": "^3.8.0",
|
|
71
|
+
"@fjall/util": "^3.8.0",
|
|
72
72
|
"constructs": "^10.6.0"
|
|
73
73
|
},
|
|
74
74
|
"overrides": {
|
|
@@ -82,5 +82,5 @@
|
|
|
82
82
|
"engines": {
|
|
83
83
|
"node": ">=18.0.0"
|
|
84
84
|
},
|
|
85
|
-
"gitHead": "
|
|
85
|
+
"gitHead": "9a137b6bd380f432f6c6e8e43b7932dec0b64a13"
|
|
86
86
|
}
|