@fjall/deploy-core 0.89.4 → 0.89.6
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 +50 -21
- package/README.md +25 -0
- package/dist/.minified +1 -0
- package/dist/src/__test-utils__/awsMockHelpers.d.ts +20 -0
- package/dist/src/__test-utils__/awsMockHelpers.js +1 -0
- package/dist/src/__test-utils__/index.d.ts +1 -0
- package/dist/src/__test-utils__/index.js +1 -0
- package/dist/src/aws/AwsProvider.d.ts +2 -2
- package/dist/src/aws/AwsProvider.js +0 -1
- package/dist/src/aws/SimpleAwsProvider.d.ts +2 -3
- package/dist/src/aws/SimpleAwsProvider.js +1 -73
- package/dist/src/aws/index.d.ts +4 -2
- package/dist/src/aws/index.js +1 -3
- package/dist/src/aws/organisations/accounts.js +10 -10
- package/dist/src/aws/organisations/backup.js +4 -2
- package/dist/src/aws/organisations/costAllocation.js +4 -2
- package/dist/src/aws/organisations/delegatedAdmin.d.ts +9 -0
- package/dist/src/aws/organisations/delegatedAdmin.js +43 -0
- package/dist/src/aws/organisations/identityCentre.d.ts +1 -1
- package/dist/src/aws/organisations/identityCentre.js +6 -2
- package/dist/src/aws/organisations/index.d.ts +4 -3
- package/dist/src/aws/organisations/index.js +1 -12
- package/dist/src/aws/organisations/ipam.js +4 -2
- package/dist/src/aws/organisations/organisation.js +27 -18
- package/dist/src/aws/organisations/organisationalUnits.d.ts +26 -6
- package/dist/src/aws/organisations/organisationalUnits.js +149 -35
- package/dist/src/aws/organisations/policies.js +4 -3
- package/dist/src/aws/organisations/ram.js +6 -2
- package/dist/src/aws/organisations/serviceAccess.js +12 -6
- package/dist/src/aws/organisations/trustedAccess.js +6 -2
- package/dist/src/aws/organisations/types.d.ts +23 -1
- package/dist/src/aws/organisations/types.js +1 -16
- package/dist/src/aws/utils/__tests__/cloudformationTestHelpers.d.ts +6 -0
- package/dist/src/aws/utils/__tests__/cloudformationTestHelpers.js +1 -0
- package/dist/src/aws/utils/cloudformationEventHelpers.d.ts +48 -0
- package/dist/src/aws/utils/cloudformationEventHelpers.js +1 -0
- package/dist/src/aws/utils/cloudformationEventTypes.d.ts +45 -0
- package/dist/src/aws/utils/cloudformationEventTypes.js +1 -0
- package/dist/src/aws/utils/cloudformationEvents.d.ts +8 -54
- package/dist/src/aws/utils/cloudformationEvents.js +1 -596
- package/dist/src/aws/utils/index.d.ts +5 -0
- package/dist/src/aws/utils/index.js +1 -0
- package/dist/src/aws/utils/stackStatus.js +1 -90
- package/dist/src/events/index.d.ts +13 -0
- package/dist/src/events/index.js +1 -0
- package/dist/src/index.d.ts +34 -17
- package/dist/src/index.js +41 -21
- package/dist/src/orchestration/__tests__/cascadeTestHelpers.d.ts +12 -0
- package/dist/src/orchestration/__tests__/cascadeTestHelpers.js +78 -0
- package/dist/src/orchestration/activeDeploymentGuard.d.ts +10 -0
- package/dist/src/orchestration/activeDeploymentGuard.js +39 -0
- package/dist/src/orchestration/applicationDeploy.js +46 -224
- package/dist/src/orchestration/applicationDeployHelpers.d.ts +39 -0
- package/dist/src/orchestration/applicationDeployHelpers.js +223 -0
- package/dist/src/orchestration/applicationDestroy.d.ts +14 -0
- package/dist/src/orchestration/applicationDestroy.js +131 -0
- package/dist/src/orchestration/builders/dockerBuilder.d.ts +17 -0
- package/dist/src/orchestration/builders/dockerBuilder.js +98 -0
- package/dist/src/orchestration/builders/frameworkRegistry.d.ts +23 -0
- package/dist/src/orchestration/builders/frameworkRegistry.js +1 -0
- package/dist/src/orchestration/builders/index.d.ts +4 -0
- package/dist/src/orchestration/builders/index.js +1 -0
- package/dist/src/orchestration/builders/openNextBuilder.d.ts +21 -0
- package/dist/src/orchestration/builders/openNextBuilder.js +144 -0
- package/dist/src/orchestration/cascadeDestroyHelpers.d.ts +30 -0
- package/dist/src/orchestration/cascadeDestroyHelpers.js +1 -0
- package/dist/src/orchestration/cascadeHelpers.d.ts +46 -0
- package/dist/src/orchestration/cascadeHelpers.js +160 -0
- package/dist/src/orchestration/contextHelpers.d.ts +47 -2
- package/dist/src/orchestration/contextHelpers.js +94 -1
- package/dist/src/orchestration/destroy.d.ts +13 -0
- package/dist/src/orchestration/destroy.js +67 -0
- package/dist/src/orchestration/detectionPipeline.d.ts +2 -11
- package/dist/src/orchestration/detectionPipeline.js +29 -10
- package/dist/src/orchestration/dockerBuildHelper.d.ts +10 -0
- package/dist/src/orchestration/dockerBuildHelper.js +49 -0
- package/dist/src/orchestration/dockerInterface.d.ts +4 -2
- package/dist/src/orchestration/index.d.ts +8 -1
- package/dist/src/orchestration/index.js +1 -3
- package/dist/src/orchestration/manifestSecretParser.d.ts +11 -0
- package/dist/src/orchestration/manifestSecretParser.js +1 -0
- package/dist/src/orchestration/openNextBuild.d.ts +28 -0
- package/dist/src/orchestration/openNextBuild.js +243 -0
- package/dist/src/orchestration/organisationDeploy.js +130 -228
- package/dist/src/orchestration/organisationDestroy.d.ts +24 -0
- package/dist/src/orchestration/organisationDestroy.js +189 -0
- package/dist/src/orchestration/organisationSetup.d.ts +6 -4
- package/dist/src/orchestration/organisationSetup.js +28 -8
- package/dist/src/orchestration/resolveOperation.js +68 -6
- package/dist/src/orchestration/serviceFactory.d.ts +4 -0
- package/dist/src/orchestration/serviceFactory.js +1 -16
- package/dist/src/orchestration/spawnHelpers.d.ts +47 -0
- package/dist/src/orchestration/spawnHelpers.js +1 -0
- package/dist/src/orchestration/stackCleanup.d.ts +39 -0
- package/dist/src/orchestration/stackCleanup.js +1 -0
- package/dist/src/orchestration/welcomeImageHelper.d.ts +15 -0
- package/dist/src/orchestration/welcomeImageHelper.js +64 -0
- package/dist/src/services/application/ApplicationStackService.d.ts +21 -30
- package/dist/src/services/application/ApplicationStackService.js +16 -234
- package/dist/src/services/application/applicationStackHelpers.d.ts +46 -0
- package/dist/src/services/application/applicationStackHelpers.js +248 -0
- package/dist/src/services/application/index.d.ts +1 -0
- package/dist/src/services/application/index.js +1 -1
- package/dist/src/services/index.d.ts +6 -0
- package/dist/src/services/index.js +1 -0
- package/dist/src/services/infrastructure/CdkArgumentBuilder.js +1 -67
- package/dist/src/services/infrastructure/CdkCommandRunner.d.ts +10 -2
- package/dist/src/services/infrastructure/CdkCommandRunner.js +18 -15
- package/dist/src/services/infrastructure/CdkErrorFormatter.js +16 -194
- package/dist/src/services/infrastructure/CdkEventMonitoring.js +1 -41
- package/dist/src/services/infrastructure/CdkOutputAnalyser.js +1 -1
- package/dist/src/services/infrastructure/CdkOutputParser.js +2 -33
- package/dist/src/services/infrastructure/CdkProcessManager.d.ts +5 -0
- package/dist/src/services/infrastructure/CdkProcessManager.js +81 -47
- package/dist/src/services/infrastructure/CdkService.d.ts +7 -52
- package/dist/src/services/infrastructure/CdkService.js +41 -82
- package/dist/src/services/infrastructure/CdkServiceTypes.d.ts +50 -0
- package/dist/src/services/infrastructure/CdkServiceTypes.js +0 -0
- package/dist/src/services/infrastructure/CloudFormationService.js +9 -10
- package/dist/src/services/infrastructure/ICdkProcessManager.d.ts +27 -0
- package/dist/src/services/infrastructure/ICdkProcessManager.js +1 -0
- package/dist/src/services/infrastructure/__tests__/cloudFormationTestHelpers.d.ts +9 -0
- package/dist/src/services/infrastructure/__tests__/cloudFormationTestHelpers.js +1 -0
- package/dist/src/services/infrastructure/cdkServiceHelpers.d.ts +9 -0
- package/dist/src/services/infrastructure/cdkServiceHelpers.js +1 -0
- package/dist/src/services/infrastructure/constructMapEnrichment.d.ts +7 -0
- package/dist/src/services/infrastructure/constructMapEnrichment.js +1 -0
- package/dist/src/services/infrastructure/index.d.ts +3 -1
- package/dist/src/services/infrastructure/index.js +1 -7
- package/dist/src/services/supporting/TemplateHashService.js +1 -1
- package/dist/src/services/supporting/helpers.js +1 -81
- package/dist/src/services/supporting/index.js +1 -3
- package/dist/src/steps/index.d.ts +1 -0
- package/dist/src/steps/index.js +1 -0
- package/dist/src/steps/stepRegistry.d.ts +71 -0
- package/dist/src/steps/stepRegistry.js +505 -0
- package/dist/src/types/FjallState.js +1 -118
- package/dist/src/types/ProgressEvent.js +1 -48
- package/dist/src/types/application/ApplicationServiceTypes.js +1 -30
- package/dist/src/types/application/index.js +1 -1
- package/dist/src/types/callbacks.d.ts +76 -4
- package/dist/src/types/callbacks.js +0 -1
- package/dist/src/types/constants.d.ts +2 -0
- package/dist/src/types/constants.js +1 -6
- package/dist/src/types/credentials.d.ts +1 -1
- package/dist/src/types/credentials.js +0 -1
- package/dist/src/types/deployment/DeploymentServiceTypes.d.ts +5 -2
- package/dist/src/types/deployment/DeploymentServiceTypes.js +1 -1
- package/dist/src/types/deployment/DeploymentTypes.d.ts +1 -0
- package/dist/src/types/deployment/DeploymentTypes.js +0 -1
- package/dist/src/types/deployment/cloudformation.js +0 -1
- package/dist/src/types/deployment/index.d.ts +3 -1
- package/dist/src/types/deployment/index.js +1 -1
- package/dist/src/types/deployment/parallel.js +1 -10
- package/dist/src/types/deploymentEventSchema.d.ts +158 -0
- package/dist/src/types/deploymentEventSchema.js +1 -0
- package/dist/src/types/detection.d.ts +22 -0
- package/dist/src/types/detection.js +1 -0
- package/dist/src/types/entitlements.d.ts +31 -0
- package/dist/src/types/entitlements.js +0 -0
- package/dist/src/types/errors/CdkError.js +1 -20
- package/dist/src/types/errors/ServiceError.d.ts +2 -1
- package/dist/src/types/errors/ServiceError.js +1 -119
- package/dist/src/types/errors/index.d.ts +2 -0
- package/dist/src/types/errors/index.js +1 -0
- package/dist/src/types/events.d.ts +3 -9
- package/dist/src/types/events.js +0 -5
- package/dist/src/types/frameworkBuilder.d.ts +96 -0
- package/dist/src/types/frameworkBuilder.js +8 -0
- package/dist/src/types/index.d.ts +19 -4
- package/dist/src/types/index.js +1 -9
- package/dist/src/types/operations.d.ts +3 -2
- package/dist/src/types/operations.js +1 -285
- package/dist/src/types/orgConfig.d.ts +2 -10
- package/dist/src/types/orgConfig.js +0 -11
- package/dist/src/types/params.d.ts +61 -0
- package/dist/src/types/patternDetection.d.ts +14 -16
- package/dist/src/types/patternDetection.js +14 -18
- package/dist/src/types/patternTypes.d.ts +19 -0
- package/dist/src/types/patternTypes.js +1 -0
- package/dist/src/types/stepDefinitions.d.ts +163 -0
- package/dist/src/types/stepDefinitions.js +98 -0
- package/dist/src/types/validation.js +0 -1
- package/dist/src/util/dockerfileDetection.d.ts +5 -0
- package/dist/src/util/dockerfileDetection.js +1 -0
- package/dist/src/util/index.d.ts +4 -3
- package/dist/src/util/index.js +1 -3
- package/dist/src/util/sequencedCallbacks.d.ts +44 -0
- package/dist/src/util/sequencedCallbacks.js +1 -0
- package/package.json +49 -8
- package/dist/src/aws/utils/CloudFormationFailureAnalyser.d.ts +0 -32
- package/dist/src/aws/utils/CloudFormationFailureAnalyser.js +0 -228
- package/dist/src/aws/utils/errors.d.ts +0 -26
- package/dist/src/aws/utils/errors.js +0 -59
- package/dist/src/util/fsHelpers.d.ts +0 -4
- package/dist/src/util/fsHelpers.js +0 -16
- package/dist/src/util/securityHelpers.d.ts +0 -31
- package/dist/src/util/securityHelpers.js +0 -124
- package/dist/src/util/singleton.d.ts +0 -2
- package/dist/src/util/singleton.js +0 -9
- package/dist/src/util/sleep.d.ts +0 -4
- package/dist/src/util/sleep.js +0 -4
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { success, failure } from "@fjall/generator";
|
|
2
|
+
import { logger } from "@fjall/util/logger";
|
|
3
|
+
import { maskSensitiveOutput } from "@fjall/util";
|
|
4
|
+
import { ORGANISATION_TYPES, getOrganisationStackName } from "../types/operations.js";
|
|
5
|
+
import { CdkContextBuilder } from "../services/supporting/CdkContextBuilder.js";
|
|
6
|
+
import { stubCallerIdentity } from "../types/deployment/index.js";
|
|
7
|
+
import { CloudFormationService } from "../services/infrastructure/CloudFormationService.js";
|
|
8
|
+
import { buildParamsContext, collectStackOutputs, assumeCascadeRole, forwardOutput } from "./contextHelpers.js";
|
|
9
|
+
import { STRUCTURAL_ENVIRONMENTS } from "@fjall/util";
|
|
10
|
+
/**
|
|
11
|
+
* Partition provider accounts into platform and member accounts.
|
|
12
|
+
* Used by both deploy and destroy orchestration.
|
|
13
|
+
*/
|
|
14
|
+
export function partitionAccounts(providerAccounts) {
|
|
15
|
+
const platformAccount = providerAccounts.find((acc) => acc.environment === STRUCTURAL_ENVIRONMENTS.PLATFORM);
|
|
16
|
+
const memberAccounts = providerAccounts.filter((acc) => acc.environment !== STRUCTURAL_ENVIRONMENTS.ROOT &&
|
|
17
|
+
acc.environment !== STRUCTURAL_ENVIRONMENTS.PLATFORM);
|
|
18
|
+
return { platformAccount, memberAccounts };
|
|
19
|
+
}
|
|
20
|
+
// Re-export for backwards compatibility (canonical definition in contextHelpers)
|
|
21
|
+
export { buildCascadeRoleArn } from "./contextHelpers.js";
|
|
22
|
+
export async function deployCascadeAccount(params, services, operation, account, deployType, callbacks, ipamPoolId) {
|
|
23
|
+
const operationKey = `${account.name}-${account.id}`;
|
|
24
|
+
const region = services.awsProvider.getRegion();
|
|
25
|
+
callbacks.onCascadeAccountStart?.(operationKey, account.id, region, deployType);
|
|
26
|
+
// Assume role in target account
|
|
27
|
+
const roleResult = await assumeCascadeRole(services.awsProvider, account.id, region, `fjall-cascade-${account.name}`);
|
|
28
|
+
if (!roleResult.success) {
|
|
29
|
+
callbacks.onCascadeAccountComplete?.(operationKey, false, maskSensitiveOutput(roleResult.error.message), region);
|
|
30
|
+
return failure(new Error(`Failed to assume role for ${account.name}: ${maskSensitiveOutput(roleResult.error.message)}`));
|
|
31
|
+
}
|
|
32
|
+
const { provider: accountProvider, credentials: cascadeCredentials } = roleResult.data;
|
|
33
|
+
// Build context for this account (includes IPAM pool ID if available)
|
|
34
|
+
const accountContext = CdkContextBuilder.buildDeploymentContext({
|
|
35
|
+
deployType,
|
|
36
|
+
target: operation.target,
|
|
37
|
+
path: operation.path,
|
|
38
|
+
region,
|
|
39
|
+
accountName: account.name,
|
|
40
|
+
callerIdentity: stubCallerIdentity(account.id),
|
|
41
|
+
ipamPoolId,
|
|
42
|
+
...buildParamsContext({
|
|
43
|
+
orgConfig: params.orgConfig,
|
|
44
|
+
identity: params.identity,
|
|
45
|
+
skipOidc: params.options?.skipOidc
|
|
46
|
+
})
|
|
47
|
+
}, { verbose: params.options?.verbose }, params.orgConfig);
|
|
48
|
+
// Bootstrap the target account
|
|
49
|
+
callbacks.onCascadeAccountPhaseChange?.(operationKey, "bootstrap", region);
|
|
50
|
+
const bootstrapResult = await services.cdkService.runCdkBootstrap(accountContext, forwardOutput(callbacks), cascadeCredentials);
|
|
51
|
+
if (!bootstrapResult.success) {
|
|
52
|
+
callbacks.onCascadeAccountComplete?.(operationKey, false, maskSensitiveOutput(`Bootstrap failed: ${bootstrapResult.error}`), region);
|
|
53
|
+
return failure(new Error(`Bootstrap failed for ${account.name}: ${maskSensitiveOutput(bootstrapResult.error)}`));
|
|
54
|
+
}
|
|
55
|
+
// Deploy the account stack
|
|
56
|
+
callbacks.onCascadeAccountPhaseChange?.(operationKey, "deploy", region);
|
|
57
|
+
const stackName = getOrganisationStackName(deployType === "platform"
|
|
58
|
+
? ORGANISATION_TYPES.PLATFORM
|
|
59
|
+
: ORGANISATION_TYPES.ACCOUNT);
|
|
60
|
+
const deployResult = await services.cdkService.runCdkDeploy(accountContext, stackName, forwardOutput(callbacks), (event) => callbacks.onCascadeAccountResourceProgress?.(operationKey, event, region), accountProvider, cascadeCredentials);
|
|
61
|
+
if (!deployResult.success) {
|
|
62
|
+
callbacks.onCascadeAccountComplete?.(operationKey, false, maskSensitiveOutput(deployResult.error), region);
|
|
63
|
+
return failure(new Error(maskSensitiveOutput(deployResult.error)));
|
|
64
|
+
}
|
|
65
|
+
// Capture stack outputs (OIDC role ARN, etc.) from the target account
|
|
66
|
+
const accountCfn = new CloudFormationService(accountProvider);
|
|
67
|
+
const outputsResult = await accountCfn.getStackOutputs(stackName);
|
|
68
|
+
if (!outputsResult.success) {
|
|
69
|
+
logger.debug("cascadeHelpers", "Failed to read cascade account stack outputs (non-critical)", { stackName, account: account.name });
|
|
70
|
+
}
|
|
71
|
+
const outputs = collectStackOutputs(outputsResult);
|
|
72
|
+
callbacks.onCascadeAccountComplete?.(operationKey, true, undefined, region, outputs);
|
|
73
|
+
return success({ outputs });
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Read Platform stack outputs to extract IPAM pool IDs for member accounts.
|
|
77
|
+
* Output keys follow the pattern `IpamPoolId{12-digit-accountId}{regionSuffix}`.
|
|
78
|
+
* Returns a map keyed by `{accountId}-{regionSuffix}` → pool ID.
|
|
79
|
+
* Non-fatal: returns empty map on any error.
|
|
80
|
+
*/
|
|
81
|
+
export async function readPlatformIpamPoolIds(services, platformAccount, callbacks) {
|
|
82
|
+
const poolIds = new Map();
|
|
83
|
+
// Assume role in platform account to read its stack outputs
|
|
84
|
+
const region = services.awsProvider.getRegion();
|
|
85
|
+
const roleResult = await assumeCascadeRole(services.awsProvider, platformAccount.id, region, `fjall-ipam-read-${platformAccount.name}`);
|
|
86
|
+
if (!roleResult.success) {
|
|
87
|
+
logger.debug("organisationDeploy", `Cannot read Platform outputs: ${roleResult.error.message}`);
|
|
88
|
+
return poolIds;
|
|
89
|
+
}
|
|
90
|
+
const platformCfn = new CloudFormationService(roleResult.data.provider);
|
|
91
|
+
const platformStackName = getOrganisationStackName(ORGANISATION_TYPES.PLATFORM);
|
|
92
|
+
const outputsResult = await platformCfn.getStackOutputs(platformStackName);
|
|
93
|
+
if (!outputsResult.success) {
|
|
94
|
+
logger.debug("organisationDeploy", `Failed to read Platform stack outputs: ${outputsResult.error.message}`);
|
|
95
|
+
return poolIds;
|
|
96
|
+
}
|
|
97
|
+
const ipamPattern = /^IpamPoolId(\d{12})(\w+)$/;
|
|
98
|
+
for (const output of outputsResult.data) {
|
|
99
|
+
const match = output.OutputKey?.match(ipamPattern);
|
|
100
|
+
if (match && output.OutputValue) {
|
|
101
|
+
const key = `${match[1]}-${match[2]}`;
|
|
102
|
+
poolIds.set(key, output.OutputValue);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
if (poolIds.size > 0) {
|
|
106
|
+
callbacks.onLog?.(`Read ${poolIds.size} IPAM pool ID(s) from Platform stack`, "info");
|
|
107
|
+
}
|
|
108
|
+
return poolIds;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Deploy configured domains: apex domains sequentially, then delegated
|
|
112
|
+
* domains in parallel. Delegates to the caller-provided DomainDeployProvider.
|
|
113
|
+
*/
|
|
114
|
+
export async function deployDomains(provider, callbacks) {
|
|
115
|
+
const domains = provider.getDomains();
|
|
116
|
+
if (domains.length === 0) {
|
|
117
|
+
return { domainsDeployed: 0, errors: [] };
|
|
118
|
+
}
|
|
119
|
+
callbacks.onCascadePhaseStart?.("domains");
|
|
120
|
+
const apexDomains = domains.filter((d) => d.type === "apex");
|
|
121
|
+
const delegatedDomains = domains.filter((d) => d.type === "delegated");
|
|
122
|
+
let domainsDeployed = 0;
|
|
123
|
+
const errors = [];
|
|
124
|
+
// Phase A: Apex domains (sequential — delegation roles must exist first)
|
|
125
|
+
for (const domain of apexDomains) {
|
|
126
|
+
const result = await provider.deployDomain(domain.name, callbacks);
|
|
127
|
+
if (result.success) {
|
|
128
|
+
domainsDeployed++;
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
errors.push(`${domain.name}: ${result.error.message}`);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
// Phase B: Delegated subdomains (parallel — independent of each other)
|
|
135
|
+
if (delegatedDomains.length > 0) {
|
|
136
|
+
const subdomainResults = await Promise.allSettled(delegatedDomains.map((domain) => provider.deployDomain(domain.name, callbacks)));
|
|
137
|
+
for (let i = 0; i < subdomainResults.length; i++) {
|
|
138
|
+
const settled = subdomainResults[i];
|
|
139
|
+
const domain = delegatedDomains[i];
|
|
140
|
+
if (!settled || !domain)
|
|
141
|
+
continue;
|
|
142
|
+
if (settled.status === "fulfilled") {
|
|
143
|
+
if (settled.value.success) {
|
|
144
|
+
domainsDeployed++;
|
|
145
|
+
}
|
|
146
|
+
else {
|
|
147
|
+
errors.push(`${domain.name}: ${settled.value.error.message}`);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
else {
|
|
151
|
+
const message = settled.reason instanceof Error
|
|
152
|
+
? settled.reason.message
|
|
153
|
+
: String(settled.reason);
|
|
154
|
+
errors.push(`${domain.name}: ${message}`);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
callbacks.onCascadePhaseComplete?.("domains");
|
|
159
|
+
return { domainsDeployed, errors };
|
|
160
|
+
}
|
|
@@ -1,9 +1,54 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type Result } from "@fjall/generator";
|
|
2
|
+
import type { ResourceEvent } from "@fjall/util/aws";
|
|
3
|
+
import type { OrgConfig } from "../types/orgConfig.js";
|
|
4
|
+
import type { DeployIdentity } from "../types/credentials.js";
|
|
5
|
+
import type { AwsProviderCredentials } from "../aws/AwsProvider.js";
|
|
6
|
+
import { SimpleAwsProvider } from "../aws/SimpleAwsProvider.js";
|
|
7
|
+
import type { DeploymentContext } from "../types/deployment/DeploymentTypes.js";
|
|
8
|
+
import type { DeployCallbacks } from "../types/callbacks.js";
|
|
9
|
+
import type { DeployServices } from "./serviceFactory.js";
|
|
2
10
|
/**
|
|
3
11
|
* Build the orgConfig/identity context fields shared by all deployment paths.
|
|
4
12
|
* CdkContextBuilder expects orgConfig as a JSON string and fjallOrgId as a string.
|
|
5
13
|
*/
|
|
6
|
-
export declare function buildParamsContext(params:
|
|
14
|
+
export declare function buildParamsContext(params: {
|
|
15
|
+
orgConfig?: OrgConfig;
|
|
16
|
+
identity?: DeployIdentity;
|
|
17
|
+
skipOidc?: boolean;
|
|
18
|
+
}): {
|
|
7
19
|
orgConfig?: string;
|
|
8
20
|
fjallOrgId?: string;
|
|
21
|
+
fjallOidcConfigured?: boolean;
|
|
9
22
|
};
|
|
23
|
+
/** Forward onOutput callback — reduces lambda repetition across orchestration files. */
|
|
24
|
+
export declare function forwardOutput(callbacks: DeployCallbacks): (chunk: string) => void;
|
|
25
|
+
/** Forward onResourceProgress callback — reduces lambda repetition across orchestration files. */
|
|
26
|
+
export declare function forwardResourceProgress(callbacks: DeployCallbacks): (event: ResourceEvent) => void;
|
|
27
|
+
/** Convert CloudFormation getStackOutputs result to Record<string, string>. */
|
|
28
|
+
export declare function collectStackOutputs(result: Result<Array<{
|
|
29
|
+
OutputKey?: string;
|
|
30
|
+
OutputValue?: string;
|
|
31
|
+
}>, unknown>): Record<string, string> | undefined;
|
|
32
|
+
/** Build the IAM role ARN used for cross-account cascade operations. */
|
|
33
|
+
export declare function buildCascadeRoleArn(accountId: string): string;
|
|
34
|
+
export interface CascadeAssumedRole {
|
|
35
|
+
provider: SimpleAwsProvider;
|
|
36
|
+
credentials: AwsProviderCredentials;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Assume a cross-account cascade role and create a scoped provider.
|
|
40
|
+
* Callers handle failure (callbacks, logging, return type) — this helper
|
|
41
|
+
* only owns the assume + provider construction.
|
|
42
|
+
*/
|
|
43
|
+
export declare function assumeCascadeRole(awsProvider: SimpleAwsProvider, accountId: string, region: string, sessionName: string): Promise<Result<CascadeAssumedRole, Error>>;
|
|
44
|
+
/**
|
|
45
|
+
* Run CDK synth and return failure with masked error if it fails.
|
|
46
|
+
* Calls `onError` on the callbacks so callers only need to handle
|
|
47
|
+
* step-specific reporting (e.g. onStepComplete) before returning.
|
|
48
|
+
*/
|
|
49
|
+
export declare function synthOrFail(services: DeployServices, context: DeploymentContext, callbacks: DeployCallbacks, errorPrefix: string): Promise<Result<void>>;
|
|
50
|
+
/**
|
|
51
|
+
* Run CDK bootstrap and return failure with masked error if it fails.
|
|
52
|
+
* Emits onCDKBootstrap status callbacks and calls onError on failure.
|
|
53
|
+
*/
|
|
54
|
+
export declare function bootstrapOrFail(services: DeployServices, context: DeploymentContext, callbacks: DeployCallbacks): Promise<Result<void>>;
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { success, failure } from "@fjall/generator";
|
|
2
|
+
import { getErrorMessage, maskSensitiveOutput } from "@fjall/util";
|
|
3
|
+
import { SimpleAwsProvider } from "../aws/SimpleAwsProvider.js";
|
|
1
4
|
/**
|
|
2
5
|
* Build the orgConfig/identity context fields shared by all deployment paths.
|
|
3
6
|
* CdkContextBuilder expects orgConfig as a JSON string and fjallOrgId as a string.
|
|
@@ -9,6 +12,96 @@ export function buildParamsContext(params) {
|
|
|
9
12
|
: {}),
|
|
10
13
|
...(params.identity !== undefined
|
|
11
14
|
? { fjallOrgId: params.identity.fjallOrgId }
|
|
12
|
-
: {})
|
|
15
|
+
: {}),
|
|
16
|
+
...(params.skipOidc ? { fjallOidcConfigured: true } : {})
|
|
13
17
|
};
|
|
14
18
|
}
|
|
19
|
+
/** Forward onOutput callback — reduces lambda repetition across orchestration files. */
|
|
20
|
+
export function forwardOutput(callbacks) {
|
|
21
|
+
return (chunk) => callbacks.onOutput?.(chunk);
|
|
22
|
+
}
|
|
23
|
+
/** Forward onResourceProgress callback — reduces lambda repetition across orchestration files. */
|
|
24
|
+
export function forwardResourceProgress(callbacks) {
|
|
25
|
+
return (event) => callbacks.onResourceProgress?.(event);
|
|
26
|
+
}
|
|
27
|
+
/** Convert CloudFormation getStackOutputs result to Record<string, string>. */
|
|
28
|
+
export function collectStackOutputs(result) {
|
|
29
|
+
if (!result.success || result.data.length === 0)
|
|
30
|
+
return undefined;
|
|
31
|
+
const record = {};
|
|
32
|
+
for (const output of result.data) {
|
|
33
|
+
if (output.OutputKey && output.OutputValue !== undefined) {
|
|
34
|
+
record[output.OutputKey] = output.OutputValue;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return Object.keys(record).length > 0 ? record : undefined;
|
|
38
|
+
}
|
|
39
|
+
/** Role name created by the Account CDK stack for cross-account cascade operations. */
|
|
40
|
+
const CASCADE_DEPLOYMENT_ROLE = "fjall-deployment-role";
|
|
41
|
+
/** Build the IAM role ARN used for cross-account cascade operations. */
|
|
42
|
+
export function buildCascadeRoleArn(accountId) {
|
|
43
|
+
return `arn:aws:iam::${accountId}:role/${CASCADE_DEPLOYMENT_ROLE}`;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Assume a cross-account cascade role and create a scoped provider.
|
|
47
|
+
* Callers handle failure (callbacks, logging, return type) — this helper
|
|
48
|
+
* only owns the assume + provider construction.
|
|
49
|
+
*/
|
|
50
|
+
export async function assumeCascadeRole(awsProvider, accountId, region, sessionName) {
|
|
51
|
+
if (!awsProvider.assumeRole) {
|
|
52
|
+
return failure(new Error("AwsProvider does not support assumeRole"));
|
|
53
|
+
}
|
|
54
|
+
const roleArn = buildCascadeRoleArn(accountId);
|
|
55
|
+
let assumed;
|
|
56
|
+
try {
|
|
57
|
+
assumed = await awsProvider.assumeRole(roleArn, sessionName);
|
|
58
|
+
}
|
|
59
|
+
catch (err) {
|
|
60
|
+
return failure(new Error(getErrorMessage(err)));
|
|
61
|
+
}
|
|
62
|
+
const provider = new SimpleAwsProvider({
|
|
63
|
+
accessKeyId: assumed.accessKeyId,
|
|
64
|
+
secretAccessKey: assumed.secretAccessKey,
|
|
65
|
+
sessionToken: assumed.sessionToken,
|
|
66
|
+
region,
|
|
67
|
+
accountId
|
|
68
|
+
});
|
|
69
|
+
return success({
|
|
70
|
+
provider,
|
|
71
|
+
credentials: {
|
|
72
|
+
accessKeyId: assumed.accessKeyId,
|
|
73
|
+
secretAccessKey: assumed.secretAccessKey,
|
|
74
|
+
sessionToken: assumed.sessionToken
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Run CDK synth and return failure with masked error if it fails.
|
|
80
|
+
* Calls `onError` on the callbacks so callers only need to handle
|
|
81
|
+
* step-specific reporting (e.g. onStepComplete) before returning.
|
|
82
|
+
*/
|
|
83
|
+
export async function synthOrFail(services, context, callbacks, errorPrefix) {
|
|
84
|
+
const synthResult = await services.cdkService.runCdkSynth(context, (chunk) => callbacks.onCdkOutput?.(chunk, "synth"));
|
|
85
|
+
if (!synthResult.success) {
|
|
86
|
+
const error = new Error(maskSensitiveOutput(`${errorPrefix}: ${synthResult.error}`));
|
|
87
|
+
callbacks.onError?.(error);
|
|
88
|
+
return failure(error);
|
|
89
|
+
}
|
|
90
|
+
return success(undefined);
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Run CDK bootstrap and return failure with masked error if it fails.
|
|
94
|
+
* Emits onCDKBootstrap status callbacks and calls onError on failure.
|
|
95
|
+
*/
|
|
96
|
+
export async function bootstrapOrFail(services, context, callbacks) {
|
|
97
|
+
callbacks.onCDKBootstrap?.("bootstrapping");
|
|
98
|
+
const bootstrapResult = await services.cdkService.runCdkBootstrap(context, forwardOutput(callbacks));
|
|
99
|
+
if (!bootstrapResult.success) {
|
|
100
|
+
callbacks.onCDKBootstrap?.("failed");
|
|
101
|
+
const error = new Error(maskSensitiveOutput(`Bootstrap failed: ${bootstrapResult.error}`));
|
|
102
|
+
callbacks.onError?.(error);
|
|
103
|
+
return failure(error);
|
|
104
|
+
}
|
|
105
|
+
callbacks.onCDKBootstrap?.("complete");
|
|
106
|
+
return success(undefined);
|
|
107
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type Result } from "@fjall/generator";
|
|
2
|
+
import type { DestroyParams, DestroyResult } from "../types/params.js";
|
|
3
|
+
/**
|
|
4
|
+
* Destroy infrastructure for a target.
|
|
5
|
+
*
|
|
6
|
+
* Primary entry point for both CLI and webapp worker. Determines whether
|
|
7
|
+
* the target is an application or organisation and routes to the
|
|
8
|
+
* appropriate orchestrator.
|
|
9
|
+
*
|
|
10
|
+
* Mirrors the deploy() entry point -- same resolution, same service
|
|
11
|
+
* factory, same callback contract.
|
|
12
|
+
*/
|
|
13
|
+
export declare function destroy(params: DestroyParams): Promise<Result<DestroyResult>>;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { success, failure } from "@fjall/generator";
|
|
2
|
+
import { isApplicationOperation, isOrganisationOperation, getApplicationStackName, getOrganisationStackName, APPLICATION_STACKS } from "../types/operations.js";
|
|
3
|
+
import { createDeployServices } from "./serviceFactory.js";
|
|
4
|
+
import { resolveOperation } from "./resolveOperation.js";
|
|
5
|
+
import { destroyApplication } from "./applicationDestroy.js";
|
|
6
|
+
import { destroyOrganisation } from "./organisationDestroy.js";
|
|
7
|
+
import { checkActiveDeployments } from "./activeDeploymentGuard.js";
|
|
8
|
+
/**
|
|
9
|
+
* Destroy infrastructure for a target.
|
|
10
|
+
*
|
|
11
|
+
* Primary entry point for both CLI and webapp worker. Determines whether
|
|
12
|
+
* the target is an application or organisation and routes to the
|
|
13
|
+
* appropriate orchestrator.
|
|
14
|
+
*
|
|
15
|
+
* Mirrors the deploy() entry point -- same resolution, same service
|
|
16
|
+
* factory, same callback contract.
|
|
17
|
+
*/
|
|
18
|
+
export async function destroy(params) {
|
|
19
|
+
const startTime = Date.now();
|
|
20
|
+
const services = createDeployServices({
|
|
21
|
+
target: params.target,
|
|
22
|
+
workingDirectory: params.workingDirectory,
|
|
23
|
+
awsCredentials: params.awsCredentials,
|
|
24
|
+
callbacks: params.callbacks,
|
|
25
|
+
options: {
|
|
26
|
+
verbose: params.options?.verbose,
|
|
27
|
+
force: params.options?.force,
|
|
28
|
+
cascade: params.options?.cascade,
|
|
29
|
+
skipConfirmation: params.options?.skipConfirmation
|
|
30
|
+
},
|
|
31
|
+
orgConfig: params.orgConfig,
|
|
32
|
+
identity: params.identity
|
|
33
|
+
});
|
|
34
|
+
const opResult = await resolveOperation(params.target, params.workingDirectory);
|
|
35
|
+
if (!opResult.success) {
|
|
36
|
+
params.callbacks.onError?.(opResult.error);
|
|
37
|
+
return opResult;
|
|
38
|
+
}
|
|
39
|
+
const operation = opResult.data;
|
|
40
|
+
// Pre-flight: reject if any target stacks are mid-operation (unless --force)
|
|
41
|
+
if (!params.options?.force) {
|
|
42
|
+
const stackNames = isApplicationOperation(operation)
|
|
43
|
+
? Object.values(APPLICATION_STACKS).map((stack) => getApplicationStackName(operation.appName, stack))
|
|
44
|
+
: [getOrganisationStackName(operation.type)];
|
|
45
|
+
const guardResult = await checkActiveDeployments(stackNames, services.cfnService);
|
|
46
|
+
if (!guardResult.success) {
|
|
47
|
+
params.callbacks.onError?.(guardResult.error);
|
|
48
|
+
return failure(guardResult.error);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
if (isApplicationOperation(operation)) {
|
|
52
|
+
const result = await destroyApplication(params, services, operation);
|
|
53
|
+
if (result.success && result.data.durationMs === undefined) {
|
|
54
|
+
return success({
|
|
55
|
+
...result.data,
|
|
56
|
+
durationMs: Date.now() - startTime
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
return result;
|
|
60
|
+
}
|
|
61
|
+
if (isOrganisationOperation(operation)) {
|
|
62
|
+
return destroyOrganisation(params, services, operation);
|
|
63
|
+
}
|
|
64
|
+
const error = new Error(`Unsupported destroy target: ${params.target}`);
|
|
65
|
+
params.callbacks.onError?.(error);
|
|
66
|
+
return failure(error);
|
|
67
|
+
}
|
|
@@ -2,18 +2,9 @@ import { type Result } from "@fjall/generator";
|
|
|
2
2
|
import type { DeployCallbacks } from "../types/callbacks.js";
|
|
3
3
|
import type { DeploymentContext } from "../types/deployment/DeploymentTypes.js";
|
|
4
4
|
import type { ApplicationOperation } from "../types/operations.js";
|
|
5
|
-
import { type AppResourceFlags } from "../types/patternDetection.js";
|
|
6
|
-
import type { PatternType } from "@fjall/generator";
|
|
7
5
|
import type { DeployServices } from "./serviceFactory.js";
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
hasDatabase: boolean;
|
|
11
|
-
hasDifferences: boolean;
|
|
12
|
-
stackChanges: Map<string, boolean>;
|
|
13
|
-
currentHashes: Map<string, string>;
|
|
14
|
-
resources: AppResourceFlags;
|
|
15
|
-
hasDockerfile: boolean;
|
|
16
|
-
}
|
|
6
|
+
import type { DetectionResult } from "../types/detection.js";
|
|
7
|
+
export type { DetectionResult };
|
|
17
8
|
/**
|
|
18
9
|
* Pre-deployment analysis pipeline.
|
|
19
10
|
*
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import { join } from "path";
|
|
2
2
|
import { success, failure } from "@fjall/generator";
|
|
3
|
-
import { logger } from "@fjall/util";
|
|
4
|
-
import {
|
|
3
|
+
import { logger } from "@fjall/util/logger";
|
|
4
|
+
import { maskSensitiveOutput } from "@fjall/util";
|
|
5
|
+
import { hasDockerfile } from "../util/dockerfileDetection.js";
|
|
6
|
+
import { deriveResourcesFromManifestStacks } from "../types/patternDetection.js";
|
|
5
7
|
import { emitProgress, PROGRESS_MESSAGES } from "../services/supporting/helpers.js";
|
|
8
|
+
import { parseRequiredSecretsFromManifest } from "./manifestSecretParser.js";
|
|
6
9
|
/**
|
|
7
10
|
* Pre-deployment analysis pipeline.
|
|
8
11
|
*
|
|
@@ -11,28 +14,43 @@ import { emitProgress, PROGRESS_MESSAGES } from "../services/supporting/helpers.
|
|
|
11
14
|
*/
|
|
12
15
|
export async function runDetectionPipeline(operation, services, context, callbacks) {
|
|
13
16
|
// 1. Detect application pattern
|
|
14
|
-
|
|
17
|
+
callbacks.onDetectionPhaseChange?.("detect", "started");
|
|
18
|
+
const resolved = services.frameworkRegistry.resolve({
|
|
19
|
+
appPath: operation.path
|
|
20
|
+
});
|
|
21
|
+
const pattern = resolved?.detection.pattern ?? null;
|
|
22
|
+
const hasDatabase = resolved?.detection.hasDatabase ?? false;
|
|
15
23
|
callbacks.onLog?.(`Pattern detected: ${pattern ?? "standard"}`, "info");
|
|
24
|
+
callbacks.onDetectionPhaseChange?.("detect", "completed");
|
|
25
|
+
const cdkOutPath = join(operation.path, "cdk.out");
|
|
16
26
|
// 2. Synthesise infrastructure
|
|
27
|
+
callbacks.onDetectionPhaseChange?.("synth", "started");
|
|
17
28
|
emitProgress(callbacks, PROGRESS_MESSAGES.SYNTH);
|
|
18
29
|
const synthResult = await services.cdkService.runCdkSynth(context, (chunk) => callbacks.onCdkOutput?.(chunk, "synth"));
|
|
19
30
|
if (!synthResult.success) {
|
|
20
|
-
return failure(new Error(`CDK synthesis failed: ${synthResult.error}`));
|
|
31
|
+
return failure(new Error(maskSensitiveOutput(`CDK synthesis failed: ${synthResult.error}`)));
|
|
32
|
+
}
|
|
33
|
+
callbacks.onDetectionPhaseChange?.("synth", "completed");
|
|
34
|
+
// 2b. Parse required secrets from manifest (pure data, no AWS calls)
|
|
35
|
+
const requiredSecrets = parseRequiredSecretsFromManifest(cdkOutPath);
|
|
36
|
+
if (requiredSecrets.length > 0) {
|
|
37
|
+
callbacks.onLog?.(`Found ${requiredSecrets.length} required secret path(s) in manifest`, "info");
|
|
21
38
|
}
|
|
22
39
|
// 3. Compute template hashes
|
|
40
|
+
callbacks.onDetectionPhaseChange?.("hash", "started");
|
|
23
41
|
emitProgress(callbacks, PROGRESS_MESSAGES.HASH);
|
|
24
|
-
const cdkOutPath = join(operation.path, "cdk.out");
|
|
25
42
|
const hashResult = await services.hashService.getTemplateHashes(cdkOutPath);
|
|
26
43
|
if (!hashResult.success) {
|
|
27
|
-
return failure(new Error(`Template hash computation failed: ${hashResult.error.message}`));
|
|
44
|
+
return failure(new Error(maskSensitiveOutput(`Template hash computation failed: ${hashResult.error.message}`)));
|
|
28
45
|
}
|
|
29
46
|
const currentHashes = hashResult.data;
|
|
30
47
|
// 4. Compare with stored state
|
|
31
48
|
const comparisonResult = await services.hashService.compareWithState(currentHashes, operation.path);
|
|
32
49
|
if (!comparisonResult.success) {
|
|
33
|
-
return failure(new Error(`Hash comparison failed: ${comparisonResult.error.message}`));
|
|
50
|
+
return failure(new Error(maskSensitiveOutput(`Hash comparison failed: ${comparisonResult.error.message}`)));
|
|
34
51
|
}
|
|
35
52
|
const comparison = comparisonResult.data;
|
|
53
|
+
callbacks.onDetectionPhaseChange?.("hash", "completed");
|
|
36
54
|
// 5. Stale hash detection: unchanged templates whose stacks don't exist in CFN
|
|
37
55
|
const stackChanges = new Map(comparison.stackChanges);
|
|
38
56
|
for (const [stackName, hasChanged] of comparison.stackChanges) {
|
|
@@ -49,8 +67,8 @@ export async function runDetectionPipeline(operation, services, context, callbac
|
|
|
49
67
|
// 6. Derive resource flags from manifest stacks
|
|
50
68
|
const stackNames = Array.from(currentHashes.keys());
|
|
51
69
|
const resources = deriveResourcesFromManifestStacks(stackNames);
|
|
52
|
-
// 7. Detect Dockerfile (Compute stack
|
|
53
|
-
const
|
|
70
|
+
// 7. Detect Dockerfile on disk (Compute stack alone is not sufficient)
|
|
71
|
+
const dockerfilePresent = resources.hasCompute && hasDockerfile(operation.path);
|
|
54
72
|
const hasDifferences = Array.from(stackChanges.values()).some(Boolean);
|
|
55
73
|
callbacks.onLog?.(`Detection complete: ${comparison.changedCount} changed, ${comparison.unchangedCount} unchanged`, "info");
|
|
56
74
|
return success({
|
|
@@ -60,6 +78,7 @@ export async function runDetectionPipeline(operation, services, context, callbac
|
|
|
60
78
|
stackChanges,
|
|
61
79
|
currentHashes,
|
|
62
80
|
resources,
|
|
63
|
-
hasDockerfile
|
|
81
|
+
hasDockerfile: dockerfilePresent,
|
|
82
|
+
requiredSecrets
|
|
64
83
|
});
|
|
65
84
|
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type Result } from "@fjall/generator";
|
|
2
|
+
import type { DeployParams } from "../types/params.js";
|
|
3
|
+
import type { DeployCallbacks } from "../types/callbacks.js";
|
|
4
|
+
import type { ApplicationOperation } from "../types/operations.js";
|
|
5
|
+
import type { DeployServices } from "./serviceFactory.js";
|
|
6
|
+
/**
|
|
7
|
+
* Run Docker build and push before deploying the Compute stack.
|
|
8
|
+
* Initialises ECR if needed, then builds and pushes the image.
|
|
9
|
+
*/
|
|
10
|
+
export declare function runDockerBuild(params: DeployParams, services: DeployServices, operation: ApplicationOperation, callbacks: DeployCallbacks): Promise<Result<void>>;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { success, failure } from "@fjall/generator";
|
|
2
|
+
import { maskSensitiveOutput } from "@fjall/util";
|
|
3
|
+
import { STEP_IDS } from "../types/stepDefinitions.js";
|
|
4
|
+
const DOCKER_BUILD_STEP_NAME = "Building and pushing Docker image";
|
|
5
|
+
/**
|
|
6
|
+
* Run Docker build and push before deploying the Compute stack.
|
|
7
|
+
* Initialises ECR if needed, then builds and pushes the image.
|
|
8
|
+
*/
|
|
9
|
+
export async function runDockerBuild(params, services, operation, callbacks) {
|
|
10
|
+
const dockerProvider = params.dockerProvider;
|
|
11
|
+
if (!dockerProvider)
|
|
12
|
+
return success(undefined);
|
|
13
|
+
const accountId = services.awsProvider.getAccountId();
|
|
14
|
+
if (!accountId) {
|
|
15
|
+
callbacks.onLog?.("Skipping Docker build — account ID not available", "warn");
|
|
16
|
+
return success(undefined);
|
|
17
|
+
}
|
|
18
|
+
const region = services.awsProvider.getRegion();
|
|
19
|
+
callbacks.onStepStart?.(STEP_IDS.DOCKER_OPERATIONS, DOCKER_BUILD_STEP_NAME);
|
|
20
|
+
// Initialise ECR repository (non-fatal — CDK creates it if needed)
|
|
21
|
+
callbacks.onLog?.("Initialising ECR repository…", "info");
|
|
22
|
+
const ecrResult = await dockerProvider.initialiseECR({
|
|
23
|
+
appName: operation.appName,
|
|
24
|
+
region,
|
|
25
|
+
accountId
|
|
26
|
+
});
|
|
27
|
+
if (!ecrResult.success) {
|
|
28
|
+
callbacks.onLog?.(`ECR initialisation failed: ${ecrResult.error.message}`, "warn");
|
|
29
|
+
}
|
|
30
|
+
// Build and push Docker image
|
|
31
|
+
callbacks.onLog?.("Building and pushing Docker image…", "info");
|
|
32
|
+
const buildResult = await dockerProvider.buildAndPush({
|
|
33
|
+
appName: operation.appName,
|
|
34
|
+
appPath: operation.path,
|
|
35
|
+
region,
|
|
36
|
+
accountId
|
|
37
|
+
}, (message, percentage, layerId, status) => {
|
|
38
|
+
callbacks.onDockerProgress?.(maskSensitiveOutput(message), percentage, layerId, status);
|
|
39
|
+
});
|
|
40
|
+
if (!buildResult.success) {
|
|
41
|
+
callbacks.onStepComplete?.(STEP_IDS.DOCKER_OPERATIONS, DOCKER_BUILD_STEP_NAME, "error");
|
|
42
|
+
const error = new Error(maskSensitiveOutput(buildResult.error.message));
|
|
43
|
+
callbacks.onError?.(error);
|
|
44
|
+
return failure(error);
|
|
45
|
+
}
|
|
46
|
+
callbacks.onStepComplete?.(STEP_IDS.DOCKER_OPERATIONS, DOCKER_BUILD_STEP_NAME, "completed");
|
|
47
|
+
callbacks.onLog?.(`Docker image pushed: ${buildResult.data.imageUri}`, "info");
|
|
48
|
+
return success(undefined);
|
|
49
|
+
}
|
|
@@ -43,14 +43,16 @@ export interface TagImagesParams {
|
|
|
43
43
|
export interface TagImagesResult {
|
|
44
44
|
taggedServices: string[];
|
|
45
45
|
}
|
|
46
|
+
/** Callback for Docker build/push progress reporting. */
|
|
47
|
+
export type DockerProgressCallback = (message: string, percentage?: number, layerId?: string, status?: string) => void;
|
|
46
48
|
/**
|
|
47
49
|
* Interface for Docker operations (build, push, ECR initialisation).
|
|
48
50
|
* CLI provides CliDockerProvider (wraps dockerode-based services).
|
|
49
51
|
* Worker passes undefined (Docker ops skipped until container deployments enabled).
|
|
50
52
|
*/
|
|
51
53
|
export interface DockerProvider {
|
|
52
|
-
buildAndPush(params: DockerBuildParams): Promise<Result<DockerBuildResult>>;
|
|
54
|
+
buildAndPush(params: DockerBuildParams, onProgress?: DockerProgressCallback): Promise<Result<DockerBuildResult>>;
|
|
53
55
|
initialiseECR(params: ECRInitParams): Promise<Result<ECRInitResult>>;
|
|
54
56
|
/** Tag existing ECR images for ECS services (non-Dockerfile apps). Optional. */
|
|
55
|
-
tagImages?(params: TagImagesParams): Promise<Result<TagImagesResult>>;
|
|
57
|
+
tagImages?(params: TagImagesParams, onProgress?: DockerProgressCallback): Promise<Result<TagImagesResult>>;
|
|
56
58
|
}
|
|
@@ -1,8 +1,15 @@
|
|
|
1
1
|
export { deploy } from "./deploy.js";
|
|
2
|
+
export { destroy } from "./destroy.js";
|
|
2
3
|
export { deployOrganisation } from "./organisationDeploy.js";
|
|
3
|
-
export
|
|
4
|
+
export { destroyOrganisation } from "./organisationDestroy.js";
|
|
5
|
+
export { cleanupFailedStack, isCleanableState, SAFE_CLEANUP_STATES } from "./stackCleanup.js";
|
|
6
|
+
export type { CascadeDestroyAccountResult } from "./cascadeDestroyHelpers.js";
|
|
7
|
+
export { partitionAccounts } from "./cascadeHelpers.js";
|
|
8
|
+
export type { DockerProvider, DockerProgressCallback, DockerServiceConfig, DockerBuildParams, DockerBuildResult, ECRInitParams, ECRInitResult, TagImagesParams, TagImagesResult } from "./dockerInterface.js";
|
|
4
9
|
export type { DomainDeployProvider, DomainConfig, DomainDeployResult } from "./domainInterface.js";
|
|
5
10
|
export type { DeployServices } from "./serviceFactory.js";
|
|
6
11
|
export type { DetectionResult } from "./detectionPipeline.js";
|
|
12
|
+
export { runOpenNextBuild } from "./openNextBuild.js";
|
|
7
13
|
export { runOrganisationSetup } from "./organisationSetup.js";
|
|
8
14
|
export type { OrgSetupPhase, OrgSetupCallbacks, OrgSetupConfig, OrgSetupResult } from "./organisationSetup.js";
|
|
15
|
+
export * from "./builders/index.js";
|
|
@@ -1,3 +1 @@
|
|
|
1
|
-
|
|
2
|
-
export { deployOrganisation } from "./organisationDeploy.js";
|
|
3
|
-
export { runOrganisationSetup } from "./organisationSetup.js";
|
|
1
|
+
import{deploy as t}from"./deploy.js";import{destroy as p}from"./destroy.js";import{deployOrganisation as n}from"./organisationDeploy.js";import{destroyOrganisation as x}from"./organisationDestroy.js";import{cleanupFailedStack as m,isCleanableState as l,SAFE_CLEANUP_STATES as s}from"./stackCleanup.js";import{partitionAccounts as u}from"./cascadeHelpers.js";import{runOpenNextBuild as c}from"./openNextBuild.js";import{runOrganisationSetup as A}from"./organisationSetup.js";export*from"./builders/index.js";export{s as SAFE_CLEANUP_STATES,m as cleanupFailedStack,t as deploy,n as deployOrganisation,p as destroy,x as destroyOrganisation,l as isCleanableState,u as partitionAccounts,c as runOpenNextBuild,A as runOrganisationSetup};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Extract required SSM secret paths from the Fjall manifest.
|
|
3
|
+
*
|
|
4
|
+
* Reads `fjall-manifest.json` from the given cdk.out directory and
|
|
5
|
+
* collects all `ssmSecretsPath/secretName` entries from ECS services
|
|
6
|
+
* and Lambda functions.
|
|
7
|
+
*
|
|
8
|
+
* Returns an empty array if the manifest does not exist, cannot be
|
|
9
|
+
* parsed, or contains no secrets. Never throws.
|
|
10
|
+
*/
|
|
11
|
+
export declare function parseRequiredSecretsFromManifest(cdkOutPath: string): string[];
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{readFileSync as m}from"fs";import{join as y}from"path";import{FJALL_MANIFEST_FILENAME as p}from"@fjall/util/constructMap";import{logger as f}from"@fjall/util/logger";function a(t){return typeof t=="object"&&t!==null&&!Array.isArray(t)}function l(t){const n=y(t,p);let i;try{i=m(n,"utf-8")}catch{return f.debug("manifestSecretParser","Manifest file not readable \u2014 no secrets extracted",{path:n}),[]}let r;try{r=JSON.parse(i)}catch{return f.debug("manifestSecretParser","Manifest is not valid JSON \u2014 no secrets extracted",{path:n}),[]}if(!a(r))return[];const o=[];if(Array.isArray(r.services))for(const s of r.services){if(!a(s))continue;const e=s;if(!(!Array.isArray(e.secrets)||e.secrets.length===0||typeof e.ssmSecretsPath!="string"))for(const c of e.secrets)typeof c=="string"&&o.push(`${e.ssmSecretsPath}/${c}`)}if(Array.isArray(r.lambdas))for(const s of r.lambdas){if(!a(s))continue;const e=s;if(!(!Array.isArray(e.secrets)||e.secrets.length===0||typeof e.ssmSecretsPath!="string"))for(const c of e.secrets)typeof c=="string"&&o.push(`${e.ssmSecretsPath}/${c}`)}return o}export{l as parseRequiredSecretsFromManifest};
|