@fjall/components-infrastructure 35.0.0 → 37.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/dist/lib/patterns/aws/database.js +4 -6
- package/dist/lib/patterns/aws/databasePropApplicability.d.ts +15 -0
- package/dist/lib/patterns/aws/databasePropApplicability.js +98 -0
- package/dist/lib/resources/aws/compute/lifecycleHookLambda.source.cjs +41 -13
- package/dist/lib/resources/aws/secrets/kms.d.ts +8 -0
- package/dist/lib/resources/aws/secrets/kms.js +12 -2
- package/dist/lib/resources/aws/secrets/stackEncryptionKey.js +6 -4
- package/dist/lib/utils/backupTierMapping.d.ts +11 -16
- package/dist/lib/utils/backupTierMapping.js +2 -15
- package/dist/lib/utils/resourceNaming.d.ts +22 -0
- package/dist/lib/utils/resourceNaming.js +32 -0
- package/package.json +3 -3
|
@@ -5,6 +5,7 @@ import { RdsAuroraGlobal } from "../../resources/aws/database/rdsAuroraGlobal.js
|
|
|
5
5
|
import { DynamoDBTable } from "../../resources/aws/database/dynamodb.js";
|
|
6
6
|
import { ClickHouseDatabase } from "./clickhouseDatabase.js";
|
|
7
7
|
import { FjallLogger, warnIfPropertiesIgnored } from "../../utils/validationLogger.js";
|
|
8
|
+
import { DATABASE_WARN_GROUPS } from "./databasePropApplicability.js";
|
|
8
9
|
import { toPascalCase } from "../../utils/capitaliseString.js";
|
|
9
10
|
import { ResourceNaming } from "../../utils/resourceNaming.js";
|
|
10
11
|
import { resolveAlertsTopic as resolveAlertsTopicShared } from "../../utils/resolveAlertsTopic.js";
|
|
@@ -127,12 +128,9 @@ function validateRelationalDatabaseProps(props) {
|
|
|
127
128
|
throw new Error("GlobalAurora database requires 'primaryRegion'. " +
|
|
128
129
|
"Specify the AWS region where the primary cluster will be created.");
|
|
129
130
|
}
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
warnIfPropertiesIgnored(props, ["readers", "writer"], ["Aurora", "GlobalAurora"], "Aurora database");
|
|
134
|
-
// GlobalAurora-only options on non-global databases
|
|
135
|
-
warnIfPropertiesIgnored(props, ["primaryRegion", "secondaryRegions", "enableGlobalWriteForwarding"], "GlobalAurora", "GlobalAurora database");
|
|
131
|
+
for (const group of DATABASE_WARN_GROUPS) {
|
|
132
|
+
warnIfPropertiesIgnored(props, group.properties, group.applicableTypes, group.typeName);
|
|
133
|
+
}
|
|
136
134
|
}
|
|
137
135
|
/**
|
|
138
136
|
* Validates database props and logs warnings for ignored or misconfigured options.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { AuroraDatabaseProps, GlobalAuroraDatabaseProps, InstanceDatabaseProps } from "./database.js";
|
|
2
|
+
import { type RelationalDatabaseType } from "./interfaces/database.js";
|
|
3
|
+
type AllDatabasePropKeys = keyof AuroraDatabaseProps | keyof InstanceDatabaseProps | keyof GlobalAuroraDatabaseProps;
|
|
4
|
+
export interface DatabaseWarnGroup {
|
|
5
|
+
properties: string[];
|
|
6
|
+
applicableTypes: RelationalDatabaseType[];
|
|
7
|
+
typeName: string;
|
|
8
|
+
}
|
|
9
|
+
export declare const DATABASE_WARN_GROUPS: readonly DatabaseWarnGroup[];
|
|
10
|
+
/**
|
|
11
|
+
* Runtime key set for tests — derived from the compiled guard, never
|
|
12
|
+
* re-listed (typescript-standards § compile-time guards).
|
|
13
|
+
*/
|
|
14
|
+
export declare const ALL_DATABASE_PROP_KEYS: AllDatabasePropKeys[];
|
|
15
|
+
export {};
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { RELATIONAL_DATABASE_TYPES } from "./interfaces/database.js";
|
|
2
|
+
/**
|
|
3
|
+
* Compile-time lockstep guard for `validateRelationalDatabaseProps`'s
|
|
4
|
+
* ignored-prop warnings — the database twin of `COMPUTE_PROP_APPLICABILITY`.
|
|
5
|
+
* The Record over the full top-level key union rejects BOTH a missing key (a
|
|
6
|
+
* new prop added to any relational props interface fails TS2741 here until
|
|
7
|
+
* classified) and a stale extra key (a removed/renamed prop fails TS2353).
|
|
8
|
+
* `"all"` means the prop is declared on every relational variant and never
|
|
9
|
+
* warns; a list names the variants that declare it, and a prop declared on
|
|
10
|
+
* two of the three variants is first-class here — the shape no
|
|
11
|
+
* single-variant hand list (or Exclude-difference bucket) can express.
|
|
12
|
+
*
|
|
13
|
+
* Applicability is DECLARATION, not type identity: `engine` and `encryption`
|
|
14
|
+
* map `"all"` although their TYPES differ per variant (`IClusterEngine` vs
|
|
15
|
+
* `IInstanceEngine`, `AuroraEncryptionConfig` vs `EncryptionConfig`) — every
|
|
16
|
+
* variant reads them, so they are never ignored and must never warn.
|
|
17
|
+
*/
|
|
18
|
+
const DATABASE_PROP_APPLICABILITY = {
|
|
19
|
+
type: "all",
|
|
20
|
+
vpc: "all",
|
|
21
|
+
databaseName: "all",
|
|
22
|
+
instanceType: "all",
|
|
23
|
+
deletionProtection: "all",
|
|
24
|
+
appBackupTier: "all",
|
|
25
|
+
alertsTopic: "all",
|
|
26
|
+
alarms: "all",
|
|
27
|
+
applicationId: "all",
|
|
28
|
+
migrations: "all",
|
|
29
|
+
databaseEngine: "all",
|
|
30
|
+
engine: "all",
|
|
31
|
+
port: "all",
|
|
32
|
+
proxy: "all",
|
|
33
|
+
credentials: "all",
|
|
34
|
+
encryption: "all",
|
|
35
|
+
backupRetention: "all",
|
|
36
|
+
databaseInsights: "all",
|
|
37
|
+
snapshotIdentifier: "all",
|
|
38
|
+
snapshotUsername: "all",
|
|
39
|
+
writer: ["Aurora", "GlobalAurora"],
|
|
40
|
+
readers: ["Aurora", "GlobalAurora"],
|
|
41
|
+
monitoringInterval: ["Aurora", "GlobalAurora"],
|
|
42
|
+
preferredMaintenanceWindow: ["Aurora", "GlobalAurora"],
|
|
43
|
+
removalPolicy: ["Aurora"],
|
|
44
|
+
engineVersion: ["Aurora"],
|
|
45
|
+
clusterIdentifier: ["Aurora"],
|
|
46
|
+
credentialsSecretName: ["Aurora"],
|
|
47
|
+
credentialsKmsAliasName: ["Aurora"],
|
|
48
|
+
serverlessV2MinCapacity: ["Aurora"],
|
|
49
|
+
serverlessV2MaxCapacity: ["Aurora"],
|
|
50
|
+
serverlessV2AutoPauseDuration: ["Aurora"],
|
|
51
|
+
enableDataApi: ["Aurora"],
|
|
52
|
+
allowVpcAccess: ["Aurora"],
|
|
53
|
+
allowedIpCidr: ["Aurora"],
|
|
54
|
+
publiclyAccessible: ["Aurora", "Instance"],
|
|
55
|
+
primaryRegion: ["GlobalAurora"],
|
|
56
|
+
secondaryRegions: ["GlobalAurora"],
|
|
57
|
+
globalClusterIdentifier: ["GlobalAurora"],
|
|
58
|
+
enableGlobalWriteForwarding: ["GlobalAurora"],
|
|
59
|
+
allocatedStorage: ["Instance"],
|
|
60
|
+
multiAz: ["Instance"],
|
|
61
|
+
readReplica: ["Instance"],
|
|
62
|
+
iamAuthentication: ["Instance"]
|
|
63
|
+
};
|
|
64
|
+
/**
|
|
65
|
+
* The variant literals are already the user-facing vocabulary (the
|
|
66
|
+
* `type: "Aurora"` a user writes), so the warning names exactly that —
|
|
67
|
+
* no label table to keep in step. Groups are keyed on the variants in
|
|
68
|
+
* `RELATIONAL_DATABASE_TYPES` order, so two entries declaring the same
|
|
69
|
+
* variants in a different order land in one group with one label.
|
|
70
|
+
*/
|
|
71
|
+
const orderedVariants = (variants) => [...variants].sort((a, b) => RELATIONAL_DATABASE_TYPES.indexOf(a) -
|
|
72
|
+
RELATIONAL_DATABASE_TYPES.indexOf(b));
|
|
73
|
+
export const DATABASE_WARN_GROUPS = (() => {
|
|
74
|
+
const groups = new Map();
|
|
75
|
+
for (const [prop, applicability] of Object.entries(DATABASE_PROP_APPLICABILITY)) {
|
|
76
|
+
if (applicability === "all")
|
|
77
|
+
continue;
|
|
78
|
+
const applicableTypes = orderedVariants(applicability);
|
|
79
|
+
const key = applicableTypes.join("+");
|
|
80
|
+
const existing = groups.get(key);
|
|
81
|
+
if (existing !== undefined) {
|
|
82
|
+
existing.properties.push(prop);
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
groups.set(key, {
|
|
86
|
+
properties: [prop],
|
|
87
|
+
applicableTypes,
|
|
88
|
+
typeName: applicableTypes.join(" or ") + " database"
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return [...groups.values()];
|
|
93
|
+
})();
|
|
94
|
+
/**
|
|
95
|
+
* Runtime key set for tests — derived from the compiled guard, never
|
|
96
|
+
* re-listed (typescript-standards § compile-time guards).
|
|
97
|
+
*/
|
|
98
|
+
export const ALL_DATABASE_PROP_KEYS = Object.keys(DATABASE_PROP_APPLICABILITY);
|
|
@@ -92,32 +92,60 @@ async function findExistingTaskArn(client, clusterArn, startedBy) {
|
|
|
92
92
|
return result.taskArns[0];
|
|
93
93
|
}
|
|
94
94
|
|
|
95
|
+
function describeFailureReason(desc) {
|
|
96
|
+
const failure = desc && desc.failures && desc.failures[0];
|
|
97
|
+
return failure && failure.reason ? failure.reason : "no failure reported";
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// DescribeTasks is eventually consistent with RunTask: for a few seconds
|
|
101
|
+
// after a launch it answers `tasks: []` with a MISSING failure for the very
|
|
102
|
+
// ARN RunTask just returned. Until the task has been seen once, a miss is
|
|
103
|
+
// "not visible yet" and the poll continues; once it has been seen, a miss is
|
|
104
|
+
// the task vanishing, which is loud. A task never seen inside one
|
|
105
|
+
// invocation's poll budget is loud too — that is a hundred seconds, not
|
|
106
|
+
// consistency lag.
|
|
95
107
|
async function pollTaskUntilStopped(client, clusterArn, taskArn, sleep) {
|
|
96
108
|
const sleepFn =
|
|
97
109
|
sleep || ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
|
|
110
|
+
let seen = false;
|
|
111
|
+
let lastMissReason = "no failure reported";
|
|
98
112
|
for (let i = 0; i < MAX_POLLS_PER_INVOCATION; i++) {
|
|
99
113
|
const desc = await client.send(
|
|
100
114
|
new DescribeTasksCommand({ cluster: clusterArn, tasks: [taskArn] })
|
|
101
115
|
);
|
|
102
116
|
const task = desc.tasks && desc.tasks[0];
|
|
103
|
-
if (
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
}
|
|
117
|
+
if (task) {
|
|
118
|
+
seen = true;
|
|
119
|
+
if (task.lastStatus === "STOPPED") {
|
|
120
|
+
const exitCode =
|
|
121
|
+
task.containers && task.containers[0]
|
|
122
|
+
? task.containers[0].exitCode
|
|
123
|
+
: undefined;
|
|
124
|
+
if (exitCode === 0) return { hookStatus: "SUCCEEDED" };
|
|
125
|
+
return {
|
|
126
|
+
hookStatus: "FAILED",
|
|
127
|
+
reason: `Task exited ${exitCode}: ${task.stoppedReason || "no reason"}`
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
} else {
|
|
131
|
+
lastMissReason = describeFailureReason(desc);
|
|
132
|
+
if (seen) {
|
|
133
|
+
return {
|
|
134
|
+
hookStatus: "FAILED",
|
|
135
|
+
reason: `DescribeTasks returned no task after it had been seen (${lastMissReason})`
|
|
136
|
+
};
|
|
137
|
+
}
|
|
116
138
|
}
|
|
117
139
|
if (i < MAX_POLLS_PER_INVOCATION - 1) {
|
|
118
140
|
await sleepFn(POLL_INTERVAL_MS);
|
|
119
141
|
}
|
|
120
142
|
}
|
|
143
|
+
if (!seen) {
|
|
144
|
+
return {
|
|
145
|
+
hookStatus: "FAILED",
|
|
146
|
+
reason: `DescribeTasks returned no task in ${MAX_POLLS_PER_INVOCATION} polls (${lastMissReason})`
|
|
147
|
+
};
|
|
148
|
+
}
|
|
121
149
|
return {
|
|
122
150
|
hookStatus: "IN_PROGRESS",
|
|
123
151
|
callBackDelay: HOOK_CALLBACK_DELAY_SECONDS
|
|
@@ -10,6 +10,14 @@ export type KeyProtectionScope = (typeof KEY_PROTECTION_SCOPES)[number];
|
|
|
10
10
|
* forgotten (tier-aware alert policy design §8 K4, `KMS_KEY_ORPHAN`).
|
|
11
11
|
*/
|
|
12
12
|
export { KMS_RETENTION_TAG_KEY } from "@fjall/util/aws";
|
|
13
|
+
/**
|
|
14
|
+
* Stamped beside the retention marker, valued with the alias name as `kms
|
|
15
|
+
* list-aliases` reports it. The alias resource goes with the stack and
|
|
16
|
+
* CloudFormation tags nothing on a KMS key, so after a stack delete this tag
|
|
17
|
+
* is the only thing that still says which stack's key a retained key was —
|
|
18
|
+
* the deploy-test teardown releases retained stack keys by it.
|
|
19
|
+
*/
|
|
20
|
+
export { KMS_ALIAS_TAG_KEY } from "@fjall/util/aws";
|
|
13
21
|
interface CustomerManagedKeyProps {
|
|
14
22
|
description?: string;
|
|
15
23
|
aliasName?: string;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { KMS_RETENTION_TAG_KEY } from "@fjall/util/aws";
|
|
1
|
+
import { KMS_ALIAS_TAG_KEY, KMS_RETENTION_TAG_KEY } from "@fjall/util/aws";
|
|
2
2
|
import { CfnOutput, Duration, RemovalPolicy, Tags } from "aws-cdk-lib";
|
|
3
3
|
import { Alias, Key } from "aws-cdk-lib/aws-kms";
|
|
4
4
|
import { Construct } from "constructs";
|
|
@@ -17,6 +17,14 @@ export const KEY_PROTECTION_SCOPES = [
|
|
|
17
17
|
* forgotten (tier-aware alert policy design §8 K4, `KMS_KEY_ORPHAN`).
|
|
18
18
|
*/
|
|
19
19
|
export { KMS_RETENTION_TAG_KEY } from "@fjall/util/aws";
|
|
20
|
+
/**
|
|
21
|
+
* Stamped beside the retention marker, valued with the alias name as `kms
|
|
22
|
+
* list-aliases` reports it. The alias resource goes with the stack and
|
|
23
|
+
* CloudFormation tags nothing on a KMS key, so after a stack delete this tag
|
|
24
|
+
* is the only thing that still says which stack's key a retained key was —
|
|
25
|
+
* the deploy-test teardown releases retained stack keys by it.
|
|
26
|
+
*/
|
|
27
|
+
export { KMS_ALIAS_TAG_KEY } from "@fjall/util/aws";
|
|
20
28
|
/**
|
|
21
29
|
* How long a scheduled-for-deletion key stays recoverable.
|
|
22
30
|
*
|
|
@@ -52,8 +60,10 @@ export class CustomerManagedKey extends Construct {
|
|
|
52
60
|
? PENDING_DELETION_WINDOW
|
|
53
61
|
: undefined
|
|
54
62
|
});
|
|
63
|
+
const aliasName = props.aliasName || ResourceNaming.defaultKeyAlias(id);
|
|
55
64
|
if (removalPolicy === RemovalPolicy.RETAIN) {
|
|
56
65
|
Tags.of(this.key).add(KMS_RETENTION_TAG_KEY, props.protects);
|
|
66
|
+
Tags.of(this.key).add(KMS_ALIAS_TAG_KEY, ResourceNaming.qualifiedKeyAlias(aliasName));
|
|
57
67
|
}
|
|
58
68
|
new CfnOutput(this, `${outputName}KeyArn`, {
|
|
59
69
|
key: `${outputName}Arn`,
|
|
@@ -61,7 +71,7 @@ export class CustomerManagedKey extends Construct {
|
|
|
61
71
|
exportName: stackScopedExportName(this, `${outputName}KeyArn`)
|
|
62
72
|
});
|
|
63
73
|
this.alias = new Alias(this, `${id}KeyAlias`, {
|
|
64
|
-
aliasName
|
|
74
|
+
aliasName,
|
|
65
75
|
targetKey: this.key
|
|
66
76
|
});
|
|
67
77
|
new CfnOutput(this, `${outputName}KeyAliasArn`, {
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { Stack } from "aws-cdk-lib";
|
|
2
2
|
import { PolicyStatement, ServicePrincipal } from "aws-cdk-lib/aws-iam";
|
|
3
3
|
import { tierDefault } from "../../../utils/backupTierMapping.js";
|
|
4
|
+
import { ResourceNaming } from "../../../utils/resourceNaming.js";
|
|
4
5
|
import { CustomerManagedKey } from "./kms.js";
|
|
5
|
-
const STACK_ENCRYPTION_KEY_ID =
|
|
6
|
+
const STACK_ENCRYPTION_KEY_ID = ResourceNaming.stackEncryptionKeyConstructId();
|
|
6
7
|
/**
|
|
7
8
|
* AWS services that publish to an application's SNS topics on the app's
|
|
8
9
|
* behalf and therefore encrypt messages through the topic's key: EventBridge
|
|
@@ -48,9 +49,10 @@ export class StackEncryptionKey extends CustomerManagedKey {
|
|
|
48
49
|
constructor(stack) {
|
|
49
50
|
super(stack, STACK_ENCRYPTION_KEY_ID, {
|
|
50
51
|
description: `Encrypts the enterprise-tier resources of ${stack.stackName} (DynamoDB tables, SNS topics, Lambda environment, ECS Exec sessions, log groups)`,
|
|
51
|
-
//
|
|
52
|
-
// the
|
|
53
|
-
|
|
52
|
+
// Derived through ResourceNaming so the deploy-test teardown, which
|
|
53
|
+
// finds the retained key by this alias after the stack is gone, cannot
|
|
54
|
+
// spell it differently.
|
|
55
|
+
aliasName: ResourceNaming.stackEncryptionKeyAlias(stack.stackName),
|
|
54
56
|
protects: "outlives-stack"
|
|
55
57
|
});
|
|
56
58
|
}
|
|
@@ -1,22 +1,17 @@
|
|
|
1
|
-
import { BACKUP_TIERS, BACKUP_TIER_DEFAULTS, type BackupTier, type BackupTierDefaults, tierDefault } from "@fjall/util";
|
|
2
|
-
/** Tag key used by AWS Backup plan selection rules */
|
|
3
|
-
export declare const BACKUP_TIER_TAG_KEY = "fjall:disasterRecovery:tier";
|
|
1
|
+
import { BACKUP_TIERS, BACKUP_TIER_DEFAULTS, BACKUP_TIER_TAG_KEY, BACKUP_TIER_TAG_MAP, type BackupTier, type BackupTierDefaults, tierDefault } from "@fjall/util";
|
|
4
2
|
/**
|
|
5
|
-
* The application backup tier vocabulary
|
|
6
|
-
* `@fjall/util` (`BACKUP_TIERS`,
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
3
|
+
* The application backup tier vocabulary, its defaults table, the DR tag key
|
|
4
|
+
* and the tier → tag-value map are owned by `@fjall/util` (`BACKUP_TIERS`,
|
|
5
|
+
* `BACKUP_TIER_DEFAULTS`, `tierDefault`, `BACKUP_TIER_TAG_KEY`,
|
|
6
|
+
* `BACKUP_TIER_TAG_MAP`) and shared with the generator (`BACKUP_VAULT_TIERS`),
|
|
7
|
+
* the CLI's synth expectations and the webapp's posture rules (which read the
|
|
8
|
+
* tag back from discovered resources), so every system derives from one
|
|
9
|
+
* declaration. Re-exported here so construct code keeps one import path for
|
|
10
|
+
* tier concerns: `App.applyBackupTier()` (app-level tag), `StandardTagsAspect`
|
|
11
|
+
* (per-resource tag) and `BackupPlan` (selection key) all read the same pair.
|
|
10
12
|
*/
|
|
11
13
|
export type { BackupTier, BackupTierDefaults };
|
|
12
|
-
export { BACKUP_TIERS, BACKUP_TIER_DEFAULTS, tierDefault };
|
|
13
|
-
/**
|
|
14
|
-
* Shared mapping from application backup tier names to AWS Backup plan tag values.
|
|
15
|
-
*
|
|
16
|
-
* Used by both App.applyBackupTier() (app-level tag) and StandardTagsAspect
|
|
17
|
-
* (per-resource tag) to ensure consistent tier → tag translation.
|
|
18
|
-
*/
|
|
19
|
-
export declare const BACKUP_TIER_TAG_MAP: Readonly<Record<BackupTier, string>>;
|
|
14
|
+
export { BACKUP_TIERS, BACKUP_TIER_DEFAULTS, BACKUP_TIER_TAG_KEY, BACKUP_TIER_TAG_MAP, tierDefault };
|
|
20
15
|
/**
|
|
21
16
|
* The tier a resource actually runs under: its own override, else the
|
|
22
17
|
* app-level tier `App.getApp({ backup: { tier } })` declared. The single
|
|
@@ -1,18 +1,5 @@
|
|
|
1
|
-
import { BACKUP_TIERS, BACKUP_TIER_DEFAULTS, tierDefault } from "@fjall/util";
|
|
2
|
-
|
|
3
|
-
export const BACKUP_TIER_TAG_KEY = "fjall:disasterRecovery:tier";
|
|
4
|
-
export { BACKUP_TIERS, BACKUP_TIER_DEFAULTS, tierDefault };
|
|
5
|
-
/**
|
|
6
|
-
* Shared mapping from application backup tier names to AWS Backup plan tag values.
|
|
7
|
-
*
|
|
8
|
-
* Used by both App.applyBackupTier() (app-level tag) and StandardTagsAspect
|
|
9
|
-
* (per-resource tag) to ensure consistent tier → tag translation.
|
|
10
|
-
*/
|
|
11
|
-
export const BACKUP_TIER_TAG_MAP = Object.freeze({
|
|
12
|
-
standard: "default",
|
|
13
|
-
resilient: "resilient",
|
|
14
|
-
enterprise: "enterprise"
|
|
15
|
-
});
|
|
1
|
+
import { BACKUP_TIERS, BACKUP_TIER_DEFAULTS, BACKUP_TIER_TAG_KEY, BACKUP_TIER_TAG_MAP, tierDefault } from "@fjall/util";
|
|
2
|
+
export { BACKUP_TIERS, BACKUP_TIER_DEFAULTS, BACKUP_TIER_TAG_KEY, BACKUP_TIER_TAG_MAP, tierDefault };
|
|
16
3
|
/**
|
|
17
4
|
* The tier a resource actually runs under: its own override, else the
|
|
18
5
|
* app-level tier `App.getApp({ backup: { tier } })` declared. The single
|
|
@@ -77,6 +77,28 @@ export declare class ResourceNaming {
|
|
|
77
77
|
* reports.
|
|
78
78
|
*/
|
|
79
79
|
static defaultKeyAlias(constructId: string): string;
|
|
80
|
+
/**
|
|
81
|
+
* Construct id of the one `StackEncryptionKey` an enterprise-tier stack
|
|
82
|
+
* mints (lib/resources/aws/secrets/stackEncryptionKey.ts). It is also the
|
|
83
|
+
* alias suffix, so the two are spelled once.
|
|
84
|
+
*/
|
|
85
|
+
static stackEncryptionKeyConstructId(): string;
|
|
86
|
+
/**
|
|
87
|
+
* Alias the stack encryption key is created under. KMS alias names are
|
|
88
|
+
* unique per account-region, so the stack name is the namespace. Unprefixed
|
|
89
|
+
* like the other alias helpers; the deploy tests that find the retained key
|
|
90
|
+
* after its stack is gone derive the reported form through
|
|
91
|
+
* [[qualifiedKeyAlias]], the same way the construct's alias tag does.
|
|
92
|
+
*/
|
|
93
|
+
static stackEncryptionKeyAlias(stackName: string): string;
|
|
94
|
+
/**
|
|
95
|
+
* An alias name as KMS reports it. The CDK `Alias` prepends `alias/` to a
|
|
96
|
+
* resolved name that lacks it, and to a token only when the token's
|
|
97
|
+
* leading literal fragment lacks it — every Fjall alias starts with the
|
|
98
|
+
* literal `cmk/`, so the two rules agree. An already-qualified name is
|
|
99
|
+
* returned as it is.
|
|
100
|
+
*/
|
|
101
|
+
static qualifiedKeyAlias(aliasName: string): string;
|
|
80
102
|
/**
|
|
81
103
|
* S3 bucket holding a VPC's flow logs. `id` is the Network construct id —
|
|
82
104
|
* the network name for an additional VPC, `${stackPrefix}Vpc` for the
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { toKebab } from "./capitaliseString.js";
|
|
2
2
|
/** ELBv2's hard limit on an Application Load Balancer's name. */
|
|
3
3
|
const LOAD_BALANCER_NAME_MAX_LENGTH = 32;
|
|
4
|
+
/** What `kms list-aliases` prepends to every alias name. */
|
|
5
|
+
const KMS_ALIAS_PREFIX = "alias/";
|
|
4
6
|
/**
|
|
5
7
|
* Centralised resource naming for infrastructure.
|
|
6
8
|
* Ensures consistent, predictable names across constructs and tests.
|
|
@@ -127,6 +129,36 @@ export class ResourceNaming {
|
|
|
127
129
|
static defaultKeyAlias(constructId) {
|
|
128
130
|
return `cmk/${constructId}`;
|
|
129
131
|
}
|
|
132
|
+
/**
|
|
133
|
+
* Construct id of the one `StackEncryptionKey` an enterprise-tier stack
|
|
134
|
+
* mints (lib/resources/aws/secrets/stackEncryptionKey.ts). It is also the
|
|
135
|
+
* alias suffix, so the two are spelled once.
|
|
136
|
+
*/
|
|
137
|
+
static stackEncryptionKeyConstructId() {
|
|
138
|
+
return "FjallStackEncryptionKey";
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Alias the stack encryption key is created under. KMS alias names are
|
|
142
|
+
* unique per account-region, so the stack name is the namespace. Unprefixed
|
|
143
|
+
* like the other alias helpers; the deploy tests that find the retained key
|
|
144
|
+
* after its stack is gone derive the reported form through
|
|
145
|
+
* [[qualifiedKeyAlias]], the same way the construct's alias tag does.
|
|
146
|
+
*/
|
|
147
|
+
static stackEncryptionKeyAlias(stackName) {
|
|
148
|
+
return `cmk/${stackName}/${ResourceNaming.stackEncryptionKeyConstructId()}`;
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* An alias name as KMS reports it. The CDK `Alias` prepends `alias/` to a
|
|
152
|
+
* resolved name that lacks it, and to a token only when the token's
|
|
153
|
+
* leading literal fragment lacks it — every Fjall alias starts with the
|
|
154
|
+
* literal `cmk/`, so the two rules agree. An already-qualified name is
|
|
155
|
+
* returned as it is.
|
|
156
|
+
*/
|
|
157
|
+
static qualifiedKeyAlias(aliasName) {
|
|
158
|
+
return aliasName.startsWith(KMS_ALIAS_PREFIX)
|
|
159
|
+
? aliasName
|
|
160
|
+
: `${KMS_ALIAS_PREFIX}${aliasName}`;
|
|
161
|
+
}
|
|
130
162
|
/**
|
|
131
163
|
* S3 bucket holding a VPC's flow logs. `id` is the Network construct id —
|
|
132
164
|
* the network name for an additional VPC, `${stackPrefix}Vpc` for the
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fjall/components-infrastructure",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "37.0.0",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "git+https://github.com/fjall-tech/fjall.git",
|
|
@@ -80,8 +80,8 @@
|
|
|
80
80
|
},
|
|
81
81
|
"dependencies": {
|
|
82
82
|
"@aws-sdk/client-organizations": "^3.1098.0",
|
|
83
|
-
"@fjall/generator": "^
|
|
84
|
-
"@fjall/util": "^
|
|
83
|
+
"@fjall/generator": "^37.0.0",
|
|
84
|
+
"@fjall/util": "^37.0.0",
|
|
85
85
|
"constructs": "^10.7.2",
|
|
86
86
|
"zod": "^4.4.3"
|
|
87
87
|
},
|