@fjall/components-infrastructure 2.12.0 → 2.14.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.
Files changed (36) hide show
  1. package/dist/lib/app.d.ts +7 -7
  2. package/dist/lib/app.js +2 -3
  3. package/dist/lib/config/aws/accountMonitoringRole.js +2 -1
  4. package/dist/lib/config/aws/cloudTrail.d.ts +13 -0
  5. package/dist/lib/config/aws/cloudTrail.js +19 -3
  6. package/dist/lib/config/aws/disasterRecovery.js +1 -1
  7. package/dist/lib/config/aws/ecrDefaultImage.js +2 -0
  8. package/dist/lib/config/aws/organisationTrail.d.ts +16 -0
  9. package/dist/lib/config/aws/organisationTrail.js +32 -0
  10. package/dist/lib/config/aws/scpPreset.js +10 -1
  11. package/dist/lib/patterns/aws/account.d.ts +9 -0
  12. package/dist/lib/patterns/aws/account.js +29 -6
  13. package/dist/lib/patterns/aws/organisation.d.ts +9 -0
  14. package/dist/lib/patterns/aws/organisation.js +51 -5
  15. package/dist/lib/patterns/aws/storage.d.ts +1 -1
  16. package/dist/lib/patterns/aws/storage.js +5 -1
  17. package/dist/lib/resources/aws/logging/cloudTrail.d.ts +48 -1
  18. package/dist/lib/resources/aws/logging/cloudTrail.js +180 -18
  19. package/dist/lib/resources/aws/messaging/eventbridge.d.ts +3 -2
  20. package/dist/lib/resources/aws/messaging/eventbridge.js +2 -2
  21. package/dist/lib/resources/aws/networking/ipamPool.js +6 -3
  22. package/dist/lib/resources/aws/networking/serviceDiscovery.d.ts +4 -3
  23. package/dist/lib/resources/aws/networking/serviceDiscovery.js +2 -3
  24. package/dist/lib/resources/aws/storage/s3.d.ts +8 -0
  25. package/dist/lib/resources/aws/storage/s3.js +19 -4
  26. package/dist/lib/utils/cdkContext.d.ts +11 -0
  27. package/dist/lib/utils/cdkContext.js +22 -1
  28. package/dist/lib/utils/env.d.ts +19 -0
  29. package/dist/lib/utils/env.js +36 -5
  30. package/dist/lib/utils/getConfig.js +32 -12
  31. package/dist/lib/utils/orgConfigParser.d.ts +10 -0
  32. package/dist/lib/utils/orgConfigParser.js +47 -23
  33. package/dist/lib/utils/removalPolicy.d.ts +15 -0
  34. package/dist/lib/utils/removalPolicy.js +32 -0
  35. package/dist/lib/utils/standardTagsAspect.js +2 -1
  36. package/package.json +4 -4
@@ -5,6 +5,16 @@ export interface ParsedOrgConfig {
5
5
  secondaryRegions: string[];
6
6
  disasterRecoveryRegion?: string;
7
7
  }
