@fjall/deploy-core 0.89.2

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 (154) hide show
  1. package/LICENSE +21 -0
  2. package/dist/src/aws/AwsProvider.d.ts +39 -0
  3. package/dist/src/aws/AwsProvider.js +1 -0
  4. package/dist/src/aws/SimpleAwsProvider.d.ts +22 -0
  5. package/dist/src/aws/SimpleAwsProvider.js +73 -0
  6. package/dist/src/aws/index.d.ts +4 -0
  7. package/dist/src/aws/index.js +3 -0
  8. package/dist/src/aws/organisations/accounts.d.ts +21 -0
  9. package/dist/src/aws/organisations/accounts.js +99 -0
  10. package/dist/src/aws/organisations/backup.d.ts +12 -0
  11. package/dist/src/aws/organisations/backup.js +28 -0
  12. package/dist/src/aws/organisations/costAllocation.d.ts +12 -0
  13. package/dist/src/aws/organisations/costAllocation.js +26 -0
  14. package/dist/src/aws/organisations/identityCentre.d.ts +8 -0
  15. package/dist/src/aws/organisations/identityCentre.js +19 -0
  16. package/dist/src/aws/organisations/index.d.ts +16 -0
  17. package/dist/src/aws/organisations/index.js +12 -0
  18. package/dist/src/aws/organisations/ipam.d.ts +7 -0
  19. package/dist/src/aws/organisations/ipam.js +18 -0
  20. package/dist/src/aws/organisations/organisation.d.ts +12 -0
  21. package/dist/src/aws/organisations/organisation.js +94 -0
  22. package/dist/src/aws/organisations/organisationalUnits.d.ts +19 -0
  23. package/dist/src/aws/organisations/organisationalUnits.js +125 -0
  24. package/dist/src/aws/organisations/policies.d.ts +7 -0
  25. package/dist/src/aws/organisations/policies.js +36 -0
  26. package/dist/src/aws/organisations/ram.d.ts +7 -0
  27. package/dist/src/aws/organisations/ram.js +15 -0
  28. package/dist/src/aws/organisations/serviceAccess.d.ts +7 -0
  29. package/dist/src/aws/organisations/serviceAccess.js +38 -0
  30. package/dist/src/aws/organisations/trustedAccess.d.ts +7 -0
  31. package/dist/src/aws/organisations/trustedAccess.js +15 -0
  32. package/dist/src/aws/organisations/types.d.ts +29 -0
  33. package/dist/src/aws/organisations/types.js +16 -0
  34. package/dist/src/aws/utils/CloudFormationFailureAnalyser.d.ts +32 -0
  35. package/dist/src/aws/utils/CloudFormationFailureAnalyser.js +228 -0
  36. package/dist/src/aws/utils/cloudformationEvents.d.ts +98 -0
  37. package/dist/src/aws/utils/cloudformationEvents.js +596 -0
  38. package/dist/src/aws/utils/errors.d.ts +26 -0
  39. package/dist/src/aws/utils/errors.js +59 -0
  40. package/dist/src/aws/utils/regions.d.ts +1 -0
  41. package/dist/src/aws/utils/regions.js +1 -0
  42. package/dist/src/aws/utils/stackStatus.d.ts +23 -0
  43. package/dist/src/aws/utils/stackStatus.js +90 -0
  44. package/dist/src/index.d.ts +35 -0
  45. package/dist/src/index.js +45 -0
  46. package/dist/src/orchestration/applicationDeploy.d.ts +11 -0
  47. package/dist/src/orchestration/applicationDeploy.js +327 -0
  48. package/dist/src/orchestration/contextHelpers.d.ts +9 -0
  49. package/dist/src/orchestration/contextHelpers.js +14 -0
  50. package/dist/src/orchestration/deploy.d.ts +10 -0
  51. package/dist/src/orchestration/deploy.js +42 -0
  52. package/dist/src/orchestration/detectionPipeline.d.ts +23 -0
  53. package/dist/src/orchestration/detectionPipeline.js +65 -0
  54. package/dist/src/orchestration/dockerInterface.d.ts +56 -0
  55. package/dist/src/orchestration/dockerInterface.js +1 -0
  56. package/dist/src/orchestration/domainInterface.d.ts +37 -0
  57. package/dist/src/orchestration/domainInterface.js +1 -0
  58. package/dist/src/orchestration/index.d.ts +8 -0
  59. package/dist/src/orchestration/index.js +3 -0
  60. package/dist/src/orchestration/organisationDeploy.d.ts +16 -0
  61. package/dist/src/orchestration/organisationDeploy.js +382 -0
  62. package/dist/src/orchestration/organisationSetup.d.ts +42 -0
  63. package/dist/src/orchestration/organisationSetup.js +227 -0
  64. package/dist/src/orchestration/resolveOperation.d.ts +10 -0
  65. package/dist/src/orchestration/resolveOperation.js +53 -0
  66. package/dist/src/orchestration/serviceFactory.d.ts +15 -0
  67. package/dist/src/orchestration/serviceFactory.js +16 -0
  68. package/dist/src/services/application/ApplicationStackService.d.ts +93 -0
  69. package/dist/src/services/application/ApplicationStackService.js +436 -0
  70. package/dist/src/services/application/index.d.ts +1 -0
  71. package/dist/src/services/application/index.js +1 -0
  72. package/dist/src/services/infrastructure/CdkArgumentBuilder.d.ts +12 -0
  73. package/dist/src/services/infrastructure/CdkArgumentBuilder.js +67 -0
  74. package/dist/src/services/infrastructure/CdkCommandRunner.d.ts +30 -0
  75. package/dist/src/services/infrastructure/CdkCommandRunner.js +241 -0
  76. package/dist/src/services/infrastructure/CdkErrorFormatter.d.ts +4 -0
  77. package/dist/src/services/infrastructure/CdkErrorFormatter.js +194 -0
  78. package/dist/src/services/infrastructure/CdkEventMonitoring.d.ts +19 -0
  79. package/dist/src/services/infrastructure/CdkEventMonitoring.js +41 -0
  80. package/dist/src/services/infrastructure/CdkOutputAnalyser.d.ts +43 -0
  81. package/dist/src/services/infrastructure/CdkOutputAnalyser.js +125 -0
  82. package/dist/src/services/infrastructure/CdkOutputParser.d.ts +8 -0
  83. package/dist/src/services/infrastructure/CdkOutputParser.js +33 -0
  84. package/dist/src/services/infrastructure/CdkProcessManager.d.ts +20 -0
  85. package/dist/src/services/infrastructure/CdkProcessManager.js +244 -0
  86. package/dist/src/services/infrastructure/CdkService.d.ts +71 -0
  87. package/dist/src/services/infrastructure/CdkService.js +254 -0
  88. package/dist/src/services/infrastructure/CloudFormationService.d.ts +79 -0
  89. package/dist/src/services/infrastructure/CloudFormationService.js +249 -0
  90. package/dist/src/services/infrastructure/index.d.ts +8 -0
  91. package/dist/src/services/infrastructure/index.js +7 -0
  92. package/dist/src/services/supporting/CdkContextBuilder.d.ts +49 -0
  93. package/dist/src/services/supporting/CdkContextBuilder.js +44 -0
  94. package/dist/src/services/supporting/TemplateHashService.d.ts +67 -0
  95. package/dist/src/services/supporting/TemplateHashService.js +152 -0
  96. package/dist/src/services/supporting/helpers.d.ts +46 -0
  97. package/dist/src/services/supporting/helpers.js +81 -0
  98. package/dist/src/services/supporting/index.d.ts +3 -0
  99. package/dist/src/services/supporting/index.js +3 -0
  100. package/dist/src/types/FjallState.d.ts +50 -0
  101. package/dist/src/types/FjallState.js +118 -0
  102. package/dist/src/types/ProgressEvent.d.ts +35 -0
  103. package/dist/src/types/ProgressEvent.js +48 -0
  104. package/dist/src/types/apiClient.d.ts +34 -0
  105. package/dist/src/types/apiClient.js +1 -0
  106. package/dist/src/types/application/ApplicationServiceTypes.d.ts +56 -0
  107. package/dist/src/types/application/ApplicationServiceTypes.js +30 -0
  108. package/dist/src/types/application/index.d.ts +1 -0
  109. package/dist/src/types/application/index.js +1 -0
  110. package/dist/src/types/callbacks.d.ts +36 -0
  111. package/dist/src/types/callbacks.js +1 -0
  112. package/dist/src/types/constants.d.ts +6 -0
  113. package/dist/src/types/constants.js +6 -0
  114. package/dist/src/types/credentials.d.ts +30 -0
  115. package/dist/src/types/credentials.js +1 -0
  116. package/dist/src/types/deployment/DeploymentServiceTypes.d.ts +23 -0
  117. package/dist/src/types/deployment/DeploymentServiceTypes.js +1 -0
  118. package/dist/src/types/deployment/DeploymentTypes.d.ts +29 -0
  119. package/dist/src/types/deployment/DeploymentTypes.js +1 -0
  120. package/dist/src/types/deployment/cloudformation.d.ts +14 -0
  121. package/dist/src/types/deployment/cloudformation.js +1 -0
  122. package/dist/src/types/deployment/index.d.ts +5 -0
  123. package/dist/src/types/deployment/index.js +1 -0
  124. package/dist/src/types/deployment/parallel.d.ts +46 -0
  125. package/dist/src/types/deployment/parallel.js +10 -0
  126. package/dist/src/types/errors/CdkError.d.ts +14 -0
  127. package/dist/src/types/errors/CdkError.js +20 -0
  128. package/dist/src/types/errors/ServiceError.d.ts +86 -0
  129. package/dist/src/types/errors/ServiceError.js +119 -0
  130. package/dist/src/types/events.d.ts +40 -0
  131. package/dist/src/types/events.js +5 -0
  132. package/dist/src/types/index.d.ts +20 -0
  133. package/dist/src/types/index.js +9 -0
  134. package/dist/src/types/operations.d.ts +193 -0
  135. package/dist/src/types/operations.js +285 -0
  136. package/dist/src/types/orgConfig.d.ts +28 -0
  137. package/dist/src/types/orgConfig.js +11 -0
  138. package/dist/src/types/params.d.ts +74 -0
  139. package/dist/src/types/params.js +1 -0
  140. package/dist/src/types/patternDetection.d.ts +43 -0
  141. package/dist/src/types/patternDetection.js +92 -0
  142. package/dist/src/types/validation.d.ts +12 -0
  143. package/dist/src/types/validation.js +1 -0
  144. package/dist/src/util/fsHelpers.d.ts +4 -0
  145. package/dist/src/util/fsHelpers.js +16 -0
  146. package/dist/src/util/index.d.ts +3 -0
  147. package/dist/src/util/index.js +3 -0
  148. package/dist/src/util/securityHelpers.d.ts +31 -0
  149. package/dist/src/util/securityHelpers.js +124 -0
  150. package/dist/src/util/singleton.d.ts +2 -0
  151. package/dist/src/util/singleton.js +9 -0
  152. package/dist/src/util/sleep.d.ts +4 -0
  153. package/dist/src/util/sleep.js +4 -0
  154. package/package.json +42 -0
