@fjall/components-infrastructure 7.2.0 → 8.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/app.d.ts +12 -0
- package/dist/lib/app.js +25 -5
- package/dist/lib/config/aws/ipam.js +4 -3
- package/dist/lib/patterns/aws/cdn.d.ts +7 -1
- package/dist/lib/patterns/aws/cdn.js +9 -2
- package/dist/lib/patterns/aws/database.js +4 -3
- package/dist/lib/patterns/aws/interfaces/domain.d.ts +6 -0
- package/dist/lib/patterns/aws/storage.js +3 -0
- package/dist/lib/patterns/aws/targets/fjallTargets.d.ts +9 -4
- package/dist/lib/patterns/aws/targets/fjallTargets.js +11 -5
- package/dist/lib/patterns/aws/targets/targetResolution.d.ts +33 -40
- package/dist/lib/patterns/aws/targets/targetResolution.js +42 -52
- package/dist/lib/resources/aws/backup/backupVault.js +2 -0
- package/dist/lib/resources/aws/cdn/cloudFront.d.ts +9 -0
- package/dist/lib/resources/aws/cdn/cloudFront.js +2 -1
- package/dist/lib/resources/aws/compute/ecs.js +4 -3
- package/dist/lib/resources/aws/compute/ecsNetworking.js +29 -15
- package/dist/lib/resources/aws/compute/ecsServiceFactory.js +2 -1
- package/dist/lib/resources/aws/compute/ecsTaskDefinition.js +6 -2
- package/dist/lib/resources/aws/database/rdsAurora.js +2 -2
- package/dist/lib/resources/aws/database/rdsAuroraGlobal.js +3 -2
- package/dist/lib/resources/aws/database/rdsHelpers.js +12 -2
- package/dist/lib/resources/aws/database/rdsInstance.js +3 -2
- package/dist/lib/resources/aws/database/rdsProxyOutput.js +2 -1
- package/dist/lib/resources/aws/logging/cloudTrail.js +3 -0
- package/dist/lib/resources/aws/networking/ipamPool.js +2 -1
- package/dist/lib/resources/aws/networking/vpc.js +8 -2
- package/dist/lib/resources/aws/secrets/kms.d.ts +24 -7
- package/dist/lib/resources/aws/secrets/kms.js +29 -6
- package/dist/lib/resources/aws/secrets/parameter.js +3 -1
- package/dist/lib/resources/aws/secrets/secret.js +5 -1
- package/dist/lib/resources/aws/storage/ecr.d.ts +6 -0
- package/dist/lib/resources/aws/storage/ecr.js +8 -4
- package/dist/lib/resources/aws/storage/s3.d.ts +10 -0
- package/dist/lib/resources/aws/storage/s3.js +9 -4
- package/dist/lib/utils/albAliasTargetRegistry.d.ts +44 -0
- package/dist/lib/utils/albAliasTargetRegistry.js +69 -0
- package/dist/lib/utils/env.d.ts +9 -0
- package/dist/lib/utils/env.js +12 -0
- package/dist/lib/utils/exportNaming.d.ts +50 -0
- package/dist/lib/utils/exportNaming.js +52 -0
- package/dist/lib/utils/orgConfigParser.d.ts +3 -8
- package/dist/lib/utils/orgConfigParser.js +4 -14
- package/dist/lib/utils/removalPolicy.d.ts +11 -3
- package/dist/lib/utils/removalPolicy.js +20 -5
- package/dist/lib/utils/resourceNaming.d.ts +50 -0
- package/dist/lib/utils/resourceNaming.js +74 -0
- package/package.json +9 -4
package/dist/lib/app.d.ts
CHANGED
|
@@ -139,6 +139,18 @@ export declare class App extends CdkApp {
|
|
|
139
139
|
* Retrieve default network stack - named as `${this.name}Network`
|
|
140
140
|
*/
|
|
141
141
|
getDefaultNetworkStack(): AwsStack;
|
|
142
|
+
/**
|
|
143
|
+
* The Network stack as a dependency, or `undefined` when the app is
|
|
144
|
+
* networkless.
|
|
145
|
+
*
|
|
146
|
+
* `getDefaultNetworkStack()` CREATES the stack as a side effect, so naming
|
|
147
|
+
* it unconditionally as a dependency is what makes a networkless app
|
|
148
|
+
* synthesise an empty `<App>Network` — zero resources, which CloudFormation
|
|
149
|
+
* rejects outright ("Template format error: At least one Resources member
|
|
150
|
+
* must be defined"). Synth is green, `cdk deploy` is not, so nothing before
|
|
151
|
+
* the deploy can tell.
|
|
152
|
+
*/
|
|
153
|
+
private networkDependency;
|
|
142
154
|
/**
|
|
143
155
|
* Retrieve default database stack - named as `${this.name}Database`
|
|
144
156
|
*/
|
package/dist/lib/app.js
CHANGED
|
@@ -12,6 +12,7 @@ import { ServiceDiscoveryNamespace } from "./resources/aws/networking/serviceDis
|
|
|
12
12
|
import { BuildkiteFactory } from "./patterns/aws/buildkite.js";
|
|
13
13
|
import { DevSubstrate } from "./patterns/aws/devSubstrate.js";
|
|
14
14
|
import { StandardTagsAspect } from "./utils/standardTagsAspect.js";
|
|
15
|
+
import { emitAppLevelAlbAliasExports } from "./utils/albAliasTargetRegistry.js";
|
|
15
16
|
import { BACKUP_TIER_TAG_KEY, BACKUP_TIER_TAG_MAP } from "./utils/backupTierMapping.js";
|
|
16
17
|
import { randomBytes } from "crypto";
|
|
17
18
|
import { getConfig } from "./utils/getConfig.js";
|
|
@@ -203,7 +204,7 @@ export class App extends CdkApp {
|
|
|
203
204
|
* by CDK when compute resources reference database resources.
|
|
204
205
|
*/
|
|
205
206
|
getDefaultComputeStack() {
|
|
206
|
-
return this.getStack(`${this.stackPrefix}Compute`, this.
|
|
207
|
+
return this.getStack(`${this.stackPrefix}Compute`, this.networkDependency());
|
|
207
208
|
}
|
|
208
209
|
/**
|
|
209
210
|
* Retrieve default network stack - named as `${this.name}Network`
|
|
@@ -211,17 +212,31 @@ export class App extends CdkApp {
|
|
|
211
212
|
getDefaultNetworkStack() {
|
|
212
213
|
return this.getStack(`${this.stackPrefix}Network`);
|
|
213
214
|
}
|
|
215
|
+
/**
|
|
216
|
+
* The Network stack as a dependency, or `undefined` when the app is
|
|
217
|
+
* networkless.
|
|
218
|
+
*
|
|
219
|
+
* `getDefaultNetworkStack()` CREATES the stack as a side effect, so naming
|
|
220
|
+
* it unconditionally as a dependency is what makes a networkless app
|
|
221
|
+
* synthesise an empty `<App>Network` — zero resources, which CloudFormation
|
|
222
|
+
* rejects outright ("Template format error: At least one Resources member
|
|
223
|
+
* must be defined"). Synth is green, `cdk deploy` is not, so nothing before
|
|
224
|
+
* the deploy can tell.
|
|
225
|
+
*/
|
|
226
|
+
networkDependency() {
|
|
227
|
+
return this.networkDisabled ? undefined : this.getDefaultNetworkStack();
|
|
228
|
+
}
|
|
214
229
|
/**
|
|
215
230
|
* Retrieve default database stack - named as `${this.name}Database`
|
|
216
231
|
*/
|
|
217
232
|
getDefaultDatabaseStack() {
|
|
218
|
-
return this.getStack(`${this.stackPrefix}Database`, this.
|
|
233
|
+
return this.getStack(`${this.stackPrefix}Database`, this.networkDependency());
|
|
219
234
|
}
|
|
220
235
|
/**
|
|
221
236
|
* Retrieve default storage stack - named as `${this.name}Storage`
|
|
222
237
|
*/
|
|
223
238
|
getDefaultStorageStack() {
|
|
224
|
-
return this.getStack(`${this.stackPrefix}Storage`, this.
|
|
239
|
+
return this.getStack(`${this.stackPrefix}Storage`, this.networkDependency());
|
|
225
240
|
}
|
|
226
241
|
/**
|
|
227
242
|
* Retrieve default CDN stack - named as `${this.name}Cdn`
|
|
@@ -236,7 +251,9 @@ export class App extends CdkApp {
|
|
|
236
251
|
// crossRegionReferences is enabled at creation so the Cdn stack can
|
|
237
252
|
// consume a us-east-1 certificate (domain gold-plating D3, BYO path).
|
|
238
253
|
// The flag is inert unless a cross-region reference is synthesised.
|
|
239
|
-
return this.getStack(`${this.stackPrefix}Cdn`, this.
|
|
254
|
+
return this.getStack(`${this.stackPrefix}Cdn`, this.networkDependency(), {
|
|
255
|
+
crossRegionReferences: true
|
|
256
|
+
});
|
|
240
257
|
}
|
|
241
258
|
/**
|
|
242
259
|
* Retrieve default messaging stack - named as `${this.name}Messaging`
|
|
@@ -246,7 +263,7 @@ export class App extends CdkApp {
|
|
|
246
263
|
* require VPC, but we maintain consistent stack dependency patterns.
|
|
247
264
|
*/
|
|
248
265
|
getDefaultMessagingStack() {
|
|
249
|
-
return this.getStack(`${this.stackPrefix}Messaging`, this.
|
|
266
|
+
return this.getStack(`${this.stackPrefix}Messaging`, this.networkDependency());
|
|
250
267
|
}
|
|
251
268
|
/**
|
|
252
269
|
* Retrieve the app-owned us-east-1 certificate stack — named
|
|
@@ -816,6 +833,9 @@ export class App extends CdkApp {
|
|
|
816
833
|
// attach directly to the App and never call getStack(), so without this
|
|
817
834
|
// they synthesise untagged. Idempotent via the aspectApplied guard.
|
|
818
835
|
this.applyTagsAspect();
|
|
836
|
+
// Must precede super.synth(): the tree is still mutable here, and only
|
|
837
|
+
// now is the ALB count per app final.
|
|
838
|
+
emitAppLevelAlbAliasExports(this);
|
|
819
839
|
const assembly = super.synth(options);
|
|
820
840
|
// After synthesis, write Fjall manifest to cdk.out
|
|
821
841
|
try {
|
|
@@ -2,6 +2,7 @@ import { CfnOutput } from "aws-cdk-lib";
|
|
|
2
2
|
import { Construct } from "constructs";
|
|
3
3
|
import { Ipam as IpamClass } from "../../resources/aws/networking/ipam.js";
|
|
4
4
|
import { getConfig } from "../../utils/getConfig.js";
|
|
5
|
+
import { IPAM_PRIVATE_DEFAULT_SCOPE_EXPORT_NAME } from "../../utils/exportNaming.js";
|
|
5
6
|
export class Ipam extends Construct {
|
|
6
7
|
privateDefaultScopeId;
|
|
7
8
|
constructor(scope, id, props) {
|
|
@@ -23,10 +24,10 @@ export class Ipam extends Construct {
|
|
|
23
24
|
]
|
|
24
25
|
});
|
|
25
26
|
this.privateDefaultScopeId = ipam.attrPrivateDefaultScopeId;
|
|
26
|
-
new CfnOutput(this,
|
|
27
|
-
key:
|
|
27
|
+
new CfnOutput(this, IPAM_PRIVATE_DEFAULT_SCOPE_EXPORT_NAME, {
|
|
28
|
+
key: IPAM_PRIVATE_DEFAULT_SCOPE_EXPORT_NAME,
|
|
28
29
|
value: ipam.attrPrivateDefaultScopeId,
|
|
29
|
-
exportName:
|
|
30
|
+
exportName: IPAM_PRIVATE_DEFAULT_SCOPE_EXPORT_NAME
|
|
30
31
|
});
|
|
31
32
|
}
|
|
32
33
|
}
|
|
@@ -13,6 +13,12 @@ import { type AnyCompute } from "./compute.js";
|
|
|
13
13
|
* Common CDN props shared across all origin types.
|
|
14
14
|
*/
|
|
15
15
|
interface BaseCdnProps {
|
|
16
|
+
/**
|
|
17
|
+
* App this CDN fronts, keying the export a `Domain` resolves with
|
|
18
|
+
* `fjallCdn(<appName>)`. `CdnFactory.build` fills it from the `App`, so it
|
|
19
|
+
* is only set by hand for a directly-instantiated `Cdn`.
|
|
20
|
+
*/
|
|
21
|
+
appName?: string;
|
|
16
22
|
cachePolicy?: CachePolicyPreset | ICachePolicy;
|
|
17
23
|
defaultAllowedMethods?: "GET_HEAD" | "GET_HEAD_OPTIONS" | "ALL";
|
|
18
24
|
behaviours?: SmartCdnBehaviour[];
|
|
@@ -151,5 +157,5 @@ export declare class CdnFactory {
|
|
|
151
157
|
* @param props - CDN configuration properties
|
|
152
158
|
* @returns Factory function that creates the CDN when invoked
|
|
153
159
|
*/
|
|
154
|
-
static build(id: string, props: ICdnProps): (
|
|
160
|
+
static build(id: string, props: ICdnProps): (app: App, scope: Construct) => Cdn;
|
|
155
161
|
}
|
|
@@ -82,6 +82,7 @@ export class Cdn extends CloudFrontDistribution {
|
|
|
82
82
|
static resolveProps(scope, id, props) {
|
|
83
83
|
const defaultOrigin = Cdn.resolveDefaultOrigin(props);
|
|
84
84
|
const behaviours = Cdn.resolveBehaviours(props.behaviours);
|
|
85
|
+
const appName = props.appName;
|
|
85
86
|
const certificate = props.certificate ??
|
|
86
87
|
(props.certificateArn
|
|
87
88
|
? Certificate.fromCertificateArn(scope, `${id}Certificate`, props.certificateArn)
|
|
@@ -96,6 +97,7 @@ export class Cdn extends CloudFrontDistribution {
|
|
|
96
97
|
}
|
|
97
98
|
: {};
|
|
98
99
|
return {
|
|
100
|
+
appName,
|
|
99
101
|
defaultOrigin,
|
|
100
102
|
defaultCachePolicy: props.cachePolicy,
|
|
101
103
|
defaultAllowedMethods: props.defaultAllowedMethods,
|
|
@@ -273,8 +275,13 @@ export class CdnFactory {
|
|
|
273
275
|
* @returns Factory function that creates the CDN when invoked
|
|
274
276
|
*/
|
|
275
277
|
static build(id, props) {
|
|
276
|
-
return (
|
|
277
|
-
|
|
278
|
+
return (app, scope) => {
|
|
279
|
+
// The App is the only place the app name is known here; without it the
|
|
280
|
+
// export falls back to the construct id and the Domain import misses.
|
|
281
|
+
return new Cdn(scope, id, {
|
|
282
|
+
...props,
|
|
283
|
+
appName: props.appName ?? app.getName()
|
|
284
|
+
});
|
|
278
285
|
};
|
|
279
286
|
}
|
|
280
287
|
}
|
|
@@ -6,6 +6,7 @@ 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
8
|
import { toPascalCase } from "../../utils/capitaliseString.js";
|
|
9
|
+
import { ResourceNaming } from "../../utils/resourceNaming.js";
|
|
9
10
|
import { resolveAlertsTopic as resolveAlertsTopicShared } from "../../utils/resolveAlertsTopic.js";
|
|
10
11
|
import { Port } from "aws-cdk-lib/aws-ec2";
|
|
11
12
|
import { Effect, PolicyStatement } from "aws-cdk-lib/aws-iam";
|
|
@@ -364,7 +365,7 @@ export class RelationalDatabase extends Construct {
|
|
|
364
365
|
FjallLogger.warn(`Aurora database '${props.databaseName}' is configured with public accessibility. ` +
|
|
365
366
|
"This is a security risk and should only be used for development.");
|
|
366
367
|
}
|
|
367
|
-
this.database = new RdsAurora(this,
|
|
368
|
+
this.database = new RdsAurora(this, ResourceNaming.rdsChildConstructId(props.databaseName), {
|
|
368
369
|
vpc: props.vpc,
|
|
369
370
|
databaseName: props.databaseName,
|
|
370
371
|
engine: resolvedEngine,
|
|
@@ -406,7 +407,7 @@ export class RelationalDatabase extends Construct {
|
|
|
406
407
|
const resolvedDatabaseEngine = props.databaseEngine ?? "postgresql";
|
|
407
408
|
const resolvedEngine = props.engine ?? getAuroraClusterEngine(resolvedDatabaseEngine);
|
|
408
409
|
const engineConfig = getEngineConfig(resolvedDatabaseEngine);
|
|
409
|
-
this.database = new RdsAuroraGlobal(this,
|
|
410
|
+
this.database = new RdsAuroraGlobal(this, ResourceNaming.globalRdsChildConstructId(props.databaseName), {
|
|
410
411
|
vpc: props.vpc,
|
|
411
412
|
databaseName: props.databaseName,
|
|
412
413
|
primaryRegion: props.primaryRegion,
|
|
@@ -439,7 +440,7 @@ export class RelationalDatabase extends Construct {
|
|
|
439
440
|
const resolvedDatabaseEngine = props.databaseEngine ?? "postgresql";
|
|
440
441
|
const resolvedEngine = props.engine ?? getInstanceEngine(resolvedDatabaseEngine);
|
|
441
442
|
const engineConfig = getEngineConfig(resolvedDatabaseEngine);
|
|
442
|
-
this.database = new RdsInstance(this,
|
|
443
|
+
this.database = new RdsInstance(this, ResourceNaming.rdsChildConstructId(props.databaseName), {
|
|
443
444
|
vpc: props.vpc,
|
|
444
445
|
databaseName: props.databaseName,
|
|
445
446
|
instanceType: props.instanceType,
|
|
@@ -11,6 +11,12 @@ export { getDomainExportNames } from "../../../utils/domainTypes.js";
|
|
|
11
11
|
export type FjallTarget = {
|
|
12
12
|
readonly kind: "ecs";
|
|
13
13
|
readonly appName: string;
|
|
14
|
+
/**
|
|
15
|
+
* Names one ALB-bearing compute when the app fronts several. Absent
|
|
16
|
+
* means the app's sole ALB, which only exists as an export when the app
|
|
17
|
+
* has exactly one (lib/utils/albAliasTargetRegistry.ts).
|
|
18
|
+
*/
|
|
19
|
+
readonly computeName?: string;
|
|
14
20
|
} | {
|
|
15
21
|
readonly kind: "cdn";
|
|
16
22
|
readonly appName: string;
|
|
@@ -60,6 +60,9 @@ export class Storage extends Construct {
|
|
|
60
60
|
: undefined;
|
|
61
61
|
this.bucket = new S3Bucket(this, `${id}Bucket`, {
|
|
62
62
|
bucketName: props.bucketName,
|
|
63
|
+
// The bucket nests one level below, so its own id is `${id}Bucket` — the
|
|
64
|
+
// author's handle for `fjallBucket(<name>)` is THIS construct's id.
|
|
65
|
+
aliasTargetName: props.bucketName ?? id,
|
|
63
66
|
versioned: props.versioned,
|
|
64
67
|
encryption: toBucketEncryption(props.encryption),
|
|
65
68
|
encryptionKey,
|
|
@@ -10,11 +10,16 @@ export type FjallAliasTarget = FjallTarget & IAliasRecordTarget;
|
|
|
10
10
|
/**
|
|
11
11
|
* Target helper for a Fjall ECS/ALB-fronted app.
|
|
12
12
|
*
|
|
13
|
-
*
|
|
14
|
-
* `${safeApp}
|
|
15
|
-
*
|
|
13
|
+
* With no `computeName`, requires the app stack to publish exports
|
|
14
|
+
* `${safeApp}AlbDnsName` and `${safeApp}AlbHostedZoneId` — which `App.synth()`
|
|
15
|
+
* emits only for an app fronting exactly ONE ALB, since "the app's ALB" names
|
|
16
|
+
* nothing otherwise. Pass `computeName` for an app with several; that pair
|
|
17
|
+
* (`${safeApp}-${safeCompute}-Alb…`) is emitted unconditionally per compute.
|
|
18
|
+
*
|
|
19
|
+
* Naming an app that has no matching export fails at CloudFormation execution
|
|
20
|
+
* with "No export named … found", after synth and every unit test pass.
|
|
16
21
|
*/
|
|
17
|
-
export declare function fjallApp(appName: string): FjallAliasTarget;
|
|
22
|
+
export declare function fjallApp(appName: string, computeName?: string): FjallAliasTarget;
|
|
18
23
|
/**
|
|
19
24
|
* Target helper for a Fjall CloudFront-fronted app.
|
|
20
25
|
*
|
|
@@ -2,15 +2,21 @@ import { resolveBucketTarget, resolveCdnTarget, resolveCustomTarget, resolveEcsT
|
|
|
2
2
|
/**
|
|
3
3
|
* Target helper for a Fjall ECS/ALB-fronted app.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
* `${safeApp}
|
|
7
|
-
*
|
|
5
|
+
* With no `computeName`, requires the app stack to publish exports
|
|
6
|
+
* `${safeApp}AlbDnsName` and `${safeApp}AlbHostedZoneId` — which `App.synth()`
|
|
7
|
+
* emits only for an app fronting exactly ONE ALB, since "the app's ALB" names
|
|
8
|
+
* nothing otherwise. Pass `computeName` for an app with several; that pair
|
|
9
|
+
* (`${safeApp}-${safeCompute}-Alb…`) is emitted unconditionally per compute.
|
|
10
|
+
*
|
|
11
|
+
* Naming an app that has no matching export fails at CloudFormation execution
|
|
12
|
+
* with "No export named … found", after synth and every unit test pass.
|
|
8
13
|
*/
|
|
9
|
-
export function fjallApp(appName) {
|
|
10
|
-
const resolved = resolveEcsTarget(appName);
|
|
14
|
+
export function fjallApp(appName, computeName) {
|
|
15
|
+
const resolved = resolveEcsTarget(appName, computeName);
|
|
11
16
|
return {
|
|
12
17
|
kind: "ecs",
|
|
13
18
|
appName,
|
|
19
|
+
...(computeName !== undefined && { computeName }),
|
|
14
20
|
bind(record, zone) {
|
|
15
21
|
return resolved.bind(record, zone);
|
|
16
22
|
}
|
|
@@ -1,64 +1,57 @@
|
|
|
1
1
|
import { type IAliasRecordTarget } from "aws-cdk-lib/aws-route53";
|
|
2
2
|
import type { FjallTarget } from "../interfaces/domain.js";
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
4
|
+
* Producer-consumer export-name contract for `Domain` alias targets.
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* CloudFormation execution with "No export named … found" — the helpers
|
|
11
|
-
* compile and synthesise, but the cross-stack import resolves at deploy.
|
|
6
|
+
* Each resolver below imports a value a *different* stack exported. The two
|
|
7
|
+
* sides agree only if they compose the same string, and CloudFormation is the
|
|
8
|
+
* first thing that checks: a mismatch synthesises, typechecks and unit-tests
|
|
9
|
+
* clean, then fails the deploy with "No export named … found".
|
|
12
10
|
*
|
|
13
|
-
* Producer
|
|
11
|
+
* | Producer | Consumer | Export name |
|
|
12
|
+
* |-------------------------------------|-----------------------|-----------------------------------------|
|
|
13
|
+
* | `App.synth()` (sole-ALB apps only) | `resolveEcsTarget` | `<App>AlbDnsName` |
|
|
14
|
+
* | `App.synth()` (sole-ALB apps only) | `resolveEcsTarget` | `<App>AlbHostedZoneId` |
|
|
15
|
+
* | `ecsNetworking.ts` app stack | `resolveEcsTarget` | `<App>-<Compute>-AlbDnsName` |
|
|
16
|
+
* | `ecsNetworking.ts` app stack | `resolveEcsTarget` | `<App>-<Compute>-AlbHostedZoneId` |
|
|
17
|
+
* | `cloudFront.ts` CDN stack | `resolveCdnTarget` | `<App>CdnDistributionDomainName` |
|
|
18
|
+
* | `s3.ts` bucket stack | `resolveBucketTarget` | `<Bucket>WebsiteEndpoint` |
|
|
19
|
+
* | `s3.ts` bucket stack | `resolveBucketTarget` | `<Bucket>WebsiteHostedZoneId` |
|
|
14
20
|
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
21
|
+
* Both sides derive the name from `@fjall/util`'s `…ExportName` helpers, which
|
|
22
|
+
* take the RAW app or bucket name and sanitise internally. Compose these
|
|
23
|
+
* strings by hand on either side and the pair silently drifts: an earlier
|
|
24
|
+
* contract standardised only the suffix and took a pre-sanitised token, so a
|
|
25
|
+
* producer keyed on its construct id emitted `MyAppCdnCdnDistributionDomainName`
|
|
26
|
+
* against a consumer asking for `MyAppCdnDistributionDomainName`, and a bucket
|
|
27
|
+
* named `www.example.com` was imported as a name CloudFormation cannot hold.
|
|
28
|
+
* Parity is pinned by `lib/__tests__/targetExportParity.test.ts`, which reads
|
|
29
|
+
* both names out of synthesised templates rather than from the helpers.
|
|
22
30
|
*/
|
|
23
|
-
export declare const EXPORT_NAMES: {
|
|
24
|
-
readonly ALB_DNS: (safeApp: string) => string;
|
|
25
|
-
readonly ALB_HOSTED_ZONE_ID: (safeApp: string) => string;
|
|
26
|
-
readonly CDN_DOMAIN: (safeApp: string) => string;
|
|
27
|
-
readonly BUCKET_WEBSITE_ENDPOINT: (safeBucket: string) => string;
|
|
28
|
-
readonly BUCKET_WEBSITE_HZ_ID: (safeBucket: string) => string;
|
|
29
|
-
};
|
|
30
31
|
/** Route53-reserved hosted-zone id for CloudFront distributions. */
|
|
31
32
|
export declare const CLOUDFRONT_HOSTED_ZONE_ID = "Z2FDTNDATAQYW2";
|
|
32
33
|
/**
|
|
33
34
|
* Resolve a Fjall ECS/ALB-fronted app to an `IAliasRecordTarget`.
|
|
34
35
|
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
* Phase 2 wires the emitters; until then, deploys of typed targets fail with
|
|
40
|
-
* "No export named … found".
|
|
36
|
+
* `appName` is the app's name, NOT the cluster or construct id — the producer
|
|
37
|
+
* in `ecsNetworking.ts` keys on the same value. `computeName` names one of
|
|
38
|
+
* several ALB-bearing computes; omit it for an app whose sole ALB is exported
|
|
39
|
+
* unqualified by `App.synth()`.
|
|
41
40
|
*/
|
|
42
|
-
export declare function resolveEcsTarget(appName: string): IAliasRecordTarget;
|
|
41
|
+
export declare function resolveEcsTarget(appName: string, computeName?: string): IAliasRecordTarget;
|
|
43
42
|
/**
|
|
44
43
|
* Resolve a Fjall CloudFront-fronted app to an `IAliasRecordTarget`.
|
|
45
44
|
*
|
|
46
|
-
*
|
|
47
|
-
* The CloudFront hosted-zone id is the globally
|
|
48
|
-
*
|
|
49
|
-
* Phase 2 wires the emitter; until then, deploys of typed targets fail with
|
|
50
|
-
* "No export named … found".
|
|
45
|
+
* `appName` is the app's name, NOT the CDN construct id (`fjall add cdn`
|
|
46
|
+
* defaults that to `<App>Cdn`). The CloudFront hosted-zone id is the globally
|
|
47
|
+
* fixed `Z2FDTNDATAQYW2`.
|
|
51
48
|
*/
|
|
52
49
|
export declare function resolveCdnTarget(appName: string): IAliasRecordTarget;
|
|
53
50
|
/**
|
|
54
51
|
* Resolve a Fjall S3 static-site bucket to an `IAliasRecordTarget`.
|
|
55
52
|
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
* - `${safeBucket}WebsiteHostedZoneId`
|
|
59
|
-
*
|
|
60
|
-
* Phase 2 wires the emitters; until then, deploys of typed targets fail with
|
|
61
|
-
* "No export named … found".
|
|
53
|
+
* `bucketName` is the bucket's name as written, dots and all — the helper
|
|
54
|
+
* strips what CloudFormation will not hold, and the producer strips the same.
|
|
62
55
|
*/
|
|
63
56
|
export declare function resolveBucketTarget(bucketName: string): IAliasRecordTarget;
|
|
64
57
|
/**
|
|
@@ -1,32 +1,33 @@
|
|
|
1
1
|
import { Fn } from "aws-cdk-lib";
|
|
2
|
-
import {
|
|
2
|
+
import { albDnsExportName, albHostedZoneIdExportName, cdnDomainExportName, bucketWebsiteEndpointExportName, bucketWebsiteHostedZoneIdExportName } from "@fjall/util";
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
4
|
+
* Producer-consumer export-name contract for `Domain` alias targets.
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* CloudFormation execution with "No export named … found" — the helpers
|
|
11
|
-
* compile and synthesise, but the cross-stack import resolves at deploy.
|
|
6
|
+
* Each resolver below imports a value a *different* stack exported. The two
|
|
7
|
+
* sides agree only if they compose the same string, and CloudFormation is the
|
|
8
|
+
* first thing that checks: a mismatch synthesises, typechecks and unit-tests
|
|
9
|
+
* clean, then fails the deploy with "No export named … found".
|
|
12
10
|
*
|
|
13
|
-
* Producer
|
|
11
|
+
* | Producer | Consumer | Export name |
|
|
12
|
+
* |-------------------------------------|-----------------------|-----------------------------------------|
|
|
13
|
+
* | `App.synth()` (sole-ALB apps only) | `resolveEcsTarget` | `<App>AlbDnsName` |
|
|
14
|
+
* | `App.synth()` (sole-ALB apps only) | `resolveEcsTarget` | `<App>AlbHostedZoneId` |
|
|
15
|
+
* | `ecsNetworking.ts` app stack | `resolveEcsTarget` | `<App>-<Compute>-AlbDnsName` |
|
|
16
|
+
* | `ecsNetworking.ts` app stack | `resolveEcsTarget` | `<App>-<Compute>-AlbHostedZoneId` |
|
|
17
|
+
* | `cloudFront.ts` CDN stack | `resolveCdnTarget` | `<App>CdnDistributionDomainName` |
|
|
18
|
+
* | `s3.ts` bucket stack | `resolveBucketTarget` | `<Bucket>WebsiteEndpoint` |
|
|
19
|
+
* | `s3.ts` bucket stack | `resolveBucketTarget` | `<Bucket>WebsiteHostedZoneId` |
|
|
14
20
|
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
21
|
+
* Both sides derive the name from `@fjall/util`'s `…ExportName` helpers, which
|
|
22
|
+
* take the RAW app or bucket name and sanitise internally. Compose these
|
|
23
|
+
* strings by hand on either side and the pair silently drifts: an earlier
|
|
24
|
+
* contract standardised only the suffix and took a pre-sanitised token, so a
|
|
25
|
+
* producer keyed on its construct id emitted `MyAppCdnCdnDistributionDomainName`
|
|
26
|
+
* against a consumer asking for `MyAppCdnDistributionDomainName`, and a bucket
|
|
27
|
+
* named `www.example.com` was imported as a name CloudFormation cannot hold.
|
|
28
|
+
* Parity is pinned by `lib/__tests__/targetExportParity.test.ts`, which reads
|
|
29
|
+
* both names out of synthesised templates rather than from the helpers.
|
|
22
30
|
*/
|
|
23
|
-
export const EXPORT_NAMES = {
|
|
24
|
-
ALB_DNS: (safeApp) => `${safeApp}AlbDnsName`,
|
|
25
|
-
ALB_HOSTED_ZONE_ID: (safeApp) => `${safeApp}AlbHostedZoneId`,
|
|
26
|
-
CDN_DOMAIN: (safeApp) => `${safeApp}CdnDistributionDomainName`,
|
|
27
|
-
BUCKET_WEBSITE_ENDPOINT: (safeBucket) => `${safeBucket}WebsiteEndpoint`,
|
|
28
|
-
BUCKET_WEBSITE_HZ_ID: (safeBucket) => `${safeBucket}WebsiteHostedZoneId`
|
|
29
|
-
};
|
|
30
31
|
/** Route53-reserved hosted-zone id for CloudFront distributions. */
|
|
31
32
|
export const CLOUDFRONT_HOSTED_ZONE_ID = "Z2FDTNDATAQYW2";
|
|
32
33
|
/**
|
|
@@ -44,47 +45,36 @@ function buildAliasTarget(dnsName, hostedZoneId) {
|
|
|
44
45
|
/**
|
|
45
46
|
* Resolve a Fjall ECS/ALB-fronted app to an `IAliasRecordTarget`.
|
|
46
47
|
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
* Phase 2 wires the emitters; until then, deploys of typed targets fail with
|
|
52
|
-
* "No export named … found".
|
|
48
|
+
* `appName` is the app's name, NOT the cluster or construct id — the producer
|
|
49
|
+
* in `ecsNetworking.ts` keys on the same value. `computeName` names one of
|
|
50
|
+
* several ALB-bearing computes; omit it for an app whose sole ALB is exported
|
|
51
|
+
* unqualified by `App.synth()`.
|
|
53
52
|
*/
|
|
54
|
-
export function resolveEcsTarget(appName) {
|
|
55
|
-
const
|
|
56
|
-
const
|
|
57
|
-
const hostedZoneId = Fn.importValue(EXPORT_NAMES.ALB_HOSTED_ZONE_ID(safeApp));
|
|
53
|
+
export function resolveEcsTarget(appName, computeName) {
|
|
54
|
+
const dnsName = Fn.importValue(albDnsExportName(appName, computeName));
|
|
55
|
+
const hostedZoneId = Fn.importValue(albHostedZoneIdExportName(appName, computeName));
|
|
58
56
|
return buildAliasTarget(dnsName, hostedZoneId);
|
|
59
57
|
}
|
|
60
58
|
/**
|
|
61
59
|
* Resolve a Fjall CloudFront-fronted app to an `IAliasRecordTarget`.
|
|
62
60
|
*
|
|
63
|
-
*
|
|
64
|
-
* The CloudFront hosted-zone id is the globally
|
|
65
|
-
*
|
|
66
|
-
* Phase 2 wires the emitter; until then, deploys of typed targets fail with
|
|
67
|
-
* "No export named … found".
|
|
61
|
+
* `appName` is the app's name, NOT the CDN construct id (`fjall add cdn`
|
|
62
|
+
* defaults that to `<App>Cdn`). The CloudFront hosted-zone id is the globally
|
|
63
|
+
* fixed `Z2FDTNDATAQYW2`.
|
|
68
64
|
*/
|
|
69
65
|
export function resolveCdnTarget(appName) {
|
|
70
|
-
const
|
|
71
|
-
const dnsName = Fn.importValue(EXPORT_NAMES.CDN_DOMAIN(safeApp));
|
|
66
|
+
const dnsName = Fn.importValue(cdnDomainExportName(appName));
|
|
72
67
|
return buildAliasTarget(dnsName, CLOUDFRONT_HOSTED_ZONE_ID);
|
|
73
68
|
}
|
|
74
69
|
/**
|
|
75
70
|
* Resolve a Fjall S3 static-site bucket to an `IAliasRecordTarget`.
|
|
76
71
|
*
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
* - `${safeBucket}WebsiteHostedZoneId`
|
|
80
|
-
*
|
|
81
|
-
* Phase 2 wires the emitters; until then, deploys of typed targets fail with
|
|
82
|
-
* "No export named … found".
|
|
72
|
+
* `bucketName` is the bucket's name as written, dots and all — the helper
|
|
73
|
+
* strips what CloudFormation will not hold, and the producer strips the same.
|
|
83
74
|
*/
|
|
84
75
|
export function resolveBucketTarget(bucketName) {
|
|
85
|
-
const
|
|
86
|
-
const
|
|
87
|
-
const hostedZoneId = Fn.importValue(EXPORT_NAMES.BUCKET_WEBSITE_HZ_ID(safeBucket));
|
|
76
|
+
const dnsName = Fn.importValue(bucketWebsiteEndpointExportName(bucketName));
|
|
77
|
+
const hostedZoneId = Fn.importValue(bucketWebsiteHostedZoneIdExportName(bucketName));
|
|
88
78
|
return buildAliasTarget(dnsName, hostedZoneId);
|
|
89
79
|
}
|
|
90
80
|
/**
|
|
@@ -104,11 +94,11 @@ export function resolveCustomTarget(dnsName, hostedZoneId) {
|
|
|
104
94
|
export function resolveTargetToDnsName(target) {
|
|
105
95
|
switch (target.kind) {
|
|
106
96
|
case "ecs":
|
|
107
|
-
return Fn.importValue(
|
|
97
|
+
return Fn.importValue(albDnsExportName(target.appName, target.computeName));
|
|
108
98
|
case "cdn":
|
|
109
|
-
return Fn.importValue(
|
|
99
|
+
return Fn.importValue(cdnDomainExportName(target.appName));
|
|
110
100
|
case "bucket":
|
|
111
|
-
return Fn.importValue(
|
|
101
|
+
return Fn.importValue(bucketWebsiteEndpointExportName(target.bucketName));
|
|
112
102
|
case "custom":
|
|
113
103
|
return target.dnsName;
|
|
114
104
|
default: {
|
|
@@ -15,6 +15,8 @@ export class BackupVault extends Construct {
|
|
|
15
15
|
new CustomerManagedKey(this, `${props.vaultName}Key`, {
|
|
16
16
|
description: `Encryption key for backup vault ${props.vaultName}`,
|
|
17
17
|
aliasName: `cmk/backupVault/${props.vaultName}`,
|
|
18
|
+
// Recovery points in a retained vault outlive the stack.
|
|
19
|
+
protects: "outlives-stack",
|
|
18
20
|
removalPolicy
|
|
19
21
|
});
|
|
20
22
|
this.vault = new Vault(this, `${props.vaultName}Vault`, {
|
|
@@ -39,6 +39,15 @@ export interface AccessGateConfig {
|
|
|
39
39
|
password: string;
|
|
40
40
|
}
|
|
41
41
|
export interface CloudFrontDistributionProps {
|
|
42
|
+
/**
|
|
43
|
+
* App this CDN fronts. Keys the cross-stack export a `Domain` resolves via
|
|
44
|
+
* `fjallCdn(<appName>)`. Falls back to the construct id, which is what the
|
|
45
|
+
* consumer previously disagreed with: `fjall add cdn` defaults the id to
|
|
46
|
+
* `${toPascalCase(appName)}Cdn`, so the producer emitted
|
|
47
|
+
* `MyAppCdnCdnDistributionDomainName` for a consumer asking for
|
|
48
|
+
* `MyAppCdnDistributionDomainName`.
|
|
49
|
+
*/
|
|
50
|
+
appName?: string;
|
|
42
51
|
defaultOrigin: CdnOriginConfig;
|
|
43
52
|
defaultCachePolicy?: CachePolicyPreset | ICachePolicy;
|
|
44
53
|
defaultAllowedMethods?: "GET_HEAD" | "GET_HEAD_OPTIONS" | "ALL";
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Construct } from "constructs";
|
|
2
2
|
import { CfnOutput } from "aws-cdk-lib";
|
|
3
3
|
import { toPascalCase } from "../../../utils/capitaliseString.js";
|
|
4
|
+
import { cdnDomainExportName } from "@fjall/util";
|
|
4
5
|
import { Distribution, PriceClass, ViewerProtocolPolicy, CachePolicy, OriginRequestPolicy, AllowedMethods, OriginProtocolPolicy, Function as CloudFrontFunction, FunctionCode, FunctionRuntime, FunctionEventType } from "aws-cdk-lib/aws-cloudfront";
|
|
5
6
|
import { S3BucketOrigin, LoadBalancerV2Origin, HttpOrigin } from "aws-cdk-lib/aws-cloudfront-origins";
|
|
6
7
|
export class CloudFrontDistribution extends Construct {
|
|
@@ -70,7 +71,7 @@ export class CloudFrontDistribution extends Construct {
|
|
|
70
71
|
});
|
|
71
72
|
new CfnOutput(this, `${outputName}DistributionDomainName`, {
|
|
72
73
|
key: `${outputName}DistributionDomainName`,
|
|
73
|
-
exportName:
|
|
74
|
+
exportName: cdnDomainExportName(props.appName ?? id),
|
|
74
75
|
value: this.distribution.distributionDomainName,
|
|
75
76
|
description: `CloudFront distribution domain name for ${id}`
|
|
76
77
|
});
|
|
@@ -3,6 +3,7 @@ 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 { stackScopedExportName } from "../../../utils/exportNaming.js";
|
|
6
7
|
import { toPascalCase } from "../../../utils/capitaliseString.js";
|
|
7
8
|
import { createEcsServiceAlarms, createLogPatternAlarms } from "../monitoring/index.js";
|
|
8
9
|
// Extracted modules
|
|
@@ -307,7 +308,7 @@ export default class EcsCluster extends Construct {
|
|
|
307
308
|
const safeServiceName = toPascalCase(serviceName);
|
|
308
309
|
new CfnOutput(this, `${this.outputName}${safeServiceName}DeployableService`, {
|
|
309
310
|
key: `${this.outputName}${safeServiceName}DeployableService`,
|
|
310
|
-
exportName: `${props.clusterName}${serviceName}DeployableService
|
|
311
|
+
exportName: stackScopedExportName(this, `${props.clusterName}${serviceName}DeployableService`),
|
|
311
312
|
value: serviceData.service.serviceArn,
|
|
312
313
|
description: `Deployable ECS Service ARN for ${serviceName} in ${props.clusterName}`
|
|
313
314
|
});
|
|
@@ -331,12 +332,12 @@ export default class EcsCluster extends Construct {
|
|
|
331
332
|
});
|
|
332
333
|
new CfnOutput(this, `${this.outputName}DeployableCluster`, {
|
|
333
334
|
key: `${this.outputName}DeployableCluster`,
|
|
334
|
-
exportName: `${props.clusterName}DeployableCluster
|
|
335
|
+
exportName: stackScopedExportName(this, `${props.clusterName}DeployableCluster`),
|
|
335
336
|
value: cluster.clusterArn
|
|
336
337
|
});
|
|
337
338
|
new CfnOutput(this, `${this.outputName}ClusterArn`, {
|
|
338
339
|
key: `${this.outputName}ClusterArn`,
|
|
339
|
-
exportName: `${props.clusterName}ClusterArn
|
|
340
|
+
exportName: stackScopedExportName(this, `${props.clusterName}ClusterArn`),
|
|
340
341
|
value: cluster.clusterArn,
|
|
341
342
|
description: `ECS Cluster ARN for ${props.clusterName}`
|
|
342
343
|
});
|