8
+ /**
9
+ * Resolve a provider account's synth-time environment. The tier/stage
10
+ * separation (decisions/2026-06-07-account-tier-vs-stage-separation.md) nulls
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").
16
+ */
17
+ export declare function resolveSynthEnvironment(account: Pick<ProviderAccount, "environment" | "tier">): string | undefined;
8
18
  /**
9
19
  * Parse orgConfig JSON from CDK context into a validated structure.
10
20
  *
@@ -1,6 +1,46 @@
1
1
  import { VAULT_LOCK_MODES, S3_BPA_MODES } from "@fjall/util/config";
2
- import { maskSensitiveOutput } from "@fjall/util";
2
+ import { ACCOUNT_TIERS, STRUCTURAL_ENVIRONMENTS, accountTier, maskSensitiveOutput } from "@fjall/util";
3
3
  import { FjallLogger } from "./validationLogger.js";
4
+ function isProviderAccount(item) {
5
+ if (typeof item !== "object" || item === null)
6
+ return false;
7
+ const rec = item;
8
+ return (typeof rec.id === "string" &&
9
+ typeof rec.name === "string" &&
10
+ // null environment (structural accounts) must survive — rejecting it drops the account at synth.
11
+ (typeof rec.environment === "string" || rec.environment === null) &&
12
+ (rec.tier === undefined || ACCOUNT_TIERS.includes(rec.tier)) &&
13
+ (rec.managed === undefined || typeof rec.managed === "boolean") &&
14
+ (rec.oidcRoleArn === undefined || typeof rec.oidcRoleArn === "string") &&
15
+ (rec.vaultLock === undefined ||
16
+ VAULT_LOCK_MODES.includes(rec.vaultLock)) &&
17
+ (rec.s3BlockPublicAccess === undefined ||
18
+ S3_BPA_MODES.includes(rec.s3BlockPublicAccess)) &&
19
+ (rec.acknowledgeImmutableVaultLock === undefined ||
20
+ typeof rec.acknowledgeImmutableVaultLock === "boolean"));
21
+ }
22
+ /**
23
+ * Resolve a provider account's synth-time environment. The tier/stage
24
+ * separation (decisions/2026-06-07-account-tier-vs-stage-separation.md) nulls
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").
30
+ */
31
+ export function resolveSynthEnvironment(account) {
32
+ if (accountTier(account) === "organisation") {
33
+ return STRUCTURAL_ENVIRONMENTS.ROOT;
34
+ }
35
+ return account.environment ?? undefined;
36
+ }
37
+ function describeRejectedAccount(item) {
38
+ if (typeof item !== "object" || item === null)
39
+ return "<unidentifiable>";
40
+ const rec = item;
41
+ const parts = [rec.id, rec.name].filter((v) => typeof v === "string");
42
+ return parts.length > 0 ? parts.join(" / ") : "<unidentifiable>";
43
+ }
4
44
  /**
5
45
  * Parse orgConfig JSON from CDK context into a validated structure.
6
46
  *
@@ -20,28 +60,12 @@ export function parseOrgConfig(raw) {
20
60
  return empty;
21
61
  const obj = parsed;
22
62
  const providerAccounts = Array.isArray(obj.providerAccounts)
23
- ? obj.providerAccounts.filter((item) => typeof item === "object" &&
24
- item !== null &&
25
- typeof item.id === "string" &&
26
- typeof item.name === "string" &&
27
- // null environment (structural accounts) must survive — rejecting it drops the account at synth.
28
- (typeof item.environment ===
29
- "string" ||
30
- item.environment === null) &&
31
- (item.managed === undefined ||
32
- typeof item.managed === "boolean") &&
33
- (item.oidcRoleArn === undefined ||
34
- typeof item.oidcRoleArn ===
35
- "string") &&
36
- (item.vaultLock === undefined ||
37
- VAULT_LOCK_MODES.includes(item.vaultLock)) &&
38
- (item.s3BlockPublicAccess ===
39
- undefined ||
40
- S3_BPA_MODES.includes(item.s3BlockPublicAccess)) &&
41
- (item.acknowledgeImmutableVaultLock ===
42
- undefined ||
43
- typeof item
44
- .acknowledgeImmutableVaultLock === "boolean"))
63
+ ? obj.providerAccounts.filter((item) => {
64
+ if (isProviderAccount(item))
65
+ return true;
66
+ FjallLogger.warn(`[fjall] Ignoring malformed provider account in orgConfig context: ${describeRejectedAccount(item)}`);
67
+ return false;
68
+ })
45
69
  : [];
46
70
  const primaryRegion = typeof obj.primaryRegion === "string" ? obj.primaryRegion : undefined;
47
71
  const secondaryRegions = Array.isArray(obj.secondaryRegions)
@@ -1,2 +1,17 @@
1
1
  import { RemovalPolicy } from "aws-cdk-lib";
2
2
  export declare function toRemovalPolicy(value?: "DESTROY" | "RETAIN" | "SNAPSHOT"): RemovalPolicy;
3
+ /**
4
+ * Resolve the env-aware removal-policy default (D17): production → RETAIN,
5
+ * every other recognised environment → DESTROY.
6
+ *
7
+ * Unlike the generic `env()` resolver (where unrecognised → default is
8
+ * benign), an unrecognised value here throws at synth: silently landing a
9
+ * typo like `ENVIRONMENT=prod` on DESTROY deletes data when the stack is
10
+ * deleted. The accept-set derives from `ACCOUNT_STAGES_WITH_ROOT` so a new
11
+ * stage added in `@fjall/util` widens it automatically. The no-signal
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
14
+ * supplied `ENVIRONMENT=unknown` collides with the sentinel string and would
15
+ * otherwise ride the silent-DESTROY path, so it is treated as unrecognised.
16
+ */
17
+ export declare function envAwareRemovalPolicyDefault(): "DESTROY" | "RETAIN";
@@ -1,4 +1,6 @@
1
+ import { ACCOUNT_STAGES_WITH_ROOT } from "@fjall/util";
1
2
  import { RemovalPolicy } from "aws-cdk-lib";