@@ -0,0 +1,125 @@
1
+ import { CreateOrganizationalUnitCommand, ListOrganizationalUnitsForParentCommand, ListParentsCommand, MoveAccountCommand } from "@aws-sdk/client-organizations";
2
+ import { success, failure } from "@fjall/generator";
3
+ import { extractErrorName } from "./types.js";
4
+ /**
5
+ * List all OUs under a parent, handling pagination.
6
+ */
7
+ async function listOUsForParent(client, parentId) {
8
+ const response = await client.send(new ListOrganizationalUnitsForParentCommand({ ParentId: parentId }));
9
+ let ous = response.OrganizationalUnits ?? [];
10
+ let nextToken = response.NextToken;
11
+ while (nextToken) {
12
+ const nextResponse = await client.send(new ListOrganizationalUnitsForParentCommand({
13
+ ParentId: parentId,
14
+ NextToken: nextToken
15
+ }));
16
+ ous = ous.concat(nextResponse.OrganizationalUnits ?? []);
17
+ nextToken = nextResponse.NextToken;
18
+ }
19
+ return ous;
20
+ }
21
+ /**
22
+ * Get the parent ID for a child (account or OU).
23
+ */
24
+ async function getParentId(client, childId) {
25
+ try {
26
+ const response = await client.send(new ListParentsCommand({ ChildId: childId }));
27
+ return response.Parents?.[0]?.Id;
28
+ }
29
+ catch (error) {
30
+ const errorName = extractErrorName(error);
31
+ if (errorName === "ChildNotFoundException") {
32
+ return undefined;
33
+ }
34
+ throw error;
35
+ }
36
+ }
37
+ /**
38
+ * Ensure the specified organisational units exist under the root.
39
+ * Creates any missing OUs. Returns a map of OU name (lowercase) → OU ID.
40
+ *
41
+ * Idempotent — existing OUs are adopted, not duplicated.
42
+ *
43
+ * @param ouNames Array of OU names to ensure exist (will be capitalised)
44
+ */
45
+ export async function ensureOrganisationalUnitsExist(client, rootId, ouNames) {
46
+ try {
47
+ const ouMap = {};
48
+ for (const ouName of ouNames) {
49
+ const capitalised = ouName.charAt(0).toUpperCase() + ouName.slice(1);
50
+ // Check if OU already exists
51
+ const existing = await listOUsForParent(client, rootId);
52
+ const match = existing.find((ou) => ou.Name === capitalised);
53
+ if (match?.Id) {
54
+ ouMap[ouName.toLowerCase()] = match.Id;
55
+ continue;
56
+ }
57
+ // Create the OU
58
+ const response = await client.send(new CreateOrganizationalUnitCommand({
59
+ Name: capitalised,
60
+ ParentId: rootId
61
+ }));
62
+ const ou = response.OrganizationalUnit;
63
+ if (!ou?.Id) {
64
+ return failure(new Error(`OU "${capitalised}" was created but has no ID`));
65
+ }
66
+ ouMap[ouName.toLowerCase()] = ou.Id;
67
+ }
68
+ return success(ouMap);
69
+ }
70
+ catch (error) {
71
+ return failure(new Error(`Failed to ensure OUs exist: ${error instanceof Error ? error.message : String(error)}`));
72
+ }
73
+ }
74
+ /**
75
+ * Place accounts into the correct organisational units.
76
+ * Skips accounts with environment "root" and accounts already in the target OU.
77
+ *
78
+ * Idempotent — accounts already in the correct OU are counted but not moved.
79
+ */
80
+ export async function placeAccountsInOUs(client, ouMap, accounts) {
81
+ try {
82
+ if (accounts.length === 0) {
83
+ return success({ moved: 0, alreadyPlaced: 0 });
84
+ }
85
+ let moved = 0;
86
+ let alreadyPlaced = 0;
87
+ for (const account of accounts) {
88
+ if (account.environment === "root") {
89
+ continue;
90
+ }
91
+ const targetOuId = ouMap[account.environment.toLowerCase()];
92
+ if (!targetOuId) {
93
+ continue;
94
+ }
95
+ const currentParentId = await getParentId(client, account.id);
96
+ if (!currentParentId) {
97
+ // Account not found in organisation
98
+ continue;
99
+ }
100
+ if (currentParentId === targetOuId) {
101
+ alreadyPlaced++;
102
+ continue;
103
+ }
104
+ try {
105
+ await client.send(new MoveAccountCommand({
106
+ AccountId: account.id,
107
+ SourceParentId: currentParentId,
108
+ DestinationParentId: targetOuId
109
+ }));
110
+ moved++;
111
+ }
112
+ catch (error) {
113
+ const errorName = extractErrorName(error);
114
+ if (errorName === "AccountNotFoundException") {
115
+ continue;
116
+ }
117
+ throw error;
118
+ }
119
+ }
120
+ return success({ moved, alreadyPlaced });
121
+ }
122
+ catch (error) {
123
+ return failure(new Error(`Failed to place accounts in OUs: ${error instanceof Error ? error.message : String(error)}`));
124
+ }
125
+ }
@@ -0,0 +1,7 @@
1
+ import { type OrganizationsClient } from "@aws-sdk/client-organizations";
2
+ import { type Result } from "@fjall/generator";
3
+ /**
4
+ * Enable all required policy types on the organisation root.
5
+ * Idempotent — skips policy types that are already enabled.
6
+ */
7
+ export declare function enablePolicyTypes(client: OrganizationsClient, rootId: string): Promise<Result<void>>;
@@ -0,0 +1,36 @@
1
+ import { EnablePolicyTypeCommand, PolicyType } from "@aws-sdk/client-organizations";
2
+ import { success, failure } from "@fjall/generator";
3
+ import { extractErrorName } from "./types.js";
4
+ const POLICY_TYPES = [
5
+ PolicyType.SERVICE_CONTROL_POLICY,
6
+ PolicyType.TAG_POLICY,
7
+ PolicyType.BACKUP_POLICY,
8
+ PolicyType.AISERVICES_OPT_OUT_POLICY
9
+ ];
10
+ /**
11
+ * Enable all required policy types on the organisation root.
12
+ * Idempotent — skips policy types that are already enabled.
13
+ */
14
+ export async function enablePolicyTypes(client, rootId) {
15
+ try {
16
+ for (const policyType of POLICY_TYPES) {
17
+ try {
18
+ await client.send(new EnablePolicyTypeCommand({
19
+ RootId: rootId,
20
+ PolicyType: policyType
21
+ }));
22
+ }
23
+ catch (error) {
24
+ const errorName = extractErrorName(error);
25
+ if (errorName === "PolicyTypeAlreadyEnabledException") {
26
+ continue;
27
+ }
28
+ throw error;
29
+ }
30
+ }
31
+ return success(undefined);
32
+ }
33
+ catch (error) {
34
+ return failure(new Error(`Failed to enable policy types: ${error instanceof Error ? error.message : String(error)}`));
35
+ }
36
+ }
@@ -0,0 +1,7 @@
1
+ import { type RAMClient } from "@aws-sdk/client-ram";
2
+ import { type Result } from "@fjall/generator";
3
+ /**
4
+ * Enable RAM sharing with the AWS Organisation.
5
+ * Idempotent — calling when already enabled is a no-op.
6
+ */
7
+ export declare function enableRamSharing(client: RAMClient): Promise<Result<void>>;
@@ -0,0 +1,15 @@
1
+ import { EnableSharingWithAwsOrganizationCommand } from "@aws-sdk/client-ram";
2
+ import { success, failure } from "@fjall/generator";
3
+ /**
4
+ * Enable RAM sharing with the AWS Organisation.
5
+ * Idempotent — calling when already enabled is a no-op.
6
+ */
7
+ export async function enableRamSharing(client) {
8
+ try {
9
+ await client.send(new EnableSharingWithAwsOrganizationCommand({}));
10
+ return success(undefined);
11
+ }
12
+ catch (error) {
13
+ return failure(new Error(`Failed to enable RAM sharing: ${error instanceof Error ? error.message : String(error)}`));
14
+ }
15
+ }
@@ -0,0 +1,7 @@
1
+ import { type OrganizationsClient } from "@aws-sdk/client-organizations";
2
+ import { type Result } from "@fjall/generator";
3
+ /**
4
+ * Enable AWS service access for all required service principals.
5
+ * Idempotent — enabling an already-enabled principal is a no-op.
6
+ */
7
+ export declare function enableServiceAccess(client: OrganizationsClient): Promise<Result<void>>;
@@ -0,0 +1,38 @@
1
+ import { EnableAWSServiceAccessCommand } from "@aws-sdk/client-organizations";
2
+ import { success, failure } from "@fjall/generator";
3
+ import { extractErrorName } from "./types.js";
4
+ const SERVICE_PRINCIPALS = [
5
+ "account.amazonaws.com",
6
+ "sso.amazonaws.com",
7
+ "ipam.amazonaws.com",
8
+ "ram.amazonaws.com",
9
+ "backup.amazonaws.com",
10
+ "member.org.stacksets.cloudformation.amazonaws.com"
11
+ ];
12
+ /**
13
+ * Enable AWS service access for all required service principals.
14
+ * Idempotent — enabling an already-enabled principal is a no-op.
15
+ */
16
+ export async function enableServiceAccess(client) {
17
+ try {
18
+ for (const principal of SERVICE_PRINCIPALS) {
19
+ try {
20
+ await client.send(new EnableAWSServiceAccessCommand({
21
+ ServicePrincipal: principal
22
+ }));
23
+ }
24
+ catch (error) {
25
+ const errorName = extractErrorName(error);
26
+ if (errorName === "AccessDeniedException") {
27
+ return failure(new Error(`Access denied when enabling service access for ${principal}. ` +
28
+ "Ensure your credentials have organizations:EnableAWSServiceAccess permission."));
29
+ }
30
+ throw error;
31
+ }
32
+ }
33
+ return success(undefined);
34
+ }
35
+ catch (error) {
36
+ return failure(new Error(`Failed to enable service access: ${error instanceof Error ? error.message : String(error)}`));
37
+ }
38
+ }
@@ -0,0 +1,7 @@
1
+ import { type CloudFormationClient } from "@aws-sdk/client-cloudformation";
2
+ import { type Result } from "@fjall/generator";
3
+ /**
4
+ * Activate trusted access for CloudFormation StackSets.
5
+ * Idempotent — calling when already activated is a no-op.
6
+ */
7
+ export declare function activateTrustedAccess(client: CloudFormationClient): Promise<Result<void>>;
@@ -0,0 +1,15 @@
1
+ import { ActivateOrganizationsAccessCommand } from "@aws-sdk/client-cloudformation";
2
+ import { success, failure } from "@fjall/generator";
3
+ /**
4
+ * Activate trusted access for CloudFormation StackSets.
5
+ * Idempotent — calling when already activated is a no-op.
6
+ */
7
+ export async function activateTrustedAccess(client) {
8
+ try {
9
+ await client.send(new ActivateOrganizationsAccessCommand({}));
10
+ return success(undefined);
11
+ }
12
+ catch (error) {
13
+ return failure(new Error(`Failed to activate trusted access: ${error instanceof Error ? error.message : String(error)}`));
14
+ }
15
+ }
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Shared types for AWS Organisation setup primitives.
3
+ */
4
+ export interface OrgDetails {
5
+ orgId: string;
6
+ rootId: string;
7
+ managementAccountId: string;
8
+ created: boolean;
9
+ }
10
+ /** Map of environment type (lowercase) → OU ID */
11
+ export type OUMap = Record<string, string>;
12
+ export interface AccountPlacementResult {
13
+ moved: number;
14
+ alreadyPlaced: number;
15
+ }
16
+ export interface AccountInfo {
17
+ id: string;
18
+ name: string;
19
+ environment: string;
20
+ }
21
+ export interface IdentityCentreStatus {
22
+ enabled: boolean;
23
+ instanceCount: number;
24
+ }
25
+ /**
26
+ * Extract the error name from an AWS SDK error.
27
+ * AWS SDK v3 errors have a `name` property that identifies the error type.
28
+ */
29
+ export declare function extractErrorName(error: unknown): string;
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Shared types for AWS Organisation setup primitives.
3
+ */
4
+ /**
5
+ * Extract the error name from an AWS SDK error.
6
+ * AWS SDK v3 errors have a `name` property that identifies the error type.
7
+ */
8
+ export function extractErrorName(error) {
9
+ if (typeof error === "object" &&
10
+ error !== null &&
11
+ "name" in error &&
12
+ typeof error.name === "string") {
13
+ return error.name;
14
+ }
15
+ return "UnknownError";
16
+ }
@@ -0,0 +1,32 @@
1
+ import { type ResourceEvent } from "./cloudformationEvents.js";
2
+ export interface RootCause {
3
+ resource: ResourceEvent;
4
+ reason: string;
5
+ category: "permissions" | "validation" | "dependency" | "limit" | "network" | "unknown";
6
+ isDirectCause: boolean;
7
+ }
8
+ export interface FailureAnalysis {
9
+ rootCause: RootCause;
10
+ affectedResources: ResourceEvent[];
11
+ dependencyChain: string[];
12
+ summary: string;
13
+ remediation: string[];
14
+ errorPattern?: string;
15
+ }
16
+ /**
17
+ * CloudFormationFailureAnalyser provides intelligent analysis of deployment failures
18
+ * It identifies root causes, dependency chains, and provides actionable remediation
19
+ */
20
+ export declare class CloudFormationFailureAnalyser {
21
+ private knownErrorPatterns;
22
+ analyseFailure(eventHistory: Map<string, ResourceEvent[]>): FailureAnalysis | null;
23
+ private findFailedResources;
24
+ private findRootCause;
25
+ private categoriseError;
26
+ private isDirectCause;
27
+ private buildDependencyChain;
28
+ private generateRemediation;
29
+ private generateSummary;
30
+ private simplifyResourceType;
31
+ private isFailedStatus;
32
+ }
@@ -0,0 +1,228 @@
1
+ /**
2
+ * CloudFormationFailureAnalyser provides intelligent analysis of deployment failures
3
+ * It identifies root causes, dependency chains, and provides actionable remediation
4
+ */
5
+ export class CloudFormationFailureAnalyser {
6
+ knownErrorPatterns = [
7
+ {
8
+ pattern: /AccessDenied|UnauthorizedOperation|Forbidden/i,
9
+ category: "permissions",
10
+ remediation: [
11
+ "Check IAM permissions for the deployment role",
12
+ "Ensure the role has necessary CloudFormation permissions",
13
+ "Verify service-linked roles are created if needed"
14
+ ]
15
+ },
16
+ {
17
+ pattern: /Invalid.*Parameter|ValidationError|Invalid.*Value/i,
18
+ category: "validation",
19
+ remediation: [
20
+ "Review parameter values in your CDK code",
21
+ "Check for typos in resource names or ARNs",
22
+ "Ensure all required parameters are provided"
23
+ ]
24
+ },
25
+ {
26
+ pattern: /Limit.*Exceeded|Quota.*Exceeded|Maximum.*reached/i,
27
+ category: "limit",
28
+ remediation: [
29
+ "Check AWS service quotas in the Service Quotas console",
30
+ "Request a quota increase if needed",
31
+ "Consider using a different region with available capacity"
32
+ ]
33
+ },
34
+ {
35
+ pattern: /Timeout|Connection.*refused|Network.*unreachable/i,
36
+ category: "network",
37
+ remediation: [
38
+ "Check network connectivity and VPC settings",
39
+ "Verify security groups and NACLs",
40
+ "Ensure endpoints are accessible"
41
+ ]
42
+ },
43
+ {
44
+ pattern: /already exists|Duplicate|ConflictException/i,
45
+ category: "validation",
46
+ remediation: [
47
+ "Resource with this name already exists",
48
+ "Consider using a different name or deleting the existing resource",
49
+ "Check if you are deploying to the correct account/region"
50
+ ]
51
+ },
52
+ {
53
+ pattern: /Role.*not.*found|Role.*does.*not.*exist/i,
54
+ category: "dependency",
55
+ remediation: [
56
+ "Ensure IAM roles are created before dependent resources",
57
+ "Check role names and ARNs are correct",
58
+ "Verify cross-stack references are properly configured"
59
+ ]
60
+ }
61
+ ];
62
+ analyseFailure(eventHistory) {
63
+ // Find all failed resources
64
+ const failedResources = this.findFailedResources(eventHistory);
65
+ if (failedResources.length === 0) {
66
+ return null;
67
+ }
68
+ // Find the root cause
69
+ const rootCause = this.findRootCause(failedResources, eventHistory);
70
+ // Build dependency chain
71
+ const dependencyChain = this.buildDependencyChain(rootCause.resource, failedResources);
72
+ // Generate remediation suggestions
73
+ const remediation = this.generateRemediation(rootCause);
74
+ // Create summary
75
+ const summary = this.generateSummary(rootCause, failedResources);
76
+ return {
77
+ rootCause,
78
+ affectedResources: failedResources,
79
+ dependencyChain,
80
+ summary,
81
+ remediation,
82
+ errorPattern: rootCause.category
83
+ };
84
+ }
85
+ findFailedResources(eventHistory) {
86
+ const failed = [];
87
+ for (const [_logicalId, events] of eventHistory) {
88
+ // Get the latest event for this resource
89
+ const latestEvent = events[events.length - 1];
90
+ if (latestEvent && this.isFailedStatus(latestEvent.status)) {
91
+ failed.push(latestEvent);
92
+ }
93
+ }
94
+ // Sort by timestamp to find the first failure
95
+ return failed.sort((a, b) => a.timestamp.getTime() - b.timestamp.getTime());
96
+ }
97
+ findRootCause(failedResources, eventHistory) {
98
+ if (failedResources.length === 0) {
99
+ return {
100
+ resource: {
101
+ logicalId: "Unknown",
102
+ resourceType: "Unknown",
103
+ status: "FAILED",
104
+ timestamp: new Date()
105
+ },
106
+ reason: "No failed resources found",
107
+ category: "unknown",
108
+ isDirectCause: false
109
+ };
110
+ }
111
+ // The first failed resource is usually the root cause
112
+ const firstFailed = failedResources[0];
113
+ // Analyse the failure reason
114
+ const category = this.categoriseError(firstFailed.statusReason || "");
115
+ // Check if this is a direct cause or a cascading failure
116
+ const isDirectCause = this.isDirectCause(firstFailed, eventHistory);
117
+ return {
118
+ resource: firstFailed,
119
+ reason: firstFailed.statusReason || "Unknown error",
120
+ category,
121
+ isDirectCause
122
+ };
123
+ }
124
+ categoriseError(errorMessage) {
125
+ for (const pattern of this.knownErrorPatterns) {
126
+ if (pattern.pattern.test(errorMessage)) {
127
+ return pattern.category;
128
+ }
129
+ }
130
+ return "unknown";
131
+ }
132
+ isDirectCause(resource, eventHistory) {
133
+ // If the error mentions another resource, it's likely cascading
134
+ const reason = resource.statusReason || "";
135
+ // Check for references to other resources
136
+ if (reason.includes("depends on") ||
137
+ reason.includes("referenced by") ||
138
+ reason.includes("required by")) {
139
+ return false;
140
+ }
141
+ // Check if this resource had any successful events before failing
142
+ const history = eventHistory.get(resource.logicalId) || [];
143
+ const hasSuccessfulEvents = history.some((e) => e.status.includes("COMPLETE") && !e.status.includes("ROLLBACK"));
144
+ // If it never succeeded, it's likely a direct cause
145
+ return !hasSuccessfulEvents;
146
+ }
147
+ buildDependencyChain(rootCause, failedResources) {
148
+ const chain = [];
149
+ // Start with root cause
150
+ chain.push(`${rootCause.logicalId} (${rootCause.resourceType}) - ROOT CAUSE`);
151
+ // Add cascading failures in chronological order
152
+ for (const resource of failedResources) {
153
+ if (resource.logicalId !== rootCause.logicalId) {
154
+ const reason = resource.statusReason || "";
155
+ if (reason.toLowerCase().includes(rootCause.logicalId.toLowerCase())) {
156
+ chain.push(` → ${resource.logicalId} (${resource.resourceType}) - Failed due to ${rootCause.logicalId}`);
157
+ }
158
+ else {
159
+ chain.push(` → ${resource.logicalId} (${resource.resourceType})`);
160
+ }
161
+ }
162
+ }
163
+ return chain;
164
+ }
165
+ generateRemediation(rootCause) {
166
+ const suggestions = [];
167
+ // Add pattern-based suggestions
168
+ for (const pattern of this.knownErrorPatterns) {
169
+ if (pattern.category === rootCause.category) {
170
+ suggestions.push(...pattern.remediation);
171
+ break;
172
+ }
173
+ }
174
+ // Add resource-specific suggestions
175
+ if (rootCause.resource.resourceType.includes("IAM")) {
176
+ suggestions.push("Review IAM policies and trust relationships");
177
+ }
178
+ else if (rootCause.resource.resourceType.includes("Lambda")) {
179
+ suggestions.push("Check Lambda function configuration and runtime");
180
+ }
181
+ else if (rootCause.resource.resourceType.includes("ECS")) {
182
+ suggestions.push("Verify ECS task definition and container configuration");
183
+ }
184
+ // Add general suggestions if no specific ones found
185
+ if (suggestions.length === 0) {
186
+ suggestions.push("Review the CloudFormation console for detailed error messages", "Check the resource configuration in your CDK code", "Ensure all dependencies are properly defined");
187
+ }
188
+ return suggestions;
189
+ }
190
+ generateSummary(rootCause, failedResources) {
191
+ const resourceType = this.simplifyResourceType(rootCause.resource.resourceType);
192
+ const failureCount = failedResources.length;
193
+ let summary = `Deployment failed: ${resourceType} "${rootCause.resource.logicalId}" `;
194
+ switch (rootCause.category) {
195
+ case "permissions":
196
+ summary += "failed due to insufficient permissions";
197
+ break;
198
+ case "validation":
199
+ summary += "failed validation";
200
+ break;
201
+ case "dependency":
202
+ summary += "has missing or invalid dependencies";
203
+ break;
204
+ case "limit":
205
+ summary += "exceeded AWS service limits";
206
+ break;
207
+ case "network":
208
+ summary += "encountered network issues";
209
+ break;
210
+ default:
211
+ summary += "failed to create";
212
+ }
213
+ if (failureCount > 1) {
214
+ summary += ` (${failureCount - 1} dependent resources also failed)`;
215
+ }
216
+ return summary;
217
+ }
218
+ simplifyResourceType(resourceType) {
219
+ return resourceType
220
+ .replace("AWS::", "")
221
+ .replace("::", " ")
222
+ .replace(/([A-Z])/g, " $1")
223
+ .trim();
224
+ }
225
+ isFailedStatus(status) {
226
+ return status.includes("FAILED");
227
+ }
228
+ }
@@ -0,0 +1,98 @@
1
+ import type { AwsProvider } from "../AwsProvider.js";
2
+ import type { FailureAnalysis } from "./CloudFormationFailureAnalyser.js";
3
+ export declare const STACK_NOT_FOUND_PATTERN = "does not exist";
4
+ export declare const CDK_NO_STACKS_MATCH = "No stacks match the name(s)";
5
+ export interface ResourceEvent {
6
+ logicalId: string;
7
+ physicalId?: string;
8
+ resourceType: string;
9
+ status: string;
10
+ statusReason?: string;
11
+ timestamp: Date;
12
+ }
13
+ export declare function isResourceEvent(event: unknown): event is ResourceEvent;
14
+ /** Interface for event logging — decouples from concrete CloudFormationEventLogger */
15
+ export interface EventLogWriter {
16
+ writeEvent(event: ResourceEvent, metadata?: {
17
+ phase?: string;
18
+ isMainStack?: boolean;
19
+ parentStack?: string;
20
+ }): void;
21
+ writeDeploymentEnd(success: boolean, finalStatus: string, failureMessage?: string): void;
22
+ writeFailureSummary(failures: Array<{
23
+ logicalId: string;
24
+ reason: string;
25
+ }>): void;
26
+ writeFailureAnalysis(analysis: {
27
+ rootCause: string;
28
+ failedResources: Array<{
29
+ logicalId: string;
30
+ resourceType: string;
31
+ reason: string;
32
+ }>;
33
+ dependencyChain?: string[];
34
+ remediation?: string[];
35
+ }): void;
36
+ writeCdkOutput(stream: "stdout" | "stderr", chunk: string): void;
37
+ getLogPath(): string;
38
+ getLogSummary(): string;
39
+ }
40
+ /** Interface for failure analysis — decouples from concrete CloudFormationFailureAnalyser */
41
+ export interface EventFailureAnalyser {
42
+ analyseFailure(eventHistory: Map<string, ResourceEvent[]>): FailureAnalysis | null;
43
+ }
44
+ /** Factory to create an EventLogWriter for a specific deployment */
45
+ export type EventLogWriterFactory = (deploymentId: string, stackName: string, region: string, deploymentName?: string) => EventLogWriter;
46
+ /** Dependencies injected into CloudFormationEventMonitor */
47
+ export interface EventMonitorDeps {
48
+ failureAnalyser?: EventFailureAnalyser;
49
+ eventLogWriterFactory?: EventLogWriterFactory;
50
+ }
51
+ export declare class CloudFormationEventMonitor {
52
+ private aws;
53
+ private seenEventIds;
54
+ private isMonitoring;
55
+ private pollInterval;
56
+ private activeNestedStacks;
57
+ private failureReasons;
58
+ private eventHistory;
59
+ private eventLogger;
60
+ private failureAnalyser;
61
+ private eventLogWriterFactory;
62
+ private lastAnalysis;
63
+ private deploymentStartTime;
64
+ private maxHistorySize;
65
+ private maxSeenEventIds;
66
+ private ipamInProgress;
67
+ constructor(aws: AwsProvider, deps?: EventMonitorDeps);
68
+ enableLogging(deploymentId: string, stackName: string, region: string, deploymentName?: string): void;
69
+ startMonitoring(stackName: string, onResourceUpdate: (event: ResourceEvent) => void, onStackComplete?: (success: boolean, message?: string) => void): Promise<void>;
70
+ stopMonitoring(): void;
71
+ private cleanup;
72
+ getResourceHistory(logicalId: string): ResourceEvent[];
73
+ getEventHistory(): Map<string, ResourceEvent[]>;
74
+ getFailureAnalysis(): FailureAnalysis | null;
75
+ getFirstFailureReason(): string | null;
76
+ getEventLogger(): EventLogWriter | null;
77
+ getEventLogPath(): string | null;
78
+ getLogSummary(): string | null;
79
+ waitForStackComplete(stackName: string, options?: {
80
+ timeout?: number;
81
+ pollInterval?: number;
82
+ onResourceUpdate?: (event: ResourceEvent) => void;
83
+ onStackComplete?: (success: boolean, message?: string) => void;
84
+ }): Promise<{
85
+ success: boolean;
86
+ status?: string;
87
+ failureReason?: string;
88
+ logPath?: string;
89
+ }>;
90
+ private isTerminalState;
91
+ private isSuccessState;
92
+ private pollEvents;
93
+ getStackStatus(stackName: string): Promise<{
94
+ status: string;
95
+ statusReason?: string;
96
+ } | null>;
97
+ getCurrentResources(stackName: string): Promise<ResourceEvent[]>;
98
+ }