@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
|
@@ -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";
|
|
@@ -207,6 +207,9 @@ export class StaticSite extends Construct {
|
|
|
207
207
|
reservedConcurrentExecutions: forms.maxConcurrency ?? FORMS_DEFAULTS.MAX_CONCURRENCY,
|
|
208
208
|
lambdaDescription: `Contact-form handler for ${this.props.name}`,
|
|
209
209
|
roleDescription: `Contact-form send role for ${this.props.name}`,
|
|
210
|
+
// An app workload, not a framework handler: tracing and encryption
|
|
211
|
+
// follow the app backup tier like every factory-built Lambda.
|
|
212
|
+
appBackupTier: this.app.getBackupTier(),
|
|
210
213
|
enableFunctionUrl: true,
|
|
211
214
|
functionUrlAuthType: FunctionUrlAuthType.NONE,
|
|
212
215
|
functionUrlCors: {
|
|
@@ -60,6 +60,8 @@ export interface CloudFrontDistributionProps {
|
|
|
60
60
|
comment?: string;
|
|
61
61
|
enableLogging?: boolean;
|
|
62
62
|
logBucket?: IBucket;
|
|
63
|
+
/** Key prefix for standard logs delivered to `logBucket`. */
|
|
64
|
+
logFilePrefix?: string;
|
|
63
65
|
priceClass?: CloudFrontPriceClass;
|
|
64
66
|
/** Adds a CloudFront Function to copy the viewer Host header into X-Forwarded-Host.
|
|
65
67
|
* Required when the origin is a Lambda Function URL or ALB behind CloudFront,
|
|
@@ -110,6 +110,7 @@ export class CloudFrontDistribution extends Construct {
|
|
|
110
110
|
comment: props.comment,
|
|
111
111
|
enableLogging: props.enableLogging,
|
|
112
112
|
logBucket: props.logBucket,
|
|
113
|
+
logFilePrefix: props.logFilePrefix,
|
|
113
114
|
priceClass: this.resolvePriceClass(props.priceClass)
|
|
114
115
|
});
|
|
115
116
|
// The CDN-qualified export is unconditional and always unique, so an app
|
|
@@ -7,6 +7,16 @@ export interface ApplicationLoadBalancerOptions {
|
|
|
7
7
|
readonly internetFacing: boolean;
|
|
8
8
|
readonly loadBalancerName?: string;
|
|
9
9
|
readonly securityGroup?: ISecurityGroup;
|
|
10
|
+
/**
|
|
11
|
+
* `deletion_protection.enabled` on the load balancer (CDK emits the
|
|
12
|
+
* attribute as `false` when unset, so an unprotected template is unchanged).
|
|
13
|
+
*/
|
|
14
|
+
readonly deletionProtection?: boolean;
|
|
15
|
+
/**
|
|
16
|
+
* Deliver access logs to the stack's shared access-log bucket under
|
|
17
|
+
* `alb/<id>/`. The bucket is created on first use.
|
|
18
|
+
*/
|
|
19
|
+
readonly accessLogs?: boolean;
|
|
10
20
|
}
|
|
11
21
|
/**
|
|
12
22
|
* Context-free Application Load Balancer factory shared by the ECS cluster path
|
|
@@ -2,6 +2,7 @@ import { Duration } from "aws-cdk-lib";
|
|
|
2
2
|
import { ApplicationLoadBalancer } from "aws-cdk-lib/aws-elasticloadbalancingv2";
|
|
3
3
|
import { SubnetType } from "aws-cdk-lib/aws-ec2";
|
|
4
4
|
import { DEFAULT_ALB_IDLE_TIMEOUT_SECONDS } from "@fjall/util/httpKeepAlive";
|
|
5
|
+
import { stackAccessLogBucket } from "../logging/accessLogBucket.js";
|
|
5
6
|
/**
|
|
6
7
|
* Context-free Application Load Balancer factory shared by the ECS cluster path
|
|
7
8
|
* and the dev substrate. Subnet placement is derived from `internetFacing`
|
|
@@ -9,7 +10,7 @@ import { DEFAULT_ALB_IDLE_TIMEOUT_SECONDS } from "@fjall/util/httpKeepAlive";
|
|
|
9
10
|
* ECS path's existing derivation so the extraction is byte-identical.
|
|
10
11
|
*/
|
|
11
12
|
export function createApplicationLoadBalancer(scope, id, options) {
|
|
12
|
-
|
|
13
|
+
const loadBalancer = new ApplicationLoadBalancer(scope, id, {
|
|
13
14
|
vpc: options.vpc,
|
|
14
15
|
internetFacing: options.internetFacing,
|
|
15
16
|
// Pinned (rather than left at the AWS default of the same value) because
|
|
@@ -23,10 +24,15 @@ export function createApplicationLoadBalancer(scope, id, options) {
|
|
|
23
24
|
...(options.loadBalancerName && {
|
|
24
25
|
loadBalancerName: options.loadBalancerName
|
|
25
26
|
}),
|
|
27
|
+
deletionProtection: options.deletionProtection,
|
|
26
28
|
vpcSubnets: {
|
|
27
29
|
subnetType: options.internetFacing
|
|
28
30
|
? SubnetType.PUBLIC
|
|
29
31
|
: SubnetType.PRIVATE_WITH_EGRESS
|
|
30
32
|
}
|
|
31
33
|
});
|
|
34
|
+
if (options.accessLogs === true) {
|
|
35
|
+
loadBalancer.logAccessLogs(stackAccessLogBucket(scope), `alb/${id}`);
|
|
36
|
+
}
|
|
37
|
+
return loadBalancer;
|
|
32
38
|
}
|
|
@@ -3,6 +3,7 @@ import { Connections, type IConnectable } from "aws-cdk-lib/aws-ec2";
|
|
|
3
3
|
import { Construct } from "constructs";
|
|
4
4
|
import type { StackBuilder } from "../base/awsStack.js";
|
|
5
5
|
import type { ApplicationListener, ApplicationLoadBalancer } from "aws-cdk-lib/aws-elasticloadbalancingv2";
|
|
6
|
+
import { StackEncryptionKey } from "../secrets/stackEncryptionKey.js";
|
|
6
7
|
import { type EcsClusterProps } from "./ecsTypes.js";
|
|
7
8
|
import { type EcsIngressProfile } from "./ingressProfile.js";
|
|
8
9
|
export * from "./ecsTypes.js";
|
|
@@ -65,6 +66,16 @@ export default class EcsCluster extends Construct implements IConnectable {
|
|
|
65
66
|
private outputName;
|
|
66
67
|
private loadBalancerDisabled;
|
|
67
68
|
private directAccessEnabled;
|
|
69
|
+
private readonly containerInsightsEnabled;
|
|
70
|
+
/**
|
|
71
|
+
* The stack's shared key when the app backup tier encrypts (resolved once,
|
|
72
|
+
* here); undefined otherwise. Read by the patterns layer for the log
|
|
73
|
+
* groups it creates beside the cluster's own, so the tier is consulted in
|
|
74
|
+
* one place.
|
|
75
|
+
*/
|
|
76
|
+
readonly encryptionKey: StackEncryptionKey | undefined;
|
|
77
|
+
private readonly loadBalancerDeletionProtection;
|
|
78
|
+
private readonly loadBalancerAccessLogs;
|
|
68
79
|
private priorityState;
|
|
69
80
|
constructor(scope: Construct, id: string, props: EcsClusterProps);
|
|
70
81
|
private get ctx();
|
|
@@ -3,6 +3,9 @@ import { Connections, Port } from "aws-cdk-lib/aws-ec2";
|
|
|
3
3
|
import { Construct } from "constructs";
|
|
4
4
|
import { CfnOutput, Aspects } from "aws-cdk-lib";
|
|
5
5
|
import { processConnections } from "../../../utils/connections.js";
|
|
6
|
+
import { tierDefault } from "../../../utils/backupTierMapping.js";
|
|
7
|
+
import { envAwareRemovalPolicyDefault } from "../../../utils/removalPolicy.js";
|
|
8
|
+
import { StackEncryptionKey } from "../secrets/stackEncryptionKey.js";
|
|
6
9
|
import { DEFAULT_FRAMEWORK_LOG_RETENTION, LogGroup } from "../logging/logGroup.js";
|
|
7
10
|
import { stackScopedExportName } from "../../../utils/exportNaming.js";
|
|
8
11
|
import { toPascalCase } from "../../../utils/capitaliseString.js";
|
|
@@ -84,6 +87,16 @@ export default class EcsCluster extends Construct {
|
|
|
84
87
|
outputName;
|
|
85
88
|
loadBalancerDisabled;
|
|
86
89
|
directAccessEnabled;
|
|
90
|
+
containerInsightsEnabled;
|
|
91
|
+
/**
|
|
92
|
+
* The stack's shared key when the app backup tier encrypts (resolved once,
|
|
93
|
+
* here); undefined otherwise. Read by the patterns layer for the log
|
|
94
|
+
* groups it creates beside the cluster's own, so the tier is consulted in
|
|
95
|
+
* one place.
|
|
96
|
+
*/
|
|
97
|
+
encryptionKey;
|
|
98
|
+
loadBalancerDeletionProtection;
|
|
99
|
+
loadBalancerAccessLogs;
|
|
87
100
|
// ALB priority management
|
|
88
101
|
priorityState = {
|
|
89
102
|
nextPriority: 100,
|
|
@@ -98,6 +111,21 @@ export default class EcsCluster extends Construct {
|
|
|
98
111
|
this.directAccessEnabled = props.cluster?.directAccess === true;
|
|
99
112
|
this.loadBalancerDisabled =
|
|
100
113
|
props.cluster?.loadBalancer === false || this.directAccessEnabled;
|
|
114
|
+
// Tier-owned defaults resolve exactly once, here; every helper reads the
|
|
115
|
+
// resolved value from ctx. An explicit prop is the override.
|
|
116
|
+
const tier = props.appBackupTier;
|
|
117
|
+
this.containerInsightsEnabled =
|
|
118
|
+
props.containerInsights ?? tierDefault(tier, "containerInsights");
|
|
119
|
+
this.encryptionKey = tierDefault(tier, "customerManagedKeys")
|
|
120
|
+
? StackEncryptionKey.of(this)
|
|
121
|
+
: undefined;
|
|
122
|
+
this.loadBalancerDeletionProtection =
|
|
123
|
+
props.cluster?.loadBalancerDeletionProtection ??
|
|
124
|
+
(tierDefault(tier, "deletionProtection") &&
|
|
125
|
+
envAwareRemovalPolicyDefault() === "RETAIN");
|
|
126
|
+
this.loadBalancerAccessLogs =
|
|
127
|
+
props.cluster?.loadBalancerAccessLogs ??
|
|
128
|
+
tierDefault(tier, "albAccessLogs");
|
|
101
129
|
validateEcsClusterProps(props, this);
|
|
102
130
|
this.cluster = this.addCluster(props);
|
|
103
131
|
for (const serviceProps of props.services) {
|
|
@@ -161,7 +189,10 @@ export default class EcsCluster extends Construct {
|
|
|
161
189
|
cluster: this.cluster,
|
|
162
190
|
outputName: this.outputName,
|
|
163
191
|
loadBalancerDisabled: this.loadBalancerDisabled,
|
|
164
|
-
directAccessEnabled: this.directAccessEnabled
|
|
192
|
+
directAccessEnabled: this.directAccessEnabled,
|
|
193
|
+
encryptionKey: this.encryptionKey,
|
|
194
|
+
loadBalancerDeletionProtection: this.loadBalancerDeletionProtection,
|
|
195
|
+
loadBalancerAccessLogs: this.loadBalancerAccessLogs
|
|
165
196
|
};
|
|
166
197
|
}
|
|
167
198
|
/** Get the cluster's load balancer. Undefined if disabled. */
|
|
@@ -317,7 +348,7 @@ export default class EcsCluster extends Construct {
|
|
|
317
348
|
config: typeof serviceProps.alarms === "object" ? serviceProps.alarms : {},
|
|
318
349
|
alarmTopic: this.props.alertsTopic,
|
|
319
350
|
applicationId: this.props.applicationId,
|
|
320
|
-
containerInsightsEnabled: this.
|
|
351
|
+
containerInsightsEnabled: this.containerInsightsEnabled
|
|
321
352
|
});
|
|
322
353
|
if (serviceProps.logAlarms && serviceProps.logAlarms.length > 0) {
|
|
323
354
|
createLogPatternAlarms({
|
|
@@ -385,18 +416,32 @@ export default class EcsCluster extends Construct {
|
|
|
385
416
|
const needsFargate = this.anyServiceUsesFargate();
|
|
386
417
|
// Every service enables execute command; without an OVERRIDE here the
|
|
387
418
|
// exec session output interleaves into the container app log groups
|
|
388
|
-
// (DEFAULT mode).
|
|
389
|
-
|
|
419
|
+
// (DEFAULT mode). Session encryption follows the tier: the shared stack
|
|
420
|
+
// key encrypts the session AND its audit log group (ECS requires the
|
|
421
|
+
// audit group to be CMK-encrypted before it will encrypt the session).
|
|
422
|
+
const encryptionKey = this.encryptionKey;
|
|
423
|
+
const execAuditLogGroup = new LogGroup(this, `${props.clusterName}ExecAuditLogGroup`, {
|
|
424
|
+
retention: DEFAULT_FRAMEWORK_LOG_RETENTION,
|
|
425
|
+
...(encryptionKey !== undefined && {
|
|
426
|
+
encryptionKey: encryptionKey.forLogGroups()
|
|
427
|
+
})
|
|
428
|
+
});
|
|
390
429
|
const cluster = new CdkCluster(this, `${props.clusterName}Cluster`, {
|
|
391
430
|
vpc: props.vpc,
|
|
392
431
|
clusterName: props.clusterName,
|
|
393
|
-
containerInsightsV2:
|
|
432
|
+
containerInsightsV2: this.containerInsightsEnabled
|
|
394
433
|
? ContainerInsights.ENABLED
|
|
395
434
|
: ContainerInsights.DISABLED,
|
|
396
435
|
enableFargateCapacityProviders: needsFargate,
|
|
397
436
|
executeCommandConfiguration: {
|
|
437
|
+
...(encryptionKey !== undefined && { kmsKey: encryptionKey.key }),
|
|
398
438
|
logging: ExecuteCommandLogging.OVERRIDE,
|
|
399
|
-
logConfiguration: {
|
|
439
|
+
logConfiguration: {
|
|
440
|
+
cloudWatchLogGroup: execAuditLogGroup,
|
|
441
|
+
...(encryptionKey !== undefined && {
|
|
442
|
+
cloudWatchEncryptionEnabled: true
|
|
443
|
+
})
|
|
444
|
+
}
|
|
400
445
|
}
|
|
401
446
|
});
|
|
402
447
|
new CfnOutput(this, `${this.outputName}DeployableCluster`, {
|
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
import type { Construct } from "constructs";
|
|
2
2
|
import type { Cluster as CdkCluster } from "aws-cdk-lib/aws-ecs";
|
|
3
|
+
import type { StackEncryptionKey } from "../secrets/stackEncryptionKey.js";
|
|
3
4
|
import type { EcsClusterProps } from "./ecsTypes.js";
|
|
4
|
-
/**
|
|
5
|
+
/**
|
|
6
|
+
* Shared state passed from EcsCluster to extracted helper functions. The
|
|
7
|
+
* tier-owned knobs are resolved once in the constructor and read here as
|
|
8
|
+
* plain values — helpers never consult the tier themselves.
|
|
9
|
+
*/
|
|
5
10
|
export interface EcsConstructContext {
|
|
6
11
|
scope: Construct;
|
|
7
12
|
props: EcsClusterProps;
|
|
@@ -9,4 +14,8 @@ export interface EcsConstructContext {
|
|
|
9
14
|
outputName: string;
|
|
10
15
|
loadBalancerDisabled: boolean;
|
|
11
16
|
directAccessEnabled: boolean;
|
|
17
|
+
/** The stack's shared key when the tier encrypts; undefined otherwise. */
|
|
18
|
+
encryptionKey: StackEncryptionKey | undefined;
|
|
19
|
+
loadBalancerDeletionProtection: boolean;
|
|
20
|
+
loadBalancerAccessLogs: boolean;
|
|
12
21
|
}
|
|
@@ -44,7 +44,9 @@ export function addLoadBalancer(ctx, anyServiceUsesEc2, asgSecurityGroup) {
|
|
|
44
44
|
loadBalancerName: truncatedLoadBalancerName,
|
|
45
45
|
...(loadBalancerSecurityGroup && {
|
|
46
46
|
securityGroup: loadBalancerSecurityGroup
|
|
47
|
-
})
|
|
47
|
+
}),
|
|
48
|
+
deletionProtection: ctx.loadBalancerDeletionProtection,
|
|
49
|
+
accessLogs: ctx.loadBalancerAccessLogs
|
|
48
50
|
});
|
|
49
51
|
new CfnOutput(ctx.scope, `${ctx.outputName}LoadBalancerDnsName`, {
|
|
50
52
|
key: `${ctx.outputName}LoadBalancerDnsName`,
|
|
@@ -148,7 +148,10 @@ export function addContainersToTask(ctx, serviceName, serviceProps, taskDefiniti
|
|
|
148
148
|
// Removal: env-aware wrapper default (D17) — production RETAIN keeps
|
|
149
149
|
// fail-closed rollback evidence; non-production DESTROY avoids orphans.
|
|
150
150
|
const logGroup = new LogGroup(ctx.scope, `${ctx.props.clusterName}${serviceName}LogGroup`, {
|
|
151
|
-
retention: DEFAULT_LOG_RETENTION
|
|
151
|
+
retention: DEFAULT_LOG_RETENTION,
|
|
152
|
+
...(ctx.encryptionKey !== undefined && {
|
|
153
|
+
encryptionKey: ctx.encryptionKey.forLogGroups()
|
|
154
|
+
})
|
|
152
155
|
});
|
|
153
156
|
for (const containerConfig of serviceProps.containers) {
|
|
154
157
|
const image = getContainerImage(ctx, serviceName, containerConfig, serviceProps);
|
|
@@ -16,6 +16,7 @@ import { type Role } from "aws-cdk-lib/aws-iam";
|
|
|
16
16
|
import { type HostedZone as FjallHostedZone } from "../networking/hostedZone.js";
|
|
17
17
|
import { type Certificate } from "aws-cdk-lib/aws-certificatemanager";
|
|
18
18
|
import { type ConnectionSpec } from "../../../utils/connector.js";
|
|
19
|
+
import type { BackupTier } from "../../../utils/backupTierMapping.js";
|
|
19
20
|
import { type RemoteConnectionSpec } from "./ecsRemoteConnections.js";
|
|
20
21
|
import { type SecretImport } from "../secrets/index.js";
|
|
21
22
|
import type { ManagedDomainBinding, ManagedDomainExports } from "../../../utils/domainTypes.js";
|
|
@@ -472,6 +473,20 @@ export interface EcsClusterClusterConfig {
|
|
|
472
473
|
* by the patterns layer (AC26 — `EcsClusterConfig.securityGroup`).
|
|
473
474
|
*/
|
|
474
475
|
securityGroup?: ISecurityGroup;
|
|
476
|
+
/**
|
|
477
|
+
* Deletion protection on the shared load balancer. Default: the app backup
|
|
478
|
+
* tier's `deletionProtection` column, applied only when the stack's
|
|
479
|
+
* environment resolves to production — a dev or staging stack is torn
|
|
480
|
+
* down by design. The destroy pre-flight switches it off before a
|
|
481
|
+
* consented destroy.
|
|
482
|
+
*/
|
|
483
|
+
loadBalancerDeletionProtection?: boolean;
|
|
484
|
+
/**
|
|
485
|
+
* Access logs from the shared load balancer, delivered to the stack's
|
|
486
|
+
* lifecycle-expired access-log bucket under `alb/<load balancer id>/`.
|
|
487
|
+
* Default: the app backup tier's `albAccessLogs` column.
|
|
488
|
+
*/
|
|
489
|
+
loadBalancerAccessLogs?: boolean;
|
|
475
490
|
}
|
|
476
491
|
/**
|
|
477
492
|
* Routing configuration for path/host-based routing on the ALB.
|
|
@@ -749,15 +764,23 @@ export interface EcsClusterProps {
|
|
|
749
764
|
*/
|
|
750
765
|
taskStopWatchdog?: false;
|
|
751
766
|
/**
|
|
752
|
-
* CloudWatch Container Insights
|
|
753
|
-
*
|
|
754
|
-
*
|
|
755
|
-
*
|
|
756
|
-
*
|
|
757
|
-
*
|
|
758
|
-
*
|
|
767
|
+
* CloudWatch Container Insights. Default: the app backup tier's
|
|
768
|
+
* `containerInsights` column (on at enterprise, off below). Enabling it
|
|
769
|
+
* bills ~$0.30/metric-month of custom `ECS/ContainerInsights` metrics per
|
|
770
|
+
* cluster plus performance-log ingestion. The per-service running-tasks
|
|
771
|
+
* alarm is the one construct consumer of that namespace (`RunningTaskCount`
|
|
772
|
+
* exists nowhere else) and is skipped when insights are off; every other
|
|
773
|
+
* alarm sits on `AWS/ECS` and `Fjall/ECS`. `false` is an explicit,
|
|
774
|
+
* auditable opt-out at enterprise.
|
|
775
|
+
*/
|
|
776
|
+
containerInsights?: boolean;
|
|
777
|
+
/**
|
|
778
|
+
* The owning app's backup tier, which the tier-owned defaults (Container
|
|
779
|
+
* Insights, exec-session and log-group encryption, load-balancer deletion
|
|
780
|
+
* protection and access logs) follow. `ComputeFactory` fills it from the
|
|
781
|
+
* App; pass it only for direct `new EcsCluster(...)` construction.
|
|
759
782
|
*/
|
|
760
|
-
|
|
783
|
+
appBackupTier?: BackupTier;
|
|
761
784
|
}
|
|
762
785
|
/**
|
|
763
786
|
* Data tracked for each service in the cluster.
|
|
@@ -3,6 +3,7 @@ import { type Bucket } from "aws-cdk-lib/aws-s3";
|
|
|
3
3
|
import { PolicyStatement, type IRole } from "aws-cdk-lib/aws-iam";
|
|
4
4
|
import { type IVpc } from "aws-cdk-lib/aws-ec2";
|
|
5
5
|
import type { RetentionDays } from "aws-cdk-lib/aws-logs";
|
|
6
|
+
import { type BackupTier } from "../../../utils/backupTierMapping.js";
|
|
6
7
|
import { type IQueue } from "aws-cdk-lib/aws-sqs";
|
|
7
8
|
import { type ITable } from "aws-cdk-lib/aws-dynamodb";
|
|
8
9
|
import { type Construct } from "constructs";
|
|
@@ -92,8 +93,24 @@ export interface LambdaFunctionProps {
|
|
|
92
93
|
alarms?: LambdaAlarmThresholds | false;
|
|
93
94
|
/** Application ID for alarm tagging. */
|
|
94
95
|
applicationId?: string;
|
|
96
|
+
/**
|
|
97
|
+
* Active X-Ray tracing. Default: the app backup tier's `lambdaTracing`
|
|
98
|
+
* column (on at enterprise).
|
|
99
|
+
*/
|
|
100
|
+
tracing?: boolean;
|
|
101
|
+
/**
|
|
102
|
+
* The owning app's backup tier, which the tier-owned defaults (tracing,
|
|
103
|
+
* environment-variable and log-group encryption) follow. `ComputeFactory`
|
|
104
|
+
* fills it from the App; pass it only for direct `new LambdaFunction(...)`
|
|
105
|
+
* construction.
|
|
106
|
+
*/
|
|
107
|
+
appBackupTier?: BackupTier;
|
|
95
108
|
}
|
|
96
|
-
|
|
109
|
+
/**
|
|
110
|
+
* Framework custom-resource handlers are tier-agnostic: the tier-owned
|
|
111
|
+
* knobs are excluded at the type rather than silently ignored.
|
|
112
|
+
*/
|
|
113
|
+
export interface SingletonFunctionProps extends Omit<LambdaFunctionProps, "tracing" | "appBackupTier"> {
|
|
97
114
|
uuid?: string;
|
|
98
115
|
ephemeralStorageSize?: number;
|
|
99
116
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Annotations, CfnOutput, Duration, Stack, Size } from "aws-cdk-lib";
|
|
2
|
-
import { SingletonFunction as singletonFunction, Function, Code, Architecture, FunctionUrlAuthType, StartingPosition, LayerVersion } from "aws-cdk-lib/aws-lambda";
|
|
2
|
+
import { SingletonFunction as singletonFunction, Function, Code, Architecture, FunctionUrlAuthType, StartingPosition, LayerVersion, Tracing } from "aws-cdk-lib/aws-lambda";
|
|
3
3
|
import { FactName } from "aws-cdk-lib/region-info";
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
@@ -8,6 +8,8 @@ import { SqsEventSource, DynamoEventSource, S3EventSource } from "aws-cdk-lib/aw
|
|
|
8
8
|
import { EventType } from "aws-cdk-lib/aws-s3";
|
|
9
9
|
import { PolicyStatement, Effect } from "aws-cdk-lib/aws-iam";
|
|
10
10
|
import { DEFAULT_FRAMEWORK_LOG_RETENTION, LogGroup } from "../logging/logGroup.js";
|
|
11
|
+
import { tierDefault } from "../../../utils/backupTierMapping.js";
|
|
12
|
+
import { StackEncryptionKey } from "../secrets/stackEncryptionKey.js";
|
|
11
13
|
import { Queue, QueueEncryption } from "aws-cdk-lib/aws-sqs";
|
|
12
14
|
import { SQS_LIMITS } from "../messaging/sqs.js";
|
|
13
15
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
@@ -167,12 +169,25 @@ export class LambdaFunction extends Function {
|
|
|
167
169
|
asyncFailureQueueWired;
|
|
168
170
|
constructor(scope, id, props) {
|
|
169
171
|
validateRuntimeNotDeprecated(props.runtime, id);
|
|
172
|
+
// Both are Fjall props, not CDK ones: `tracing` collides with the CDK
|
|
173
|
+
// enum of the same name and `appBackupTier` is unknown to CDK.
|
|
174
|
+
const { tracing, appBackupTier, ...cdkProps } = props;
|
|
170
175
|
const vpcSubnets = props.vpc
|
|
171
176
|
? { subnetType: resolvePrivateSubnetType(props.vpc) }
|
|
172
177
|
: undefined;
|
|
178
|
+
const tracingEnabled = tracing ?? tierDefault(appBackupTier, "lambdaTracing");
|
|
179
|
+
// Grant-based: Lambda encrypts the environment through a KMS grant the
|
|
180
|
+
// deploying role creates, so the execution role needs no key policy.
|
|
181
|
+
const encryptionKey = tierDefault(appBackupTier, "customerManagedKeys")
|
|
182
|
+
? StackEncryptionKey.of(scope)
|
|
183
|
+
: undefined;
|
|
173
184
|
super(scope, id, {
|
|
174
|
-
...
|
|
185
|
+
...cdkProps,
|
|
175
186
|
vpcSubnets,
|
|
187
|
+
...(tracingEnabled && { tracing: Tracing.ACTIVE }),
|
|
188
|
+
...(encryptionKey !== undefined && {
|
|
189
|
+
environmentEncryption: encryptionKey.key
|
|
190
|
+
}),
|
|
176
191
|
timeout: Duration.seconds(props.timeout ?? LAMBDA_DEFAULT_TIMEOUT_SECONDS),
|
|
177
192
|
memorySize: props.memorySize ?? 128,
|
|
178
193
|
ephemeralStorageSize: props.ephemeralStorageSize
|
|
@@ -181,7 +196,10 @@ export class LambdaFunction extends Function {
|
|
|
181
196
|
description: props.lambdaDescription ?? `${id} Lambda`,
|
|
182
197
|
environment: props.environment,
|
|
183
198
|
logGroup: new LogGroup(scope, `${id}LogGroup`, {
|
|
184
|
-
retention: props.logGroupRetention ?? DEFAULT_FRAMEWORK_LOG_RETENTION
|
|
199
|
+
retention: props.logGroupRetention ?? DEFAULT_FRAMEWORK_LOG_RETENTION,
|
|
200
|
+
...(encryptionKey !== undefined && {
|
|
201
|
+
encryptionKey: encryptionKey.forLogGroups()
|
|
202
|
+
})
|
|
185
203
|
})
|
|
186
204
|
});
|
|
187
205
|
this.asyncFailureQueueProp = props.asyncFailureQueue;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Construct } from "constructs";
|
|
2
|
+
import { type BackupTier } from "../../../utils/backupTierMapping.js";
|
|
2
3
|
import { type ITable } from "aws-cdk-lib/aws-dynamodb";
|
|
3
4
|
import { type IGrantable, type Grant } from "aws-cdk-lib/aws-iam";
|
|
4
5
|
export type DynamoDBAttributeType = "S" | "N" | "B";
|
|
@@ -24,9 +25,27 @@ export interface DynamoDBTableProps {
|
|
|
24
25
|
timeToLiveAttribute?: string;
|
|
25
26
|
stream?: "NEW_IMAGE" | "OLD_IMAGE" | "NEW_AND_OLD_IMAGES" | "KEYS_ONLY";
|
|
26
27
|
pointInTimeRecovery?: boolean;
|
|
28
|
+
/**
|
|
29
|
+
* Encryption at rest. Default: the app backup tier's `customerManagedKeys`
|
|
30
|
+
* column — `CUSTOMER_MANAGED` at enterprise, `AWS_MANAGED` otherwise.
|
|
31
|
+
* `CUSTOMER_MANAGED` always encrypts with the stack's shared
|
|
32
|
+
* [[StackEncryptionKey]]; switching a live table's key is an in-place
|
|
33
|
+
* update.
|
|
34
|
+
*/
|
|
27
35
|
encryption?: "AWS_OWNED" | "AWS_MANAGED" | "CUSTOMER_MANAGED";
|
|
36
|
+
/**
|
|
37
|
+
* DynamoDB `DeletionProtectionEnabled`. Default: the app backup tier's
|
|
38
|
+
* `deletionProtection` column (on at resilient and enterprise). The
|
|
39
|
+
* destroy pre-flight switches it off before a consented destroy.
|
|
40
|
+
*/
|
|
28
41
|
deletionProtection?: boolean;
|
|
29
42
|
removalPolicy?: "DESTROY" | "RETAIN" | "SNAPSHOT";
|
|
43
|
+
/**
|
|
44
|
+
* The owning app's backup tier, which the tier-owned defaults (encryption,
|
|
45
|
+
* deletion protection) follow. `DatabaseFactory` fills it from the App;
|
|
46
|
+
* pass it only for direct `new DynamoDBTable(...)` construction.
|
|
47
|
+
*/
|
|
48
|
+
appBackupTier?: BackupTier;
|
|
30
49
|
}
|
|
31
50
|
export declare class DynamoDBTable extends Construct {
|
|
32
51
|
readonly id: string;
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { Construct } from "constructs";
|
|
2
2
|
import { CfnOutput } from "aws-cdk-lib";
|
|
3
|
+
import { tierDefault } from "../../../utils/backupTierMapping.js";
|
|
3
4
|
import { toRemovalPolicy } from "../../../utils/removalPolicy.js";
|
|
5
|
+
import { StackEncryptionKey } from "../secrets/stackEncryptionKey.js";
|
|
4
6
|
import { Table, AttributeType, BillingMode, StreamViewType, ProjectionType, TableEncryption } from "aws-cdk-lib/aws-dynamodb";
|
|
5
7
|
function toAttributeType(type) {
|
|
6
8
|
switch (type) {
|
|
@@ -64,6 +66,12 @@ export class DynamoDBTable extends Construct {
|
|
|
64
66
|
constructor(scope, id, props) {
|
|
65
67
|
super(scope, id);
|
|
66
68
|
this.id = id;
|
|
69
|
+
const encryption = props.encryption ??
|
|
70
|
+
(tierDefault(props.appBackupTier, "customerManagedKeys")
|
|
71
|
+
? "CUSTOMER_MANAGED"
|
|
72
|
+
: "AWS_MANAGED");
|
|
73
|
+
const deletionProtection = props.deletionProtection ??
|
|
74
|
+
tierDefault(props.appBackupTier, "deletionProtection");
|
|
67
75
|
this.table = new Table(this, `${id}Table`, {
|
|
68
76
|
tableName: props.tableName,
|
|
69
77
|
partitionKey: {
|
|
@@ -83,8 +91,11 @@ export class DynamoDBTable extends Construct {
|
|
|
83
91
|
pointInTimeRecoverySpecification: {
|
|
84
92
|
pointInTimeRecoveryEnabled: props.pointInTimeRecovery ?? true
|
|
85
93
|
},
|
|
86
|
-
encryption: toTableEncryption(
|
|
87
|
-
|
|
94
|
+
encryption: toTableEncryption(encryption),
|
|
95
|
+
...(encryption === "CUSTOMER_MANAGED" && {
|
|
96
|
+
encryptionKey: StackEncryptionKey.of(this).key
|
|
97
|
+
}),
|
|
98
|
+
deletionProtection,
|
|
88
99
|
removalPolicy: toRemovalPolicy(props.removalPolicy),
|
|
89
100
|
timeToLiveAttribute: props.timeToLiveAttribute
|
|
90
101
|
});
|
|
@@ -3,6 +3,7 @@ import { Connections, type IConnectable, type IVpc } from "aws-cdk-lib/aws-ec2";
|
|
|
3
3
|
import { DatabaseCluster, type CfnDBCluster, type IClusterEngine } from "aws-cdk-lib/aws-rds";
|
|
4
4
|
import { Construct } from "constructs";
|
|
5
5
|
import { Secret } from "../secrets/index.js";
|
|
6
|
+
import { type BackupTier } from "../../../utils/backupTierMapping.js";
|
|
6
7
|
import type { ITopic } from "aws-cdk-lib/aws-sns";
|
|
7
8
|
import { type RdsAlarmThresholds } from "../monitoring/index.js";
|
|
8
9
|
import { type EngineConfig, type ProxyConfig, type CredentialsConfig, type AuroraEncryptionConfig, type AuroraWriterConfig, type AuroraReadersConfig, type DatabaseInsightsConfig, type SnapshotTarget } from "../../../utils/databaseTypes.js";
|
|
@@ -40,7 +41,17 @@ interface RdsProps {
|
|
|
40
41
|
proxy?: ProxyConfig | false;
|
|
41
42
|
credentials?: CredentialsConfig;
|
|
42
43
|
encryption?: AuroraEncryptionConfig;
|
|
44
|
+
/**
|
|
45
|
+
* Automated backup retention in days. Defaults to the app backup tier's
|
|
46
|
+
* column (`tierDefault(appBackupTier, "rdsBackupRetentionDays")`).
|
|
47
|
+
*/
|
|
43
48
|
backupRetention?: number;
|
|
49
|
+
/**
|
|
50
|
+
* The owning app's backup tier (`App.getApp({ backup: { tier } })`), which
|
|
51
|
+
* the retention default follows. `DatabaseFactory` fills it from the App;
|
|
52
|
+
* pass it only for direct `new RdsAurora(...)` construction.
|
|
53
|
+
*/
|
|
54
|
+
appBackupTier?: BackupTier;
|
|
44
55
|
monitoringInterval?: Duration;
|
|
45
56
|
preferredMaintenanceWindow?: string;
|
|
46
57
|
port?: number;
|
|
@@ -6,8 +6,9 @@ import { Construct } from "constructs";
|
|
|
6
6
|
import { SecurityGroup } from "../networking/securityGroup.js";
|
|
7
7
|
import { Secret } from "../secrets/index.js";
|
|
8
8
|
import { ResourceNaming } from "../../../utils/resourceNaming.js";
|
|
9
|
+
import { tierDefault } from "../../../utils/backupTierMapping.js";
|
|
9
10
|
import { addProxyCfnOutput } from "./rdsProxyOutput.js";
|
|
10
|
-
import { getDatabaseInsightsRetention } from "./index.js";
|
|
11
|
+
import { getCdkDatabaseInsightsMode, getDatabaseInsightsRetention } from "./index.js";
|
|
11
12
|
import { RDS_DEFAULTS, SERVERLESS_V2_AUTO_PAUSE_MIN_SECONDS, SERVERLESS_V2_AUTO_PAUSE_MAX_SECONDS, MIN_AUTO_PAUSE_POSTGRES_VERSION } from "./rdsDefaults.js";
|
|
12
13
|
import { createRdsAlarms } from "../monitoring/index.js";
|
|
13
14
|
import { DEFAULT_POSTGRES_ENGINE_CONFIG, resolveDatabaseInsights, resolveStorageEncryptionKey, resolvePerformanceInsightsKey, addMultiUserSecretRotation, warnIfSnapshotUsernameAssumed } from "./rdsHelpers.js";
|
|
@@ -138,8 +139,26 @@ export class RdsAurora extends Construct {
|
|
|
138
139
|
const writerConfig = props.writer ?? {};
|
|
139
140
|
const writerPI = writerConfig.enableDatabaseInsights ?? piEnabled;
|
|
140
141
|
const writerSuffix = writerConfig.identifierSuffix;
|
|
141
|
-
|
|
142
|
+
// The tier owns the Database Insights mode; an explicit `mode` wins.
|
|
143
|
+
const diMode = piConfig?.mode ??
|
|
144
|
+
tierDefault(props.appBackupTier, "databaseInsightsMode");
|
|
142
145
|
const performanceInsightsRetention = getDatabaseInsightsRetention(diMode);
|
|
146
|
+
// Advanced mode is a cluster-level setting that CDK validates against
|
|
147
|
+
// every instance: the cluster must carry Performance Insights with the
|
|
148
|
+
// same 15-month retention and key the instances do, or synth throws a
|
|
149
|
+
// retention mismatch. Emitted only when it applies, so a standard
|
|
150
|
+
// template stays byte-identical.
|
|
151
|
+
const advancedInsights = piEnabled && diMode === "advanced";
|
|
152
|
+
const clusterInsightsProps = advancedInsights
|
|
153
|
+
? {
|
|
154
|
+
enablePerformanceInsights: true,
|
|
155
|
+
performanceInsightRetention: performanceInsightsRetention,
|
|
156
|
+
...(performanceInsightsKey && {
|
|
157
|
+
performanceInsightEncryptionKey: performanceInsightsKey
|
|
158
|
+
}),
|
|
159
|
+
databaseInsightsMode: getCdkDatabaseInsightsMode(diMode)
|
|
160
|
+
}
|
|
161
|
+
: {};
|
|
143
162
|
const writer = ClusterInstance.serverlessV2(`${this.databaseNameValue}Writer`, {
|
|
144
163
|
enablePerformanceInsights: writerPI,
|
|
145
164
|
performanceInsightEncryptionKey: writerPI
|
|
@@ -170,7 +189,8 @@ export class RdsAurora extends Construct {
|
|
|
170
189
|
? { subnetType: SubnetType.PUBLIC }
|
|
171
190
|
: { subnetType: SubnetType.PRIVATE_WITH_EGRESS };
|
|
172
191
|
this.backupRetentionDaysValue =
|
|
173
|
-
props.backupRetention ??
|
|
192
|
+
props.backupRetention ??
|
|
193
|
+
tierDefault(props.appBackupTier, "rdsBackupRetentionDays");
|
|
174
194
|
// Common props shared across all cluster creation paths
|
|
175
195
|
const baseClusterProps = {
|
|
176
196
|
vpc: props.vpc,
|
|
@@ -194,6 +214,7 @@ export class RdsAurora extends Construct {
|
|
|
194
214
|
removalPolicy: props.removalPolicy ?? RemovalPolicy.SNAPSHOT,
|
|
195
215
|
deletionProtection: props.deletionProtection ?? true,
|
|
196
216
|
iamAuthentication: true,
|
|
217
|
+
...clusterInsightsProps,
|
|
197
218
|
...(props.serverlessV2MinCapacity !== undefined && {
|
|
198
219
|
serverlessV2MinCapacity: props.serverlessV2MinCapacity
|
|
199
220
|
}),
|
|
@@ -6,6 +6,7 @@ import { type ITopic } from "aws-cdk-lib/aws-sns";
|
|
|
6
6
|
import { type Secret } from "../secrets/index.js";
|
|
7
7
|
import { type RdsAlarmThresholds } from "../monitoring/index.js";
|
|
8
8
|
import { type EngineConfig, type ProxyConfig, type CredentialsConfig, type AuroraEncryptionConfig, type AuroraWriterConfig, type AuroraReadersConfig, type DatabaseInsightsConfig, type SnapshotTarget } from "../../../utils/databaseTypes.js";
|
|
9
|
+
import { type BackupTier } from "../../../utils/backupTierMapping.js";
|
|
9
10
|
interface RdsAuroraGlobalProps {
|
|
10
11
|
vpc: IVpc;
|
|
11
12
|
databaseName: string;
|
|
@@ -37,8 +38,17 @@ interface RdsAuroraGlobalProps {
|
|
|
37
38
|
credentials?: CredentialsConfig;
|
|
38
39
|
/** Encryption configuration */
|
|
39
40
|
encryption?: AuroraEncryptionConfig;
|
|
40
|
-
/**
|
|
41
|
+
/**
|
|
42
|
+
* Backup retention in days. Defaults to the app backup tier's column
|
|
43
|
+
* (`tierDefault(appBackupTier, "rdsBackupRetentionDays")`) on every
|
|
44
|
+
* regional cluster.
|
|
45
|
+
*/
|
|
41
46
|
backupRetention?: number;
|
|
47
|
+
/**
|
|
48
|
+
* The owning app's backup tier, forwarded to each regional `RdsAurora`.
|
|
49
|
+
* `DatabaseFactory` fills it from the App.
|
|
50
|
+
*/
|
|
51
|
+
appBackupTier?: BackupTier;
|
|
42
52
|
/** CloudWatch monitoring interval. Default: 1 minute */
|
|
43
53
|
monitoringInterval?: Duration;
|
|
44
54
|
/** Preferred maintenance window. Default: "Sat:12:30-Sat:20:30" */
|
|
@@ -83,6 +83,7 @@ export class RdsAuroraGlobal extends Construct {
|
|
|
83
83
|
encryption: this.props.encryption,
|
|
84
84
|
// Operational configuration
|
|
85
85
|
backupRetention: this.props.backupRetention,
|
|
86
|
+
appBackupTier: this.props.appBackupTier,
|
|
86
87
|
monitoringInterval: this.props.monitoringInterval,
|
|
87
88
|
preferredMaintenanceWindow: this.props.preferredMaintenanceWindow,
|
|
88
89
|
port: this.props.port,
|
|
@@ -4,8 +4,6 @@ export declare const RDS_DEFAULTS: Readonly<{
|
|
|
4
4
|
readonly PREFERRED_MAINTENANCE_WINDOW: "Sat:12:30-Sat:20:30";
|
|
5
5
|
/** 1 minute — balances monitoring granularity against CloudWatch costs */
|
|
6
6
|
readonly MONITORING_INTERVAL: Duration;
|
|
7
|
-
/** Default backup retention in days — applied uniformly across Aurora and RDS Instance */
|
|
8
|
-
readonly BACKUP_RETENTION_DEFAULT_DAYS: 14;
|
|
9
7
|
/** Default instance type — primary and read-replica must stay in lockstep */
|
|
10
8
|
readonly DEFAULT_INSTANCE_TYPE: "t4g.large";
|
|
11
9
|
/** Default storage autoscaling ceiling (GiB) — applied to primary and replica */
|