3
+ import { getEnvironment, hasExplicitEnvironmentSignal, UNKNOWN_ENVIRONMENT } from "./env.js";
2
4
  export function toRemovalPolicy(value) {
3
5
  switch (value) {
4
6
  case "DESTROY":
@@ -10,3 +12,33 @@ export function toRemovalPolicy(value) {
10
12
  return RemovalPolicy.RETAIN;
11
13
  }
12
14
  }
15
+ const REMOVAL_DEFAULT_ENVIRONMENTS = new Set(ACCOUNT_STAGES_WITH_ROOT);
16
+ /**
17
+ * Resolve the env-aware removal-policy default (D17): production → RETAIN,
18
+ * every other recognised environment → DESTROY.
19
+ *
20
+ * Unlike the generic `env()` resolver (where unrecognised → default is
21
+ * benign), an unrecognised value here throws at synth: silently landing a
22
+ * typo like `ENVIRONMENT=prod` on DESTROY deletes data when the stack is
23
+ * deleted. The accept-set derives from `ACCOUNT_STAGES_WITH_ROOT` so a new
24
+ * stage added in `@fjall/util` widens it automatically. The no-signal
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
27
+ * supplied `ENVIRONMENT=unknown` collides with the sentinel string and would
28
+ * otherwise ride the silent-DESTROY path, so it is treated as unrecognised.
29
+ */
30
+ export function envAwareRemovalPolicyDefault() {
31
+ const environment = getEnvironment();
32
+ if (environment === UNKNOWN_ENVIRONMENT && !hasExplicitEnvironmentSignal()) {
33
+ return "DESTROY";
34
+ }
35
+ if (!REMOVAL_DEFAULT_ENVIRONMENTS.has(environment)) {
36
+ throw new Error(`Unrecognised environment "${environment}" — refusing to resolve the ` +
37
+ `env-aware removal-policy default (non-production environments ` +
38
+ `default to DESTROY, which deletes data when the stack is deleted). ` +
39
+ `Valid values: ${ACCOUNT_STAGES_WITH_ROOT.join(", ")}. ` +
40
+ `Set ENVIRONMENT or pass -c environment=<value>, or set an explicit ` +
41
+ `removalPolicy on the construct.`);
42
+ }
43
+ return environment === "production" ? "RETAIN" : "DESTROY";
44
+ }
@@ -1,5 +1,6 @@
1
1
  import { CfnResource, Stack, Tags } from "aws-cdk-lib";
2
2
  import { BACKUP_TIER_TAG_KEY, BACKUP_TIER_TAG_MAP } from "./backupTierMapping.js";
3
+ import { formatIpamPairTagValue, IPAM_OPERATIONS_POOL_TAG_KEY } from "@fjall/util/aws";
3
4
  /**
4
5
  * Aspect to apply special Fjall tags to specific resource types.
5
6
  *
@@ -49,7 +50,7 @@ export class StandardTagsAspect {
49
50
  process.env.CDK_DEFAULT_ACCOUNT;
50
51
  const region = stack.region || process.env.CDK_DEFAULT_REGION;
51
52
  if (accountId && region) {
52
- Tags.of(vpc).add("fjall:operations:pool", `${accountId}-${region}`);
53
+ Tags.of(vpc).add(IPAM_OPERATIONS_POOL_TAG_KEY, formatIpamPairTagValue(accountId, region));
53
54
  }
54
55
  }
55
56
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fjall/components-infrastructure",
3
- "version": "2.12.0",
3
+ "version": "2.14.0",
4
4
  "license": "SEE LICENSE IN LICENSE",
5
5
  "type": "module",
6
6
  "bin": {
@@ -63,8 +63,8 @@
63
63
  },
64
64
  "dependencies": {
65
65
  "@aws-sdk/client-organizations": "^3.1038.0",
66
- "@fjall/generator": "^2.12.0",
67
- "@fjall/util": "^2.12.0",
66
+ "@fjall/generator": "^2.14.0",
67
+ "@fjall/util": "^2.14.0",
68
68
  "constructs": "^10.0.0",
69
69
  "uuid": "^14.0.0"
70
70
  },
@@ -79,5 +79,5 @@
79
79
  "engines": {
80
80
  "node": ">=18.0.0"
81
81
  },
82
- "gitHead": "dca39a47da956d3d94c689dd782fe285d711d25e"
82
+ "gitHead": "ce434478f9e3d5e5ccf979c152a237c81e4acee5"
83
83
  }