@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.
- package/LICENSE +21 -0
- package/dist/src/aws/AwsProvider.d.ts +39 -0
- package/dist/src/aws/AwsProvider.js +1 -0
- package/dist/src/aws/SimpleAwsProvider.d.ts +22 -0
- package/dist/src/aws/SimpleAwsProvider.js +73 -0
- package/dist/src/aws/index.d.ts +4 -0
- package/dist/src/aws/index.js +3 -0
- package/dist/src/aws/organisations/accounts.d.ts +21 -0
- package/dist/src/aws/organisations/accounts.js +99 -0
- package/dist/src/aws/organisations/backup.d.ts +12 -0
- package/dist/src/aws/organisations/backup.js +28 -0
- package/dist/src/aws/organisations/costAllocation.d.ts +12 -0
- package/dist/src/aws/organisations/costAllocation.js +26 -0
- package/dist/src/aws/organisations/identityCentre.d.ts +8 -0
- package/dist/src/aws/organisations/identityCentre.js +19 -0
- package/dist/src/aws/organisations/index.d.ts +16 -0
- package/dist/src/aws/organisations/index.js +12 -0
- package/dist/src/aws/organisations/ipam.d.ts +7 -0
- package/dist/src/aws/organisations/ipam.js +18 -0
- package/dist/src/aws/organisations/organisation.d.ts +12 -0
- package/dist/src/aws/organisations/organisation.js +94 -0
- package/dist/src/aws/organisations/organisationalUnits.d.ts +19 -0
- package/dist/src/aws/organisations/organisationalUnits.js +125 -0
- package/dist/src/aws/organisations/policies.d.ts +7 -0
- package/dist/src/aws/organisations/policies.js +36 -0
- package/dist/src/aws/organisations/ram.d.ts +7 -0
- package/dist/src/aws/organisations/ram.js +15 -0
- package/dist/src/aws/organisations/serviceAccess.d.ts +7 -0
- package/dist/src/aws/organisations/serviceAccess.js +38 -0
- package/dist/src/aws/organisations/trustedAccess.d.ts +7 -0
- package/dist/src/aws/organisations/trustedAccess.js +15 -0
- package/dist/src/aws/organisations/types.d.ts +29 -0
- package/dist/src/aws/organisations/types.js +16 -0
- package/dist/src/aws/utils/CloudFormationFailureAnalyser.d.ts +32 -0
- package/dist/src/aws/utils/CloudFormationFailureAnalyser.js +228 -0
- package/dist/src/aws/utils/cloudformationEvents.d.ts +98 -0
- package/dist/src/aws/utils/cloudformationEvents.js +596 -0
- package/dist/src/aws/utils/errors.d.ts +26 -0
- package/dist/src/aws/utils/errors.js +59 -0
- package/dist/src/aws/utils/regions.d.ts +1 -0
- package/dist/src/aws/utils/regions.js +1 -0
- package/dist/src/aws/utils/stackStatus.d.ts +23 -0
- package/dist/src/aws/utils/stackStatus.js +90 -0
- package/dist/src/index.d.ts +35 -0
- package/dist/src/index.js +45 -0
- package/dist/src/orchestration/applicationDeploy.d.ts +11 -0
- package/dist/src/orchestration/applicationDeploy.js +327 -0
- package/dist/src/orchestration/contextHelpers.d.ts +9 -0
- package/dist/src/orchestration/contextHelpers.js +14 -0
- package/dist/src/orchestration/deploy.d.ts +10 -0
- package/dist/src/orchestration/deploy.js +42 -0
- package/dist/src/orchestration/detectionPipeline.d.ts +23 -0
- package/dist/src/orchestration/detectionPipeline.js +65 -0
- package/dist/src/orchestration/dockerInterface.d.ts +56 -0
- package/dist/src/orchestration/dockerInterface.js +1 -0
- package/dist/src/orchestration/domainInterface.d.ts +37 -0
- package/dist/src/orchestration/domainInterface.js +1 -0
- package/dist/src/orchestration/index.d.ts +8 -0
- package/dist/src/orchestration/index.js +3 -0
- package/dist/src/orchestration/organisationDeploy.d.ts +16 -0
- package/dist/src/orchestration/organisationDeploy.js +382 -0
- package/dist/src/orchestration/organisationSetup.d.ts +42 -0
- package/dist/src/orchestration/organisationSetup.js +227 -0
- package/dist/src/orchestration/resolveOperation.d.ts +10 -0
- package/dist/src/orchestration/resolveOperation.js +53 -0
- package/dist/src/orchestration/serviceFactory.d.ts +15 -0
- package/dist/src/orchestration/serviceFactory.js +16 -0
- package/dist/src/services/application/ApplicationStackService.d.ts +93 -0
- package/dist/src/services/application/ApplicationStackService.js +436 -0
- package/dist/src/services/application/index.d.ts +1 -0
- package/dist/src/services/application/index.js +1 -0
- package/dist/src/services/infrastructure/CdkArgumentBuilder.d.ts +12 -0
- package/dist/src/services/infrastructure/CdkArgumentBuilder.js +67 -0
- package/dist/src/services/infrastructure/CdkCommandRunner.d.ts +30 -0
- package/dist/src/services/infrastructure/CdkCommandRunner.js +241 -0
- package/dist/src/services/infrastructure/CdkErrorFormatter.d.ts +4 -0
- package/dist/src/services/infrastructure/CdkErrorFormatter.js +194 -0
- package/dist/src/services/infrastructure/CdkEventMonitoring.d.ts +19 -0
- package/dist/src/services/infrastructure/CdkEventMonitoring.js +41 -0
- package/dist/src/services/infrastructure/CdkOutputAnalyser.d.ts +43 -0
- package/dist/src/services/infrastructure/CdkOutputAnalyser.js +125 -0
- package/dist/src/services/infrastructure/CdkOutputParser.d.ts +8 -0
- package/dist/src/services/infrastructure/CdkOutputParser.js +33 -0
- package/dist/src/services/infrastructure/CdkProcessManager.d.ts +20 -0
- package/dist/src/services/infrastructure/CdkProcessManager.js +244 -0
- package/dist/src/services/infrastructure/CdkService.d.ts +71 -0
- package/dist/src/services/infrastructure/CdkService.js +254 -0
- package/dist/src/services/infrastructure/CloudFormationService.d.ts +79 -0
- package/dist/src/services/infrastructure/CloudFormationService.js +249 -0
- package/dist/src/services/infrastructure/index.d.ts +8 -0
- package/dist/src/services/infrastructure/index.js +7 -0
- package/dist/src/services/supporting/CdkContextBuilder.d.ts +49 -0
- package/dist/src/services/supporting/CdkContextBuilder.js +44 -0
- package/dist/src/services/supporting/TemplateHashService.d.ts +67 -0
- package/dist/src/services/supporting/TemplateHashService.js +152 -0
- package/dist/src/services/supporting/helpers.d.ts +46 -0
- package/dist/src/services/supporting/helpers.js +81 -0
- package/dist/src/services/supporting/index.d.ts +3 -0
- package/dist/src/services/supporting/index.js +3 -0
- package/dist/src/types/FjallState.d.ts +50 -0
- package/dist/src/types/FjallState.js +118 -0
- package/dist/src/types/ProgressEvent.d.ts +35 -0
- package/dist/src/types/ProgressEvent.js +48 -0
- package/dist/src/types/apiClient.d.ts +34 -0
- package/dist/src/types/apiClient.js +1 -0
- package/dist/src/types/application/ApplicationServiceTypes.d.ts +56 -0
- package/dist/src/types/application/ApplicationServiceTypes.js +30 -0
- package/dist/src/types/application/index.d.ts +1 -0
- package/dist/src/types/application/index.js +1 -0
- package/dist/src/types/callbacks.d.ts +36 -0
- package/dist/src/types/callbacks.js +1 -0
- package/dist/src/types/constants.d.ts +6 -0
- package/dist/src/types/constants.js +6 -0
- package/dist/src/types/credentials.d.ts +30 -0
- package/dist/src/types/credentials.js +1 -0
- package/dist/src/types/deployment/DeploymentServiceTypes.d.ts +23 -0
- package/dist/src/types/deployment/DeploymentServiceTypes.js +1 -0
- package/dist/src/types/deployment/DeploymentTypes.d.ts +29 -0
- package/dist/src/types/deployment/DeploymentTypes.js +1 -0
- package/dist/src/types/deployment/cloudformation.d.ts +14 -0
- package/dist/src/types/deployment/cloudformation.js +1 -0
- package/dist/src/types/deployment/index.d.ts +5 -0
- package/dist/src/types/deployment/index.js +1 -0
- package/dist/src/types/deployment/parallel.d.ts +46 -0
- package/dist/src/types/deployment/parallel.js +10 -0
- package/dist/src/types/errors/CdkError.d.ts +14 -0
- package/dist/src/types/errors/CdkError.js +20 -0
- package/dist/src/types/errors/ServiceError.d.ts +86 -0
- package/dist/src/types/errors/ServiceError.js +119 -0
- package/dist/src/types/events.d.ts +40 -0
- package/dist/src/types/events.js +5 -0
- package/dist/src/types/index.d.ts +20 -0
- package/dist/src/types/index.js +9 -0
- package/dist/src/types/operations.d.ts +193 -0
- package/dist/src/types/operations.js +285 -0
- package/dist/src/types/orgConfig.d.ts +28 -0
- package/dist/src/types/orgConfig.js +11 -0
- package/dist/src/types/params.d.ts +74 -0
- package/dist/src/types/params.js +1 -0
- package/dist/src/types/patternDetection.d.ts +43 -0
- package/dist/src/types/patternDetection.js +92 -0
- package/dist/src/types/validation.d.ts +12 -0
- package/dist/src/types/validation.js +1 -0
- package/dist/src/util/fsHelpers.d.ts +4 -0
- package/dist/src/util/fsHelpers.js +16 -0
- package/dist/src/util/index.d.ts +3 -0
- package/dist/src/util/index.js +3 -0
- package/dist/src/util/securityHelpers.d.ts +31 -0
- package/dist/src/util/securityHelpers.js +124 -0
- package/dist/src/util/singleton.d.ts +2 -0
- package/dist/src/util/singleton.js +9 -0
- package/dist/src/util/sleep.d.ts +4 -0
- package/dist/src/util/sleep.js +4 -0
- 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
|
+
}
|