@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,227 @@
|
|
|
1
|
+
import { success, failure } from "@fjall/generator";
|
|
2
|
+
import { OrganizationsClient } from "@aws-sdk/client-organizations";
|
|
3
|
+
import { RAMClient } from "@aws-sdk/client-ram";
|
|
4
|
+
import { CloudFormationClient } from "@aws-sdk/client-cloudformation";
|
|
5
|
+
import { EC2Client } from "@aws-sdk/client-ec2";
|
|
6
|
+
import { BackupClient } from "@aws-sdk/client-backup";
|
|
7
|
+
import { CostExplorerClient } from "@aws-sdk/client-cost-explorer";
|
|
8
|
+
import { SSOAdminClient } from "@aws-sdk/client-sso-admin";
|
|
9
|
+
import { ensureOrganisationExists } from "../aws/organisations/organisation.js";
|
|
10
|
+
import { enablePolicyTypes } from "../aws/organisations/policies.js";
|
|
11
|
+
import { enableServiceAccess } from "../aws/organisations/serviceAccess.js";
|
|
12
|
+
import { enableRamSharing } from "../aws/organisations/ram.js";
|
|
13
|
+
import { activateTrustedAccess } from "../aws/organisations/trustedAccess.js";
|
|
14
|
+
import { enableIpamDelegatedAdmin } from "../aws/organisations/ipam.js";
|
|
15
|
+
import { updateBackupGlobalSettings } from "../aws/organisations/backup.js";
|
|
16
|
+
import { listAccounts, createAccount } from "../aws/organisations/accounts.js";
|
|
17
|
+
import { ensureOrganisationalUnitsExist, placeAccountsInOUs } from "../aws/organisations/organisationalUnits.js";
|
|
18
|
+
import { activateCostAllocationTags } from "../aws/organisations/costAllocation.js";
|
|
19
|
+
import { checkIdentityCentreStatus } from "../aws/organisations/identityCentre.js";
|
|
20
|
+
/**
|
|
21
|
+
* Orchestrate the full AWS Organisation setup sequence.
|
|
22
|
+
*
|
|
23
|
+
* Runs 12 phases sequentially. Non-fatal phase failures are recorded
|
|
24
|
+
* and execution continues. The only fatal failure is phase 1
|
|
25
|
+
* (create-organisation) since all subsequent phases depend on the org ID.
|
|
26
|
+
*/
|
|
27
|
+
export async function runOrganisationSetup(awsProvider, config, callbacks) {
|
|
28
|
+
const phasesCompleted = [];
|
|
29
|
+
const phasesSkipped = [];
|
|
30
|
+
const errors = [];
|
|
31
|
+
const createdAccounts = [];
|
|
32
|
+
let identityCentreStatus;
|
|
33
|
+
const orgsClient = awsProvider.getClient(OrganizationsClient);
|
|
34
|
+
const ramClient = awsProvider.getClient(RAMClient);
|
|
35
|
+
const cfnClient = awsProvider.getClient(CloudFormationClient);
|
|
36
|
+
const ec2Client = awsProvider.getClient(EC2Client);
|
|
37
|
+
const backupClient = awsProvider.getClient(BackupClient);
|
|
38
|
+
const ceClient = awsProvider.getClient(CostExplorerClient);
|
|
39
|
+
const ssoClient = awsProvider.getClient(SSOAdminClient);
|
|
40
|
+
// Phase 1: Ensure organisation exists (fatal if fails)
|
|
41
|
+
callbacks?.onPhaseStart?.("create-organisation");
|
|
42
|
+
callbacks?.onProgress?.("Ensuring AWS Organisation exists");
|
|
43
|
+
const orgResult = await ensureOrganisationExists(orgsClient);
|
|
44
|
+
if (!orgResult.success) {
|
|
45
|
+
callbacks?.onError?.("create-organisation", orgResult.error);
|
|
46
|
+
callbacks?.onPhaseComplete?.("create-organisation", "error");
|
|
47
|
+
return failure(orgResult.error);
|
|
48
|
+
}
|
|
49
|
+
const { orgId, rootId } = orgResult.data;
|
|
50
|
+
callbacks?.onPhaseComplete?.("create-organisation", "completed");
|
|
51
|
+
phasesCompleted.push("create-organisation");
|
|
52
|
+
// Phase 2: Enable policy types
|
|
53
|
+
await executePhase("enable-policies", () => {
|
|
54
|
+
callbacks?.onProgress?.("Enabling organisation policy types");
|
|
55
|
+
return enablePolicyTypes(orgsClient, rootId);
|
|
56
|
+
}, phasesCompleted, errors, callbacks);
|
|
57
|
+
// Phase 3: Enable service access
|
|
58
|
+
await executePhase("enable-service-access", () => {
|
|
59
|
+
callbacks?.onProgress?.("Enabling AWS service access");
|
|
60
|
+
return enableServiceAccess(orgsClient);
|
|
61
|
+
}, phasesCompleted, errors, callbacks);
|
|
62
|
+
// Phase 4: Enable RAM sharing
|
|
63
|
+
await executePhase("enable-ram-sharing", () => {
|
|
64
|
+
callbacks?.onProgress?.("Enabling RAM sharing");
|
|
65
|
+
return enableRamSharing(ramClient);
|
|
66
|
+
}, phasesCompleted, errors, callbacks);
|
|
67
|
+
// Phase 5: Activate trusted access
|
|
68
|
+
await executePhase("activate-trusted-access", () => {
|
|
69
|
+
callbacks?.onProgress?.("Activating CloudFormation trusted access");
|
|
70
|
+
return activateTrustedAccess(cfnClient);
|
|
71
|
+
}, phasesCompleted, errors, callbacks);
|
|
72
|
+
// Phase 6: Enable IPAM delegated admin
|
|
73
|
+
await executePhase("enable-ipam", () => {
|
|
74
|
+
callbacks?.onProgress?.("Enabling IPAM delegated administrator");
|
|
75
|
+
return enableIpamDelegatedAdmin(ec2Client, config.platformAccountId);
|
|
76
|
+
}, phasesCompleted, errors, callbacks);
|
|
77
|
+
// Phase 7: Configure backup settings
|
|
78
|
+
await executePhase("configure-backup", () => {
|
|
79
|
+
callbacks?.onProgress?.("Updating backup global settings");
|
|
80
|
+
return updateBackupGlobalSettings(backupClient);
|
|
81
|
+
}, phasesCompleted, errors, callbacks);
|
|
82
|
+
// Phase 8: Create missing accounts
|
|
83
|
+
callbacks?.onPhaseStart?.("create-accounts");
|
|
84
|
+
callbacks?.onProgress?.("Checking for missing accounts");
|
|
85
|
+
const accountsResult = await createMissingAccounts(orgsClient, config.accounts, createdAccounts);
|
|
86
|
+
if (!accountsResult.success) {
|
|
87
|
+
errors.push({
|
|
88
|
+
phase: "create-accounts",
|
|
89
|
+
error: accountsResult.error.message
|
|
90
|
+
});
|
|
91
|
+
callbacks?.onError?.("create-accounts", accountsResult.error);
|
|
92
|
+
callbacks?.onPhaseComplete?.("create-accounts", "error");
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
phasesCompleted.push("create-accounts");
|
|
96
|
+
callbacks?.onPhaseComplete?.("create-accounts", "completed");
|
|
97
|
+
}
|
|
98
|
+
// Phase 9: Ensure organisational units exist
|
|
99
|
+
let ouMap = {};
|
|
100
|
+
callbacks?.onPhaseStart?.("create-organisational-units");
|
|
101
|
+
callbacks?.onProgress?.("Ensuring organisational units exist");
|
|
102
|
+
const ouResult = await ensureOrganisationalUnitsExist(orgsClient, rootId, config.organisationalUnits);
|
|
103
|
+
if (!ouResult.success) {
|
|
104
|
+
errors.push({
|
|
105
|
+
phase: "create-organisational-units",
|
|
106
|
+
error: ouResult.error.message
|
|
107
|
+
});
|
|
108
|
+
callbacks?.onError?.("create-organisational-units", ouResult.error);
|
|
109
|
+
callbacks?.onPhaseComplete?.("create-organisational-units", "error");
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
ouMap = ouResult.data;
|
|
113
|
+
phasesCompleted.push("create-organisational-units");
|
|
114
|
+
callbacks?.onPhaseComplete?.("create-organisational-units", "completed");
|
|
115
|
+
}
|
|
116
|
+
// Phase 10: Place accounts in OUs
|
|
117
|
+
if (Object.keys(ouMap).length > 0 && config.accountPlacements) {
|
|
118
|
+
const accountInfos = buildAccountInfos(config.accountPlacements);
|
|
119
|
+
await executePhase("place-accounts", () => {
|
|
120
|
+
callbacks?.onProgress?.("Placing accounts in organisational units");
|
|
121
|
+
return placeAccountsInOUs(orgsClient, ouMap, accountInfos);
|
|
122
|
+
}, phasesCompleted, errors, callbacks);
|
|
123
|
+
}
|
|
124
|
+
else {
|
|
125
|
+
phasesSkipped.push("place-accounts");
|
|
126
|
+
callbacks?.onPhaseStart?.("place-accounts");
|
|
127
|
+
callbacks?.onPhaseComplete?.("place-accounts", "skipped");
|
|
128
|
+
}
|
|
129
|
+
// Phase 11: Activate cost allocation tags
|
|
130
|
+
const tags = config.costAllocationTags ?? [];
|
|
131
|
+
if (tags.length > 0) {
|
|
132
|
+
await executePhase("activate-cost-tags", () => {
|
|
133
|
+
callbacks?.onProgress?.("Activating cost allocation tags");
|
|
134
|
+
return activateCostAllocationTags(ceClient, tags.map((t) => ({ TagKey: t })));
|
|
135
|
+
}, phasesCompleted, errors, callbacks);
|
|
136
|
+
}
|
|
137
|
+
else {
|
|
138
|
+
phasesSkipped.push("activate-cost-tags");
|
|
139
|
+
callbacks?.onPhaseStart?.("activate-cost-tags");
|
|
140
|
+
callbacks?.onPhaseComplete?.("activate-cost-tags", "skipped");
|
|
141
|
+
}
|
|
142
|
+
// Phase 12: Check Identity Centre
|
|
143
|
+
if (config.skipIdentityCentre) {
|
|
144
|
+
phasesSkipped.push("check-identity-centre");
|
|
145
|
+
callbacks?.onPhaseStart?.("check-identity-centre");
|
|
146
|
+
callbacks?.onPhaseComplete?.("check-identity-centre", "skipped");
|
|
147
|
+
}
|
|
148
|
+
else {
|
|
149
|
+
callbacks?.onPhaseStart?.("check-identity-centre");
|
|
150
|
+
callbacks?.onProgress?.("Checking Identity Centre status");
|
|
151
|
+
const icResult = await checkIdentityCentreStatus(ssoClient);
|
|
152
|
+
if (!icResult.success) {
|
|
153
|
+
errors.push({
|
|
154
|
+
phase: "check-identity-centre",
|
|
155
|
+
error: icResult.error.message
|
|
156
|
+
});
|
|
157
|
+
callbacks?.onError?.("check-identity-centre", icResult.error);
|
|
158
|
+
callbacks?.onPhaseComplete?.("check-identity-centre", "error");
|
|
159
|
+
}
|
|
160
|
+
else {
|
|
161
|
+
identityCentreStatus = icResult.data.enabled ? "enabled" : "not-enabled";
|
|
162
|
+
phasesCompleted.push("check-identity-centre");
|
|
163
|
+
callbacks?.onPhaseComplete?.("check-identity-centre", "completed");
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
return success({
|
|
167
|
+
organisationId: orgId,
|
|
168
|
+
createdAccounts,
|
|
169
|
+
identityCentreStatus,
|
|
170
|
+
phasesCompleted,
|
|
171
|
+
phasesSkipped,
|
|
172
|
+
errors
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Execute a single phase with standard callback handling.
|
|
177
|
+
* Records success or error; never throws.
|
|
178
|
+
*/
|
|
179
|
+
async function executePhase(phase, fn, phasesCompleted, errors, callbacks) {
|
|
180
|
+
callbacks?.onPhaseStart?.(phase);
|
|
181
|
+
const result = await fn();
|
|
182
|
+
if (!result.success) {
|
|
183
|
+
errors.push({ phase, error: result.error.message });
|
|
184
|
+
callbacks?.onError?.(phase, result.error);
|
|
185
|
+
callbacks?.onPhaseComplete?.(phase, "error");
|
|
186
|
+
}
|
|
187
|
+
else {
|
|
188
|
+
phasesCompleted.push(phase);
|
|
189
|
+
callbacks?.onPhaseComplete?.(phase, "completed");
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* List existing accounts, then create any that are missing by name.
|
|
194
|
+
*/
|
|
195
|
+
async function createMissingAccounts(client, desiredAccounts, createdAccounts) {
|
|
196
|
+
const listResult = await listAccounts(client);
|
|
197
|
+
if (!listResult.success) {
|
|
198
|
+
return failure(listResult.error);
|
|
199
|
+
}
|
|
200
|
+
const existingNames = new Set(listResult.data
|
|
201
|
+
.map((a) => a.Name?.toLowerCase())
|
|
202
|
+
.filter((n) => n !== undefined));
|
|
203
|
+
for (const desired of desiredAccounts) {
|
|
204
|
+
if (existingNames.has(desired.name.toLowerCase())) {
|
|
205
|
+
continue;
|
|
206
|
+
}
|
|
207
|
+
const createResult = await createAccount(client, desired.name, desired.email);
|
|
208
|
+
if (!createResult.success) {
|
|
209
|
+
return failure(createResult.error);
|
|
210
|
+
}
|
|
211
|
+
createdAccounts.push({
|
|
212
|
+
name: createResult.data.accountName,
|
|
213
|
+
accountId: createResult.data.accountId
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
return success(undefined);
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* Convert account placement config into AccountInfo[] for placeAccountsInOUs.
|
|
220
|
+
*/
|
|
221
|
+
function buildAccountInfos(placements) {
|
|
222
|
+
return Object.entries(placements).map(([accountId, environment]) => ({
|
|
223
|
+
id: accountId,
|
|
224
|
+
name: accountId,
|
|
225
|
+
environment
|
|
226
|
+
}));
|
|
227
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type Result } from "@fjall/generator";
|
|
2
|
+
import type { DeploymentOperation } from "../types/operations.js";
|
|
3
|
+
/**
|
|
4
|
+
* Determine the deployment operation type from the target string and filesystem.
|
|
5
|
+
*
|
|
6
|
+
* - If target matches an organisation type → OrganisationOperation
|
|
7
|
+
* - If fjall/<target> directory exists → ApplicationOperation
|
|
8
|
+
* - Otherwise → failure
|
|
9
|
+
*/
|
|
10
|
+
export declare function resolveOperation(target: string, workingDirectory: string): Promise<Result<DeploymentOperation>>;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { join, relative } from "path";
|
|
2
|
+
import { success, failure } from "@fjall/generator";
|
|
3
|
+
import { ORGANISATION_TYPES } from "../types/operations.js";
|
|
4
|
+
import { fileExists } from "../util/fsHelpers.js";
|
|
5
|
+
const ORGANISATION_TYPE_VALUES = new Set(Object.values(ORGANISATION_TYPES));
|
|
6
|
+
const TARGET_PATTERN = /^[a-z][a-z0-9-]*$/;
|
|
7
|
+
/**
|
|
8
|
+
* Determine the deployment operation type from the target string and filesystem.
|
|
9
|
+
*
|
|
10
|
+
* - If target matches an organisation type → OrganisationOperation
|
|
11
|
+
* - If fjall/<target> directory exists → ApplicationOperation
|
|
12
|
+
* - Otherwise → failure
|
|
13
|
+
*/
|
|
14
|
+
export async function resolveOperation(target, workingDirectory) {
|
|
15
|
+
// Check for organisation-level deployment
|
|
16
|
+
const normalisedTarget = target.toLowerCase();
|
|
17
|
+
if (ORGANISATION_TYPE_VALUES.has(normalisedTarget)) {
|
|
18
|
+
return success({
|
|
19
|
+
kind: "organisation",
|
|
20
|
+
type: normalisedTarget,
|
|
21
|
+
target,
|
|
22
|
+
path: workingDirectory
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
// Check for account-prefixed targets (e.g., "account-prod")
|
|
26
|
+
if (normalisedTarget.startsWith("account")) {
|
|
27
|
+
return success({
|
|
28
|
+
kind: "organisation",
|
|
29
|
+
type: ORGANISATION_TYPES.ACCOUNT,
|
|
30
|
+
target,
|
|
31
|
+
path: workingDirectory
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
// Validate target before joining into filesystem path
|
|
35
|
+
if (!TARGET_PATTERN.test(target)) {
|
|
36
|
+
return failure(new Error(`Invalid target "${target}": must start with a lowercase letter and contain only lowercase letters, numbers, and hyphens`));
|
|
37
|
+
}
|
|
38
|
+
// Check for application deployment
|
|
39
|
+
const appPath = join(workingDirectory, "fjall", target);
|
|
40
|
+
// Defence-in-depth: ensure resolved path stays under workingDirectory
|
|
41
|
+
const rel = relative(workingDirectory, appPath);
|
|
42
|
+
if (rel.startsWith("..")) {
|
|
43
|
+
return failure(new Error(`Invalid target "${target}": resolved path escapes working directory`));
|
|
44
|
+
}
|
|
45
|
+
if (await fileExists(appPath)) {
|
|
46
|
+
return success({
|
|
47
|
+
kind: "application",
|
|
48
|
+
appName: target,
|
|
49
|
+
path: appPath
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
return failure(new Error(`Target "${target}" is not a recognised organisation type and no directory found at fjall/${target}`));
|
|
53
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { SimpleAwsProvider } from "../aws/SimpleAwsProvider.js";
|
|
2
|
+
import { CdkService } from "../services/infrastructure/CdkService.js";
|
|
3
|
+
import { CloudFormationService } from "../services/infrastructure/CloudFormationService.js";
|
|
4
|
+
import { ApplicationStackService } from "../services/application/ApplicationStackService.js";
|
|
5
|
+
import { TemplateHashService } from "../services/supporting/TemplateHashService.js";
|
|
6
|
+
import type { DeployParams } from "../types/params.js";
|
|
7
|
+
export interface DeployServices {
|
|
8
|
+
awsProvider: SimpleAwsProvider;
|
|
9
|
+
cdkService: CdkService;
|
|
10
|
+
cfnService: CloudFormationService;
|
|
11
|
+
stackService: ApplicationStackService;
|
|
12
|
+
hashService: TemplateHashService;
|
|
13
|
+
}
|
|
14
|
+
/** CDK needs AWS credentials as env vars, so exportToEnv() is called immediately. */
|
|
15
|
+
export declare function createDeployServices(params: DeployParams): DeployServices;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { SimpleAwsProvider } from "../aws/SimpleAwsProvider.js";
|
|
2
|
+
import { CdkService } from "../services/infrastructure/CdkService.js";
|
|
3
|
+
import { CloudFormationService } from "../services/infrastructure/CloudFormationService.js";
|
|
4
|
+
import { ApplicationStackService } from "../services/application/ApplicationStackService.js";
|
|
5
|
+
import { TemplateHashService } from "../services/supporting/TemplateHashService.js";
|
|
6
|
+
/** CDK needs AWS credentials as env vars, so exportToEnv() is called immediately. */
|
|
7
|
+
export function createDeployServices(params) {
|
|
8
|
+
const awsProvider = new SimpleAwsProvider(params.awsCredentials);
|
|
9
|
+
awsProvider.exportToEnv();
|
|
10
|
+
const cdkService = new CdkService();
|
|
11
|
+
const cfnService = new CloudFormationService(awsProvider);
|
|
12
|
+
const stackService = new ApplicationStackService(cdkService, cfnService);
|
|
13
|
+
stackService.setAwsContext(awsProvider);
|
|
14
|
+
const hashService = new TemplateHashService();
|
|
15
|
+
return { awsProvider, cdkService, cfnService, stackService, hashService };
|
|
16
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import type { CdkService } from "../infrastructure/CdkService.js";
|
|
2
|
+
import type { CloudFormationService } from "../infrastructure/CloudFormationService.js";
|
|
3
|
+
import type { AwsProvider } from "../../aws/AwsProvider.js";
|
|
4
|
+
import type { ResourceEvent } from "../../types/events.js";
|
|
5
|
+
import type { DeploymentContext } from "../../types/deployment/DeploymentTypes.js";
|
|
6
|
+
import { type ApplicationStack } from "../../types/operations.js";
|
|
7
|
+
import type { ParallelDeploymentResult } from "../../types/deployment/parallel.js";
|
|
8
|
+
import { type AppResourceFlags } from "../../types/patternDetection.js";
|
|
9
|
+
import { type Result } from "@fjall/generator";
|
|
10
|
+
import { ApplicationError, type StackDeploymentData } from "../../types/application/ApplicationServiceTypes.js";
|
|
11
|
+
/**
|
|
12
|
+
* Service for deploying and destroying individual CloudFormation stacks.
|
|
13
|
+
*
|
|
14
|
+
* Handles single-stack deploy/destroy, parallel operations, and the full
|
|
15
|
+
* destroy-all-stacks orchestration.
|
|
16
|
+
*
|
|
17
|
+
* Unlike the CLI version, services are constructor-injected rather than
|
|
18
|
+
* fetched via singleton factories.
|
|
19
|
+
*/
|
|
20
|
+
export declare class ApplicationStackService {
|
|
21
|
+
private cdkService;
|
|
22
|
+
private cloudFormationService;
|
|
23
|
+
private aws;
|
|
24
|
+
constructor(cdkService: CdkService, cloudFormationService: CloudFormationService);
|
|
25
|
+
setAwsContext(aws: AwsProvider | undefined): void;
|
|
26
|
+
/**
|
|
27
|
+
* Deploy a specific stack type for an application
|
|
28
|
+
*/
|
|
29
|
+
deployStack(stackType: ApplicationStack, context: DeploymentContext, callbacks?: {
|
|
30
|
+
onOutput?: (chunk: string) => void;
|
|
31
|
+
onResourceProgress?: (event: ResourceEvent) => void;
|
|
32
|
+
}): Promise<Result<StackDeploymentData, ApplicationError>>;
|
|
33
|
+
/**
|
|
34
|
+
* Deploy multiple stacks in parallel within a deployment phase.
|
|
35
|
+
*
|
|
36
|
+
* Uses Promise.allSettled to ensure all stacks are attempted even if some fail.
|
|
37
|
+
*/
|
|
38
|
+
deployStacksInParallel(stacks: readonly ApplicationStack[], context: DeploymentContext, callbacks?: {
|
|
39
|
+
onOutput?: (chunk: string, stackId: ApplicationStack) => void;
|
|
40
|
+
onResourceProgress?: (event: ResourceEvent, stackId: ApplicationStack) => void;
|
|
41
|
+
onStackComplete?: (stack: ApplicationStack, success: boolean, duration: number, error?: Error) => void;
|
|
42
|
+
}): Promise<Result<ParallelDeploymentResult[], ApplicationError>>;
|
|
43
|
+
/**
|
|
44
|
+
* Destroy a specific stack type
|
|
45
|
+
*/
|
|
46
|
+
destroyStack(stackType: ApplicationStack, context: DeploymentContext, callbacks?: {
|
|
47
|
+
onOutput?: (chunk: string) => void;
|
|
48
|
+
onResourceProgress?: (event: ResourceEvent) => void;
|
|
49
|
+
}, useCdkOut?: boolean): Promise<Result<StackDeploymentData, ApplicationError>>;
|
|
50
|
+
/**
|
|
51
|
+
* Destroy multiple stacks in parallel within a destroy phase.
|
|
52
|
+
*
|
|
53
|
+
* IMPORTANT: For parallel operations, useCdkOut must be true and CDK synth must
|
|
54
|
+
* be run beforehand. Multiple CDK commands cannot run in parallel because they
|
|
55
|
+
* all try to synthesise to the same cdk.out directory.
|
|
56
|
+
*/
|
|
57
|
+
destroyStacksInParallel(stacks: readonly ApplicationStack[], context: DeploymentContext, callbacks?: {
|
|
58
|
+
onOutput?: (chunk: string, stackId: ApplicationStack) => void;
|
|
59
|
+
onResourceProgress?: (event: ResourceEvent, stackId: ApplicationStack) => void;
|
|
60
|
+
onStackComplete?: (stack: ApplicationStack, success: boolean, duration: number, error?: Error) => void;
|
|
61
|
+
}, useCdkOut?: boolean): Promise<Result<ParallelDeploymentResult[], ApplicationError>>;
|
|
62
|
+
/**
|
|
63
|
+
* Get stack outputs for a deployed stack
|
|
64
|
+
*/
|
|
65
|
+
getStackOutputs(appName: string, stackType: ApplicationStack): Promise<Result<Record<string, string | number | boolean>, ApplicationError>>;
|
|
66
|
+
/**
|
|
67
|
+
* Resolve the website URL for an application from CloudFormation stack outputs.
|
|
68
|
+
* Checks Compute stack for LoadBalancer URL, then CDN stack for CloudFront distribution.
|
|
69
|
+
*/
|
|
70
|
+
resolveWebsiteUrl(appName: string): Promise<string | undefined>;
|
|
71
|
+
/**
|
|
72
|
+
* Destroy all stacks for an application in reverse order.
|
|
73
|
+
* Supports parallel destruction for OpenNext patterns (nextjs/payload).
|
|
74
|
+
*/
|
|
75
|
+
destroyAllStacks(context: DeploymentContext, callbacks?: {
|
|
76
|
+
onOutput?: (chunk: string, stackId?: ApplicationStack) => void;
|
|
77
|
+
onResourceProgress?: (event: ResourceEvent, stackId?: ApplicationStack) => void;
|
|
78
|
+
onStackStart?: (stackType: ApplicationStack, stackName: string) => void;
|
|
79
|
+
onStackComplete?: (stackType: ApplicationStack, result: Result<StackDeploymentData, ApplicationError>) => void | Promise<void>;
|
|
80
|
+
onParallelPhaseStart?: (stacks: readonly ApplicationStack[], description: string) => void;
|
|
81
|
+
onParallelPhaseComplete?: (results: ParallelDeploymentResult[]) => void;
|
|
82
|
+
}, resources?: AppResourceFlags): Promise<Result<{
|
|
83
|
+
message: string;
|
|
84
|
+
}, ApplicationError>>;
|
|
85
|
+
/**
|
|
86
|
+
* Convert "doesn't exist" errors to success for destroy operations.
|
|
87
|
+
*/
|
|
88
|
+
private convertDestroyResultIfNotExists;
|
|
89
|
+
/**
|
|
90
|
+
* Handle destroy errors and determine if we should continue or fail
|
|
91
|
+
*/
|
|
92
|
+
private handleDestroyError;
|
|
93
|
+
}
|