@fjall/components-infrastructure 7.3.0 → 9.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/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 +27 -9
- 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/rdsAuroraGlobal.js +2 -1
- package/dist/lib/resources/aws/database/rdsHelpers.js +12 -2
- package/dist/lib/resources/aws/database/rdsInstance.js +2 -1
- 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 +6 -1
- package/dist/lib/resources/aws/secrets/kms.d.ts +24 -7
- package/dist/lib/resources/aws/secrets/kms.js +27 -5
- package/dist/lib/resources/aws/secrets/parameter.js +3 -1
- package/dist/lib/resources/aws/secrets/secret.js +4 -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/package.json +5 -4
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { Construct } from "constructs";
|
|
2
|
+
/**
|
|
3
|
+
* Qualify a CloudFormation export name with the stack that emits it.
|
|
4
|
+
*
|
|
5
|
+
* Export names are unique per account per region, so a name built only from a
|
|
6
|
+
* construct id or a cluster name is shared by every app that happens to use
|
|
7
|
+
* the same id — and the ids inside a pattern are literals the app author never
|
|
8
|
+
* chooses. Two apps then collide, and the second one fails at CloudFormation
|
|
9
|
+
* execution ("Export with name X is already exported by stack Y") after its
|
|
10
|
+
* first resources exist, so it rolls back rather than refusing up front.
|
|
11
|
+
*
|
|
12
|
+
* Stack names are already unique per account+region (CloudFormation enforces
|
|
13
|
+
* it) and Fjall derives them from the app name, so prefixing with the stack
|
|
14
|
+
* name makes the export globally unique AND puts the app name in it.
|
|
15
|
+
*
|
|
16
|
+
* The suffix must stay last. Consumers match these by suffix and by app-name
|
|
17
|
+
* token, never by whole string — `EcsServiceResolver` filters on
|
|
18
|
+
* `endsWith("deployableservice")` (deploy-core
|
|
19
|
+
* services/infrastructure/EcsServiceResolver.ts) and the webapp finds a linked
|
|
20
|
+
* app's CIDR by `ListExports` + a `-vpc-cidr` suffix filter
|
|
21
|
+
* (webapp app/.server/services/infrastructure/captureVpcCidr.ts). Prefixing is
|
|
22
|
+
* transparent to both; changing the tail is not.
|
|
23
|
+
*
|
|
24
|
+
* NOT for exports a consumer reconstructs by whole name. Those are contracts
|
|
25
|
+
* with their own vocabulary module and must keep their exact spelling:
|
|
26
|
+
* `getDomainExportNames` (@fjall/util), `EXPORT_NAMES`
|
|
27
|
+
* (patterns/aws/targets/targetResolution.ts), `SHARED_ALARM_TOPIC_EXPORT_NAME`,
|
|
28
|
+
* the CloudTrail output keys, and `${toPascalCase(appName)}EcrRepositoryName`
|
|
29
|
+
* (read by `resolveEcrRepositoryName` in the CLI). Account-level singletons
|
|
30
|
+
* (`AccountId`, `Environment`, `OrganisationId`, `FjallAuditRoleArn`, …) also
|
|
31
|
+
* keep their bare names: exactly one is meant to exist, so a collision there
|
|
32
|
+
* correctly reports a duplicate deployment.
|
|
33
|
+
*/
|
|
34
|
+
export declare function stackScopedExportName(scope: Construct, suffix: string): string;
|
|
35
|
+
/**
|
|
36
|
+
* Export name for the account's IPAM private default scope.
|
|
37
|
+
*
|
|
38
|
+
* Deliberately unqualified: exactly one IPAM exists per account, so a
|
|
39
|
+
* collision here correctly reports a duplicate deployment rather than an
|
|
40
|
+
* accidental clash between two apps.
|
|
41
|
+
*
|
|
42
|
+
* It lives here rather than beside the construct that emits it because the
|
|
43
|
+
* producer is in `config/` and the consumer (`IpamPool`) is in `resources/`,
|
|
44
|
+
* which may not import from `config/`. Both sides must read the same
|
|
45
|
+
* constant: CloudFormation export names are case-sensitive, and the two sites
|
|
46
|
+
* previously disagreed on the leading capital — so the import could never
|
|
47
|
+
* resolve, and the only reason nothing broke is that every caller passed
|
|
48
|
+
* `ipamScope` explicitly and never reached the fallback.
|
|
49
|
+
*/
|
|
50
|
+
export declare const IPAM_PRIVATE_DEFAULT_SCOPE_EXPORT_NAME = "IpamPrivateDefaultScopeId";
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { Stack } from "aws-cdk-lib";
|
|
2
|
+
/**
|
|
3
|
+
* Qualify a CloudFormation export name with the stack that emits it.
|
|
4
|
+
*
|
|
5
|
+
* Export names are unique per account per region, so a name built only from a
|
|
6
|
+
* construct id or a cluster name is shared by every app that happens to use
|
|
7
|
+
* the same id — and the ids inside a pattern are literals the app author never
|
|
8
|
+
* chooses. Two apps then collide, and the second one fails at CloudFormation
|
|
9
|
+
* execution ("Export with name X is already exported by stack Y") after its
|
|
10
|
+
* first resources exist, so it rolls back rather than refusing up front.
|
|
11
|
+
*
|
|
12
|
+
* Stack names are already unique per account+region (CloudFormation enforces
|
|
13
|
+
* it) and Fjall derives them from the app name, so prefixing with the stack
|
|
14
|
+
* name makes the export globally unique AND puts the app name in it.
|
|
15
|
+
*
|
|
16
|
+
* The suffix must stay last. Consumers match these by suffix and by app-name
|
|
17
|
+
* token, never by whole string — `EcsServiceResolver` filters on
|
|
18
|
+
* `endsWith("deployableservice")` (deploy-core
|
|
19
|
+
* services/infrastructure/EcsServiceResolver.ts) and the webapp finds a linked
|
|
20
|
+
* app's CIDR by `ListExports` + a `-vpc-cidr` suffix filter
|
|
21
|
+
* (webapp app/.server/services/infrastructure/captureVpcCidr.ts). Prefixing is
|
|
22
|
+
* transparent to both; changing the tail is not.
|
|
23
|
+
*
|
|
24
|
+
* NOT for exports a consumer reconstructs by whole name. Those are contracts
|
|
25
|
+
* with their own vocabulary module and must keep their exact spelling:
|
|
26
|
+
* `getDomainExportNames` (@fjall/util), `EXPORT_NAMES`
|
|
27
|
+
* (patterns/aws/targets/targetResolution.ts), `SHARED_ALARM_TOPIC_EXPORT_NAME`,
|
|
28
|
+
* the CloudTrail output keys, and `${toPascalCase(appName)}EcrRepositoryName`
|
|
29
|
+
* (read by `resolveEcrRepositoryName` in the CLI). Account-level singletons
|
|
30
|
+
* (`AccountId`, `Environment`, `OrganisationId`, `FjallAuditRoleArn`, …) also
|
|
31
|
+
* keep their bare names: exactly one is meant to exist, so a collision there
|
|
32
|
+
* correctly reports a duplicate deployment.
|
|
33
|
+
*/
|
|
34
|
+
export function stackScopedExportName(scope, suffix) {
|
|
35
|
+
return `${Stack.of(scope).stackName}${suffix}`;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Export name for the account's IPAM private default scope.
|
|
39
|
+
*
|
|
40
|
+
* Deliberately unqualified: exactly one IPAM exists per account, so a
|
|
41
|
+
* collision here correctly reports a duplicate deployment rather than an
|
|
42
|
+
* accidental clash between two apps.
|
|
43
|
+
*
|
|
44
|
+
* It lives here rather than beside the construct that emits it because the
|
|
45
|
+
* producer is in `config/` and the consumer (`IpamPool`) is in `resources/`,
|
|
46
|
+
* which may not import from `config/`. Both sides must read the same
|
|
47
|
+
* constant: CloudFormation export names are case-sensitive, and the two sites
|
|
48
|
+
* previously disagreed on the leading capital — so the import could never
|
|
49
|
+
* resolve, and the only reason nothing broke is that every caller passed
|
|
50
|
+
* `ipamScope` explicitly and never reached the fallback.
|
|
51
|
+
*/
|
|
52
|
+
export const IPAM_PRIVATE_DEFAULT_SCOPE_EXPORT_NAME = "IpamPrivateDefaultScopeId";
|
|
@@ -6,15 +6,10 @@ export interface ParsedOrgConfig {
|
|
|
6
6
|
disasterRecoveryRegion?: string;
|
|
7
7
|
}
|
|
8
8
|
/**
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* the wire `environment` for organisation-tier accounts, but scaffolded org
|
|
12
|
-
* entry points gate on `config.environment === "root"` — decode the tier back
|
|
13
|
-
* to the historical "root" marker via the sanctioned `accountTier()` decoder.
|
|
14
|
-
* Workload stages pass through verbatim; a null stage on any other tier stays
|
|
15
|
-
* unresolved (callers fall back to "unknown").
|
|
9
|
+
* Re-exported from `@fjall/util` so the synth-time reader and deploy-core's
|
|
10
|
+
* pre-synth resolver cannot drift. Do not redeclare it here.
|
|
16
11
|
*/
|
|
17
|
-
export
|
|
12
|
+
export { resolveSynthEnvironment } from "@fjall/util";
|
|
18
13
|
/**
|
|
19
14
|
* Parse orgConfig JSON from CDK context into a validated structure.
|
|
20
15
|
*
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { VAULT_LOCK_MODES, S3_BPA_MODES } from "@fjall/util/config";
|
|
2
|
-
import { ACCOUNT_TIERS,
|
|
2
|
+
import { ACCOUNT_TIERS, maskSensitiveOutput } from "@fjall/util";
|
|
3
3
|
import { FjallLogger } from "./validationLogger.js";
|
|
4
4
|
function isProviderAccount(item) {
|
|
5
5
|
if (typeof item !== "object" || item === null)
|
|
@@ -20,20 +20,10 @@ function isProviderAccount(item) {
|
|
|
20
20
|
typeof rec.acknowledgeImmutableVaultLock === "boolean"));
|
|
21
21
|
}
|
|
22
22
|
/**
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
* the wire `environment` for organisation-tier accounts, but scaffolded org
|
|
26
|
-
* entry points gate on `config.environment === "root"` — decode the tier back
|
|
27
|
-
* to the historical "root" marker via the sanctioned `accountTier()` decoder.
|
|
28
|
-
* Workload stages pass through verbatim; a null stage on any other tier stays
|
|
29
|
-
* unresolved (callers fall back to "unknown").
|
|
23
|
+
* Re-exported from `@fjall/util` so the synth-time reader and deploy-core's
|
|
24
|
+
* pre-synth resolver cannot drift. Do not redeclare it here.
|
|
30
25
|
*/
|
|
31
|
-
export
|
|
32
|
-
if (accountTier(account) === "organisation") {
|
|
33
|
-
return STRUCTURAL_ENVIRONMENTS.ROOT;
|
|
34
|
-
}
|
|
35
|
-
return account.environment ?? undefined;
|
|
36
|
-
}
|
|
26
|
+
export { resolveSynthEnvironment } from "@fjall/util";
|
|
37
27
|
function describeRejectedAccount(item) {
|
|
38
28
|
if (typeof item !== "object" || item === null)
|
|
39
29
|
return "<unidentifiable>";
|
|
@@ -8,10 +8,18 @@ export declare function toRemovalPolicy(value?: "DESTROY" | "RETAIN" | "SNAPSHOT
|
|
|
8
8
|
* benign), an unrecognised value here throws at synth: silently landing a
|
|
9
9
|
* typo like `ENVIRONMENT=prod` on DESTROY deletes data when the stack is
|
|
10
10
|
* deleted. The accept-set derives from `ACCOUNT_STAGES_WITH_ROOT` so a new
|
|
11
|
-
* stage added in `@fjall/util` widens it automatically.
|
|
12
|
-
* sentinel keeps the historical warn-and-DESTROY behaviour — raw `cdk synth`
|
|
13
|
-
* without context and null-stage cascade synths rely on it. An explicitly
|
|
11
|
+
* stage added in `@fjall/util` widens it automatically. An explicitly
|
|
14
12
|
* supplied `ENVIRONMENT=unknown` collides with the sentinel string and would
|
|
15
13
|
* otherwise ride the silent-DESTROY path, so it is treated as unrecognised.
|
|
14
|
+
*
|
|
15
|
+
* The no-signal sentinel keeps warn-and-DESTROY for a BARE `cdk synth`: a
|
|
16
|
+
* developer inspecting a template deploys nothing, so the policy it prints
|
|
17
|
+
* costs nothing. It is not a safe default for a synth that is about to
|
|
18
|
+
* deploy, so a Fjall-driven one (`isFjallDrivenSynth`) throws instead — there,
|
|
19
|
+
* "no signal" means the account resolved to no environment, and reading that
|
|
20
|
+
* as DESTROY is how a production stack ends up with `Delete` on every bucket.
|
|
21
|
+
* deploy-core resolves the environment before synth and passes it explicitly
|
|
22
|
+
* (deploy-core orchestration/deployEnvironment.ts), so a conforming deploy
|
|
23
|
+
* reaches neither branch; this is the backstop for one that does.
|
|
16
24
|
*/
|
|
17
25
|
export declare function envAwareRemovalPolicyDefault(): "DESTROY" | "RETAIN";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ACCOUNT_STAGES_WITH_ROOT } from "@fjall/util";
|
|
2
2
|
import { RemovalPolicy } from "aws-cdk-lib";
|
|
3
|
-
import { getEnvironment, hasExplicitEnvironmentSignal, UNKNOWN_ENVIRONMENT } from "./env.js";
|
|
3
|
+
import { getEnvironment, hasExplicitEnvironmentSignal, isFjallDrivenSynth, UNKNOWN_ENVIRONMENT } from "./env.js";
|
|
4
4
|
export function toRemovalPolicy(value) {
|
|
5
5
|
switch (value) {
|
|
6
6
|
case "DESTROY":
|
|
@@ -21,16 +21,31 @@ const REMOVAL_DEFAULT_ENVIRONMENTS = new Set(ACCOUNT_STAGES_WITH_ROOT);
|
|
|
21
21
|
* benign), an unrecognised value here throws at synth: silently landing a
|
|
22
22
|
* typo like `ENVIRONMENT=prod` on DESTROY deletes data when the stack is
|
|
23
23
|
* deleted. The accept-set derives from `ACCOUNT_STAGES_WITH_ROOT` so a new
|
|
24
|
-
* stage added in `@fjall/util` widens it automatically.
|
|
25
|
-
* sentinel keeps the historical warn-and-DESTROY behaviour — raw `cdk synth`
|
|
26
|
-
* without context and null-stage cascade synths rely on it. An explicitly
|
|
24
|
+
* stage added in `@fjall/util` widens it automatically. An explicitly
|
|
27
25
|
* supplied `ENVIRONMENT=unknown` collides with the sentinel string and would
|
|
28
26
|
* otherwise ride the silent-DESTROY path, so it is treated as unrecognised.
|
|
27
|
+
*
|
|
28
|
+
* The no-signal sentinel keeps warn-and-DESTROY for a BARE `cdk synth`: a
|
|
29
|
+
* developer inspecting a template deploys nothing, so the policy it prints
|
|
30
|
+
* costs nothing. It is not a safe default for a synth that is about to
|
|
31
|
+
* deploy, so a Fjall-driven one (`isFjallDrivenSynth`) throws instead — there,
|
|
32
|
+
* "no signal" means the account resolved to no environment, and reading that
|
|
33
|
+
* as DESTROY is how a production stack ends up with `Delete` on every bucket.
|
|
34
|
+
* deploy-core resolves the environment before synth and passes it explicitly
|
|
35
|
+
* (deploy-core orchestration/deployEnvironment.ts), so a conforming deploy
|
|
36
|
+
* reaches neither branch; this is the backstop for one that does.
|
|
29
37
|
*/
|
|
30
38
|
export function envAwareRemovalPolicyDefault() {
|
|
31
39
|
const environment = getEnvironment();
|
|
32
40
|
if (environment === UNKNOWN_ENVIRONMENT && !hasExplicitEnvironmentSignal()) {
|
|
33
|
-
|
|
41
|
+
if (!isFjallDrivenSynth())
|
|
42
|
+
return "DESTROY";
|
|
43
|
+
throw new Error(`Refusing to resolve the env-aware removal-policy default: this deploy ` +
|
|
44
|
+
`carries an organisation config, but the account it targets resolved ` +
|
|
45
|
+
`to no environment. Defaulting to DESTROY here would delete data when ` +
|
|
46
|
+
`the stack is deleted. Set the target account's environment in the ` +
|
|
47
|
+
`organisation config, or pass -c environment=<value> explicitly. ` +
|
|
48
|
+
`Valid values: ${ACCOUNT_STAGES_WITH_ROOT.join(", ")}.`);
|
|
34
49
|
}
|
|
35
50
|
if (!REMOVAL_DEFAULT_ENVIRONMENTS.has(environment)) {
|
|
36
51
|
throw new Error(`Unrecognised environment "${environment}" — refusing to resolve the ` +
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fjall/components-infrastructure",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "9.0.0",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "git+https://github.com/fjall-tech/fjall.git",
|
|
@@ -47,8 +47,9 @@
|
|
|
47
47
|
"test:watch": "vitest",
|
|
48
48
|
"contract:manifest": "FJALL_WRITE_CONTRACT_MANIFEST=1 vitest run lib/__tests__/_contract/contract.test.ts",
|
|
49
49
|
"cdk": "cdk",
|
|
50
|
-
"typecheck": "tsc --noEmit && tsc --noEmit -p tsconfig.test.json",
|
|
50
|
+
"typecheck": "tsc --noEmit && tsc --noEmit -p tsconfig.test.json && tsc --noEmit -p tsconfig.deploy-tests.json",
|
|
51
51
|
"typecheck:tests": "tsc --noEmit -p tsconfig.test.json",
|
|
52
|
+
"typecheck:deploy-tests": "tsc --noEmit -p tsconfig.deploy-tests.json",
|
|
52
53
|
"check:scripts": "tsc --project tsconfig.scripts.json",
|
|
53
54
|
"deploy-test": "tsx deploy-tests/run.mts",
|
|
54
55
|
"deploy-test:ops": "tsx deploy-tests/ops.mts",
|
|
@@ -78,8 +79,8 @@
|
|
|
78
79
|
},
|
|
79
80
|
"dependencies": {
|
|
80
81
|
"@aws-sdk/client-organizations": "^3.1098.0",
|
|
81
|
-
"@fjall/generator": "^
|
|
82
|
-
"@fjall/util": "^
|
|
82
|
+
"@fjall/generator": "^9.0.0",
|
|
83
|
+
"@fjall/util": "^9.0.0",
|
|
83
84
|
"constructs": "^10.7.2"
|
|
84
85
|
},
|
|
85
86
|
"overrides": {
|