@fjall/deploy-core 8.0.0 → 9.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/.build-source-hash +8 -7
- package/dist/.minified +1 -1
- package/dist/src/orchestration/stackCleanup/insightsLogGroups.d.ts +48 -0
- package/dist/src/orchestration/stackCleanup/insightsLogGroups.js +1 -0
- package/dist/src/orchestration/stackCleanup.d.ts +1 -0
- package/dist/src/orchestration/stackCleanup.js +1 -1
- package/dist/src/services/application/ApplicationStackService.js +1 -1
- package/package.json +5 -4
package/dist/.build-source-hash
CHANGED
|
@@ -9,12 +9,12 @@
|
|
|
9
9
|
"package.json",
|
|
10
10
|
"tsconfig.json"
|
|
11
11
|
],
|
|
12
|
-
"hash": "
|
|
12
|
+
"hash": "9ef363451ba87e24afa6c25c9d5e341a83cf68239e9d3bc477697697b42e0dfc",
|
|
13
13
|
"files": {
|
|
14
14
|
"src/__test-utils__/awsMockHelpers.ts": "fea371c1b1869678",
|
|
15
15
|
"src/__test-utils__/index.ts": "e03cf35821157dc4",
|
|
16
16
|
"src/__test-utils__/lambdaManifestFixture.ts": "fcbe79471c6bc91a",
|
|
17
|
-
"src/__test-utils__/stackCleanupMock.ts": "
|
|
17
|
+
"src/__test-utils__/stackCleanupMock.ts": "279ae989432734b5",
|
|
18
18
|
"src/aws/AwsProvider.ts": "8bbe053f15dfc046",
|
|
19
19
|
"src/aws/SimpleAwsProvider.ts": "41e5a4dee4736249",
|
|
20
20
|
"src/aws/cloudtrail/orgTrailDelivery.ts": "d743cdeb12b335fe",
|
|
@@ -157,9 +157,10 @@
|
|
|
157
157
|
"src/orchestration/secretArnResolver.ts": "20041aca6eb7e762",
|
|
158
158
|
"src/orchestration/serviceFactory.ts": "ec35c9eb8015d540",
|
|
159
159
|
"src/orchestration/spawnHelpers.ts": "cbad657ae7039a86",
|
|
160
|
-
"src/orchestration/stackCleanup.ts": "
|
|
160
|
+
"src/orchestration/stackCleanup.ts": "26dfef4375530902",
|
|
161
161
|
"src/orchestration/stackCleanup/bucketOps.ts": "59a3c8349e766947",
|
|
162
162
|
"src/orchestration/stackCleanup/failedStack.ts": "48b700639d8001c7",
|
|
163
|
+
"src/orchestration/stackCleanup/insightsLogGroups.ts": "6bd85a61d1e9f1ec",
|
|
163
164
|
"src/orchestration/stackCleanup/logging.ts": "dbc9e25542320848",
|
|
164
165
|
"src/orchestration/stackCleanup/messages.ts": "959252d06b1a73a5",
|
|
165
166
|
"src/orchestration/stackCleanup/orphanSweep.ts": "c35d8fe48f564df8",
|
|
@@ -175,7 +176,7 @@
|
|
|
175
176
|
"src/orchestration/unlock/toResourcePolicyStatements.ts": "ddd15625c785a48d",
|
|
176
177
|
"src/orchestration/unlock/unlockBucket.ts": "d08d7cb6243d7cec",
|
|
177
178
|
"src/orchestration/unlock/unlockQueue.ts": "f7e8eccc258d763d",
|
|
178
|
-
"src/services/application/ApplicationStackService.ts": "
|
|
179
|
+
"src/services/application/ApplicationStackService.ts": "45f697f7f884af0a",
|
|
179
180
|
"src/services/application/applicationStackHelpers.ts": "04e1758b97ffc5fc",
|
|
180
181
|
"src/services/application/index.ts": "ee810be803dfda9c",
|
|
181
182
|
"src/services/index.ts": "8c441c1b37540650",
|
|
@@ -257,9 +258,9 @@
|
|
|
257
258
|
"src/util/sleepAbortable.ts": "f9cac3bf8a79a476",
|
|
258
259
|
"src/version.ts": "1811a4475823b27e",
|
|
259
260
|
"../scripts/minify-dist.mjs": "6b2e4b0df8aec601",
|
|
260
|
-
"package.json": "
|
|
261
|
+
"package.json": "0efc528123b80c01",
|
|
261
262
|
"tsconfig.json": "1929cf3f99fe9b1e"
|
|
262
263
|
},
|
|
263
|
-
"outputHash": "
|
|
264
|
-
"outputFileCount":
|
|
264
|
+
"outputHash": "a9b602f42c12a7b007801069edd7eadb6ae0649560861bc7a53e9b29e014b168",
|
|
265
|
+
"outputFileCount": 485
|
|
265
266
|
}
|
package/dist/.minified
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
242 files minified at 2026-08-09T10:17:15.054Z
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { CloudFormationClient } from "@aws-sdk/client-cloudformation";
|
|
2
|
+
import { type CloudWatchLogsClient } from "@aws-sdk/client-cloudwatch-logs";
|
|
3
|
+
import type { DeployCallbacks } from "../../types/callbacks.js";
|
|
4
|
+
/**
|
|
5
|
+
* Container Insights mints `/aws/ecs/containerinsights/<cluster>/performance`
|
|
6
|
+
* itself. No CDK or CloudFormation property owns it, so CFN cannot delete what
|
|
7
|
+
* it never created and stack deletion always strands it. Every Fjall ECS
|
|
8
|
+
* cluster enables Container Insights v2 unconditionally (`addCluster` in
|
|
9
|
+
* `resources/aws/compute/ecs.ts`), so every destroyed app leaks exactly one
|
|
10
|
+
* group per cluster.
|
|
11
|
+
*
|
|
12
|
+
* Shared with the deploy-test residue oracle, which matches the same shape
|
|
13
|
+
* (`deploy-tests/lib/residueOracle.mts`) — the two must agree or the pipeline
|
|
14
|
+
* flags residue this reap already removed.
|
|
15
|
+
*/
|
|
16
|
+
export declare function insightsLogGroupName(clusterName: string): string;
|
|
17
|
+
/**
|
|
18
|
+
* Record the Container Insights log groups the stack's ECS clusters will
|
|
19
|
+
* strand, so {@link reapInsightsLogGroups} can delete them once the stack is
|
|
20
|
+
* gone.
|
|
21
|
+
*
|
|
22
|
+
* Call BEFORE the destroy — after CFN reports the stack deleted its resource
|
|
23
|
+
* list is unreadable, and this captured list is the only record of which
|
|
24
|
+
* groups the reap may touch. Deriving the names from clusters we are about to
|
|
25
|
+
* delete is what keeps the reap's deletion predicate closed: it can never name
|
|
26
|
+
* a group belonging to a live cluster, an unrelated app, or a shared
|
|
27
|
+
* AWS-managed group such as `RDSOSMetrics`.
|
|
28
|
+
*
|
|
29
|
+
* Returns [] on any read failure — no capture means no deletion, so a
|
|
30
|
+
* degraded read leaves today's leak rather than widening the blast radius.
|
|
31
|
+
*/
|
|
32
|
+
export declare function captureInsightsLogGroups(cfnClient: CloudFormationClient, stackName: string, abortSignal?: AbortSignal): Promise<readonly string[]>;
|
|
33
|
+
export interface InsightsReapSummary {
|
|
34
|
+
/** Groups deleted by this pass. */
|
|
35
|
+
deleted: string[];
|
|
36
|
+
/** Groups a delete failed for; each warned via callbacks. */
|
|
37
|
+
failed: string[];
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Delete the Container Insights log groups captured before the destroy.
|
|
41
|
+
*
|
|
42
|
+
* Run only after CFN reports the stack deleted. Per-group failures warn and
|
|
43
|
+
* continue — a stranded log group is a cost and tidiness problem, never a
|
|
44
|
+
* reason to fail a destroy that already succeeded. A group that does not exist
|
|
45
|
+
* is the ordinary case (a cluster that never ran a task never gets one) and is
|
|
46
|
+
* neither a deletion nor a failure.
|
|
47
|
+
*/
|
|
48
|
+
export declare function reapInsightsLogGroups(logsClient: CloudWatchLogsClient, logGroupNames: readonly string[], callbacks?: DeployCallbacks, abortSignal?: AbortSignal): Promise<InsightsReapSummary>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
var l=Object.defineProperty;var i=(r,e)=>l(r,"name",{value:e,configurable:!0});import{DeleteLogGroupCommand as g}from"@aws-sdk/client-cloudwatch-logs";import{logger as c}from"@fjall/util/logger";import{getErrorMessage as d,maskSensitiveOutput as p}from"@fjall/util";import{composeSdkAbortSignal as f,extractErrorName as m,isAborted as h}from"../../aws/organisations/types.js";import{STACK_NOT_FOUND_PATTERN as C}from"../../types/constants.js";import{STACK_CLEANUP_LOG as a,warnViaCallbacks as R}from"./logging.js";import{collectStackResources as E}from"./stackResources.js";const N=["ResourceNotFoundException","ResourceNotFound"];function S(r){return`/aws/ecs/containerinsights/${r}/performance`}i(S,"insightsLogGroupName");function G(r){const e=r.startsWith("arn:")?r.split("/").pop():r;return e!==void 0&&e!==""?e:void 0}i(G,"clusterNameFromPhysicalId");async function T(r,e,u){try{const n=await E(r,e,o=>o.ResourceType==="AWS::ECS::Cluster",o=>o.PhysicalResourceId,u),t=[];for(const o of n){const s=G(o);s!==void 0&&t.push(S(s))}return t}catch(n){return n instanceof Error&&n.message?.includes(C)?(c.debug(a,`Stack ${e} does not exist, no Container Insights groups to capture`),[]):(c.debug(a,`Could not list ${e} resources for the Container Insights capture`,{stackName:e,error:p(d(n))}),[])}}i(T,"captureInsightsLogGroups");async function $(r,e,u,n){const t={deleted:[],failed:[]};for(const o of e){if(h(n))break;try{await r.send(new g({logGroupName:o}),{abortSignal:f(n)}),t.deleted.push(o)}catch(s){if(N.includes(m(s)))continue;t.failed.push(o),R(`Could not delete Container Insights log group ${o}: ${p(d(s))}`,u)}}return t.deleted.length>0&&c.debug(a,`Reaped ${t.deleted.length} Container Insights log group(s)`,{deleted:t.deleted}),t}i($,"reapInsightsLogGroups");export{T as captureInsightsLogGroups,S as insightsLogGroupName,$ as reapInsightsLogGroups};
|
|
@@ -13,4 +13,5 @@ export { BUCKET_GONE_ERROR_NAMES, PRE_EMPTY_TAG_KEYS, emptyS3Bucket, isBucketGon
|
|
|
13
13
|
export { formatQuarantineSuspectedMessage, formatRetainedBucketsMessage } from "./stackCleanup/messages.js";
|
|
14
14
|
export { capturedSweepBuckets, preEmptyStackBuckets, type PreEmptyBucketsSummary } from "./stackCleanup/preEmpty.js";
|
|
15
15
|
export { cleanupFailedStack } from "./stackCleanup/failedStack.js";
|
|
16
|
+
export { captureInsightsLogGroups, insightsLogGroupName, reapInsightsLogGroups, type InsightsReapSummary } from "./stackCleanup/insightsLogGroups.js";
|
|
16
17
|
export { sweepOrphanedDestroyBuckets, type OrphanSweepSummary } from "./stackCleanup/orphanSweep.js";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{SAFE_CLEANUP_STATES as r,isCleanableState as
|
|
1
|
+
import{SAFE_CLEANUP_STATES as r,isCleanableState as o}from"../types/constants.js";import{isQuarantineDetail as s,isRetainedBucketsDetail as p}from"../types/callbacks.js";import{BUCKET_GONE_ERROR_NAMES as i,PRE_EMPTY_TAG_KEYS as c,emptyS3Bucket as m,isBucketGoneError as n}from"./stackCleanup/bucketOps.js";import{formatQuarantineSuspectedMessage as S,formatRetainedBucketsMessage as f}from"./stackCleanup/messages.js";import{capturedSweepBuckets as g,preEmptyStackBuckets as x}from"./stackCleanup/preEmpty.js";import{cleanupFailedStack as _}from"./stackCleanup/failedStack.js";import{captureInsightsLogGroups as l,insightsLogGroupName as G,reapInsightsLogGroups as R}from"./stackCleanup/insightsLogGroups.js";import{sweepOrphanedDestroyBuckets as T}from"./stackCleanup/orphanSweep.js";export{i as BUCKET_GONE_ERROR_NAMES,c as PRE_EMPTY_TAG_KEYS,r as SAFE_CLEANUP_STATES,l as captureInsightsLogGroups,g as capturedSweepBuckets,_ as cleanupFailedStack,m as emptyS3Bucket,S as formatQuarantineSuspectedMessage,f as formatRetainedBucketsMessage,G as insightsLogGroupName,n as isBucketGoneError,o as isCleanableState,s as isQuarantineDetail,p as isRetainedBucketsDetail,x as preEmptyStackBuckets,R as reapInsightsLogGroups,T as sweepOrphanedDestroyBuckets};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
var N=Object.defineProperty;var P=(
|
|
1
|
+
var N=Object.defineProperty;var P=(v,e)=>N(v,"name",{value:e,configurable:!0});import{CloudFormationClient as O}from"@aws-sdk/client-cloudformation";import{CloudWatchLogsClient as F}from"@aws-sdk/client-cloudwatch-logs";import{S3Client as A}from"@aws-sdk/client-s3";import{captureInsightsLogGroups as _,capturedSweepBuckets as I,preEmptyStackBuckets as B,reapInsightsLogGroups as E,sweepOrphanedDestroyBuckets as $}from"../../orchestration/stackCleanup.js";import{STACK_NOT_FOUND_PATTERN as K,CDK_NO_STACKS_MATCH as M}from"../../types/constants.js";import{assertLeaseBeforeStack as W}from"../../orchestration/application/leaseGate.js";import{getApplicationStackName as w}from"../../types/operations.js";import{success as y,failure as h}from"@fjall/generator";import{ApplicationError as C}from"../../types/application/ApplicationServiceTypes.js";import{logger as S}from"@fjall/util/logger";import{maskSensitiveOutput as R}from"@fjall/util";import{convertCloudFormationOutputsToRecord as D}from"../supporting/helpers.js";import{destroyAllStacks as G,mapSettledResults as L,resolveWebsiteUrl as U}from"./applicationStackHelpers.js";class te{static{P(this,"ApplicationStackService")}cdkService;cloudFormationService;aws;constructor(e,o,t){this.cdkService=e,this.cloudFormationService=o,this.aws=t}async runCdkSynth(e){return this.cdkService.runCdkSynth(e)}async deployStack(e,o,t,a){const n=o.target,u=w(n,e),c=await this.cdkService.runCdkDeploy(o,u,t?.onOutput,t?.onResourceProgress,this.aws,void 0,a?.parameters,a?.exclusively);if(c.success){const r=c.data;S.debug("ApplicationStackService","CDK deploy result",{stackName:u,stackType:e,success:!0,message:r.message,status:r.status,skipped:r.details?.skipped,hasOutput:!!r.details?.output});const i=await this.cloudFormationService.getStackOutputs(u,void 0,a?.abortSignal);S.debug("ApplicationStackService","Stack outputs after deploy",{stackName:u,hasOutputs:i.success&&i.data.length>0,outputCount:i.success?i.data.length:0});const s=i.success&&i.data.length>0?D(i.data):void 0;return y({stackType:e,stackName:u,skipped:r.details?.skipped===!0,outputs:s})}else{const r=R(c.error||`Failed to deploy ${e} infrastructure`);return S.error("ApplicationStackService","Stack deployment failed",{stackType:e,target:o.target,error:r}),h(new C(r,{errorType:"deployment_failed",appName:o.target,operation:"deployStack",stackType:e}))}}async deployStacksInParallel(e,o,t){const a=t?.onOutput,n=t?.onResourceProgress,u=t?.onStackComplete,c=await Promise.allSettled(e.map(async r=>{const i=Date.now(),s=await this.deployStack(r,o,{onOutput:a?l=>a(l,r):void 0,onResourceProgress:n?l=>n(l,r):void 0},{exclusively:!0}),p=Date.now()-i,d={stack:r,success:s.success,error:s.success?void 0:s.error,duration:p,...s.success&&s.data.outputs!==void 0?{outputs:s.data.outputs}:{}};return!s.success&&s.error&&S.debug("deployStacksInParallel",`Stack ${r} failed`,{error:s.error.message}),u?.(r,s.success,p,s.success?void 0:s.error),d}));return y(L(c,e))}async destroyStack(e,o,t,a,n,u){const c=o.target,r=w(c,e);let i=[],s=[],p=a===!0;this.aws!==void 0&&!p&&(p=(await this.cdkService.runCdkSynth(o)).success);const d=await W(u,r,n,"destroy");if(d!==void 0)return h(d);if(this.aws!==void 0)if(s=await _(this.aws.getClient(O),r,n),p){const g=await B(this.aws.getClient(O),this.aws.getClient(A),r,{onLog:t?.onLog,onStackCleanupProgress:t?.onStackCleanupProgress},n);i=I(g)}else S.debug("ApplicationStackService","Skipping pre-empty pass after failed validation synth \u2014 the destroy will surface the error",{stackName:r});else S.debug("ApplicationStackService","No AWS provider available, skipping pre-empty pass",{stackName:r});const l=a===!0||p&&o.assemblyDir===void 0,f=await this.cdkService.runCdkDestroy(o,r,t?.onOutput,t?.onResourceProgress,this.aws,l);if(f.success)return this.aws!==void 0&&i.length>0&&await $(this.aws.getClient(A),i,{onLog:t?.onLog,onStackCleanupProgress:t?.onStackCleanupProgress},n),this.aws!==void 0&&s.length>0&&await E(this.aws.getClient(F),s,{onLog:t?.onLog},n),y({stackType:e,stackName:r,skipped:!1,outputs:void 0});{const g=R(f.error||`Failed to destroy ${e} stack`);return S.error("ApplicationStackService","Stack destroy failed",{stackType:e,target:o.target,error:g}),h(new C(g,{errorType:"destroy_failed",appName:o.target,operation:"destroyStack",stackType:e}))}}async destroyStacksInParallel(e,o,t,a,n){const u=t?.onOutput,c=t?.onResourceProgress,r=t?.onStackComplete,i=await Promise.allSettled(e.map(async s=>{const p=Date.now(),d=await this.destroyStack(s,o,{onOutput:u?k=>u(k,s):void 0,onResourceProgress:c?k=>c(k,s):void 0,onLog:t?.onLog,onStackCleanupProgress:t?.onStackCleanupProgress},a,n),l=Date.now()-p,f=d.success?"":d.error?.message??"",g=f.includes(K)||f.includes(M),m={stack:s,success:d.success||g,error:d.success||g?void 0:d.error,duration:l};return!m.success&&m.error&&S.debug("destroyStacksInParallel",`Stack ${s} failed`,{error:m.error.message}),r?.(s,m.success,l,m.success?void 0:m.error),m}));return y(L(i,e))}async getStackOutputs(e,o,t){const a=w(e,o),n=await this.cloudFormationService.getStackOutputs(a,void 0,t);return n.success?y(D(n.data)):h(new C(`Failed to get outputs for ${a}`,{errorType:"stack_error",appName:e,operation:"getStackOutputs",stackType:o,details:n.error}))}async resolveWebsiteUrl(e){return U(e,this.cloudFormationService)}async destroyAllStacks(e,o,t,a,n){return G(this,e,o,t,a,n)}}export{te as ApplicationStackService};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fjall/deploy-core",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "9.0.0",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "git+https://github.com/fjall-tech/fjall.git",
|
|
@@ -77,6 +77,7 @@
|
|
|
77
77
|
"@aws-sdk/client-backup": "^3.1098.0",
|
|
78
78
|
"@aws-sdk/client-cloudformation": "^3.1098.0",
|
|
79
79
|
"@aws-sdk/client-cloudtrail": "^3.1098.0",
|
|
80
|
+
"@aws-sdk/client-cloudwatch-logs": "^3.1098.0",
|
|
80
81
|
"@aws-sdk/client-cost-explorer": "^3.1098.0",
|
|
81
82
|
"@aws-sdk/client-dynamodb": "^3.1098.0",
|
|
82
83
|
"@aws-sdk/client-ec2": "^3.1098.0",
|
|
@@ -95,15 +96,15 @@
|
|
|
95
96
|
"@aws-sdk/client-ssm": "^3.1098.0",
|
|
96
97
|
"@aws-sdk/client-sso-admin": "^3.1098.0",
|
|
97
98
|
"@aws-sdk/client-sts": "^3.1098.0",
|
|
98
|
-
"@fjall/generator": "^
|
|
99
|
-
"@fjall/util": "^
|
|
99
|
+
"@fjall/generator": "^9.0.0",
|
|
100
|
+
"@fjall/util": "^9.0.0",
|
|
100
101
|
"@smithy/node-http-handler": "^4.9.13",
|
|
101
102
|
"aws-cdk": "^2.1134.0",
|
|
102
103
|
"tsx": "^4.23.1",
|
|
103
104
|
"zod": "^4.4.3"
|
|
104
105
|
},
|
|
105
106
|
"peerDependencies": {
|
|
106
|
-
"@fjall/components-infrastructure": ">=
|
|
107
|
+
"@fjall/components-infrastructure": ">=9.0.0 <10.0.0"
|
|
107
108
|
},
|
|
108
109
|
"peerDependenciesMeta": {
|
|
109
110
|
"@fjall/components-infrastructure": {
|