@fjall/components-infrastructure 31.0.0 → 33.0.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/README.md +1 -1
- package/dist/lib/config/audit.js +2 -1
- package/dist/lib/patterns/aws/buildkite/pins.js +1 -2
- package/dist/lib/patterns/aws/cdn.d.ts +14 -0
- package/dist/lib/patterns/aws/cdn.js +31 -3
- package/dist/lib/patterns/aws/clickhouseDatabase.d.ts +8 -0
- package/dist/lib/patterns/aws/clickhouseDatabase.js +15 -2
- package/dist/lib/patterns/aws/compute.js +4 -2
- package/dist/lib/patterns/aws/computeEcs.d.ts +7 -0
- package/dist/lib/patterns/aws/computeEcs.js +26 -3
- package/dist/lib/patterns/aws/computeEcsTypes.d.ts +25 -2
- package/dist/lib/patterns/aws/computeLambda.d.ts +12 -0
- package/dist/lib/patterns/aws/computeLambda.js +2 -0
- package/dist/lib/patterns/aws/computePropApplicability.js +2 -0
- package/dist/lib/patterns/aws/database.d.ts +14 -0
- package/dist/lib/patterns/aws/database.js +16 -6
- package/dist/lib/patterns/aws/fivetranProxy.d.ts +0 -1
- package/dist/lib/patterns/aws/fivetranProxy.js +6 -1
- package/dist/lib/patterns/aws/interfaces/pattern.d.ts +7 -7
- package/dist/lib/patterns/aws/messaging.js +4 -1
- package/dist/lib/patterns/aws/patternDomain.d.ts +1 -1
- package/dist/lib/patterns/aws/patternDomain.js +1 -1
- package/dist/lib/patterns/aws/payload.js +1 -3
- package/dist/lib/patterns/aws/staticSite.d.ts +6 -6
- package/dist/lib/patterns/aws/staticSite.js +9 -6
- package/dist/lib/resources/aws/cdn/cloudFront.d.ts +2 -0
- package/dist/lib/resources/aws/cdn/cloudFront.js +1 -0
- package/dist/lib/resources/aws/compute/applicationLoadBalancer.d.ts +10 -0
- package/dist/lib/resources/aws/compute/applicationLoadBalancer.js +7 -1
- package/dist/lib/resources/aws/compute/ecs.d.ts +11 -0
- package/dist/lib/resources/aws/compute/ecs.js +51 -6
- package/dist/lib/resources/aws/compute/ecsContext.d.ts +10 -1
- package/dist/lib/resources/aws/compute/ecsNetworking.js +3 -1
- package/dist/lib/resources/aws/compute/ecsTaskDefinition.js +4 -1
- package/dist/lib/resources/aws/compute/ecsTypes.d.ts +31 -8
- package/dist/lib/resources/aws/compute/lambda.d.ts +18 -1
- package/dist/lib/resources/aws/compute/lambda.js +21 -3
- package/dist/lib/resources/aws/database/dynamodb.d.ts +19 -0
- package/dist/lib/resources/aws/database/dynamodb.js +13 -2
- package/dist/lib/resources/aws/database/rdsAurora.d.ts +11 -0
- package/dist/lib/resources/aws/database/rdsAurora.js +24 -3
- package/dist/lib/resources/aws/database/rdsAuroraGlobal.d.ts +11 -1
- package/dist/lib/resources/aws/database/rdsAuroraGlobal.js +1 -0
- package/dist/lib/resources/aws/database/rdsDefaults.d.ts +0 -2
- package/dist/lib/resources/aws/database/rdsDefaults.js +0 -2
- package/dist/lib/resources/aws/database/rdsInstance.d.ts +11 -0
- package/dist/lib/resources/aws/database/rdsInstance.js +19 -4
- package/dist/lib/resources/aws/logging/accessLogBucket.d.ts +22 -0
- package/dist/lib/resources/aws/logging/accessLogBucket.js +41 -0
- package/dist/lib/resources/aws/logging/index.d.ts +1 -0
- package/dist/lib/resources/aws/logging/index.js +1 -0
- package/dist/lib/resources/aws/messaging/sns.d.ts +14 -5
- package/dist/lib/resources/aws/messaging/sns.js +7 -1
- package/dist/lib/resources/aws/secrets/index.d.ts +1 -0
- package/dist/lib/resources/aws/secrets/index.js +1 -0
- package/dist/lib/resources/aws/secrets/stackEncryptionKey.d.ts +47 -0
- package/dist/lib/resources/aws/secrets/stackEncryptionKey.js +114 -0
- package/dist/lib/utils/backupTierMapping.d.ts +10 -2
- package/dist/lib/utils/backupTierMapping.js +3 -1
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -102,7 +102,7 @@ requires you explicitly configure your `AwsStack` with an `account` and `region.
|
|
|
102
102
|
```typescript
|
|
103
103
|
const env = {
|
|
104
104
|
region: "us-east-1",
|
|
105
|
-
account: "
|
|
105
|
+
account: "123456789012"
|
|
106
106
|
};
|
|
107
107
|
|
|
108
108
|
const vpcStack = new AwsStack(`${id}Network`, { ...env }).addNetwork(
|
package/dist/lib/config/audit.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { FJALL_PLATFORM_ACCOUNT_ID } from "./aws/platform.js";
|
|
1
2
|
/**
|
|
2
3
|
* Audit configuration for cross-account CloudQuery access
|
|
3
4
|
*/
|
|
@@ -6,7 +7,7 @@ export const FJALL_AUDIT_CONFIG = {
|
|
|
6
7
|
* Fjall webapp AWS account ID
|
|
7
8
|
* This account will be granted permission to assume audit roles for CloudQuery
|
|
8
9
|
*/
|
|
9
|
-
webappAwsAccountId:
|
|
10
|
+
webappAwsAccountId: FJALL_PLATFORM_ACCOUNT_ID,
|
|
10
11
|
/**
|
|
11
12
|
* IAM role naming convention
|
|
12
13
|
*/
|
|
@@ -69,8 +69,7 @@ export function resolvePinnedAmiId(region, architecture) {
|
|
|
69
69
|
if (!isSupportedRegion(region)) {
|
|
70
70
|
throw new Error(`Buildkite: no pinned AMI for region '${region}'. Supported: ` +
|
|
71
71
|
`${Object.keys(BUILDKITE_STACK_PINS.amiIdsByRegion).join(", ")}. ` +
|
|
72
|
-
`Add the region to BUILDKITE_STACK_PINS
|
|
73
|
-
`(aiDocs/runbooks/buildkite-stack-bump-runbook.md).`);
|
|
72
|
+
`Add the region to BUILDKITE_STACK_PINS.`);
|
|
74
73
|
}
|
|
75
74
|
return BUILDKITE_STACK_PINS.amiIdsByRegion[region][architecture];
|
|
76
75
|
}
|
|
@@ -7,6 +7,7 @@ import type App from "../../app.js";
|
|
|
7
7
|
import { CloudFrontDistribution, type CachePolicyPreset, type AccessGateConfig } from "../../resources/aws/cdn/index.js";
|
|
8
8
|
import { type ICdn } from "./interfaces/cdn.js";
|
|
9
9
|
import { type StaticSiteRouting, type CloudFrontPriceClass } from "@fjall/util";
|
|
10
|
+
import { type BackupTier } from "../../utils/backupTierMapping.js";
|
|
10
11
|
import { type ManagedDomainBinding, type ManagedDomainExports } from "../../utils/domainTypes.js";
|
|
11
12
|
import { type Storage } from "./storage.js";
|
|
12
13
|
import { type AnyCompute } from "./compute.js";
|
|
@@ -81,11 +82,24 @@ interface BaseCdnProps {
|
|
|
81
82
|
certificate?: ICertificate;
|
|
82
83
|
certificateArn?: string;
|
|
83
84
|
comment?: string;
|
|
85
|
+
/**
|
|
86
|
+
* Standard (S3) access logging. Default: the app backup tier's
|
|
87
|
+
* `cloudFrontLogging` column — enterprise logs to the stack's shared,
|
|
88
|
+
* lifecycle-expired access-log bucket under `cloudfront/<id>/`. Set either
|
|
89
|
+
* knob to take over: `enableLogging: false` opts out at any tier,
|
|
90
|
+
* `logBucket` delivers to a bucket of your own.
|
|
91
|
+
*/
|
|
84
92
|
enableLogging?: boolean;
|
|
85
93
|
logBucket?: IBucket;
|
|
86
94
|
priceClass?: CloudFrontPriceClass;
|
|
87
95
|
forwardHostHeader?: boolean;
|
|
88
96
|
accessGate?: false | AccessGateConfig;
|
|
97
|
+
/**
|
|
98
|
+
* The owning app's backup tier, which the logging default follows.
|
|
99
|
+
* `CdnFactory.build` fills it from the `App`; only set by hand for a
|
|
100
|
+
* directly-instantiated `Cdn`.
|
|
101
|
+
*/
|
|
102
|
+
appBackupTier?: BackupTier;
|
|
89
103
|
}
|
|
90
104
|
/**
|
|
91
105
|
* S3 origin CDN props - use with S3 bucket
|
|
@@ -3,12 +3,40 @@ import { Certificate } from "aws-cdk-lib/aws-certificatemanager";
|
|
|
3
3
|
import { CloudFrontTarget, LoadBalancerTarget } from "aws-cdk-lib/aws-route53-targets";
|
|
4
4
|
import { CloudFrontDistribution } from "../../resources/aws/cdn/index.js";
|
|
5
5
|
import { AliasRecord } from "../../resources/aws/networking/index.js";
|
|
6
|
+
import { tierDefault } from "../../utils/backupTierMapping.js";
|
|
7
|
+
import { stackAccessLogBucket } from "../../resources/aws/logging/index.js";
|
|
6
8
|
import { getSafeZoneName, toPascalCase } from "../../utils/capitaliseString.js";
|
|
7
9
|
import { recordLabelWithin } from "../../utils/domainTypes.js";
|
|
8
10
|
import { resolvePatternZone, resolvePatternCloudFrontCertificate } from "./patternDomain.js";
|
|
9
11
|
import { isStorage } from "./storage.js";
|
|
10
12
|
import { CdnEcsOriginResolver, cdnOriginRefusal, internalAlbMessage, normaliseDnsName } from "./cdnAppOrigin.js";
|
|
11
13
|
import { isCompute, isEcsCompute, isLambdaCompute } from "./compute.js";
|
|
14
|
+
/**
|
|
15
|
+
* An explicit `enableLogging` or `logBucket` is the override and passes
|
|
16
|
+
* through untouched; otherwise the tier decides, and a tier-enabled CDN logs
|
|
17
|
+
* to the stack's shared access-log bucket under its own prefix.
|
|
18
|
+
*/
|
|
19
|
+
function resolveCdnLogging(scope, id, props) {
|
|
20
|
+
if (props.enableLogging !== undefined || props.logBucket !== undefined) {
|
|
21
|
+
return {
|
|
22
|
+
enableLogging: props.enableLogging,
|
|
23
|
+
logBucket: props.logBucket,
|
|
24
|
+
logFilePrefix: undefined
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
if (!tierDefault(props.appBackupTier, "cloudFrontLogging")) {
|
|
28
|
+
return {
|
|
29
|
+
enableLogging: undefined,
|
|
30
|
+
logBucket: undefined,
|
|
31
|
+
logFilePrefix: undefined
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
return {
|
|
35
|
+
enableLogging: true,
|
|
36
|
+
logBucket: stackAccessLogBucket(scope),
|
|
37
|
+
logFilePrefix: `cloudfront/${id}/`
|
|
38
|
+
};
|
|
39
|
+
}
|
|
12
40
|
/**
|
|
13
41
|
* The distribution's own alias names, normalised — consumed by the
|
|
14
42
|
* origin-loop guard and by the ECS origin resolver (auto-resolution excludes
|
|
@@ -458,8 +486,7 @@ export class Cdn extends CloudFrontDistribution {
|
|
|
458
486
|
domainNames,
|
|
459
487
|
certificate,
|
|
460
488
|
comment: props.comment,
|
|
461
|
-
|
|
462
|
-
logBucket: props.logBucket,
|
|
489
|
+
...resolveCdnLogging(scope, id, props),
|
|
463
490
|
priceClass: props.priceClass,
|
|
464
491
|
forwardHostHeader: props.forwardHostHeader ?? impliedForwardHostHeader,
|
|
465
492
|
accessGate: props.accessGate,
|
|
@@ -676,7 +703,8 @@ export class CdnFactory {
|
|
|
676
703
|
return new Cdn(scope, id, {
|
|
677
704
|
...props,
|
|
678
705
|
appName: props.appName ?? app.getName(),
|
|
679
|
-
app: props.app ?? app
|
|
706
|
+
app: props.app ?? app,
|
|
707
|
+
appBackupTier: props.appBackupTier ?? app.getBackupTier()
|
|
680
708
|
});
|
|
681
709
|
};
|
|
682
710
|
}
|
|
@@ -3,6 +3,7 @@ import { type TaskDefinition } from "aws-cdk-lib/aws-ecs";
|
|
|
3
3
|
import { type IBucket } from "aws-cdk-lib/aws-s3";
|
|
4
4
|
import { Construct } from "constructs";
|
|
5
5
|
import { Secret, type SecretImport } from "../../resources/aws/secrets/secret.js";
|
|
6
|
+
import { type BackupTier } from "../../utils/backupTierMapping.js";
|
|
6
7
|
import { type ClickHouseAlarmThresholds } from "../../resources/aws/monitoring/index.js";
|
|
7
8
|
import { type ClickHouseSchemaAdmin, type ProfileSpec } from "../../resources/aws/database/clickhouseSchemas.js";
|
|
8
9
|
import { type ISecret } from "aws-cdk-lib/aws-secretsmanager";
|
|
@@ -283,6 +284,13 @@ export interface ClickHouseDatabaseProps {
|
|
|
283
284
|
* RDS branch of `DatabaseFactory.build`.
|
|
284
285
|
*/
|
|
285
286
|
applicationId?: string;
|
|
287
|
+
/**
|
|
288
|
+
* The owning app's backup tier, which the tier-owned defaults of the
|
|
289
|
+
* compute this pattern builds (Container Insights, exec-session and
|
|
290
|
+
* log-group encryption) follow. `DatabaseFactory` fills it from the App;
|
|
291
|
+
* pass it only for direct `new ClickHouseDatabase(...)` construction.
|
|
292
|
+
*/
|
|
293
|
+
appBackupTier?: BackupTier;
|
|
286
294
|
}
|
|
287
295
|
/**
|
|
288
296
|
* Public-facing re-export of the alarm knob so factory consumers can type
|
|
@@ -15,6 +15,8 @@ import App from "../../app.js";
|
|
|
15
15
|
import { S3Bucket } from "../../resources/aws/storage/s3.js";
|
|
16
16
|
import { Secret } from "../../resources/aws/secrets/secret.js";
|
|
17
17
|
import { LogGroup } from "../../resources/aws/logging/logGroup.js";
|
|
18
|
+
import { StackEncryptionKey } from "../../resources/aws/secrets/stackEncryptionKey.js";
|
|
19
|
+
import { tierDefault } from "../../utils/backupTierMapping.js";
|
|
18
20
|
import { createClickHouseSecurityGroup } from "../../resources/aws/database/clickhouseSecurityGroup.js";
|
|
19
21
|
import { buildMaintenancePreamble, maintenanceOkMarker, maintenanceRun } from "../../resources/aws/database/clickhouseMaintenanceScript.js";
|
|
20
22
|
import { buildClickHouseEntrypointWrapper, buildClickHouseUserData, generateUsersConfigXml } from "../../resources/aws/database/clickhouseUserData.js";
|
|
@@ -385,16 +387,24 @@ export class ClickHouseDatabase extends Construct {
|
|
|
385
387
|
this.#backupBucket = backupBucket;
|
|
386
388
|
this.#coldTierBucket = coldTierBucket;
|
|
387
389
|
this.#migrationsConfig = props.migrations;
|
|
390
|
+
// The maintenance-task log groups sit beside the compute's own container
|
|
391
|
+
// and exec-audit groups and follow the same tier: the stack's shared key
|
|
392
|
+
// at enterprise, nothing otherwise.
|
|
393
|
+
const taskLogGroupEncryption = tierDefault(props.appBackupTier, "customerManagedKeys")
|
|
394
|
+
? { encryptionKey: StackEncryptionKey.of(this).forLogGroups() }
|
|
395
|
+
: {};
|
|
388
396
|
const backupTaskLogGroup = backupEnabled
|
|
389
397
|
? new LogGroup(this, "ClickHouseBackupTaskLogGroup", {
|
|
390
|
-
retention: RetentionDays.TWO_WEEKS
|
|
398
|
+
retention: RetentionDays.TWO_WEEKS,
|
|
399
|
+
...taskLogGroupEncryption
|
|
391
400
|
})
|
|
392
401
|
: undefined;
|
|
393
402
|
// Explicit rather than `logRetention` so the maintenance-status metric
|
|
394
403
|
// filters can attach to it alongside the backup task's group.
|
|
395
404
|
const optimiseTaskLogGroup = optimiseEnabled
|
|
396
405
|
? new LogGroup(this, "ClickHouseOptimiseTaskLogGroup", {
|
|
397
|
-
retention: RetentionDays.ONE_WEEK
|
|
406
|
+
retention: RetentionDays.ONE_WEEK,
|
|
407
|
+
...taskLogGroupEncryption
|
|
398
408
|
})
|
|
399
409
|
: undefined;
|
|
400
410
|
// Resolved BEFORE the user data is built: the declared volume size is
|
|
@@ -576,6 +586,9 @@ export class ClickHouseDatabase extends Construct {
|
|
|
576
586
|
containerInsights: true
|
|
577
587
|
}),
|
|
578
588
|
...(applicationId !== undefined && { applicationId }),
|
|
589
|
+
...(props.appBackupTier !== undefined && {
|
|
590
|
+
appBackupTier: props.appBackupTier
|
|
591
|
+
}),
|
|
579
592
|
cluster: {
|
|
580
593
|
loadBalancer: false,
|
|
581
594
|
securityGroup,
|
|
@@ -164,7 +164,8 @@ export class ComputeFactory {
|
|
|
164
164
|
computeProps = {
|
|
165
165
|
...computeProps,
|
|
166
166
|
appName: computeProps.appName ?? app.getName(),
|
|
167
|
-
applicationId: computeProps.applicationId ?? app.getName()
|
|
167
|
+
applicationId: computeProps.applicationId ?? app.getName(),
|
|
168
|
+
appBackupTier: computeProps.appBackupTier ?? app.getBackupTier()
|
|
168
169
|
};
|
|
169
170
|
}
|
|
170
171
|
// Create specific compute class based on type discriminator
|
|
@@ -241,7 +242,8 @@ export class ComputeFactory {
|
|
|
241
242
|
// Add appName to props for Lambda (needed for secrets path derivation)
|
|
242
243
|
const lambdaComputeProps = {
|
|
243
244
|
...computeProps,
|
|
244
|
-
appName: app.getName()
|
|
245
|
+
appName: app.getName(),
|
|
246
|
+
appBackupTier: computeProps.appBackupTier ?? app.getBackupTier()
|
|
245
247
|
};
|
|
246
248
|
// `ecrRepository` is already defaulted to the app's shared default
|
|
247
249
|
// container registry above (line ~323) when omitted — the same
|
|
@@ -171,6 +171,13 @@ export declare class EcsCompute extends Construct implements IEcsCompute {
|
|
|
171
171
|
* the version entry the app containers get.
|
|
172
172
|
*/
|
|
173
173
|
private resolveSchemaGateMaterialisation;
|
|
174
|
+
/**
|
|
175
|
+
* Encryption for a log group this pattern creates beside the cluster's
|
|
176
|
+
* own (scheduled-task and migration-task logs): the stack's shared key
|
|
177
|
+
* when the app backup tier encrypts, nothing otherwise — the same key the
|
|
178
|
+
* cluster resolved for its service and exec-audit groups.
|
|
179
|
+
*/
|
|
180
|
+
private tierLogGroupEncryption;
|
|
174
181
|
private materialiseScheduledTasks;
|
|
175
182
|
private buildScheduledTaskDefinition;
|
|
176
183
|
/**
|
|
@@ -955,6 +955,12 @@ export class EcsCompute extends Construct {
|
|
|
955
955
|
domainConfig: props.cluster.domainConfig,
|
|
956
956
|
...(props.cluster.securityGroup !== undefined && {
|
|
957
957
|
securityGroup: props.cluster.securityGroup
|
|
958
|
+
}),
|
|
959
|
+
...(props.cluster.loadBalancerDeletionProtection !== undefined && {
|
|
960
|
+
loadBalancerDeletionProtection: props.cluster.loadBalancerDeletionProtection
|
|
961
|
+
}),
|
|
962
|
+
...(props.cluster.loadBalancerAccessLogs !== undefined && {
|
|
963
|
+
loadBalancerAccessLogs: props.cluster.loadBalancerAccessLogs
|
|
958
964
|
})
|
|
959
965
|
}
|
|
960
966
|
: undefined;
|
|
@@ -970,7 +976,12 @@ export class EcsCompute extends Construct {
|
|
|
970
976
|
applicationId: props.applicationId ??
|
|
971
977
|
(App.hasInstance() ? App.getInstance().getName() : undefined),
|
|
972
978
|
...(props.taskStopWatchdog === false && { taskStopWatchdog: false }),
|
|
973
|
-
...(props.containerInsights
|
|
979
|
+
...(props.containerInsights !== undefined && {
|
|
980
|
+
containerInsights: props.containerInsights
|
|
981
|
+
}),
|
|
982
|
+
...(props.appBackupTier !== undefined && {
|
|
983
|
+
appBackupTier: props.appBackupTier
|
|
984
|
+
})
|
|
974
985
|
};
|
|
975
986
|
this.ecsCluster = new EcsCluster(this, `${id}Ecs`, ecsProps);
|
|
976
987
|
this.connections = this.ecsCluster.connections;
|
|
@@ -1155,6 +1166,16 @@ export class EcsCompute extends Construct {
|
|
|
1155
1166
|
defaultImageTag: CONSTRUCTS_VERSION
|
|
1156
1167
|
};
|
|
1157
1168
|
}
|
|
1169
|
+
/**
|
|
1170
|
+
* Encryption for a log group this pattern creates beside the cluster's
|
|
1171
|
+
* own (scheduled-task and migration-task logs): the stack's shared key
|
|
1172
|
+
* when the app backup tier encrypts, nothing otherwise — the same key the
|
|
1173
|
+
* cluster resolved for its service and exec-audit groups.
|
|
1174
|
+
*/
|
|
1175
|
+
tierLogGroupEncryption() {
|
|
1176
|
+
const key = this.ecsCluster.encryptionKey;
|
|
1177
|
+
return key === undefined ? {} : { encryptionKey: key.forLogGroups() };
|
|
1178
|
+
}
|
|
1158
1179
|
materialiseScheduledTasks(id, props) {
|
|
1159
1180
|
const scheduledTasks = props.cluster?.scheduledTasks;
|
|
1160
1181
|
if (!scheduledTasks || scheduledTasks.length === 0)
|
|
@@ -1200,7 +1221,8 @@ export class EcsCompute extends Construct {
|
|
|
1200
1221
|
// removal default (D17).
|
|
1201
1222
|
const logGroup = entry.logGroup ??
|
|
1202
1223
|
new LogGroup(this, `${id}${toPascalCase(entry.name)}LogGroup`, {
|
|
1203
|
-
retention: entry.logRetention ?? DEFAULT_LOG_RETENTION
|
|
1224
|
+
retention: entry.logRetention ?? DEFAULT_LOG_RETENTION,
|
|
1225
|
+
...this.tierLogGroupEncryption()
|
|
1204
1226
|
});
|
|
1205
1227
|
const logging = new AwsLogDriver({
|
|
1206
1228
|
streamPrefix: `/ecs-scheduled/${id}/${entry.name}`,
|
|
@@ -1429,7 +1451,8 @@ export class EcsCompute extends Construct {
|
|
|
1429
1451
|
const inheritedImage = service.taskDefinition.defaultContainer?.imageName;
|
|
1430
1452
|
const image = resolveMigrationImage(svcConfig, migrations, inheritedImage);
|
|
1431
1453
|
const logGroup = new LogGroup(this, `${idPrefix}LogGroup`, {
|
|
1432
|
-
retention: RetentionDays.ONE_MONTH
|
|
1454
|
+
retention: RetentionDays.ONE_MONTH,
|
|
1455
|
+
...this.tierLogGroupEncryption()
|
|
1433
1456
|
});
|
|
1434
1457
|
const executionRole = new Role(this, `${idPrefix}ExecutionRole`, {
|
|
1435
1458
|
assumedBy: new ServicePrincipal("ecs-tasks.amazonaws.com")
|
|
@@ -5,6 +5,7 @@ import { type ILogGroup, type RetentionDays } from "aws-cdk-lib/aws-logs";
|
|
|
5
5
|
import { type PolicyDocument, type IManagedPolicy, type PolicyStatement } from "aws-cdk-lib/aws-iam";
|
|
6
6
|
import type { ITopic } from "aws-cdk-lib/aws-sns";
|
|
7
7
|
import { type ConnectionSpec } from "./interfaces/connector.js";
|
|
8
|
+
import type { BackupTier } from "../../utils/backupTierMapping.js";
|
|
8
9
|
import { type RemoteConnectionSpec } from "../../resources/aws/compute/ecsRemoteConnections.js";
|
|
9
10
|
import { type EcsRoutingConfig, type EcsContainerDependency } from "../../resources/aws/compute/ecsTypes.js";
|
|
10
11
|
import { ScalingType, type DomainConfig, type EcsCapacityProvider, type Ec2CapacityConfig, type QueueScalingConfig } from "../../resources/aws/compute/ecs.js";
|
|
@@ -623,6 +624,19 @@ export interface EcsClusterConfig {
|
|
|
623
624
|
* (e.g. ClickHouseDatabase) that own their security group lifecycle.
|
|
624
625
|
*/
|
|
625
626
|
securityGroup?: ISecurityGroup;
|
|
627
|
+
/**
|
|
628
|
+
* Deletion protection on the shared load balancer. Default: the app backup
|
|
629
|
+
* tier's `deletionProtection` column, applied only when the stack's
|
|
630
|
+
* environment resolves to production. The destroy pre-flight switches it
|
|
631
|
+
* off before a consented destroy.
|
|
632
|
+
*/
|
|
633
|
+
loadBalancerDeletionProtection?: boolean;
|
|
634
|
+
/**
|
|
635
|
+
* Access logs from the shared load balancer into the stack's
|
|
636
|
+
* lifecycle-expired access-log bucket. Default: the app backup tier's
|
|
637
|
+
* `albAccessLogs` column.
|
|
638
|
+
*/
|
|
639
|
+
loadBalancerAccessLogs?: boolean;
|
|
626
640
|
/**
|
|
627
641
|
* Scheduled tasks materialised into `Ec2TaskDefinition`s and registered via
|
|
628
642
|
* `app.addSchedule(...)` against the existing `EcsScheduleTarget` shape
|
|
@@ -1074,9 +1088,18 @@ export interface EcsComputeProps {
|
|
|
1074
1088
|
* `RunningTaskCount` liveness alarms (`ecsAlarms.ts` gates them on this
|
|
1075
1089
|
* flag because the metric only exists in `ECS/ContainerInsights`).
|
|
1076
1090
|
* Scale-to-zero services should pair it with
|
|
1077
|
-
* `alarms: { runningTasksMinimum: 0 }` or they idle in ALARM.
|
|
1091
|
+
* `alarms: { runningTasksMinimum: 0 }` or they idle in ALARM. Default:
|
|
1092
|
+
* the app backup tier's `containerInsights` column (on at enterprise);
|
|
1093
|
+
* `false` is an explicit, auditable opt-out.
|
|
1078
1094
|
*/
|
|
1079
|
-
containerInsights?:
|
|
1095
|
+
containerInsights?: boolean;
|
|
1080
1096
|
/** Application ID for alarm tagging (used by webhook to map alarms to applications). */
|
|
1081
1097
|
applicationId?: string;
|
|
1098
|
+
/**
|
|
1099
|
+
* The owning app's backup tier. Auto-derived from the App by
|
|
1100
|
+
* ComputeFactory; the tier-owned defaults (Container Insights, exec-session
|
|
1101
|
+
* and log-group encryption, load-balancer deletion protection and access
|
|
1102
|
+
* logs) follow it. Only specify for direct construction.
|
|
1103
|
+
*/
|
|
1104
|
+
appBackupTier?: BackupTier;
|
|
1082
1105
|
}
|
|
@@ -4,6 +4,7 @@ import { Connections, type IVpc, type ISecurityGroup } from "aws-cdk-lib/aws-ec2
|
|
|
4
4
|
import { Repository } from "aws-cdk-lib/aws-ecr";
|
|
5
5
|
import { type RepositoryImage } from "aws-cdk-lib/aws-ecs";
|
|
6
6
|
import { Construct } from "constructs";
|
|
7
|
+
import type { BackupTier } from "../../utils/backupTierMapping.js";
|
|
7
8
|
import { type ILambdaCompute } from "./interfaces/compute.js";
|
|
8
9
|
import { type ConnectionSpec } from "./interfaces/connector.js";
|
|
9
10
|
import { LambdaFunction } from "../../resources/aws/compute/lambda.js";
|
|
@@ -95,6 +96,17 @@ interface BaseLambdaProps {
|
|
|
95
96
|
* Only specify for advanced use cases.
|
|
96
97
|
*/
|
|
97
98
|
appName?: string;
|
|
99
|
+
/**
|
|
100
|
+
* The owning app's backup tier. Auto-derived from the App by
|
|
101
|
+
* ComputeFactory; the tier-owned defaults (X-Ray tracing, environment and
|
|
102
|
+
* log-group encryption) follow it. Only specify for direct construction.
|
|
103
|
+
*/
|
|
104
|
+
appBackupTier?: BackupTier;
|
|
105
|
+
/**
|
|
106
|
+
* Active X-Ray tracing. Default: the app backup tier's `lambdaTracing`
|
|
107
|
+
* column (on at enterprise).
|
|
108
|
+
*/
|
|
109
|
+
tracing?: boolean;
|
|
98
110
|
/**
|
|
99
111
|
* Resources this Lambda needs to connect to (e.g., databases, S3 buckets, SQS queues).
|
|
100
112
|
* Creates security group rules for IConnectable resources and IAM grants for IAM resources.
|
|
@@ -167,6 +167,8 @@ export class LambdaCompute extends Construct {
|
|
|
167
167
|
secretsImport: props.secretsImport,
|
|
168
168
|
environment: props.environment,
|
|
169
169
|
appName: props.appName,
|
|
170
|
+
appBackupTier: props.appBackupTier,
|
|
171
|
+
tracing: props.tracing,
|
|
170
172
|
functionName: id
|
|
171
173
|
};
|
|
172
174
|
this.lambdaFunction = new LambdaFunction(scope, `${id}Lambda`, lambdaProps);
|
|
@@ -5,6 +5,7 @@ import type { RdsAlarmThresholds } from "../../resources/aws/monitoring/index.js
|
|
|
5
5
|
import type App from "../../app.js";
|
|
6
6
|
import { type DynamoDBKeySchema, type DynamoDBGlobalSecondaryIndex } from "../../resources/aws/database/dynamodb.js";
|
|
7
7
|
import { ClickHouseDatabase, type ClickHouseDatabaseProps, type ClickHouseAlarmThresholds } from "./clickhouseDatabase.js";
|
|
8
|
+
import { type BackupTier } from "../../utils/backupTierMapping.js";
|
|
8
9
|
import { type Connections, type IConnectable, type IVpc } from "aws-cdk-lib/aws-ec2";
|
|
9
10
|
import { type ITable } from "aws-cdk-lib/aws-dynamodb";
|
|
10
11
|
import { type Secret, type SecretImport } from "../../resources/aws/secrets/index.js";
|
|
@@ -40,6 +41,13 @@ type BaseDatabaseProps = {
|
|
|
40
41
|
databaseName: string;
|
|
41
42
|
instanceType?: string;
|
|
42
43
|
deletionProtection?: boolean;
|
|
44
|
+
/**
|
|
45
|
+
* The owning app's backup tier (`App.getApp({ backup: { tier } })`), which
|
|
46
|
+
* the tier-owned defaults (backup retention) follow. `DatabaseFactory`
|
|
47
|
+
* fills it from the App as it does `vpc` and `applicationId`; pass it only
|
|
48
|
+
* for direct `new RelationalDatabase(...)` construction.
|
|
49
|
+
*/
|
|
50
|
+
appBackupTier?: BackupTier;
|
|
43
51
|
/** SNS topic for alarm notifications. Accepts ITopic, ARN string, or "import:ExportName". */
|
|
44
52
|
alertsTopic?: ITopic | string;
|
|
45
53
|
/** Alarm thresholds. false to disable, undefined for defaults, object to override. */
|
|
@@ -194,6 +202,12 @@ export interface DynamoDBDatabaseProps {
|
|
|
194
202
|
encryption?: "AWS_OWNED" | "AWS_MANAGED" | "CUSTOMER_MANAGED";
|
|
195
203
|
deletionProtection?: boolean;
|
|
196
204
|
removalPolicy?: "DESTROY" | "RETAIN" | "SNAPSHOT";
|
|
205
|
+
/**
|
|
206
|
+
* The owning app's backup tier, which the tier-owned defaults (encryption,
|
|
207
|
+
* deletion protection) follow. `DatabaseFactory` fills it from the App;
|
|
208
|
+
* pass it only for direct `new DynamoDBDatabase(...)` construction.
|
|
209
|
+
*/
|
|
210
|
+
appBackupTier?: BackupTier;
|
|
197
211
|
}
|
|
198
212
|
/**
|
|
199
213
|
* Relational database props union (Aurora, Instance, GlobalAurora).
|
|
@@ -130,7 +130,7 @@ function validateRelationalDatabaseProps(props) {
|
|
|
130
130
|
// Instance-only options on Aurora/GlobalAurora
|
|
131
131
|
warnIfPropertiesIgnored(props, ["readReplica", "multiAz", "allocatedStorage", "iamAuthentication"], "Instance", "Instance database");
|
|
132
132
|
// Aurora-only options on Instance
|
|
133
|
-
warnIfPropertiesIgnored(props, ["readers", "writer"
|
|
133
|
+
warnIfPropertiesIgnored(props, ["readers", "writer"], ["Aurora", "GlobalAurora"], "Aurora database");
|
|
134
134
|
// GlobalAurora-only options on non-global databases
|
|
135
135
|
warnIfPropertiesIgnored(props, ["primaryRegion", "secondaryRegions", "enableGlobalWriteForwarding"], "GlobalAurora", "GlobalAurora database");
|
|
136
136
|
}
|
|
@@ -170,21 +170,27 @@ export class DatabaseFactory {
|
|
|
170
170
|
static build(id, props) {
|
|
171
171
|
return (app, scope) => {
|
|
172
172
|
if (props.type === "DynamoDB") {
|
|
173
|
-
|
|
174
|
-
|
|
173
|
+
const dynamoProps = {
|
|
174
|
+
...props,
|
|
175
|
+
appBackupTier: props.appBackupTier ?? app.getBackupTier()
|
|
176
|
+
};
|
|
177
|
+
validateDatabaseProps(dynamoProps);
|
|
178
|
+
return new DynamoDBDatabase(scope, id, dynamoProps);
|
|
175
179
|
}
|
|
176
180
|
if (props.type === "ClickHouse") {
|
|
177
181
|
const clickhouseProps = {
|
|
178
182
|
...props,
|
|
179
183
|
vpc: props.vpc ?? app.getVpc(),
|
|
180
|
-
applicationId: props.applicationId ?? app.getName()
|
|
184
|
+
applicationId: props.applicationId ?? app.getName(),
|
|
185
|
+
appBackupTier: props.appBackupTier ?? app.getBackupTier()
|
|
181
186
|
};
|
|
182
187
|
return new ClickHouseDatabase(scope, id, clickhouseProps);
|
|
183
188
|
}
|
|
184
189
|
const databaseProps = {
|
|
185
190
|
...props,
|
|
186
191
|
vpc: props.vpc ?? app.getVpc(),
|
|
187
|
-
applicationId: props.applicationId ?? app.getName()
|
|
192
|
+
applicationId: props.applicationId ?? app.getName(),
|
|
193
|
+
appBackupTier: props.appBackupTier ?? app.getBackupTier()
|
|
188
194
|
};
|
|
189
195
|
validateDatabaseProps(databaseProps);
|
|
190
196
|
return new RelationalDatabase(scope, id, databaseProps);
|
|
@@ -223,7 +229,8 @@ export class DynamoDBDatabase extends Construct {
|
|
|
223
229
|
pointInTimeRecovery: props.pointInTimeRecovery,
|
|
224
230
|
encryption: props.encryption,
|
|
225
231
|
deletionProtection: props.deletionProtection,
|
|
226
|
-
removalPolicy: props.removalPolicy
|
|
232
|
+
removalPolicy: props.removalPolicy,
|
|
233
|
+
appBackupTier: props.appBackupTier
|
|
227
234
|
};
|
|
228
235
|
this.table = new DynamoDBTable(this, id, tableProps);
|
|
229
236
|
}
|
|
@@ -390,6 +397,7 @@ export class RelationalDatabase extends Construct {
|
|
|
390
397
|
credentials: props.credentials,
|
|
391
398
|
encryption: props.encryption,
|
|
392
399
|
backupRetention: props.backupRetention,
|
|
400
|
+
appBackupTier: props.appBackupTier,
|
|
393
401
|
monitoringInterval: props.monitoringInterval,
|
|
394
402
|
preferredMaintenanceWindow: props.preferredMaintenanceWindow,
|
|
395
403
|
port: props.port,
|
|
@@ -429,6 +437,7 @@ export class RelationalDatabase extends Construct {
|
|
|
429
437
|
credentials: props.credentials,
|
|
430
438
|
encryption: props.encryption,
|
|
431
439
|
backupRetention: props.backupRetention,
|
|
440
|
+
appBackupTier: props.appBackupTier,
|
|
432
441
|
monitoringInterval: props.monitoringInterval,
|
|
433
442
|
preferredMaintenanceWindow: props.preferredMaintenanceWindow,
|
|
434
443
|
port: props.port,
|
|
@@ -459,6 +468,7 @@ export class RelationalDatabase extends Construct {
|
|
|
459
468
|
backupRetention: props.backupRetention !== undefined
|
|
460
469
|
? Duration.days(props.backupRetention)
|
|
461
470
|
: undefined,
|
|
471
|
+
appBackupTier: props.appBackupTier,
|
|
462
472
|
databaseInsights: props.databaseInsights,
|
|
463
473
|
proxy: props.proxy,
|
|
464
474
|
readReplica: props.readReplica,
|
|
@@ -11,7 +11,12 @@ export class FivetranProxy extends Stack {
|
|
|
11
11
|
serviceName: "fivetranProxy",
|
|
12
12
|
vpc: props.vpc,
|
|
13
13
|
instanceType: props.instanceType || "t2.micro",
|
|
14
|
-
|
|
14
|
+
// Not a caller's choice. This proxy exists so Fivetran can reach a
|
|
15
|
+
// private database over SSH — it provisions the `fivetran` user and its
|
|
16
|
+
// authorized_keys above — so the instance must be publicly reachable on
|
|
17
|
+
// port 22. The prop that used to sit here was `props.enableSSH || true`,
|
|
18
|
+
// which accepted a `false` it could never honour.
|
|
19
|
+
enableSSH: true,
|
|
15
20
|
env: {
|
|
16
21
|
account: props.env?.account,
|
|
17
22
|
region: props.env?.region
|
|
@@ -162,7 +162,7 @@ export interface PatternLambdaConfig {
|
|
|
162
162
|
memorySize?: number;
|
|
163
163
|
/** Timeout in seconds. Range: 1-900. */
|
|
164
164
|
timeout?: number;
|
|
165
|
-
/** Ephemeral storage size in
|
|
165
|
+
/** Ephemeral storage size in MiB. Range: 512-10240. Default: 512. */
|
|
166
166
|
ephemeralStorageSize?: number;
|
|
167
167
|
}
|
|
168
168
|
/**
|
|
@@ -468,16 +468,16 @@ export interface StaticSiteCdnConfig {
|
|
|
468
468
|
* optional contact-form endpoint (Lambda Function URL → SES, CloudFront-fronted).
|
|
469
469
|
*
|
|
470
470
|
* @example
|
|
471
|
-
* app.addPattern(PatternFactory.build("
|
|
471
|
+
* app.addPattern(PatternFactory.build("StorefrontSite", {
|
|
472
472
|
* type: "staticsite",
|
|
473
|
-
* name: "
|
|
474
|
-
* source: "../../
|
|
473
|
+
* name: "storefront",
|
|
474
|
+
* source: "../../storefront-static-export",
|
|
475
475
|
* build: { command: "npm run build", outputDir: "out" },
|
|
476
476
|
* routing: "multipage",
|
|
477
477
|
* security: { headers: true },
|
|
478
|
-
* domain: "
|
|
479
|
-
* zoneName: "
|
|
480
|
-
* forms: { to: "hello@
|
|
478
|
+
* domain: "storefront.example",
|
|
479
|
+
* zoneName: "storefront.example", // or a CLI-injected `managedDomain` binding
|
|
480
|
+
* forms: { to: "hello@storefront.example", corsOrigin: "https://storefront.example" }
|
|
481
481
|
* }));
|
|
482
482
|
*/
|
|
483
483
|
export interface IStaticSiteProps {
|
|
@@ -315,7 +315,10 @@ export class MessagingFactory {
|
|
|
315
315
|
applicationId: props.applicationId ?? app.getName()
|
|
316
316
|
});
|
|
317
317
|
case "topic":
|
|
318
|
-
return new TopicMessaging(scope, id,
|
|
318
|
+
return new TopicMessaging(scope, id, {
|
|
319
|
+
...props,
|
|
320
|
+
appBackupTier: props.appBackupTier ?? app.getBackupTier()
|
|
321
|
+
});
|
|
319
322
|
case "eventBus":
|
|
320
323
|
return new EventBusMessaging(scope, id, props);
|
|
321
324
|
default: {
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
*
|
|
8
8
|
* - Nothing here guesses a hosted zone. The old `extractRootDomain` label
|
|
9
9
|
* split resolved every multi-label public suffix wrongly (e.g.
|
|
10
|
-
* '
|
|
10
|
+
* 'example.com.au' → zone 'com.au') and mis-placed delegated
|
|
11
11
|
* sub-zone records; it is deleted. Zone identity now arrives explicitly, in
|
|
12
12
|
* priority order: a CLI-injected {@link ManagedDomainBinding} (literal
|
|
13
13
|
* values — crosses accounts and regions), the legacy
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
*
|
|
8
8
|
* - Nothing here guesses a hosted zone. The old `extractRootDomain` label
|
|
9
9
|
* split resolved every multi-label public suffix wrongly (e.g.
|
|
10
|
-
* '
|
|
10
|
+
* 'example.com.au' → zone 'com.au') and mis-placed delegated
|
|
11
11
|
* sub-zone records; it is deleted. Zone identity now arrives explicitly, in
|
|
12
12
|
* priority order: a CLI-injected {@link ManagedDomainBinding} (literal
|
|
13
13
|
* values — crosses accounts and regions), the legacy
|
|
@@ -41,8 +41,6 @@ const PAYLOAD_DEFAULTS = {
|
|
|
41
41
|
SOURCE: "../..",
|
|
42
42
|
DATABASE: {
|
|
43
43
|
TYPE: "Instance",
|
|
44
|
-
/** AWS default; 7 days covers most accidental data loss recovery scenarios */
|
|
45
|
-
BACKUP_RETENTION: 7,
|
|
46
44
|
DELETION_PROTECTION: true
|
|
47
45
|
},
|
|
48
46
|
COMPUTE: {
|
|
@@ -172,7 +170,7 @@ export class Payload extends Construct {
|
|
|
172
170
|
return {
|
|
173
171
|
databaseName: dbConfig.databaseName ?? toValidDatabaseName(this.props.name),
|
|
174
172
|
databaseEngine: dbConfig.databaseEngine ?? "postgresql",
|
|
175
|
-
backupRetention: dbConfig.backupRetention
|
|
173
|
+
backupRetention: dbConfig.backupRetention,
|
|
176
174
|
deletionProtection: dbConfig.deletionProtection ??
|
|
177
175
|
PAYLOAD_DEFAULTS.DATABASE.DELETION_PROTECTION,
|
|
178
176
|
publiclyAccessible: dbConfig.publiclyAccessible,
|
|
@@ -13,16 +13,16 @@
|
|
|
13
13
|
* - Contact-form Lambda + Function URL (when `forms` is set)
|
|
14
14
|
*
|
|
15
15
|
* @example
|
|
16
|
-
* const site = app.addPattern(PatternFactory.build("
|
|
16
|
+
* const site = app.addPattern(PatternFactory.build("StorefrontSite", {
|
|
17
17
|
* type: "staticsite",
|
|
18
|
-
* name: "
|
|
19
|
-
* source: "../../
|
|
18
|
+
* name: "storefront",
|
|
19
|
+
* source: "../../storefront-static-export",
|
|
20
20
|
* build: { command: "npm run build", outputDir: "out" },
|
|
21
21
|
* routing: "multipage",
|
|
22
22
|
* security: { headers: true },
|
|
23
|
-
* domain: "
|
|
24
|
-
* zoneName: "
|
|
25
|
-
* forms: { to: "hello@
|
|
23
|
+
* domain: "storefront.example",
|
|
24
|
+
* zoneName: "storefront.example", // or a CLI-injected `managedDomain` binding
|
|
25
|
+
* forms: { to: "hello@storefront.example", corsOrigin: "https://storefront.example" }
|
|
26
26
|
* }));
|
|
27
27
|
*/
|
|
28
28
|
import { Construct } from "constructs";
|