@fjall/deploy-core 2.24.0 → 2.27.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.
Files changed (47) hide show
  1. package/dist/.minified +1 -1
  2. package/dist/src/aws/index.d.ts +1 -1
  3. package/dist/src/aws/index.js +1 -1
  4. package/dist/src/aws/organisations/index.d.ts +1 -1
  5. package/dist/src/aws/organisations/index.js +1 -1
  6. package/dist/src/aws/organisations/organisationalUnits.d.ts +15 -0
  7. package/dist/src/aws/organisations/organisationalUnits.js +1 -1
  8. package/dist/src/index.d.ts +4 -2
  9. package/dist/src/index.js +1 -1
  10. package/dist/src/orchestration/application/applicationDeploy.js +1 -1
  11. package/dist/src/orchestration/application/codeOnlyDeploy.d.ts +6 -0
  12. package/dist/src/orchestration/application/codeOnlyDeploy.js +1 -1
  13. package/dist/src/orchestration/application/databaseEndpointReconcile.d.ts +77 -0
  14. package/dist/src/orchestration/application/databaseEndpointReconcile.js +1 -0
  15. package/dist/src/orchestration/application/deploySessionPolicy.d.ts +9 -1
  16. package/dist/src/orchestration/application/deploySessionPolicy.js +1 -1
  17. package/dist/src/orchestration/application/dockerBuildHelper.js +1 -1
  18. package/dist/src/orchestration/application/ecrCacheRepository.d.ts +14 -0
  19. package/dist/src/orchestration/application/ecrCacheRepository.js +1 -0
  20. package/dist/src/orchestration/application/serviceImageTagsRollback.d.ts +56 -0
  21. package/dist/src/orchestration/application/serviceImageTagsRollback.js +1 -0
  22. package/dist/src/orchestration/application/taskDefinitionRoll.d.ts +33 -0
  23. package/dist/src/orchestration/application/taskDefinitionRoll.js +1 -0
  24. package/dist/src/orchestration/index.d.ts +4 -0
  25. package/dist/src/orchestration/index.js +1 -1
  26. package/dist/src/orchestration/organisationDeploy/orgCascadeDeploy.js +3 -3
  27. package/dist/src/orchestration/organisationDeploy/orgContext.d.ts +7 -0
  28. package/dist/src/orchestration/organisationDeploy/orgContext.js +1 -1
  29. package/dist/src/orchestration/restart/restartApplication.d.ts +69 -0
  30. package/dist/src/orchestration/restart/restartApplication.js +1 -0
  31. package/dist/src/orchestration/restart/secretsDrift.d.ts +54 -0
  32. package/dist/src/orchestration/restart/secretsDrift.js +1 -0
  33. package/dist/src/services/infrastructure/CdkArgumentBuilder.js +1 -1
  34. package/dist/src/services/infrastructure/CdkServiceTypes.d.ts +7 -0
  35. package/dist/src/services/infrastructure/CloudFormationService.d.ts +17 -0
  36. package/dist/src/services/infrastructure/CloudFormationService.js +1 -1
  37. package/dist/src/services/infrastructure/EcrImageInspectorService.d.ts +11 -1
  38. package/dist/src/services/infrastructure/EcrImageInspectorService.js +1 -1
  39. package/dist/src/services/infrastructure/SsmParameterMetadataService.d.ts +23 -0
  40. package/dist/src/services/infrastructure/SsmParameterMetadataService.js +1 -0
  41. package/dist/src/services/infrastructure/cdkServiceHelpers.js +1 -1
  42. package/dist/src/services/supporting/CdkContextBuilder.d.ts +1 -0
  43. package/dist/src/services/supporting/CdkContextBuilder.js +1 -1
  44. package/dist/src/types/deployment/DeploymentTypes.d.ts +1 -0
  45. package/dist/src/types/index.d.ts +1 -1
  46. package/dist/src/types/params.d.ts +27 -0
  47. package/package.json +4 -4
package/dist/.minified CHANGED
@@ -1 +1 @@
1
- 178 files minified at 2026-07-05T07:50:53.066Z
1
+ 185 files minified at 2026-07-07T01:04:28.789Z
@@ -6,7 +6,7 @@ export { buildTargetSetUnreadyAdvisory } from "./targetSetAdvisory.js";
6
6
  export type { TargetSetUnreadyAdvisoryParams } from "./targetSetAdvisory.js";
7
7
  export { checkRegionEnabled, regionDisabledMessage } from "./regions/regionOptStatus.js";
8
8
  export type { RegionAvailability } from "./regions/regionOptStatus.js";
9
- export { ensureOrganisationExists, describeOrganisation, enablePolicyTypes, enableServiceAccess, SERVICE_PRINCIPALS, enableRamSharing, activateTrustedAccess, enableIpamDelegatedAdmin, updateBackupGlobalSettings, listAccounts, findAccount, createAccount, ensureOrganisationalUnitsExist, placeAccountsInOUs, buildAccountToOUMap, activateCostAllocationTags, checkIdentityCentreStatus, snapshotIdentityCentre, composeSdkAbortSignal, extractErrorName, isAborted, isOULeaf, DEFAULT_MAX_PAGES, IDENTITY_STORE_PAGE_SIZE, drainPages, registerSecurityDelegates, SECURITY_SERVICE_PRINCIPALS, enableCentralisedRootAccess, ROOT_ACCESS_FEATURES, RootAccessEnablementError } from "./organisations/index.js";
9
+ export { ensureOrganisationExists, describeOrganisation, enablePolicyTypes, enableServiceAccess, SERVICE_PRINCIPALS, enableRamSharing, activateTrustedAccess, enableIpamDelegatedAdmin, updateBackupGlobalSettings, listAccounts, findAccount, createAccount, ensureOrganisationalUnitsExist, placeAccountsInOUs, buildAccountToOUMap, findDevelopmentOuId, activateCostAllocationTags, checkIdentityCentreStatus, snapshotIdentityCentre, composeSdkAbortSignal, extractErrorName, isAborted, isOULeaf, DEFAULT_MAX_PAGES, IDENTITY_STORE_PAGE_SIZE, drainPages, registerSecurityDelegates, SECURITY_SERVICE_PRINCIPALS, enableCentralisedRootAccess, ROOT_ACCESS_FEATURES, RootAccessEnablementError } from "./organisations/index.js";
10
10
  export type { OrgDetails, OUMap, OUTree, AccountPlacementResult, AccountInfo, DrainPagesOptions, PagedResult, IdentityCentreStatus, IdentityCentreSnapshot, IdentityCentreSnapshotGroup, IdentityCentreSnapshotOptions, IdentityCentreSnapshotResult, IdentityCentreSnapshotUser, BackupGlobalSettings, CostAllocationTag, CreateAccountResult, RootAccessFeature, RootAccessEnablementSummary } from "./organisations/index.js";
11
11
  export { CloudFormationEventMonitor } from "./utils/cloudformationEvents.js";
12
12
  export type { AwsClientLike, EventLogWriter, EventFailureAnalyser, EventLogWriterFactory, EventMonitorDeps } from "./utils/cloudformationEvents.js";
@@ -1 +1 @@
1
- import{SimpleAwsProvider as a}from"./SimpleAwsProvider.js";import{checkTargetReadiness as s,describeTargetReadinessReason as o,buildTargetUnreadyAdvisory as n,probeAccountStackExists as i}from"./targetReadiness.js";import{buildTargetSetUnreadyAdvisory as d}from"./targetSetAdvisory.js";import{checkRegionEnabled as A,regionDisabledMessage as E}from"./regions/regionOptStatus.js";import{ensureOrganisationExists as g,describeOrganisation as b,enablePolicyTypes as u,enableServiceAccess as p,SERVICE_PRINCIPALS as R,enableRamSharing as T,activateTrustedAccess as m,enableIpamDelegatedAdmin as I,updateBackupGlobalSettings as C,listAccounts as x,findAccount as _,createAccount as y,ensureOrganisationalUnitsExist as O,placeAccountsInOUs as P,buildAccountToOUMap as U,activateCostAllocationTags as f,checkIdentityCentreStatus as v,snapshotIdentityCentre as k,composeSdkAbortSignal as h,extractErrorName as D,isAborted as L,isOULeaf as M,DEFAULT_MAX_PAGES as N,IDENTITY_STORE_PAGE_SIZE as F,drainPages as G,registerSecurityDelegates as V,SECURITY_SERVICE_PRINCIPALS as Y,enableCentralisedRootAccess as w,ROOT_ACCESS_FEATURES as B,RootAccessEnablementError as X}from"./organisations/index.js";import{CloudFormationEventMonitor as j}from"./utils/cloudformationEvents.js";export{j as CloudFormationEventMonitor,N as DEFAULT_MAX_PAGES,F as IDENTITY_STORE_PAGE_SIZE,B as ROOT_ACCESS_FEATURES,X as RootAccessEnablementError,Y as SECURITY_SERVICE_PRINCIPALS,R as SERVICE_PRINCIPALS,a as SimpleAwsProvider,f as activateCostAllocationTags,m as activateTrustedAccess,U as buildAccountToOUMap,d as buildTargetSetUnreadyAdvisory,n as buildTargetUnreadyAdvisory,v as checkIdentityCentreStatus,A as checkRegionEnabled,s as checkTargetReadiness,h as composeSdkAbortSignal,y as createAccount,b as describeOrganisation,o as describeTargetReadinessReason,G as drainPages,w as enableCentralisedRootAccess,I as enableIpamDelegatedAdmin,u as enablePolicyTypes,T as enableRamSharing,p as enableServiceAccess,g as ensureOrganisationExists,O as ensureOrganisationalUnitsExist,D as extractErrorName,_ as findAccount,L as isAborted,M as isOULeaf,x as listAccounts,P as placeAccountsInOUs,i as probeAccountStackExists,E as regionDisabledMessage,V as registerSecurityDelegates,k as snapshotIdentityCentre,C as updateBackupGlobalSettings};
1
+ import{SimpleAwsProvider as a}from"./SimpleAwsProvider.js";import{checkTargetReadiness as o,describeTargetReadinessReason as s,buildTargetUnreadyAdvisory as n,probeAccountStackExists as i}from"./targetReadiness.js";import{buildTargetSetUnreadyAdvisory as d}from"./targetSetAdvisory.js";import{checkRegionEnabled as A,regionDisabledMessage as E}from"./regions/regionOptStatus.js";import{ensureOrganisationExists as g,describeOrganisation as u,enablePolicyTypes as b,enableServiceAccess as p,SERVICE_PRINCIPALS as m,enableRamSharing as I,activateTrustedAccess as R,enableIpamDelegatedAdmin as T,updateBackupGlobalSettings as C,listAccounts as x,findAccount as O,createAccount as _,ensureOrganisationalUnitsExist as f,placeAccountsInOUs as y,buildAccountToOUMap as P,findDevelopmentOuId as U,activateCostAllocationTags as v,checkIdentityCentreStatus as k,snapshotIdentityCentre as D,composeSdkAbortSignal as h,extractErrorName as L,isAborted as M,isOULeaf as N,DEFAULT_MAX_PAGES as F,IDENTITY_STORE_PAGE_SIZE as G,drainPages as V,registerSecurityDelegates as Y,SECURITY_SERVICE_PRINCIPALS as w,enableCentralisedRootAccess as B,ROOT_ACCESS_FEATURES as X,RootAccessEnablementError as Z}from"./organisations/index.js";import{CloudFormationEventMonitor as q}from"./utils/cloudformationEvents.js";export{q as CloudFormationEventMonitor,F as DEFAULT_MAX_PAGES,G as IDENTITY_STORE_PAGE_SIZE,X as ROOT_ACCESS_FEATURES,Z as RootAccessEnablementError,w as SECURITY_SERVICE_PRINCIPALS,m as SERVICE_PRINCIPALS,a as SimpleAwsProvider,v as activateCostAllocationTags,R as activateTrustedAccess,P as buildAccountToOUMap,d as buildTargetSetUnreadyAdvisory,n as buildTargetUnreadyAdvisory,k as checkIdentityCentreStatus,A as checkRegionEnabled,o as checkTargetReadiness,h as composeSdkAbortSignal,_ as createAccount,u as describeOrganisation,s as describeTargetReadinessReason,V as drainPages,B as enableCentralisedRootAccess,T as enableIpamDelegatedAdmin,b as enablePolicyTypes,I as enableRamSharing,p as enableServiceAccess,g as ensureOrganisationExists,f as ensureOrganisationalUnitsExist,L as extractErrorName,O as findAccount,U as findDevelopmentOuId,M as isAborted,N as isOULeaf,x as listAccounts,y as placeAccountsInOUs,i as probeAccountStackExists,E as regionDisabledMessage,Y as registerSecurityDelegates,D as snapshotIdentityCentre,C as updateBackupGlobalSettings};
@@ -14,7 +14,7 @@ export { activateTrustedAccess } from "./trustedAccess.js";
14
14
  export { enableIpamDelegatedAdmin } from "./ipam.js";
15
15
  export { updateBackupGlobalSettings } from "./backup.js";
16
16
  export { listAccounts, findAccount, createAccount } from "./accounts.js";
17
- export { ensureOrganisationalUnitsExist, placeAccountsInOUs, buildAccountToOUMap } from "./organisationalUnits.js";
17
+ export { ensureOrganisationalUnitsExist, placeAccountsInOUs, buildAccountToOUMap, findDevelopmentOuId } from "./organisationalUnits.js";
18
18
  export { activateCostAllocationTags } from "./costAllocation.js";
19
19
  export { checkIdentityCentreStatus } from "./identityCentre.js";
20
20
  export { snapshotIdentityCentre } from "./identityCentreSnapshot.js";
@@ -1 +1 @@
1
- import{composeSdkAbortSignal as r,extractErrorName as o,isAborted as a,isOULeaf as s}from"./types.js";import{DEFAULT_MAX_PAGES as c,IDENTITY_STORE_PAGE_SIZE as i,drainPages as p}from"./pagination.js";import{ensureOrganisationExists as A,describeOrganisation as l}from"./organisation.js";import{enablePolicyTypes as E}from"./policies.js";import{enableServiceAccess as S,SERVICE_PRINCIPALS as I}from"./serviceAccess.js";import{enableRamSharing as u}from"./ram.js";import{activateTrustedAccess as g}from"./trustedAccess.js";import{enableIpamDelegatedAdmin as R}from"./ipam.js";import{updateBackupGlobalSettings as _}from"./backup.js";import{listAccounts as P,findAccount as U,createAccount as y}from"./accounts.js";import{ensureOrganisationalUnitsExist as L,placeAccountsInOUs as N,buildAccountToOUMap as h}from"./organisationalUnits.js";import{activateCostAllocationTags as v}from"./costAllocation.js";import{checkIdentityCentreStatus as F}from"./identityCentre.js";import{snapshotIdentityCentre as V}from"./identityCentreSnapshot.js";import{registerSecurityDelegates as B,SECURITY_SERVICE_PRINCIPALS as X}from"./delegatedAdmin.js";import{enableCentralisedRootAccess as j,ROOT_ACCESS_FEATURES as q,RootAccessEnablementError as w}from"./rootAccess.js";export{c as DEFAULT_MAX_PAGES,i as IDENTITY_STORE_PAGE_SIZE,q as ROOT_ACCESS_FEATURES,w as RootAccessEnablementError,X as SECURITY_SERVICE_PRINCIPALS,I as SERVICE_PRINCIPALS,v as activateCostAllocationTags,g as activateTrustedAccess,h as buildAccountToOUMap,F as checkIdentityCentreStatus,r as composeSdkAbortSignal,y as createAccount,l as describeOrganisation,p as drainPages,j as enableCentralisedRootAccess,R as enableIpamDelegatedAdmin,E as enablePolicyTypes,u as enableRamSharing,S as enableServiceAccess,A as ensureOrganisationExists,L as ensureOrganisationalUnitsExist,o as extractErrorName,U as findAccount,a as isAborted,s as isOULeaf,P as listAccounts,N as placeAccountsInOUs,B as registerSecurityDelegates,V as snapshotIdentityCentre,_ as updateBackupGlobalSettings};
1
+ import{composeSdkAbortSignal as o,extractErrorName as r,isAborted as a,isOULeaf as n}from"./types.js";import{DEFAULT_MAX_PAGES as c,IDENTITY_STORE_PAGE_SIZE as i,drainPages as p}from"./pagination.js";import{ensureOrganisationExists as A,describeOrganisation as l}from"./organisation.js";import{enablePolicyTypes as x}from"./policies.js";import{enableServiceAccess as S,SERVICE_PRINCIPALS as d}from"./serviceAccess.js";import{enableRamSharing as u}from"./ram.js";import{activateTrustedAccess as g}from"./trustedAccess.js";import{enableIpamDelegatedAdmin as R}from"./ipam.js";import{updateBackupGlobalSettings as O}from"./backup.js";import{listAccounts as P,findAccount as U,createAccount as y}from"./accounts.js";import{ensureOrganisationalUnitsExist as v,placeAccountsInOUs as L,buildAccountToOUMap as N,findDevelopmentOuId as h}from"./organisationalUnits.js";import{activateCostAllocationTags as G}from"./costAllocation.js";import{checkIdentityCentreStatus as M}from"./identityCentre.js";import{snapshotIdentityCentre as Y}from"./identityCentreSnapshot.js";import{registerSecurityDelegates as X,SECURITY_SERVICE_PRINCIPALS as Z}from"./delegatedAdmin.js";import{enableCentralisedRootAccess as q,ROOT_ACCESS_FEATURES as w,RootAccessEnablementError as z}from"./rootAccess.js";export{c as DEFAULT_MAX_PAGES,i as IDENTITY_STORE_PAGE_SIZE,w as ROOT_ACCESS_FEATURES,z as RootAccessEnablementError,Z as SECURITY_SERVICE_PRINCIPALS,d as SERVICE_PRINCIPALS,G as activateCostAllocationTags,g as activateTrustedAccess,N as buildAccountToOUMap,M as checkIdentityCentreStatus,o as composeSdkAbortSignal,y as createAccount,l as describeOrganisation,p as drainPages,q as enableCentralisedRootAccess,R as enableIpamDelegatedAdmin,x as enablePolicyTypes,u as enableRamSharing,S as enableServiceAccess,A as ensureOrganisationExists,v as ensureOrganisationalUnitsExist,r as extractErrorName,U as findAccount,h as findDevelopmentOuId,a as isAborted,n as isOULeaf,P as listAccounts,L as placeAccountsInOUs,X as registerSecurityDelegates,Y as snapshotIdentityCentre,O as updateBackupGlobalSettings};
@@ -37,3 +37,18 @@ export declare function buildAccountToOUMap(tree: OUTree, ouMap: OUMap, prefix?:
37
37
  * Idempotent — accounts already in the correct OU are counted but not moved.
38
38
  */
39
39
  export declare function placeAccountsInOUs(client: OrganizationsClient, ouMap: OUMap, accounts: AccountInfo[], accountToOU?: Record<string, string>, abortSignal?: AbortSignal): Promise<Result<AccountPlacementResult>>;
40
+ /**
41
+ * Find the id of the organisational unit named "development" (case-insensitive),
42
+ * searching breadth-first from the root up to DEV_OU_SEARCH_MAX_DEPTH levels.
43
+ * Supports both OU shapes ensureOrganisationalUnitsExist creates: flat
44
+ * (development at root) and nested (workloads.development).
45
+ *
46
+ * Collects EVERY match rather than returning the shallowest one: a flat→nested
47
+ * migration leaves a stray empty "development" OU at the root alongside the
48
+ * real nested one, and silently attaching the dev-isolation SCP to the stray
49
+ * would isolate nothing. Exactly one match → its id; zero → `undefined` (the
50
+ * org has no development OU — the caller treats that as "nothing to isolate",
51
+ * not an error); two or more → a loud failure naming the ids so the operator
52
+ * disambiguates, because a security backstop must never attach to the wrong OU.
53
+ */
54
+ export declare function findDevelopmentOuId(client: OrganizationsClient, rootId: string, abortSignal?: AbortSignal): Promise<Result<string | undefined>>;
@@ -1 +1 @@
1
- import{CreateOrganizationalUnitCommand as b,ListOrganizationalUnitsForParentCommand as C,ListParentsCommand as x,MoveAccountCommand as $}from"@aws-sdk/client-organizations";import{success as d,failure as u}from"@fjall/generator";import{extractErrorName as I,isOULeaf as p,composeSdkAbortSignal as m,isAborted as y,AWS_ERROR_NAMES as A}from"./types.js";import{accountTier as F,getErrorMessage as U}from"@fjall/util";async function g(n,a,e){const t=await n.send(new C({ParentId:a}),{abortSignal:m(e)});let r=t.OrganizationalUnits??[],o=t.NextToken;for(;o;){if(y(e))throw new Error("Aborted: OU listing cancelled by shutdown signal");const s=await n.send(new C({ParentId:a,NextToken:o}),{abortSignal:m(e)});r=r.concat(s.OrganizationalUnits??[]),o=s.NextToken}return r}async function T(n,a,e){try{return(await n.send(new x({ChildId:a}),{abortSignal:m(e)})).Parents?.[0]?.Id}catch(t){if(I(t)===A.CHILD_NOT_FOUND)return;throw t}}async function L(n,a,e,t,r){const o=t.find(s=>s.Name===e);if(o?.Id)return d(o.Id);try{const c=(await n.send(new b({Name:e,ParentId:a}),{abortSignal:m(r)})).OrganizationalUnit;return c?.Id?d(c.Id):u(new Error(`OU "${e}" was created but has no ID`))}catch(s){return u(new Error(`Failed to create OU "${e}": ${U(s)}`))}}function P(n){return n.charAt(0).toUpperCase()+n.slice(1)}async function k(n,a,e,t){const r={};if(e.length===0)return d(r);const o=await g(n,a,t);for(const s of e){const c=P(s),i=await L(n,a,c,o,t);if(!i.success)return u(i.error);r[s.toLowerCase()]=i.data,o.push({Id:i.data,Name:c})}return d(r)}async function E(n,a,e,t,r,o,s){const c=await g(n,a,s);for(const[i,f]of Object.entries(e)){const w=P(i),O=await L(n,a,w,c,s);if(!O.success)return u(O.error);const h=O.data,N=r?`${r}.${i.toLowerCase()}`:i.toLowerCase();if(t[N]=h,r){const l=i.toLowerCase();o.has(l)||(t[l]=h)}if(c.push({Id:h,Name:w}),!p(f)){const l=await E(n,h,f,t,N,o,s);if(!l.success)return l}}return d(void 0)}async function v(n,a,e,t){if(y(t))return u(new Error("Aborted: OU reconciliation cancelled by shutdown signal"));try{if(Array.isArray(e))return await k(n,a,e,t);const r={},o=new Set(Object.keys(e).map(c=>c.toLowerCase())),s=await E(n,a,e,r,"",o,t);return s.success?d(r):u(s.error)}catch(r){return u(new Error(`Failed to ensure OUs exist: ${U(r)}`))}}function _(n,a,e=""){const t={};for(const[r,o]of Object.entries(n)){const s=e?`${e}.${r.toLowerCase()}`:r.toLowerCase(),c=a[s];if(p(o)){if(c)for(const i of o)t[i.toLowerCase()]=c}else Object.assign(t,_(o,a,s))}return t}async function M(n,a,e,t,r){try{if(e.length===0)return d({moved:0,alreadyPlaced:0});let o=0,s=0;for(const c of e){if(y(r))return u(new Error("Aborted: account placement cancelled by shutdown signal"));if(F(c)==="organisation")continue;const i=t?t[c.name.toLowerCase()]:a[c.environment.toLowerCase()];if(!i)continue;const f=await T(n,c.id,r);if(f){if(f===i){s++;continue}try{await n.send(new $({AccountId:c.id,SourceParentId:f,DestinationParentId:i}),{abortSignal:m(r)}),o++}catch(w){if(I(w)===A.ACCOUNT_NOT_FOUND)continue;throw w}}}return d({moved:o,alreadyPlaced:s})}catch(o){return u(new Error(`Failed to place accounts in OUs: ${U(o)}`))}}export{_ as buildAccountToOUMap,v as ensureOrganisationalUnitsExist,M as placeAccountsInOUs};
1
+ import{CreateOrganizationalUnitCommand as x,ListOrganizationalUnitsForParentCommand as C,ListParentsCommand as b,MoveAccountCommand as _}from"@aws-sdk/client-organizations";import{success as f,failure as u}from"@fjall/generator";import{extractErrorName as N,isOULeaf as A,composeSdkAbortSignal as m,isAborted as p,AWS_ERROR_NAMES as E}from"./types.js";import{accountTier as v,getErrorMessage as y}from"@fjall/util";async function U(o,s,t){const e=await o.send(new C({ParentId:s}),{abortSignal:m(t)});let n=e.OrganizationalUnits??[],a=e.NextToken;for(;a;){if(p(t))throw new Error("Aborted: OU listing cancelled by shutdown signal");const r=await o.send(new C({ParentId:s,NextToken:a}),{abortSignal:m(t)});n=n.concat(r.OrganizationalUnits??[]),a=r.NextToken}return n}async function F(o,s,t){try{return(await o.send(new b({ChildId:s}),{abortSignal:m(t)})).Parents?.[0]?.Id}catch(e){if(N(e)===E.CHILD_NOT_FOUND)return;throw e}}async function L(o,s,t,e,n){const a=e.find(r=>r.Name===t);if(a?.Id)return f(a.Id);try{const c=(await o.send(new x({Name:t,ParentId:s}),{abortSignal:m(n)})).OrganizationalUnit;return c?.Id?f(c.Id):u(new Error(`OU "${t}" was created but has no ID`))}catch(r){return u(new Error(`Failed to create OU "${t}": ${y(r)}`))}}function P(o){return o.charAt(0).toUpperCase()+o.slice(1)}async function D(o,s,t,e){const n={};if(t.length===0)return f(n);const a=await U(o,s,e);for(const r of t){const c=P(r),i=await L(o,s,c,a,e);if(!i.success)return u(i.error);n[r.toLowerCase()]=i.data,a.push({Id:i.data,Name:c})}return f(n)}async function $(o,s,t,e,n,a,r){const c=await U(o,s,r);for(const[i,d]of Object.entries(t)){const l=P(i),h=await L(o,s,l,c,r);if(!h.success)return u(h.error);const O=h.data,g=n?`${n}.${i.toLowerCase()}`:i.toLowerCase();if(e[g]=O,n){const w=i.toLowerCase();a.has(w)||(e[w]=O)}if(c.push({Id:O,Name:l}),!A(d)){const w=await $(o,O,d,e,g,a,r);if(!w.success)return w}}return f(void 0)}async function H(o,s,t,e){if(p(e))return u(new Error("Aborted: OU reconciliation cancelled by shutdown signal"));try{if(Array.isArray(t))return await D(o,s,t,e);const n={},a=new Set(Object.keys(t).map(c=>c.toLowerCase())),r=await $(o,s,t,n,"",a,e);return r.success?f(n):u(r.error)}catch(n){return u(new Error(`Failed to ensure OUs exist: ${y(n)}`))}}function T(o,s,t=""){const e={};for(const[n,a]of Object.entries(o)){const r=t?`${t}.${n.toLowerCase()}`:n.toLowerCase(),c=s[r];if(A(a)){if(c)for(const i of a)e[i.toLowerCase()]=c}else Object.assign(e,T(a,s,r))}return e}async function S(o,s,t,e,n){try{if(t.length===0)return f({moved:0,alreadyPlaced:0});let a=0,r=0;for(const c of t){if(p(n))return u(new Error("Aborted: account placement cancelled by shutdown signal"));if(v(c)==="organisation")continue;const i=e?e[c.name.toLowerCase()]:s[c.environment.toLowerCase()];if(!i)continue;const d=await F(o,c.id,n);if(d){if(d===i){r++;continue}try{await o.send(new _({AccountId:c.id,SourceParentId:d,DestinationParentId:i}),{abortSignal:m(n)}),a++}catch(l){if(N(l)===E.ACCOUNT_NOT_FOUND)continue;throw l}}}return f({moved:a,alreadyPlaced:r})}catch(a){return u(new Error(`Failed to place accounts in OUs: ${y(a)}`))}}const I="development",k=3;async function K(o,s,t){try{const e=[];let n=[s];for(let a=0;a<k;a++){const r=[];for(const c of n){if(p(t))return u(new Error("Aborted: development-OU lookup cancelled by shutdown signal"));const i=await U(o,c,t);for(const d of i)if(d.Id!==void 0){if(d.Name?.toLowerCase()===I){e.push(d.Id);continue}r.push(d.Id)}}if(r.length===0)break;n=r}return e.length===0?f(void 0):e.length>1?u(new Error(`Found ${e.length} organisational units named "${I}" (${e.join(", ")}); cannot safely target the dev-isolation SCP. A flat\u2192nested migration can leave a stray empty "${I}" OU at the root \u2014 remove the duplicate so exactly one remains.`)):f(e[0])}catch(e){return u(new Error(`Failed to locate development OU: ${y(e)}`))}}export{T as buildAccountToOUMap,H as ensureOrganisationalUnitsExist,K as findDevelopmentOuId,S as placeAccountsInOUs};
@@ -15,14 +15,14 @@ export { DeploymentEventSchema, DEPLOYMENT_EVENT_TYPES, DEPLOYMENT_EVENT_RESOURC
15
15
  export type { DeploymentEvent, DeploymentEventType, DeploymentEventResourceCategory, DeploymentEventCascadePhase, DeploymentEventCascadeAccountStatus } from "./types/index.js";
16
16
  export { DeployEventSchema, StepProgressSchema, DeployResourceSchema, PlanChangeSchema, PlanPropertyChangeSchema, ChangeCountsSchema, DEPLOY_EVENT_CONTRACT, DEPLOY_EVENT_CONTRACT_MAJOR, DEPLOY_EVENT_CONTRACT_MINOR, SUPPORTED_CONTRACT_MARKERS, DEPLOY_EVENT_ID_MAX, DEPLOY_EVENT_NAME_MAX, DEPLOY_EVENT_MESSAGE_MAX, DEPLOY_EVENT_STATUS_REASON_MAX, DEPLOY_EVENT_ERROR_MESSAGE_MAX, DEPLOY_EVENT_TRAIL_DETAIL_MAX, DEPLOY_EVENT_OUTPUT_MAX, DEPLOY_EVENT_ARN_MAX, DEPLOY_EVENT_URL_MAX, STEP_COMPLETE_STATUSES, LOG_LEVELS, STACK_CLEANUP_PHASES, CASCADE_ACCOUNT_PHASES, DEPLOY_RESULTS, DETECTION_PHASES, PHASE_STATUSES, BUILDERS, CASCADE_OUTCOME_RESULTS, PLAN_ACTIONS, REPLACEMENT_MODES, APPROVAL_KINDS, APPROVAL_DECISIONS, ALL_PROGRESS_KINDS, createDeployEmitter, parseDeployEvent, parseDeployContractVersion } from "./types/index.js";
17
17
  export type { DeployEvent, ProgressKind, StepProgress, DeployResource, PlanChange, PlanPropertyChange, ChangeCounts, PlanAction, ReplacementMode, ApprovalKind, ApprovalDecision, DeployRunResult, DeployEmitter, CreateDeployEmitterOptions, ParsedDeployEvent, StackCleanupPhase, CascadeAccountPhase, LogLevel } from "./types/index.js";
18
- export type { AwsCredentials, DeployIdentity, ScopedBuildSecretSessionMinter, DeployCallbacks, StepCompleteStatus, StackCleanupQuarantineDetail, StackCleanupRetainedBucketsDetail, ProgressEvent, ProgressEventType, ResourceEvent, AwsAuthResult, CascadeDeploymentResult, CascadePhase, BuildPushStartEvent, BuildPushProgressEvent, BuildPushCompleteEvent, TaskDefRegisteredEvent, ECSCompleteEvent, MigrationsStartEvent, MigrationsCompleteEvent, TrailMigrationPhase, TrailMigrationStatus, TrailMigrationPhaseEvent, ApiClientInterface, EntitlementsData, DeployParams, DeployOptions, DeploymentType, DeployResult, DestroyParams, DestroyOptions, DestroyResult, ApprovalGate, ApprovalGatePlanDetail, ApprovalRequest, GateResolution, OrgConfig, ProviderAccount, RootAccessManagementMode, SSOSession, Entitlements } from "./types/index.js";
18
+ export type { AwsCredentials, DeployIdentity, ScopedBuildSecretSessionMinter, DeployCallbacks, StepCompleteStatus, StackCleanupQuarantineDetail, StackCleanupRetainedBucketsDetail, ProgressEvent, ProgressEventType, ResourceEvent, AwsAuthResult, CascadeDeploymentResult, CascadePhase, BuildPushStartEvent, BuildPushProgressEvent, BuildPushCompleteEvent, TaskDefRegisteredEvent, ECSCompleteEvent, MigrationsStartEvent, MigrationsCompleteEvent, TrailMigrationPhase, TrailMigrationStatus, TrailMigrationPhaseEvent, ApiClientInterface, EntitlementsData, DeployParams, DeployOptions, DatabaseEndpointRestartRequest, DeploymentType, DeployResult, DestroyParams, DestroyOptions, DestroyResult, ApprovalGate, ApprovalGatePlanDetail, ApprovalRequest, GateResolution, OrgConfig, ProviderAccount, RootAccessManagementMode, SSOSession, Entitlements } from "./types/index.js";
19
19
  export type { AwsProvider, AwsProviderCredentials, AwsSdkClientConstructor } from "./aws/index.js";
20
20
  export { SimpleAwsProvider } from "./aws/index.js";
21
21
  export { checkTargetReadiness, describeTargetReadinessReason, buildTargetUnreadyAdvisory, buildTargetSetUnreadyAdvisory, probeAccountStackExists } from "./aws/index.js";
22
22
  export type { TargetReadinessAccount, TargetReadinessClients, TargetReadinessReason, TargetReadinessVerdict, TargetSetUnreadyAdvisoryParams } from "./aws/index.js";
23
23
  export { checkRegionEnabled, regionDisabledMessage } from "./aws/index.js";
24
24
  export type { RegionAvailability } from "./aws/index.js";
25
- export { ensureOrganisationExists, describeOrganisation, enablePolicyTypes, enableServiceAccess, SERVICE_PRINCIPALS, enableRamSharing, activateTrustedAccess, enableIpamDelegatedAdmin, updateBackupGlobalSettings, listAccounts, findAccount, createAccount, ensureOrganisationalUnitsExist, placeAccountsInOUs, buildAccountToOUMap, activateCostAllocationTags, checkIdentityCentreStatus, snapshotIdentityCentre, composeSdkAbortSignal, extractErrorName, isAborted, DEFAULT_MAX_PAGES, IDENTITY_STORE_PAGE_SIZE, drainPages, isOULeaf, registerSecurityDelegates, SECURITY_SERVICE_PRINCIPALS, enableCentralisedRootAccess, ROOT_ACCESS_FEATURES, RootAccessEnablementError } from "./aws/index.js";
25
+ export { ensureOrganisationExists, describeOrganisation, enablePolicyTypes, enableServiceAccess, SERVICE_PRINCIPALS, enableRamSharing, activateTrustedAccess, enableIpamDelegatedAdmin, updateBackupGlobalSettings, listAccounts, findAccount, createAccount, ensureOrganisationalUnitsExist, placeAccountsInOUs, buildAccountToOUMap, findDevelopmentOuId, activateCostAllocationTags, checkIdentityCentreStatus, snapshotIdentityCentre, composeSdkAbortSignal, extractErrorName, isAborted, DEFAULT_MAX_PAGES, IDENTITY_STORE_PAGE_SIZE, drainPages, isOULeaf, registerSecurityDelegates, SECURITY_SERVICE_PRINCIPALS, enableCentralisedRootAccess, ROOT_ACCESS_FEATURES, RootAccessEnablementError } from "./aws/index.js";
26
26
  export type { OrgDetails, OUMap, OUTree, AccountPlacementResult, AccountInfo, DrainPagesOptions, PagedResult, IdentityCentreStatus, IdentityCentreSnapshot, IdentityCentreSnapshotGroup, IdentityCentreSnapshotOptions, IdentityCentreSnapshotResult, IdentityCentreSnapshotUser, BackupGlobalSettings, CostAllocationTag, CreateAccountResult, RootAccessFeature, RootAccessEnablementSummary } from "./aws/index.js";
27
27
  export { STEP_IDS, STEP_NAMES, INFRASTRUCTURE_STEP_NAMES, INFRA_STEP_NAME } from "./types/index.js";
28
28
  export type { StepId, InfrastructureStepName, StepDefinition, StepDeploymentType, Operation, StepContext } from "./types/index.js";
@@ -44,6 +44,8 @@ export { fileExists } from "@fjall/util/fsHelpers";
44
44
  export { type Result, success, failure, isSuccess, isFailure } from "@fjall/generator";
45
45
  export { deploy } from "./orchestration/index.js";
46
46
  export { destroy } from "./orchestration/index.js";
47
+ export { restart, probeSecretsDrift, computeSecretsDrift, deriveStaleParameters, extractConsumedSsmParameters } from "./orchestration/index.js";
48
+ export type { RestartParams, RestartResult, SecretsDriftProbeParams, ApplicationSecretsDrift, ServiceSecretsDrift, StaleParameter } from "./orchestration/index.js";
47
49
  export { partitionAccounts } from "./orchestration/index.js";
48
50
  export { buildRegionList, buildAccountRegionPairs, cascadeHomeRegion, cascadeOperationKey } from "./orchestration/index.js";
49
51
  export type { AccountRegionPair } from "./orchestration/index.js";
package/dist/src/index.js CHANGED
@@ -1 +1 @@
1
- import{DeploymentEventSchema as t,DEPLOYMENT_EVENT_TYPES as o,DEPLOYMENT_EVENT_RESOURCE_CATEGORIES as E,DEPLOYMENT_EVENT_STATUS_REASON_MAX as a,DEPLOYMENT_EVENT_ERROR_MESSAGE_MAX as i,DEPLOYMENT_EVENT_TRAIL_DETAIL_MAX as s,CASCADE_PHASES as S,CASCADE_ACCOUNT_STATUSES as n}from"./types/index.js";import{DeployEventSchema as A,StepProgressSchema as T,DeployResourceSchema as _,PlanChangeSchema as l,PlanPropertyChangeSchema as p,ChangeCountsSchema as O,DEPLOY_EVENT_CONTRACT as P,DEPLOY_EVENT_CONTRACT_MAJOR as R,DEPLOY_EVENT_CONTRACT_MINOR as u,SUPPORTED_CONTRACT_MARKERS as m,DEPLOY_EVENT_ID_MAX as d,DEPLOY_EVENT_NAME_MAX as C,DEPLOY_EVENT_MESSAGE_MAX as N,DEPLOY_EVENT_STATUS_REASON_MAX as D,DEPLOY_EVENT_ERROR_MESSAGE_MAX as L,DEPLOY_EVENT_TRAIL_DETAIL_MAX as f,DEPLOY_EVENT_OUTPUT_MAX as g,DEPLOY_EVENT_ARN_MAX as I,DEPLOY_EVENT_URL_MAX as y,STEP_COMPLETE_STATUSES as x,LOG_LEVELS as U,STACK_CLEANUP_PHASES as M,CASCADE_ACCOUNT_PHASES as v,DEPLOY_RESULTS as k,DETECTION_PHASES as Y,PHASE_STATUSES as h,BUILDERS as b,CASCADE_OUTCOME_RESULTS as V,PLAN_ACTIONS as F,REPLACEMENT_MODES as B,APPROVAL_KINDS as G,APPROVAL_DECISIONS as K,ALL_PROGRESS_KINDS as X,createDeployEmitter as H,parseDeployEvent as w,parseDeployContractVersion as j}from"./types/index.js";import{SimpleAwsProvider as q}from"./aws/index.js";import{checkTargetReadiness as W,describeTargetReadinessReason as Z,buildTargetUnreadyAdvisory as z,buildTargetSetUnreadyAdvisory as $,probeAccountStackExists as ee}from"./aws/index.js";import{checkRegionEnabled as te,regionDisabledMessage as oe}from"./aws/index.js";import{ensureOrganisationExists as ae,describeOrganisation as ie,enablePolicyTypes as se,enableServiceAccess as Se,SERVICE_PRINCIPALS as ne,enableRamSharing as ce,activateTrustedAccess as Ae,enableIpamDelegatedAdmin as Te,updateBackupGlobalSettings as _e,listAccounts as le,findAccount as pe,createAccount as Oe,ensureOrganisationalUnitsExist as Pe,placeAccountsInOUs as Re,buildAccountToOUMap as ue,activateCostAllocationTags as me,checkIdentityCentreStatus as de,snapshotIdentityCentre as Ce,composeSdkAbortSignal as Ne,extractErrorName as De,isAborted as Le,DEFAULT_MAX_PAGES as fe,IDENTITY_STORE_PAGE_SIZE as ge,drainPages as Ie,isOULeaf as ye,registerSecurityDelegates as xe,SECURITY_SERVICE_PRINCIPALS as Ue,enableCentralisedRootAccess as Me,ROOT_ACCESS_FEATURES as ve,RootAccessEnablementError as ke}from"./aws/index.js";import{STEP_IDS as he,STEP_NAMES as be,INFRASTRUCTURE_STEP_NAMES as Ve,INFRA_STEP_NAME as Fe}from"./types/index.js";import{ProgressReporter as Ge,APPLICATION_STACKS as Ke,ORGANISATION_TYPES as Xe,APPLICATION_DEPLOY_ORDER as He,APPLICATION_DESTROY_ORDER as we,OPENNEXT_DEPLOY_ORDER as je,OPENNEXT_DESTROY_ORDER as Qe,PARALLEL_DEPLOY_GROUPS as qe,PARALLEL_DESTROY_GROUPS as Je,OPENNEXT_PARALLEL_GROUPS as We,PARALLEL_OPERATION_TYPES as Ze,isApplicationOperation as ze,isOrganisationOperation as $e,getParallelDeployGroups as er,getParallelDestroyGroups as rr,getApplicationDeployOrder as tr,getApplicationDestroyOrder as or,getApplicationStackName as Er,getOrganisationStackName as ar,isApplicationStack as ir,isQuarantineDetail as sr,isRetainedBucketsDetail as Sr,getApplicationStepName as nr,getApplicationStepId as cr,toPascalCase as Ar,isOpenNextPattern as Tr,OPENNEXT_PATTERNS as _r,deriveResourcesFromManifestStacks as lr,STACK_NOT_FOUND_PATTERN as pr,STACK_FAILED_STATE_PATTERN as Or,CDK_NO_STACKS_MATCH as Pr,INFRASTRUCTURE_FILENAME as Rr,ApplicationError as ur,wrapApplicationError as mr,FjallStateFileSchema as dr,readStateFile as Cr,writeStateFile as Nr,createEmptyState as Dr,deleteStateFile as Lr,updateTemplateHash as fr,getStateFilePath as gr,stubCallerIdentity as Ir}from"./types/index.js";import{CloudFormationEventMonitor as xr}from"./aws/index.js";import{CdkService as Mr,CdkArgumentBuilder as vr,CdkProcessManager as kr,CdkEventMonitor as Yr,startStackMonitoring as hr,DEFAULT_DEPLOY_TIMEOUT_MS as br,isCdkError as Vr,formatInfrastructureError as Fr,getStructuralHint as Br,getSourceContext as Gr,hasCdkDifferences as Kr,parseDiffOutput as Xr,CloudFormationService as Hr,CloudFormationError as wr,EcsService as jr,EcsError as Qr,EcsServiceResolver as qr,TemplateHashService as Jr,TemplateHashError as Wr,CdkContextBuilder as Zr,emitProgress as zr,PROGRESS_MESSAGES as $r,parseBuildPhase as et,buildStepContextBuildConfig as rt,convertCloudFormationOutputsToRecord as tt,ApplicationStackService as ot}from"./services/index.js";import{CdkError as at}from"./types/errors/index.js";import{BaseServiceError as st,ValidationError as St,AuthError as nt,AwsError as ct,DeploymentError as At,NetworkError as Tt,FileSystemError as _t,ConfigError as lt,toServiceError as pt}from"./types/errors/index.js";import{filterDangerousEnvVars as Pt,maskSensitiveOutput as Rt,parseShellArgs as ut,sleep as mt}from"@fjall/util";import{artefactOutputKey as Ct,DeployModeSchema as Nt,ServiceArtefactSchema as Dt,ServiceArtefactsSchema as Lt}from"@fjall/util";import{hasDockerfile as gt}from"./util/dockerfileDetection.js";import{sleepAbortable as yt}from"./util/sleepAbortable.js";import{createSequencedCallbacks as Ut}from"./util/sequencedCallbacks.js";import{fileExists as vt}from"@fjall/util/fsHelpers";import{success as Yt,failure as ht,isSuccess as bt,isFailure as Vt}from"@fjall/generator";import{deploy as Bt}from"./orchestration/index.js";import{destroy as Kt}from"./orchestration/index.js";import{partitionAccounts as Ht}from"./orchestration/index.js";import{buildRegionList as jt,buildAccountRegionPairs as Qt,cascadeHomeRegion as qt,cascadeOperationKey as Jt}from"./orchestration/index.js";import{projectScalarSummary as Zt,projectAccountRows as zt}from"./orchestration/index.js";import{reconcileProviderAccounts as eo,mergeReconciledProviderAccounts as ro}from"./orchestration/index.js";import{decideNextTransition as oo,reconcileTrailMigration as Eo,decommissionMemberTrailStorage as ao,ORG_TRAIL_BUCKET_OUTPUT_KEY as io,TRAIL_BUCKET_OUTPUT_KEY as so,TRAIL_KEY_ARN_OUTPUT_KEY as So}from"./orchestration/index.js";import{unlockBucket as co,unlockQueue as Ao}from"./orchestration/index.js";import{triageBucketPolicy as _o,isEnforceSslStatement as lo,toResourcePolicyStatements as po,restoreBucketPolicy as Oo,synthesiseEnforceSslDocument as Po,ensureEnforceSsl as Ro,restoreAndReconcileQuarantinedBucket as uo}from"./orchestration/index.js";import{verifyOrgTrailDelivery as Co}from"./aws/cloudtrail/orgTrailDelivery.js";import{assumeRootForTask as Do,isRootTaskPolicyArn as Lo,ROOT_TASK_POLICY_ARNS as fo,MAX_ROOT_SESSION_SECONDS as go}from"./aws/sts/assumeRoot.js";import{parseAccountsConfiguration as yo,flattenAccountsToEnvironments as xo,extractAllAccountNames as Uo,accountsConfigToOUTree as Mo,isStringArray as vo,isAccountsConfig as ko,isOuOnlyAccountBucket as Yo,OU_ONLY_ACCOUNT_BUCKETS as ho}from"./orchestration/index.js";import{runOpenNextBuild as Vo}from"./orchestration/index.js";import{runOrganisationSetup as Bo,ORG_SETUP_PHASES as Go}from"./orchestration/index.js";import{resolveBuildSecrets as Xo,resolveSecretRefValue as Ho,resolveBuildSecretSessionProvider as wo,sourcedRefsFromBuildArgs as jo,resolveBuildArgs as Qo,buildBuildSecretSessionPolicy as qo,buildDeploySessionPolicy as Jo,partitionForRegion as Wo,validateBuildGroup as Zo}from"./orchestration/index.js";import{FrameworkRegistry as $o}from"./orchestration/index.js";import{openNextBuilder as rE,dockerBuilder as tE}from"./orchestration/index.js";import{StepRegistry as EE,getDestroyStepId as aE}from"./steps/index.js";import{buildDeployPlan as sE,computeDeployPlan as SE,computeAssemblyDigest as nE,digestHead as cE,classifyImpact as AE,derivePropertyChanges as TE,isDataLoss as _E,isStatefulResourceType as lE,renderPlanSummary as pE,renderPlanLines as OE,toWirePlanChanges as PE,signApprovalToken as RE,verifyApprovalToken as uE,DEFAULT_APPROVAL_TTL_MS as mE,APPROVAL_TOKEN_PATTERN as dE,runApprovalGate as CE}from"./orchestration/index.js";export{X as ALL_PROGRESS_KINDS,He as APPLICATION_DEPLOY_ORDER,we as APPLICATION_DESTROY_ORDER,Ke as APPLICATION_STACKS,K as APPROVAL_DECISIONS,G as APPROVAL_KINDS,dE as APPROVAL_TOKEN_PATTERN,ur as ApplicationError,ot as ApplicationStackService,nt as AuthError,ct as AwsError,b as BUILDERS,st as BaseServiceError,v as CASCADE_ACCOUNT_PHASES,n as CASCADE_ACCOUNT_STATUSES,V as CASCADE_OUTCOME_RESULTS,S as CASCADE_PHASES,Pr as CDK_NO_STACKS_MATCH,vr as CdkArgumentBuilder,Zr as CdkContextBuilder,at as CdkError,Yr as CdkEventMonitor,kr as CdkProcessManager,Mr as CdkService,O as ChangeCountsSchema,wr as CloudFormationError,xr as CloudFormationEventMonitor,Hr as CloudFormationService,lt as ConfigError,mE as DEFAULT_APPROVAL_TTL_MS,br as DEFAULT_DEPLOY_TIMEOUT_MS,fe as DEFAULT_MAX_PAGES,i as DEPLOYMENT_EVENT_ERROR_MESSAGE_MAX,E as DEPLOYMENT_EVENT_RESOURCE_CATEGORIES,a as DEPLOYMENT_EVENT_STATUS_REASON_MAX,s as DEPLOYMENT_EVENT_TRAIL_DETAIL_MAX,o as DEPLOYMENT_EVENT_TYPES,I as DEPLOY_EVENT_ARN_MAX,P as DEPLOY_EVENT_CONTRACT,R as DEPLOY_EVENT_CONTRACT_MAJOR,u as DEPLOY_EVENT_CONTRACT_MINOR,L as DEPLOY_EVENT_ERROR_MESSAGE_MAX,d as DEPLOY_EVENT_ID_MAX,N as DEPLOY_EVENT_MESSAGE_MAX,C as DEPLOY_EVENT_NAME_MAX,g as DEPLOY_EVENT_OUTPUT_MAX,D as DEPLOY_EVENT_STATUS_REASON_MAX,f as DEPLOY_EVENT_TRAIL_DETAIL_MAX,y as DEPLOY_EVENT_URL_MAX,k as DEPLOY_RESULTS,Y as DETECTION_PHASES,A as DeployEventSchema,Nt as DeployModeSchema,_ as DeployResourceSchema,At as DeploymentError,t as DeploymentEventSchema,Qr as EcsError,jr as EcsService,qr as EcsServiceResolver,_t as FileSystemError,dr as FjallStateFileSchema,$o as FrameworkRegistry,ge as IDENTITY_STORE_PAGE_SIZE,Rr as INFRASTRUCTURE_FILENAME,Ve as INFRASTRUCTURE_STEP_NAMES,Fe as INFRA_STEP_NAME,U as LOG_LEVELS,go as MAX_ROOT_SESSION_SECONDS,Tt as NetworkError,je as OPENNEXT_DEPLOY_ORDER,Qe as OPENNEXT_DESTROY_ORDER,We as OPENNEXT_PARALLEL_GROUPS,_r as OPENNEXT_PATTERNS,Xe as ORGANISATION_TYPES,Go as ORG_SETUP_PHASES,io as ORG_TRAIL_BUCKET_OUTPUT_KEY,ho as OU_ONLY_ACCOUNT_BUCKETS,qe as PARALLEL_DEPLOY_GROUPS,Je as PARALLEL_DESTROY_GROUPS,Ze as PARALLEL_OPERATION_TYPES,h as PHASE_STATUSES,F as PLAN_ACTIONS,$r as PROGRESS_MESSAGES,l as PlanChangeSchema,p as PlanPropertyChangeSchema,Ge as ProgressReporter,B as REPLACEMENT_MODES,ve as ROOT_ACCESS_FEATURES,fo as ROOT_TASK_POLICY_ARNS,ke as RootAccessEnablementError,Ue as SECURITY_SERVICE_PRINCIPALS,ne as SERVICE_PRINCIPALS,M as STACK_CLEANUP_PHASES,Or as STACK_FAILED_STATE_PATTERN,pr as STACK_NOT_FOUND_PATTERN,x as STEP_COMPLETE_STATUSES,he as STEP_IDS,be as STEP_NAMES,m as SUPPORTED_CONTRACT_MARKERS,Dt as ServiceArtefactSchema,Lt as ServiceArtefactsSchema,q as SimpleAwsProvider,T as StepProgressSchema,EE as StepRegistry,so as TRAIL_BUCKET_OUTPUT_KEY,So as TRAIL_KEY_ARN_OUTPUT_KEY,Wr as TemplateHashError,Jr as TemplateHashService,St as ValidationError,Mo as accountsConfigToOUTree,me as activateCostAllocationTags,Ae as activateTrustedAccess,Ct as artefactOutputKey,Do as assumeRootForTask,Qt as buildAccountRegionPairs,ue as buildAccountToOUMap,qo as buildBuildSecretSessionPolicy,sE as buildDeployPlan,Jo as buildDeploySessionPolicy,jt as buildRegionList,rt as buildStepContextBuildConfig,$ as buildTargetSetUnreadyAdvisory,z as buildTargetUnreadyAdvisory,qt as cascadeHomeRegion,Jt as cascadeOperationKey,de as checkIdentityCentreStatus,te as checkRegionEnabled,W as checkTargetReadiness,AE as classifyImpact,Ne as composeSdkAbortSignal,nE as computeAssemblyDigest,SE as computeDeployPlan,tt as convertCloudFormationOutputsToRecord,Oe as createAccount,H as createDeployEmitter,Dr as createEmptyState,Ut as createSequencedCallbacks,oo as decideNextTransition,ao as decommissionMemberTrailStorage,Lr as deleteStateFile,Bt as deploy,TE as derivePropertyChanges,lr as deriveResourcesFromManifestStacks,ie as describeOrganisation,Z as describeTargetReadinessReason,Kt as destroy,cE as digestHead,tE as dockerBuilder,Ie as drainPages,zr as emitProgress,Me as enableCentralisedRootAccess,Te as enableIpamDelegatedAdmin,se as enablePolicyTypes,ce as enableRamSharing,Se as enableServiceAccess,Ro as ensureEnforceSsl,ae as ensureOrganisationExists,Pe as ensureOrganisationalUnitsExist,Uo as extractAllAccountNames,De as extractErrorName,ht as failure,vt as fileExists,Pt as filterDangerousEnvVars,pe as findAccount,xo as flattenAccountsToEnvironments,Fr as formatInfrastructureError,tr as getApplicationDeployOrder,or as getApplicationDestroyOrder,Er as getApplicationStackName,cr as getApplicationStepId,nr as getApplicationStepName,aE as getDestroyStepId,ar as getOrganisationStackName,er as getParallelDeployGroups,rr as getParallelDestroyGroups,Gr as getSourceContext,gr as getStateFilePath,Br as getStructuralHint,Kr as hasCdkDifferences,gt as hasDockerfile,Le as isAborted,ko as isAccountsConfig,ze as isApplicationOperation,ir as isApplicationStack,Vr as isCdkError,_E as isDataLoss,lo as isEnforceSslStatement,Vt as isFailure,ye as isOULeaf,Tr as isOpenNextPattern,$e as isOrganisationOperation,Yo as isOuOnlyAccountBucket,sr as isQuarantineDetail,Sr as isRetainedBucketsDetail,Lo as isRootTaskPolicyArn,lE as isStatefulResourceType,vo as isStringArray,bt as isSuccess,le as listAccounts,Rt as maskSensitiveOutput,ro as mergeReconciledProviderAccounts,rE as openNextBuilder,yo as parseAccountsConfiguration,et as parseBuildPhase,j as parseDeployContractVersion,w as parseDeployEvent,Xr as parseDiffOutput,ut as parseShellArgs,Ht as partitionAccounts,Wo as partitionForRegion,Re as placeAccountsInOUs,ee as probeAccountStackExists,zt as projectAccountRows,Zt as projectScalarSummary,Cr as readStateFile,eo as reconcileProviderAccounts,Eo as reconcileTrailMigration,oe as regionDisabledMessage,xe as registerSecurityDelegates,OE as renderPlanLines,pE as renderPlanSummary,Qo as resolveBuildArgs,wo as resolveBuildSecretSessionProvider,Xo as resolveBuildSecrets,Ho as resolveSecretRefValue,uo as restoreAndReconcileQuarantinedBucket,Oo as restoreBucketPolicy,CE as runApprovalGate,Vo as runOpenNextBuild,Bo as runOrganisationSetup,RE as signApprovalToken,mt as sleep,yt as sleepAbortable,Ce as snapshotIdentityCentre,jo as sourcedRefsFromBuildArgs,hr as startStackMonitoring,Ir as stubCallerIdentity,Yt as success,Po as synthesiseEnforceSslDocument,Ar as toPascalCase,po as toResourcePolicyStatements,pt as toServiceError,PE as toWirePlanChanges,_o as triageBucketPolicy,co as unlockBucket,Ao as unlockQueue,_e as updateBackupGlobalSettings,fr as updateTemplateHash,Zo as validateBuildGroup,uE as verifyApprovalToken,Co as verifyOrgTrailDelivery,mr as wrapApplicationError,Nr as writeStateFile};
1
+ import{DeploymentEventSchema as t,DEPLOYMENT_EVENT_TYPES as o,DEPLOYMENT_EVENT_RESOURCE_CATEGORIES as a,DEPLOYMENT_EVENT_STATUS_REASON_MAX as E,DEPLOYMENT_EVENT_ERROR_MESSAGE_MAX as i,DEPLOYMENT_EVENT_TRAIL_DETAIL_MAX as s,CASCADE_PHASES as S,CASCADE_ACCOUNT_STATUSES as n}from"./types/index.js";import{DeployEventSchema as A,StepProgressSchema as l,DeployResourceSchema as T,PlanChangeSchema as _,PlanPropertyChangeSchema as p,ChangeCountsSchema as O,DEPLOY_EVENT_CONTRACT as P,DEPLOY_EVENT_CONTRACT_MAJOR as u,DEPLOY_EVENT_CONTRACT_MINOR as R,SUPPORTED_CONTRACT_MARKERS as m,DEPLOY_EVENT_ID_MAX as d,DEPLOY_EVENT_NAME_MAX as C,DEPLOY_EVENT_MESSAGE_MAX as N,DEPLOY_EVENT_STATUS_REASON_MAX as D,DEPLOY_EVENT_ERROR_MESSAGE_MAX as L,DEPLOY_EVENT_TRAIL_DETAIL_MAX as f,DEPLOY_EVENT_OUTPUT_MAX as g,DEPLOY_EVENT_ARN_MAX as I,DEPLOY_EVENT_URL_MAX as x,STEP_COMPLETE_STATUSES as y,LOG_LEVELS as U,STACK_CLEANUP_PHASES as M,CASCADE_ACCOUNT_PHASES as v,DEPLOY_RESULTS as k,DETECTION_PHASES as Y,PHASE_STATUSES as h,BUILDERS as b,CASCADE_OUTCOME_RESULTS as V,PLAN_ACTIONS as F,REPLACEMENT_MODES as B,APPROVAL_KINDS as G,APPROVAL_DECISIONS as K,ALL_PROGRESS_KINDS as X,createDeployEmitter as H,parseDeployEvent as w,parseDeployContractVersion as j}from"./types/index.js";import{SimpleAwsProvider as q}from"./aws/index.js";import{checkTargetReadiness as W,describeTargetReadinessReason as Z,buildTargetUnreadyAdvisory as z,buildTargetSetUnreadyAdvisory as $,probeAccountStackExists as ee}from"./aws/index.js";import{checkRegionEnabled as te,regionDisabledMessage as oe}from"./aws/index.js";import{ensureOrganisationExists as Ee,describeOrganisation as ie,enablePolicyTypes as se,enableServiceAccess as Se,SERVICE_PRINCIPALS as ne,enableRamSharing as ce,activateTrustedAccess as Ae,enableIpamDelegatedAdmin as le,updateBackupGlobalSettings as Te,listAccounts as _e,findAccount as pe,createAccount as Oe,ensureOrganisationalUnitsExist as Pe,placeAccountsInOUs as ue,buildAccountToOUMap as Re,findDevelopmentOuId as me,activateCostAllocationTags as de,checkIdentityCentreStatus as Ce,snapshotIdentityCentre as Ne,composeSdkAbortSignal as De,extractErrorName as Le,isAborted as fe,DEFAULT_MAX_PAGES as ge,IDENTITY_STORE_PAGE_SIZE as Ie,drainPages as xe,isOULeaf as ye,registerSecurityDelegates as Ue,SECURITY_SERVICE_PRINCIPALS as Me,enableCentralisedRootAccess as ve,ROOT_ACCESS_FEATURES as ke,RootAccessEnablementError as Ye}from"./aws/index.js";import{STEP_IDS as be,STEP_NAMES as Ve,INFRASTRUCTURE_STEP_NAMES as Fe,INFRA_STEP_NAME as Be}from"./types/index.js";import{ProgressReporter as Ke,APPLICATION_STACKS as Xe,ORGANISATION_TYPES as He,APPLICATION_DEPLOY_ORDER as we,APPLICATION_DESTROY_ORDER as je,OPENNEXT_DEPLOY_ORDER as Qe,OPENNEXT_DESTROY_ORDER as qe,PARALLEL_DEPLOY_GROUPS as Je,PARALLEL_DESTROY_GROUPS as We,OPENNEXT_PARALLEL_GROUPS as Ze,PARALLEL_OPERATION_TYPES as ze,isApplicationOperation as $e,isOrganisationOperation as er,getParallelDeployGroups as rr,getParallelDestroyGroups as tr,getApplicationDeployOrder as or,getApplicationDestroyOrder as ar,getApplicationStackName as Er,getOrganisationStackName as ir,isApplicationStack as sr,isQuarantineDetail as Sr,isRetainedBucketsDetail as nr,getApplicationStepName as cr,getApplicationStepId as Ar,toPascalCase as lr,isOpenNextPattern as Tr,OPENNEXT_PATTERNS as _r,deriveResourcesFromManifestStacks as pr,STACK_NOT_FOUND_PATTERN as Or,STACK_FAILED_STATE_PATTERN as Pr,CDK_NO_STACKS_MATCH as ur,INFRASTRUCTURE_FILENAME as Rr,ApplicationError as mr,wrapApplicationError as dr,FjallStateFileSchema as Cr,readStateFile as Nr,writeStateFile as Dr,createEmptyState as Lr,deleteStateFile as fr,updateTemplateHash as gr,getStateFilePath as Ir,stubCallerIdentity as xr}from"./types/index.js";import{CloudFormationEventMonitor as Ur}from"./aws/index.js";import{CdkService as vr,CdkArgumentBuilder as kr,CdkProcessManager as Yr,CdkEventMonitor as hr,startStackMonitoring as br,DEFAULT_DEPLOY_TIMEOUT_MS as Vr,isCdkError as Fr,formatInfrastructureError as Br,getStructuralHint as Gr,getSourceContext as Kr,hasCdkDifferences as Xr,parseDiffOutput as Hr,CloudFormationService as wr,CloudFormationError as jr,EcsService as Qr,EcsError as qr,EcsServiceResolver as Jr,TemplateHashService as Wr,TemplateHashError as Zr,CdkContextBuilder as zr,emitProgress as $r,PROGRESS_MESSAGES as et,parseBuildPhase as rt,buildStepContextBuildConfig as tt,convertCloudFormationOutputsToRecord as ot,ApplicationStackService as at}from"./services/index.js";import{CdkError as it}from"./types/errors/index.js";import{BaseServiceError as St,ValidationError as nt,AuthError as ct,AwsError as At,DeploymentError as lt,NetworkError as Tt,FileSystemError as _t,ConfigError as pt,toServiceError as Ot}from"./types/errors/index.js";import{filterDangerousEnvVars as ut,maskSensitiveOutput as Rt,parseShellArgs as mt,sleep as dt}from"@fjall/util";import{artefactOutputKey as Nt,DeployModeSchema as Dt,ServiceArtefactSchema as Lt,ServiceArtefactsSchema as ft}from"@fjall/util";import{hasDockerfile as It}from"./util/dockerfileDetection.js";import{sleepAbortable as yt}from"./util/sleepAbortable.js";import{createSequencedCallbacks as Mt}from"./util/sequencedCallbacks.js";import{fileExists as kt}from"@fjall/util/fsHelpers";import{success as ht,failure as bt,isSuccess as Vt,isFailure as Ft}from"@fjall/generator";import{deploy as Gt}from"./orchestration/index.js";import{destroy as Xt}from"./orchestration/index.js";import{restart as wt,probeSecretsDrift as jt,computeSecretsDrift as Qt,deriveStaleParameters as qt,extractConsumedSsmParameters as Jt}from"./orchestration/index.js";import{partitionAccounts as Zt}from"./orchestration/index.js";import{buildRegionList as $t,buildAccountRegionPairs as eo,cascadeHomeRegion as ro,cascadeOperationKey as to}from"./orchestration/index.js";import{projectScalarSummary as ao,projectAccountRows as Eo}from"./orchestration/index.js";import{reconcileProviderAccounts as so,mergeReconciledProviderAccounts as So}from"./orchestration/index.js";import{decideNextTransition as co,reconcileTrailMigration as Ao,decommissionMemberTrailStorage as lo,ORG_TRAIL_BUCKET_OUTPUT_KEY as To,TRAIL_BUCKET_OUTPUT_KEY as _o,TRAIL_KEY_ARN_OUTPUT_KEY as po}from"./orchestration/index.js";import{unlockBucket as Po,unlockQueue as uo}from"./orchestration/index.js";import{triageBucketPolicy as mo,isEnforceSslStatement as Co,toResourcePolicyStatements as No,restoreBucketPolicy as Do,synthesiseEnforceSslDocument as Lo,ensureEnforceSsl as fo,restoreAndReconcileQuarantinedBucket as go}from"./orchestration/index.js";import{verifyOrgTrailDelivery as xo}from"./aws/cloudtrail/orgTrailDelivery.js";import{assumeRootForTask as Uo,isRootTaskPolicyArn as Mo,ROOT_TASK_POLICY_ARNS as vo,MAX_ROOT_SESSION_SECONDS as ko}from"./aws/sts/assumeRoot.js";import{parseAccountsConfiguration as ho,flattenAccountsToEnvironments as bo,extractAllAccountNames as Vo,accountsConfigToOUTree as Fo,isStringArray as Bo,isAccountsConfig as Go,isOuOnlyAccountBucket as Ko,OU_ONLY_ACCOUNT_BUCKETS as Xo}from"./orchestration/index.js";import{runOpenNextBuild as wo}from"./orchestration/index.js";import{runOrganisationSetup as Qo,ORG_SETUP_PHASES as qo}from"./orchestration/index.js";import{resolveBuildSecrets as Wo,resolveSecretRefValue as Zo,resolveBuildSecretSessionProvider as zo,sourcedRefsFromBuildArgs as $o,resolveBuildArgs as ea,buildBuildSecretSessionPolicy as ra,buildDeploySessionPolicy as ta,partitionForRegion as oa,validateBuildGroup as aa}from"./orchestration/index.js";import{FrameworkRegistry as ia}from"./orchestration/index.js";import{openNextBuilder as Sa,dockerBuilder as na}from"./orchestration/index.js";import{StepRegistry as Aa,getDestroyStepId as la}from"./steps/index.js";import{buildDeployPlan as _a,computeDeployPlan as pa,computeAssemblyDigest as Oa,digestHead as Pa,classifyImpact as ua,derivePropertyChanges as Ra,isDataLoss as ma,isStatefulResourceType as da,renderPlanSummary as Ca,renderPlanLines as Na,toWirePlanChanges as Da,signApprovalToken as La,verifyApprovalToken as fa,DEFAULT_APPROVAL_TTL_MS as ga,APPROVAL_TOKEN_PATTERN as Ia,runApprovalGate as xa}from"./orchestration/index.js";export{X as ALL_PROGRESS_KINDS,we as APPLICATION_DEPLOY_ORDER,je as APPLICATION_DESTROY_ORDER,Xe as APPLICATION_STACKS,K as APPROVAL_DECISIONS,G as APPROVAL_KINDS,Ia as APPROVAL_TOKEN_PATTERN,mr as ApplicationError,at as ApplicationStackService,ct as AuthError,At as AwsError,b as BUILDERS,St as BaseServiceError,v as CASCADE_ACCOUNT_PHASES,n as CASCADE_ACCOUNT_STATUSES,V as CASCADE_OUTCOME_RESULTS,S as CASCADE_PHASES,ur as CDK_NO_STACKS_MATCH,kr as CdkArgumentBuilder,zr as CdkContextBuilder,it as CdkError,hr as CdkEventMonitor,Yr as CdkProcessManager,vr as CdkService,O as ChangeCountsSchema,jr as CloudFormationError,Ur as CloudFormationEventMonitor,wr as CloudFormationService,pt as ConfigError,ga as DEFAULT_APPROVAL_TTL_MS,Vr as DEFAULT_DEPLOY_TIMEOUT_MS,ge as DEFAULT_MAX_PAGES,i as DEPLOYMENT_EVENT_ERROR_MESSAGE_MAX,a as DEPLOYMENT_EVENT_RESOURCE_CATEGORIES,E as DEPLOYMENT_EVENT_STATUS_REASON_MAX,s as DEPLOYMENT_EVENT_TRAIL_DETAIL_MAX,o as DEPLOYMENT_EVENT_TYPES,I as DEPLOY_EVENT_ARN_MAX,P as DEPLOY_EVENT_CONTRACT,u as DEPLOY_EVENT_CONTRACT_MAJOR,R as DEPLOY_EVENT_CONTRACT_MINOR,L as DEPLOY_EVENT_ERROR_MESSAGE_MAX,d as DEPLOY_EVENT_ID_MAX,N as DEPLOY_EVENT_MESSAGE_MAX,C as DEPLOY_EVENT_NAME_MAX,g as DEPLOY_EVENT_OUTPUT_MAX,D as DEPLOY_EVENT_STATUS_REASON_MAX,f as DEPLOY_EVENT_TRAIL_DETAIL_MAX,x as DEPLOY_EVENT_URL_MAX,k as DEPLOY_RESULTS,Y as DETECTION_PHASES,A as DeployEventSchema,Dt as DeployModeSchema,T as DeployResourceSchema,lt as DeploymentError,t as DeploymentEventSchema,qr as EcsError,Qr as EcsService,Jr as EcsServiceResolver,_t as FileSystemError,Cr as FjallStateFileSchema,ia as FrameworkRegistry,Ie as IDENTITY_STORE_PAGE_SIZE,Rr as INFRASTRUCTURE_FILENAME,Fe as INFRASTRUCTURE_STEP_NAMES,Be as INFRA_STEP_NAME,U as LOG_LEVELS,ko as MAX_ROOT_SESSION_SECONDS,Tt as NetworkError,Qe as OPENNEXT_DEPLOY_ORDER,qe as OPENNEXT_DESTROY_ORDER,Ze as OPENNEXT_PARALLEL_GROUPS,_r as OPENNEXT_PATTERNS,He as ORGANISATION_TYPES,qo as ORG_SETUP_PHASES,To as ORG_TRAIL_BUCKET_OUTPUT_KEY,Xo as OU_ONLY_ACCOUNT_BUCKETS,Je as PARALLEL_DEPLOY_GROUPS,We as PARALLEL_DESTROY_GROUPS,ze as PARALLEL_OPERATION_TYPES,h as PHASE_STATUSES,F as PLAN_ACTIONS,et as PROGRESS_MESSAGES,_ as PlanChangeSchema,p as PlanPropertyChangeSchema,Ke as ProgressReporter,B as REPLACEMENT_MODES,ke as ROOT_ACCESS_FEATURES,vo as ROOT_TASK_POLICY_ARNS,Ye as RootAccessEnablementError,Me as SECURITY_SERVICE_PRINCIPALS,ne as SERVICE_PRINCIPALS,M as STACK_CLEANUP_PHASES,Pr as STACK_FAILED_STATE_PATTERN,Or as STACK_NOT_FOUND_PATTERN,y as STEP_COMPLETE_STATUSES,be as STEP_IDS,Ve as STEP_NAMES,m as SUPPORTED_CONTRACT_MARKERS,Lt as ServiceArtefactSchema,ft as ServiceArtefactsSchema,q as SimpleAwsProvider,l as StepProgressSchema,Aa as StepRegistry,_o as TRAIL_BUCKET_OUTPUT_KEY,po as TRAIL_KEY_ARN_OUTPUT_KEY,Zr as TemplateHashError,Wr as TemplateHashService,nt as ValidationError,Fo as accountsConfigToOUTree,de as activateCostAllocationTags,Ae as activateTrustedAccess,Nt as artefactOutputKey,Uo as assumeRootForTask,eo as buildAccountRegionPairs,Re as buildAccountToOUMap,ra as buildBuildSecretSessionPolicy,_a as buildDeployPlan,ta as buildDeploySessionPolicy,$t as buildRegionList,tt as buildStepContextBuildConfig,$ as buildTargetSetUnreadyAdvisory,z as buildTargetUnreadyAdvisory,ro as cascadeHomeRegion,to as cascadeOperationKey,Ce as checkIdentityCentreStatus,te as checkRegionEnabled,W as checkTargetReadiness,ua as classifyImpact,De as composeSdkAbortSignal,Oa as computeAssemblyDigest,pa as computeDeployPlan,Qt as computeSecretsDrift,ot as convertCloudFormationOutputsToRecord,Oe as createAccount,H as createDeployEmitter,Lr as createEmptyState,Mt as createSequencedCallbacks,co as decideNextTransition,lo as decommissionMemberTrailStorage,fr as deleteStateFile,Gt as deploy,Ra as derivePropertyChanges,pr as deriveResourcesFromManifestStacks,qt as deriveStaleParameters,ie as describeOrganisation,Z as describeTargetReadinessReason,Xt as destroy,Pa as digestHead,na as dockerBuilder,xe as drainPages,$r as emitProgress,ve as enableCentralisedRootAccess,le as enableIpamDelegatedAdmin,se as enablePolicyTypes,ce as enableRamSharing,Se as enableServiceAccess,fo as ensureEnforceSsl,Ee as ensureOrganisationExists,Pe as ensureOrganisationalUnitsExist,Vo as extractAllAccountNames,Jt as extractConsumedSsmParameters,Le as extractErrorName,bt as failure,kt as fileExists,ut as filterDangerousEnvVars,pe as findAccount,me as findDevelopmentOuId,bo as flattenAccountsToEnvironments,Br as formatInfrastructureError,or as getApplicationDeployOrder,ar as getApplicationDestroyOrder,Er as getApplicationStackName,Ar as getApplicationStepId,cr as getApplicationStepName,la as getDestroyStepId,ir as getOrganisationStackName,rr as getParallelDeployGroups,tr as getParallelDestroyGroups,Kr as getSourceContext,Ir as getStateFilePath,Gr as getStructuralHint,Xr as hasCdkDifferences,It as hasDockerfile,fe as isAborted,Go as isAccountsConfig,$e as isApplicationOperation,sr as isApplicationStack,Fr as isCdkError,ma as isDataLoss,Co as isEnforceSslStatement,Ft as isFailure,ye as isOULeaf,Tr as isOpenNextPattern,er as isOrganisationOperation,Ko as isOuOnlyAccountBucket,Sr as isQuarantineDetail,nr as isRetainedBucketsDetail,Mo as isRootTaskPolicyArn,da as isStatefulResourceType,Bo as isStringArray,Vt as isSuccess,_e as listAccounts,Rt as maskSensitiveOutput,So as mergeReconciledProviderAccounts,Sa as openNextBuilder,ho as parseAccountsConfiguration,rt as parseBuildPhase,j as parseDeployContractVersion,w as parseDeployEvent,Hr as parseDiffOutput,mt as parseShellArgs,Zt as partitionAccounts,oa as partitionForRegion,ue as placeAccountsInOUs,ee as probeAccountStackExists,jt as probeSecretsDrift,Eo as projectAccountRows,ao as projectScalarSummary,Nr as readStateFile,so as reconcileProviderAccounts,Ao as reconcileTrailMigration,oe as regionDisabledMessage,Ue as registerSecurityDelegates,Na as renderPlanLines,Ca as renderPlanSummary,ea as resolveBuildArgs,zo as resolveBuildSecretSessionProvider,Wo as resolveBuildSecrets,Zo as resolveSecretRefValue,wt as restart,go as restoreAndReconcileQuarantinedBucket,Do as restoreBucketPolicy,xa as runApprovalGate,wo as runOpenNextBuild,Qo as runOrganisationSetup,La as signApprovalToken,dt as sleep,yt as sleepAbortable,Ne as snapshotIdentityCentre,$o as sourcedRefsFromBuildArgs,br as startStackMonitoring,xr as stubCallerIdentity,ht as success,Lo as synthesiseEnforceSslDocument,lr as toPascalCase,No as toResourcePolicyStatements,Ot as toServiceError,Da as toWirePlanChanges,mo as triageBucketPolicy,Po as unlockBucket,uo as unlockQueue,Te as updateBackupGlobalSettings,gr as updateTemplateHash,aa as validateBuildGroup,fa as verifyApprovalToken,xo as verifyOrgTrailDelivery,dr as wrapApplicationError,Dr as writeStateFile};
@@ -1 +1 @@
1
- import{join as $}from"path";import{CloudFormationClient as V}from"@aws-sdk/client-cloudformation";import{EC2Client as X}from"@aws-sdk/client-ec2";import{success as P,failure as g}from"@fjall/generator";import{logger as T}from"@fjall/util/logger";import{imageTagParameterName as Z,maskSensitiveOutput as y}from"@fjall/util";import{parseDockerServicesFromManifest as ee}from"@fjall/util/manifest";import{stubCallerIdentity as te}from"../../types/deployment/index.js";import{getApplicationDeployOrder as oe,getApplicationStackName as re,getApplicationStepName as W,getApplicationStepId as q,APPLICATION_STACKS as O}from"../../types/operations.js";import{CdkContextBuilder as ne}from"../../services/supporting/CdkContextBuilder.js";import{buildParamsContext as ae,bootstrapOrFail as ie}from"../contextHelpers.js";import{runDetectionPipeline as se}from"./detectionPipeline.js";import{STEP_IDS as N,STEP_NAMES as ce}from"../../types/stepDefinitions.js";import{StepRegistry as le}from"../../steps/stepRegistry.js";import{checkTargetReadiness as de}from"../../aws/targetReadiness.js";import{cascadeHomeRegion as pe}from"../organisation/cascadeHelpers.js";import{withStepLifecycle as ue}from"../stepLifecycle.js";import{DOCKER_BUILD_STEP_NAME as z,runDockerBuild as fe}from"./dockerBuildHelper.js";import{getParallelPhase2Stacks as ge,deployParallelPhase as me,deployStackSequential as ke,runDockerPreCompute as ye,createBuildCallbacks as he}from"./applicationDeployHelpers.js";import{deployCodeOnly as Se}from"./codeOnlyDeploy.js";import{collectFullDeployArtefacts as be}from"./artefactCollection.js";import{runApprovalGate as De}from"./approvalGate.js";const R="applicationDeploy";function we(i){const s=Object.entries(i);if(s.length===0)return;const n={};for(const[t,a]of s){const h=Z(t);n[h]=a}return n}async function qe(i,s,n){const{callbacks:t,options:a}=i,h=Date.now(),A=a?.mode??(a?.imageTag?"rollback":a?.deployOnly?"code-only":"full");if(a?.mode!==void 0&&a.mode!=="rollback"&&a.imageTag){const e=new Error(`--image-tag requires rollback mode (explicit mode '${a.mode}' was supplied alongside an image tag)`);return t.onError?.(e),g(e)}if(a?.mode==="rollback"&&!a.imageTag){const e=new Error("rollback mode requires --image-tag <tag> naming the image to roll to");return t.onError?.(e),g(e)}const w=ne.buildDeploymentContext({deployType:"application",target:n.appName,path:n.path,region:s.awsProvider.getRegion(),callerIdentity:te(s.awsProvider.getAccountId()),...ae({orgConfig:i.orgConfig,identity:i.identity,skipOidc:i.options?.skipOidc})},{verbose:a?.verbose,infraOnly:a?.infraOnly},i.orgConfig),p=s.frameworkRegistry.resolve({appPath:n.path});let C;const b=A==="rollback";if(p&&!b){C=p.builder.plan({appPath:n.path},p.detection);const e=he(t),r=await p.builder.build(n.path,C,e,{skipBuild:a?.skipBuild,infraOnly:a?.infraOnly});if(!r.success){const o=new Error(y(r.error.message));return t.onError?.(o),g(o)}}if(A==="code-only"||b){t.onLog?.(b?`Rollback mode \u2014 rolling to image tag ${a?.imageTag}`:"Deploy-only mode \u2014 skipping infrastructure pipeline","info");const e=$(n.path,"cdk.out"),r=ee(e),o=r.length>0,d=p?.detection.hasDockerfile===!0,l=o||d;T.debug(R,"Deploy-only branch entered",{mode:A,imageTag:a?.imageTag,appName:n.appName,appPath:n.path,cdkOutPath:e,dockerProviderAvailable:i.dockerProvider!==void 0,builderName:p?.builder.name,hasDockerfileFromManifest:o,hasDockerfileFromDisk:d,hasDockerfile:l,manifestDockerServiceCount:r.length,manifestDockerPaths:r.map(k=>k.docker.path)}),!o&&!d&&t.onLog?.("No Dockerfile detected via manifest or appPath \u2014 skipping Docker build. If this app uses a cross-repo Dockerfile, ensure a full deploy has run first to populate cdk.out/fjall-manifest.json.","warn");let u={};if(!b&&i.dockerProvider!==void 0&&l){T.debug(R,"Running Docker build before code-only deploy",{source:o?"manifest":"disk"});const k=await fe(i,s,n,t);if(!k.success)return g(k.error);u=k.data.contentHashTagsByService}else T.debug(R,"Skipping Docker build",{reason:b?"rollback":i.dockerProvider===void 0?"no dockerProvider":"no Dockerfile detected"});return Se(i,s,n,u)}t.onLog?.("Analysing infrastructure\u2026","info");const v=await se(n,s,w,t);if(!v.success){const e=new Error(y(v.error.message));return t.onError?.(e),g(e)}const c=v.data;try{await t.onDetectionComplete?.({...c,builderName:p?.builder.name??"unknown"})}catch(e){const r=e instanceof Error?e.message:String(e),o=new Error(y(r));return t.onError?.(o),g(o)}const Y={deploymentType:"application",operation:"deploy",deployOnly:!1,infraOnly:a?.infraOnly??!1,hasDockerfile:c.hasDockerfile,pattern:c.pattern,resources:c.resources,...p&&{builderName:p.builder.name}},M=le.getSteps(Y),L=M.findIndex(e=>e.id===N.TARGET_READINESS),_=await ue(t,{stepId:N.TARGET_READINESS,stepName:ce.TARGET_READINESS,...L>=0&&{stepIndex:L,totalSteps:M.length}},async()=>{if(a?.skipReadinessCheck)return t.onLog?.("Skipping target readiness check (--skip-readiness-check)","warn"),{kind:"skipped",data:void 0};const e=s.awsProvider.getAccountId(),r=i.orgConfig?.providerAccounts.find(u=>u.id===e)?.name,o=s.awsProvider.getCredentials(),d=pe(i.orgConfig),l=await de({cloudFormation:s.awsProvider.getClient(V),ec2:new X({region:d,...o!==void 0&&{credentials:o}})},{id:e,...r!==void 0&&{name:r}},s.awsProvider.getRegion(),i.orgConfig,i.abortSignal);return l.success?l.data.ready?{kind:"completed",data:void 0}:{kind:"error",error:new Error(y(l.data.advisory))}:{kind:"error",error:new Error(y(l.error.message))}},i.abortSignal);if(!_.success)return _;const m=C?C.deployOrder:oe({pattern:c.pattern,resources:c.resources}),S=m.length,x=c.hasDockerfile&&i.dockerProvider!==void 0&&m.includes(O.COMPUTE)&&a?.infraOnly!==!0;if(!c.hasDifferences&&!a?.force&&!x){t.onLog?.("No infrastructure changes detected","info"),t.onLog?.(a?.infraOnly===!0?"Infrastructure-only mode \u2014 application code was not rebuilt. Run with --deploy-only to deploy code changes.":"Nothing to deploy. Run with --deploy-only to redeploy application code.","info");const e=c.hasDockerfile&&i.dockerProvider!==void 0&&m.includes(O.COMPUTE);for(let o=0;o<m.length;o++){const d=m[o];e&&d===O.COMPUTE&&(t.onStepStart?.(N.DOCKER_OPERATIONS,z),t.onStepComplete?.(N.DOCKER_OPERATIONS,z,"skipped"));const l=q(d,"deploy"),u=W(d,"deploy");t.onStepStart?.(l,u,o,S),t.onStepComplete?.(l,u,"skipped",o,S)}const r=await s.stackService.resolveWebsiteUrl(n.appName);return P({target:n.appName,deploymentType:"application",outputs:r?{websiteUrl:r}:void 0,noChanges:!0,artefacts:[],durationMs:Date.now()-h})}if(i.approvalGate!==void 0||a?.approvalToken!==void 0&&a.approvalToken!==""||a?.planOnly===!0){const e=$(n.path,"cdk.out"),r=await De({approvalGate:i.approvalGate,options:a??{},target:n.appName,cdkOutPath:e,detection:{stackChanges:c.stackChanges,currentHashes:c.currentHashes},cfnService:s.cfnService,callbacks:t});if(!r.success)return r;const o=r.data;if(o.kind==="rejected")return t.onLog?.(`Deploy rejected: ${y(o.reason)}`,"warn"),P({target:n.appName,deploymentType:"application",rejected:!0,artefacts:[],...o.planDetail!==void 0?{planDetail:o.planDetail}:{},durationMs:Date.now()-h});if(o.kind==="awaiting")return P({target:n.appName,deploymentType:"application",awaitingApproval:!0,approvalToken:o.approvalToken,assemblyDigest:o.assemblyDigest,planDetail:o.planDetail,artefacts:[],durationMs:Date.now()-h})}const B=await ie(s,w,t);if(!B.success)return B;const D={},E=new Map;let H;for(let e=0;e<m.length;e++){const r=m[e],o=re(n.appName,r),d=q(r,"deploy"),l=W(r,"deploy"),u=c.stackChanges.get(o)??!0,k=r===O.COMPUTE&&x;if(!u&&!a?.force&&!k){t.onStepStart?.(d,l,e,S),t.onLog?.(`Skipping ${r} \u2014 no changes detected`,"info"),t.onStepComplete?.(d,l,"skipped",e,S);continue}const I=ge(m,e,c.stackChanges,n.appName,a?.force);if(I.length>=2){const K=await me(I,n,s,w,t,e,S,c,D,E);if(!K.success)return g(K.error);e+=I.length-1;continue}const f=await ye(r,i,s,n,t,c.hasDockerfile);if(f!==null&&!f.success)return g(f.error);f!==null&&f.success&&(H=f.data);const Q=f!==null&&f.success?f.data.contentHashTagsByService:{},G=we(Q),j=await ke(r,s,w,t,e,S,D,G!==void 0?{parameters:G}:void 0);if(!j.success)return g(j.error);const F=c.currentHashes.get(o);F&&E.set(o,F)}if(E.size>0){const e=await s.hashService.updateStateAfterDeploy(n.path,E);if(!e.success){const r=y(e.error.message);T.debug(R,"Failed to update state file",{error:r}),t.onLog?.(`Warning: failed to update state file \u2014 next deploy may re-deploy unchanged stacks: ${r}`,"warn")}}const U=await s.stackService.resolveWebsiteUrl(n.appName);U&&(D.websiteUrl=U);const J=await be(s,n.appName,H,t);return P({target:n.appName,deploymentType:"application",outputs:Object.keys(D).length>0?D:void 0,artefacts:J,durationMs:Date.now()-h})}export{qe as deployApplication};
1
+ import{join as Y}from"path";import{CloudFormationClient as ne}from"@aws-sdk/client-cloudformation";import{EC2Client as oe}from"@aws-sdk/client-ec2";import{success as v,failure as l}from"@fjall/generator";import{logger as N}from"@fjall/util/logger";import{ImageTagSchema as ie,imageTagParameterName as se,maskSensitiveOutput as k}from"@fjall/util";import{parseDockerServicesFromManifest as ce}from"@fjall/util/manifest";import{stubCallerIdentity as le}from"../../types/deployment/index.js";import{getApplicationDeployOrder as de,getApplicationStackName as J,getApplicationStepName as Q,getApplicationStepId as V,APPLICATION_STACKS as S}from"../../types/operations.js";import{CdkContextBuilder as pe}from"../../services/supporting/CdkContextBuilder.js";import{buildParamsContext as ue,bootstrapOrFail as ge}from"../contextHelpers.js";import{runDetectionPipeline as fe}from"./detectionPipeline.js";import{STEP_IDS as R,STEP_NAMES as me}from"../../types/stepDefinitions.js";import{StepRegistry as ke}from"../../steps/stepRegistry.js";import{checkTargetReadiness as ye}from"../../aws/targetReadiness.js";import{cascadeHomeRegion as he}from"../organisation/cascadeHelpers.js";import{withStepLifecycle as be}from"../stepLifecycle.js";import{DOCKER_BUILD_STEP_NAME as X,runDockerBuild as Se}from"./dockerBuildHelper.js";import{getParallelPhase2Stacks as we,deployParallelPhase as Te,deployStackSequential as De,runDockerPreCompute as ve,createBuildCallbacks as Ee}from"./applicationDeployHelpers.js";import{captureDatabaseConnectionOutputs as Ce,captureServiceTaskDefinitions as Oe,reconcileDatabaseEndpointChange as Ie}from"./databaseEndpointReconcile.js";import{deployCodeOnly as Ne}from"./codeOnlyDeploy.js";import{collectFullDeployArtefacts as Re}from"./artefactCollection.js";import{runApprovalGate as Pe}from"./approvalGate.js";const P="applicationDeploy";function Ae(e){const i=Object.entries(e);if(i.length===0)return;const a={};for(const[r,s]of i){const h=se(r);a[h]=s}return a}function Me(e){if(e?.mode!==void 0&&e.mode!=="rollback"){if(e.imageTag)return l(new Error(`--image-tag requires rollback mode (explicit mode '${e.mode}' was supplied alongside an image tag)`));if(e.serviceImageTags)return l(new Error(`serviceImageTags requires rollback mode (explicit mode '${e.mode}' was supplied alongside serviceImageTags)`))}if(e?.mode==="rollback"){if(e.imageTag&&e.serviceImageTags)return l(new Error("rollback mode accepts exactly one of --image-tag or serviceImageTags \u2014 they are mutually exclusive"));if(!e.imageTag&&!e.serviceImageTags)return l(new Error("rollback mode requires --image-tag <tag> or serviceImageTags naming the image(s) to roll to"))}if(e?.serviceImageTags!==void 0){if(e.serviceName)return l(new Error("serviceImageTags already names its services \u2014 --service cannot be combined with it"));const i=Object.entries(e.serviceImageTags);if(i.length===0)return l(new Error("serviceImageTags must name at least one service"));for(const[a,r]of i){const s=ie.safeParse(r);if(!s.success)return l(new Error(`serviceImageTags.${a} is not a valid image tag: ${s.error.message}`))}}return v(void 0)}async function nt(e,i,a){const{callbacks:r,options:s}=e,h=Date.now(),E=s?.mode??(s?.imageTag||s?.serviceImageTags?"rollback":s?.deployOnly?"code-only":"full");if(E==="restart"){const t=new Error('mode "restart" is not a deploy \u2014 call the restart() entry point (fjall rollout) instead');return r.onError?.(t),l(t)}const B=Me(s);if(!B.success){const t=k(B.error.message),n=new Error(t);return r.onError?.(n),l(n)}const C=pe.buildDeploymentContext({deployType:"application",target:a.appName,path:a.path,region:i.awsProvider.getRegion(),callerIdentity:le(i.awsProvider.getAccountId()),...ue({orgConfig:e.orgConfig,identity:e.identity,skipOidc:e.options?.skipOidc})},{verbose:s?.verbose,infraOnly:s?.infraOnly},e.orgConfig),u=i.frameworkRegistry.resolve({appPath:a.path});let O;const w=E==="rollback";if(u&&!w){O=u.builder.plan({appPath:a.path},u.detection);const t=Ee(r),n=await u.builder.build(a.path,O,t,{skipBuild:s?.skipBuild,infraOnly:s?.infraOnly});if(!n.success){const o=new Error(k(n.error.message));return r.onError?.(o),l(o)}}if(E==="code-only"||w){r.onLog?.(w?s?.serviceImageTags?`Rollback mode \u2014 rolling ${Object.keys(s.serviceImageTags).length} service(s) to their release-recorded image tags`:`Rollback mode \u2014 rolling to image tag ${s?.imageTag}`:"Deploy-only mode \u2014 skipping infrastructure pipeline","info");const t=Y(a.path,"cdk.out"),n=ce(t),o=n.length>0,p=u?.detection.hasDockerfile===!0,d=o||p;N.debug(P,"Deploy-only branch entered",{mode:E,imageTag:s?.imageTag,serviceImageTags:s?.serviceImageTags,appName:a.appName,appPath:a.path,cdkOutPath:t,dockerProviderAvailable:e.dockerProvider!==void 0,builderName:u?.builder.name,hasDockerfileFromManifest:o,hasDockerfileFromDisk:p,hasDockerfile:d,manifestDockerServiceCount:n.length,manifestDockerPaths:n.map(y=>y.docker.path)}),!o&&!p&&r.onLog?.("No Dockerfile detected via manifest or appPath \u2014 skipping Docker build. If this app uses a cross-repo Dockerfile, ensure a full deploy has run first to populate cdk.out/fjall-manifest.json.","warn");let f={};if(!w&&e.dockerProvider!==void 0&&d){N.debug(P,"Running Docker build before code-only deploy",{source:o?"manifest":"disk"});const y=await Se(e,i,a,r);if(!y.success)return l(y.error);f=y.data.contentHashTagsByService}else N.debug(P,"Skipping Docker build",{reason:w?"rollback":e.dockerProvider===void 0?"no dockerProvider":"no Dockerfile detected"});return Ne(e,i,a,f)}r.onLog?.("Analysing infrastructure\u2026","info");const A=await fe(a,i,C,r);if(!A.success){const t=new Error(k(A.error.message));return r.onError?.(t),l(t)}const c=A.data;try{await r.onDetectionComplete?.({...c,builderName:u?.builder.name??"unknown"})}catch(t){const n=t instanceof Error?t.message:String(t),o=new Error(k(n));return r.onError?.(o),l(o)}const Z={deploymentType:"application",operation:"deploy",deployOnly:!1,infraOnly:s?.infraOnly??!1,hasDockerfile:c.hasDockerfile,pattern:c.pattern,resources:c.resources,...u&&{builderName:u.builder.name}},L=ke.getSteps(Z),_=L.findIndex(t=>t.id===R.TARGET_READINESS),j=await be(r,{stepId:R.TARGET_READINESS,stepName:me.TARGET_READINESS,..._>=0&&{stepIndex:_,totalSteps:L.length}},async()=>{if(s?.skipReadinessCheck)return r.onLog?.("Skipping target readiness check (--skip-readiness-check)","warn"),{kind:"skipped",data:void 0};const t=i.awsProvider.getAccountId(),n=e.orgConfig?.providerAccounts.find(f=>f.id===t)?.name,o=i.awsProvider.getCredentials(),p=he(e.orgConfig),d=await ye({cloudFormation:i.awsProvider.getClient(ne),ec2:new oe({region:p,...o!==void 0&&{credentials:o}})},{id:t,...n!==void 0&&{name:n}},i.awsProvider.getRegion(),e.orgConfig,e.abortSignal);return d.success?d.data.ready?{kind:"completed",data:void 0}:{kind:"error",error:new Error(k(d.data.advisory))}:{kind:"error",error:new Error(k(d.error.message))}},e.abortSignal);if(!j.success)return j;const g=O?O.deployOrder:de({pattern:c.pattern,resources:c.resources}),b=g.length,H=c.hasDockerfile&&e.dockerProvider!==void 0&&g.includes(S.COMPUTE)&&s?.infraOnly!==!0;if(!c.hasDifferences&&!s?.force&&!H){r.onLog?.("No infrastructure changes detected","info"),r.onLog?.(s?.infraOnly===!0?"Infrastructure-only mode \u2014 application code was not rebuilt. Run with --deploy-only to deploy code changes.":"Nothing to deploy. Run with --deploy-only to redeploy application code.","info");const t=c.hasDockerfile&&e.dockerProvider!==void 0&&g.includes(S.COMPUTE);for(let o=0;o<g.length;o++){const p=g[o];t&&p===S.COMPUTE&&(r.onStepStart?.(R.DOCKER_OPERATIONS,X),r.onStepComplete?.(R.DOCKER_OPERATIONS,X,"skipped"));const d=V(p,"deploy"),f=Q(p,"deploy");r.onStepStart?.(d,f,o,b),r.onStepComplete?.(d,f,"skipped",o,b)}const n=await i.stackService.resolveWebsiteUrl(a.appName);return v({target:a.appName,deploymentType:"application",outputs:n?{websiteUrl:n}:void 0,noChanges:!0,artefacts:[],durationMs:Date.now()-h})}if(e.approvalGate!==void 0||s?.approvalToken!==void 0&&s.approvalToken!==""||s?.planOnly===!0){const t=Y(a.path,"cdk.out"),n=await Pe({approvalGate:e.approvalGate,options:s??{},target:a.appName,cdkOutPath:t,detection:{stackChanges:c.stackChanges,currentHashes:c.currentHashes},cfnService:i.cfnService,callbacks:r});if(!n.success)return n;const o=n.data;if(o.kind==="rejected")return r.onLog?.(`Deploy rejected: ${k(o.reason)}`,"warn"),v({target:a.appName,deploymentType:"application",rejected:!0,artefacts:[],...o.planDetail!==void 0?{planDetail:o.planDetail}:{},durationMs:Date.now()-h});if(o.kind==="awaiting")return v({target:a.appName,deploymentType:"application",awaitingApproval:!0,approvalToken:o.approvalToken,assemblyDigest:o.assemblyDigest,planDetail:o.planDetail,artefacts:[],durationMs:Date.now()-h})}const U=await ge(i,C,r);if(!U.success)return U;const G=J(a.appName,S.DATABASE),M=g.includes(S.DATABASE)&&((c.stackChanges.get(G)??!0)||s?.force===!0),ee=M?await Ce(i.cfnService,a.appName):void 0,te=M?await Oe(i.ecsResolver,i.ecsService,a.appName):void 0,T={},D=new Map;let $;const I=async()=>{!M||!D.has(G)||await Ie({appName:a.appName,connectionOutputsBefore:ee,serviceTaskDefinitionsBefore:te,services:{cfnService:i.cfnService,ecsService:i.ecsService,ecsResolver:i.ecsResolver},callbacks:r,...e.confirmDatabaseEndpointRestart!==void 0?{confirmRestart:e.confirmDatabaseEndpointRestart}:{},...e.abortSignal!==void 0?{abortSignal:e.abortSignal}:{}})};for(let t=0;t<g.length;t++){const n=g[t],o=J(a.appName,n),p=V(n,"deploy"),d=Q(n,"deploy"),f=c.stackChanges.get(o)??!0,y=n===S.COMPUTE&&H;if(!f&&!s?.force&&!y){r.onStepStart?.(p,d,t,b),r.onLog?.(`Skipping ${n} \u2014 no changes detected`,"info"),r.onStepComplete?.(p,d,"skipped",t,b);continue}const x=we(g,t,c.stackChanges,a.appName,s?.force);if(x.length>=2){const z=await Te(x,a,i,C,r,t,b,c,T,D);if(!z.success)return await I(),l(z.error);t+=x.length-1;continue}const m=await ve(n,e,i,a,r,c.hasDockerfile);if(m!==null&&!m.success)return await I(),l(m.error);m!==null&&m.success&&($=m.data);const ae=m!==null&&m.success?m.data.contentHashTagsByService:{},K=Ae(ae),W=await De(n,i,C,r,t,b,T,K!==void 0?{parameters:K}:void 0);if(!W.success)return await I(),l(W.error);const q=c.currentHashes.get(o);q&&D.set(o,q)}if(await I(),D.size>0){const t=await i.hashService.updateStateAfterDeploy(a.path,D);if(!t.success){const n=k(t.error.message);N.debug(P,"Failed to update state file",{error:n}),r.onLog?.(`Warning: failed to update state file \u2014 next deploy may re-deploy unchanged stacks: ${n}`,"warn")}}const F=await i.stackService.resolveWebsiteUrl(a.appName);F&&(T.websiteUrl=F);const re=await Re(i,a.appName,$,r);return v({target:a.appName,deploymentType:"application",outputs:Object.keys(T).length>0?T:void 0,artefacts:re,durationMs:Date.now()-h})}export{nt as deployApplication};
@@ -54,6 +54,12 @@ export declare function rebaseRollbackTagForService(suppliedTag: string, service
54
54
  * per service via `rebaseRollbackTagForService` so each service rolls its OWN
55
55
  * `<service>-…` image family (a multi-image stack shares one ECR repo, so an
56
56
  * un-rebased tag would silently roll the wrong service's image).
57
+ *
58
+ * `options.serviceImageTags` (F12) is a THIRD, mutually-exclusive tag source:
59
+ * an exact per-service map (bare service name → tag) recorded by a prior
60
+ * Release. Unlike `imageTag`, map-supplied tags are never re-based — they
61
+ * already name the exact service — and never fall back to tag pinning; see
62
+ * `serviceImageTagsRollback.ts`.
57
63
  */
58
64
  export declare function deployCodeOnly(params: DeployParams, services: DeployServices, operation: ApplicationOperation, contentHashTagsByService: Record<string, string>): Promise<Result<DeployResult>>;
59
65
  /**
@@ -1 +1 @@
1
- import{join as N}from"path";import{success as W,failure as C}from"@fjall/generator";import{logger as b}from"@fjall/util/logger";import{artefactOutputKey as L,maskSensitiveOutput as y}from"@fjall/util";import{parseDockerServicesFromManifest as q}from"@fjall/util/manifest";import{APPLICATION_STACKS as _,getApplicationStepId as z,getApplicationStepName as G}from"../../types/operations.js";import{extractEcsServiceName as U}from"../../aws/utils/arnParser.js";import{stripTagOrDigest as j}from"../../services/infrastructure/EcrImageInspectorService.js";import{withStepLifecycle as K}from"../stepLifecycle.js";const I="codeOnlyDeploy";function H(t,c){if(t)return c[t.name]}function X(t,c){const r=c.toLowerCase();return t.find(e=>e.name.toLowerCase()===r)??t.find(e=>r.includes(e.name.toLowerCase()))}function Y(t,c,r){const e=c.toLowerCase(),l=t.toLowerCase(),g=r.map(m=>m.toLowerCase()).filter(m=>m!==""&&l.startsWith(`${m}-`)).sort((m,E)=>E.length-m.length)[0];if(g===void 0||g===e)return t;const v=t.slice(g.length);return`${e}${v}`}const J=0,Q=1;async function le(t,c,r,e){const{callbacks:l,options:g}=t,v=Date.now(),m=z(_.COMPUTE,"deploy"),E=G(_.COMPUTE,"deploy");return K(l,{stepId:m,stepName:E,stepIndex:J,totalSteps:Q},async()=>{const d=g?.imageTag;d&&l.onLog?.(`Rolling to supplied image tag ${d}`,"info");const S=q(N(r.path,"cdk.out"));b.debug(I,"Code-only rollout starting",{explicitRollbackTag:d,workingDirectory:t.workingDirectory,contentHashTagsByService:e,manifestServiceCount:S.length,manifestServices:S.map(i=>({name:i.name,target:i.docker.target}))}),l.onLog?.("Discovering ECS cluster and services\u2026","info");const h=await c.ecsResolver.getDeployableClusterAndServices(r.appName);if(!h.success)return b.debug(I,"ECS discovery failed",{appName:r.appName,error:y(h.error.message)}),{kind:"error",error:h.error};const{clusterArn:a,serviceArns:$}=h.data;if(b.debug(I,"ECS discovery complete",{appName:r.appName,clusterArn:a,serviceCount:$.length,serviceArns:$}),!a||$.length===0){const i=`No deployable ECS services found for ${r.appName}`;return l.onLog?.(i,"warn"),{kind:"skipped",data:{target:r.appName,deploymentType:"application",durationMs:Date.now()-v,noChanges:!0,artefacts:[]}}}const T=g?.serviceName?.toLowerCase(),w=T===""?void 0:T,f=w?$.filter(i=>{const u=(U(i)??"").toLowerCase();return u===w||u.endsWith(w)}):$;if(w&&f.length===0){const i=$.map(u=>U(u)??"unknown").join(", ");return{kind:"error",error:new Error(`Service '${w}' not found. Available: ${i||"none"}`)}}const D={},O=[],p=[],A=$.map(i=>U(i)).filter(i=>i!==void 0&&i!=="");for(let i=0;i<f.length;i++){const u=f[i];if(!u)continue;const n=U(u)??`service-${i+1}`,R=`[${i+1}/${f.length}] ${n}`,M=X(S,n),x=H(M,e);let o,P;if(d!==void 0)o=Y(d,n,A),o!==d?(P=d,l.onLog?.(`Re-based image tag ${d} \u2192 ${o} for ${n}`,"info")):A.length>1&&!d.toLowerCase().startsWith(`${n.toLowerCase()}-`)&&l.onLog?.(`Image tag ${d} has no recognised service prefix; applying verbatim to ${n} \u2014 confirm it points at this service's image`,"warn");else if(x!==void 0)o=x;else{const F=`No image available to deploy for ${n}: no content-hash tag was produced this run. Run a full deploy to build the image, or pass --image-tag <tag> to roll a specific existing image.`;l.onLog?.(F,"warn"),p.push(`${n}: ${F}`);continue}b.debug(I,"Starting service rollout",{serviceName:n,serviceArn:u,imageTag:o,explicitRollbackTag:d,contentHashTag:x,manifestServiceMatched:M?.name,dockerTarget:M?.docker.target,index:i,total:f.length}),l.onECSUpdate?.(`${R}: rolling out image tag ${o}`,n);const s=await V(c,a,u,n,o,l,P);if(b.debug(I,"Service rollout completed",{serviceName:n,success:s.success,...s.success?{taskDefinitionArn:s.data.taskDefinitionArn,imageDigest:s.data.imageDigest,durationMs:s.data.durationMs}:{error:y(s.error.message)}}),!s.success){p.push(`${n}: ${y(s.error.message)}`);continue}D[L(n,"TaskDefinition")]=s.data.taskDefinitionArn,D[L(n,"PreviousTaskDefinition")]=s.data.previousTaskDefinitionArn,D[L(n,"ImageTag")]=o,D[L(n,"ImageUri")]=s.data.imageUri,D[L(n,"EcrRepositoryArn")]=s.data.ecrRepositoryArn,s.data.imageDigest!==void 0&&(D[L(n,"ImageDigest")]=s.data.imageDigest),O.push({serviceName:n,imageTag:o,imageUri:s.data.imageUri,taskDefinitionArn:s.data.taskDefinitionArn,previousTaskDefinitionArn:s.data.previousTaskDefinitionArn,...s.data.imageDigest!==void 0?{imageDigest:s.data.imageDigest}:{},...s.data.ecrRepositoryArn!==""?{ecrRepositoryArn:s.data.ecrRepositoryArn}:{}})}const k=Date.now()-v;if(p.length>0){const i=f.length-p.length,u=f.length,n=p.length===u?`All ${u} ECS service rollouts failed: ${p.join("; ")}`:`Partial rollout: ${i}/${u} services succeeded. Failures: ${p.join("; ")}`;return{kind:"error",error:new Error(n)}}return{kind:"completed",data:{target:r.appName,deploymentType:"application",outputs:Object.keys(D).length>0?D:void 0,artefacts:O,durationMs:k}}},t.abortSignal)}async function V(t,c,r,e,l,g,v){const m=Date.now(),E=await t.ecsService.describeServices(c,[r]);if(!E.success)return C(new Error(`Failed to describe service ${e}: ${E.error.message}`));const d=E.data[0];if(!d?.taskDefinition)return C(new Error(`Service ${e} has no task definition`));const S=d.taskDefinition,h=await t.ecsService.getLatestTaskDefinition(S);if(!h.success)return C(new Error(`Failed to fetch task definition for ${e}: ${h.error.message}`));const a=h.data;if(!a||!a.containerDefinitions||a.containerDefinitions.length===0)return C(new Error(`Task definition for ${e} has no container definitions`));const $=a.containerDefinitions.find(o=>o.image)?.image;if(!$)return C(new Error(`Task definition for ${e} has no container image \u2014 cannot derive repository URI`));const T=j($),w=await t.ecrImageInspector.getImageDigest(T,l);let f;if(w.success)f=w.data;else{if(v!==void 0)return C(new Error(`Re-based image tag '${l}' (from supplied '${v}') was not found in ECR for ${e}: ${y(w.error.message)}. The supplied tag's build has no image for ${e} \u2014 supply that service's own tag, or pass --service to roll a single service.`));g.onLog?.(`Could not resolve image digest for ${e} (${y(w.error.message)}); falling back to tag pinning`,"warn")}const D=o=>f!==void 0?`${o}@${f}`:`${o}:${l}`,O=a.containerDefinitions.map(o=>o.image?{...o,image:D(j(o.image))}:o),p=a.family;if(!p)return C(new Error(`Task definition for ${e} has no family`));const A=await t.ecsService.registerTaskDefinition({family:p,taskRoleArn:a.taskRoleArn,executionRoleArn:a.executionRoleArn,networkMode:a.networkMode,containerDefinitions:O,volumes:a.volumes,placementConstraints:a.placementConstraints,requiresCompatibilities:a.requiresCompatibilities,cpu:a.cpu,memory:a.memory,runtimePlatform:a.runtimePlatform,proxyConfiguration:a.proxyConfiguration,inferenceAccelerators:a.inferenceAccelerators,pidMode:a.pidMode,ipcMode:a.ipcMode,ephemeralStorage:a.ephemeralStorage});if(!A.success)return C(new Error(`Failed to register task definition for ${e}: ${A.error.message}`));const k=A.data;g.onTaskDefRegistered?.({serviceName:e,taskDefinitionArn:k,previousTaskDefinitionArn:S,revision:B(k),family:p,...f!==void 0?{imageDigest:f}:{}});const i=await t.ecsService.updateService(c,r,{taskDefinition:k,forceNewDeployment:!0});if(!i.success)return C(new Error(`Failed to update service ${e}: ${i.error.message}`));const u=await t.ecsService.pollDeployment({clusterArn:c,serviceArn:r,waitForCompletion:!0,progressCallback:o=>{const P=o.latestEvent?` \u2014 ${y(o.latestEvent)}`:"";g.onECSProgress?.(`${e}: ${y(o.message)}${P}`,o.percentComplete)}}),n=Date.now()-m;if(!u.success){const o=y(u.error.message);return g.onECSComplete?.({serviceName:e,serviceArn:r,success:!1,taskDefinitionArn:k,durationMs:n,reason:o}),C(new Error(`ECS rollout failed for ${e}: ${o}`))}const R=await t.ecsService.getDeploymentStatus(c,r);g.onECSComplete?.({serviceName:e,serviceArn:r,success:!0,taskDefinitionArn:k,durationMs:n,finalRunningCount:R.success?R.data.runningCount:void 0,finalDesiredCount:R.success?R.data.desiredCount:void 0}),b.debug(I,`Rolled out ${e} successfully`,{serviceArn:r,newTaskDefArn:k,previousTaskDefArn:S,durationMs:n});const M=`${T}:${l}`,x=Z(T);return W({taskDefinitionArn:k,previousTaskDefinitionArn:S,imageUri:M,ecrRepositoryArn:x,imageDigest:f,durationMs:n})}function Z(t){const c=t.match(/^(\d{12})\.dkr\.ecr\.([a-z0-9-]+)\.amazonaws\.com\/(.+)$/);if(!c)return"";const[,r,e,l]=c;return`arn:aws:ecr:${e}:${r}:repository/${l}`}function B(t){const c=t.lastIndexOf(":");if(c<0)return 0;const r=t.slice(c+1);return/^\d+$/.test(r)?parseInt(r,10):0}export{le as deployCodeOnly,Z as deriveEcrRepositoryArn,H as lookupContentHashTag,Y as rebaseRollbackTagForService};
1
+ import{join as W}from"path";import{success as H,failure as O}from"@fjall/generator";import{logger as A}from"@fjall/util/logger";import{artefactOutputKey as I,imageTagParameterName as X,maskSensitiveOutput as C}from"@fjall/util";import{parseDockerServicesFromManifest as Y}from"@fjall/util/manifest";import{APPLICATION_STACKS as x,getApplicationStackName as q,getApplicationStepId as J,getApplicationStepName as Q}from"../../types/operations.js";import{extractEcsServiceName as L}from"../../aws/utils/arnParser.js";import{isAborted as K}from"../../aws/organisations/types.js";import{stripTagOrDigest as V}from"../../services/infrastructure/EcrImageInspectorService.js";import{withStepLifecycle as Z}from"../stepLifecycle.js";import{registerAndRollTaskDefinition as B}from"./taskDefinitionRoll.js";import{matchesServiceName as ee,matchServiceImageTagsToLiveServices as te,preflightResolveServiceImageTagDigests as re,resolveServiceTaskDefinition as ie}from"./serviceImageTagsRollback.js";const N="codeOnlyDeploy";function ne(t,s){if(t)return s[t.name]}function ae(t,s){const e=s.toLowerCase();return t.find(i=>i.name.toLowerCase()===e)??t.find(i=>e.includes(i.name.toLowerCase()))}function oe(t,s,e){const i=s.toLowerCase(),n=t.toLowerCase(),d=e.map(c=>c.toLowerCase()).filter(c=>c!==""&&n.startsWith(`${c}-`)).sort((c,m)=>m.length-c.length)[0];if(d===void 0||d===i)return t;const f=t.slice(d.length);return`${i}${f}`}const se=0,ce=1;function j(t,s,e,i,n){i[I(t,"TaskDefinition")]=e.taskDefinitionArn,i[I(t,"PreviousTaskDefinition")]=e.previousTaskDefinitionArn,i[I(t,"ImageTag")]=s,i[I(t,"ImageUri")]=e.imageUri,i[I(t,"EcrRepositoryArn")]=e.ecrRepositoryArn,e.imageDigest!==void 0&&(i[I(t,"ImageDigest")]=e.imageDigest),n.push({serviceName:t,imageTag:s,imageUri:e.imageUri,taskDefinitionArn:e.taskDefinitionArn,previousTaskDefinitionArn:e.previousTaskDefinitionArn,...e.imageDigest!==void 0?{imageDigest:e.imageDigest}:{},...e.ecrRepositoryArn!==""?{ecrRepositoryArn:e.ecrRepositoryArn}:{}})}async function F(t,s,e,i,n){if(e.length===0)return;const d=q(s,x.COMPUTE),f={};for(const{serviceName:m,imageTag:l}of e)f[X(m)]=l;const c=await t.cfnService.updateStackParameters(d,f,n);if(!c.success){i.onLog?.(`Rollback succeeded but the CloudFormation ImageTag parameter reconcile failed \u2014 the next full deploy may revert this rollback. Reason: ${C(c.error.message)}`,"warn");return}c.data.skippedKeys.length>0&&(i.onLog?.(`Rollback succeeded but ${c.data.skippedKeys.length} ImageTag parameter(s) were not found on stack ${d} (${c.data.skippedKeys.join(", ")}) \u2014 the next full deploy may revert those services`,"warn"),A.debug(N,"ImageTag reconcile skipped stale parameters",{stackName:d,skippedKeys:c.data.skippedKeys}))}async function Se(t,s,e,i){const{callbacks:n,options:d}=t,f=Date.now(),c=J(x.COMPUTE,"deploy"),m=Q(x.COMPUTE,"deploy");return Z(n,{stepId:c,stepName:m,stepIndex:se,totalSteps:ce},async()=>{const l=d?.imageTag,$=d?.serviceImageTags;l?n.onLog?.(`Rolling to supplied image tag ${l}`,"info"):$&&n.onLog?.(`Rolling ${Object.keys($).length} service(s) to their release-recorded image tags`,"info");const k=Y(W(e.path,"cdk.out"));A.debug(N,"Code-only rollout starting",{explicitRollbackTag:l,serviceImageTags:$,workingDirectory:t.workingDirectory,contentHashTagsByService:i,manifestServiceCount:k.length,manifestServices:k.map(o=>({name:o.name,target:o.docker.target}))}),n.onLog?.("Discovering ECS cluster and services\u2026","info");const u=await s.ecsResolver.getDeployableClusterAndServices(e.appName);if(!u.success)return A.debug(N,"ECS discovery failed",{appName:e.appName,error:C(u.error.message)}),{kind:"error",error:u.error};const{clusterArn:D,serviceArns:r}=u.data;if(A.debug(N,"ECS discovery complete",{appName:e.appName,clusterArn:D,serviceCount:r.length,serviceArns:r}),!D||r.length===0){const o=`No deployable ECS services found for ${e.appName}`;return n.onLog?.(o,"warn"),{kind:"skipped",data:{target:e.appName,deploymentType:"application",durationMs:Date.now()-f,noChanges:!0,artefacts:[]}}}if($!==void 0)return ge(s,e,D,r,$,n,t.abortSignal,f);const a=d?.serviceName?.toLowerCase(),y=a===""?void 0:a,p=y?r.filter(o=>ee(L(o)??"",y)):r;if(y&&p.length===0){const o=r.map(w=>L(w)??"unknown").join(", ");return{kind:"error",error:new Error(`Service '${y}' not found. Available: ${o||"none"}`)}}const v={},E=[],g=[],b=r.map(o=>L(o)).filter(o=>o!==void 0&&o!=="");for(let o=0;o<p.length;o++){if(K(t.abortSignal))return{kind:"error",error:new Error(`Rollout cancelled after ${E.length}/${p.length} services`)};const w=p[o];if(!w)continue;const h=L(w)??`service-${o+1}`,G=`[${o+1}/${p.length}] ${h}`,R=ae(k,h),M=ne(R,i);let T,P;if(l!==void 0)T=oe(l,h,b),T!==l?(P=l,n.onLog?.(`Re-based image tag ${l} \u2192 ${T} for ${h}`,"info")):b.length>1&&!l.toLowerCase().startsWith(`${h.toLowerCase()}-`)&&n.onLog?.(`Image tag ${l} has no recognised service prefix; applying verbatim to ${h} \u2014 confirm it points at this service's image`,"warn");else if(M!==void 0)T=M;else{const U=`No image available to deploy for ${h}: no content-hash tag was produced this run. Run a full deploy to build the image, or pass --image-tag <tag> to roll a specific existing image.`;n.onLog?.(U,"warn"),g.push(`${h}: ${U}`);continue}A.debug(N,"Starting service rollout",{serviceName:h,serviceArn:w,imageTag:T,explicitRollbackTag:l,contentHashTag:M,manifestServiceMatched:R?.name,dockerTarget:R?.docker.target,index:o,total:p.length}),n.onECSUpdate?.(`${G}: rolling out image tag ${T}`,h);const S=await _(s,D,w,h,T,n,P,void 0,t.abortSignal);if(A.debug(N,"Service rollout completed",{serviceName:h,success:S.success,...S.success?{taskDefinitionArn:S.data.taskDefinitionArn,imageDigest:S.data.imageDigest,durationMs:S.data.durationMs}:{error:C(S.error.message)}}),!S.success){g.push(`${h}: ${C(S.error.message)}`);continue}j(h,T,S.data,v,E)}const z=Date.now()-f;if(g.length>0){const o=p.length-g.length,w=p.length,h=g.length===w?`All ${w} ECS service rollouts failed: ${g.join("; ")}`:`Partial rollout: ${o}/${w} services succeeded. Failures: ${g.join("; ")}`;return{kind:"error",error:new Error(h)}}return l!==void 0&&await F(s,e.appName,E.map(o=>({serviceName:o.serviceName,imageTag:o.imageTag})),n,t.abortSignal),{kind:"completed",data:{target:e.appName,deploymentType:"application",outputs:Object.keys(v).length>0?v:void 0,artefacts:E,durationMs:z}}},t.abortSignal)}async function ge(t,s,e,i,n,d,f,c){const m=te(i,n);for(const r of m.unmatchedLiveServiceNames)d.onLog?.(`Service ${r} is not part of this release \u2014 leaving it untouched`,"warn");for(const r of m.staleMapKeys)d.onLog?.(`Service ${r} from the release no longer exists in the stack`,"warn");if(m.matched.length===0)return{kind:"error",error:new Error("None of the services in serviceImageTags matched a live ECS service in this stack \u2014 nothing to roll")};const l=await re(t,e,m.matched,f);if(!l.success)return{kind:"error",error:l.error};const $={},k=[],u=[];for(let r=0;r<m.matched.length;r++){if(K(f))return{kind:"error",error:new Error(`Rollout cancelled after ${k.length}/${m.matched.length} services`)};const a=m.matched[r];if(!a)continue;const y=`[${r+1}/${m.matched.length}] ${a.serviceName}`,p=l.data.get(a.serviceArn);if(p===void 0){u.push(`${a.serviceName}: pre-flight digest missing after a successful pre-flight`);continue}A.debug(N,"Starting service rollout (serviceImageTags)",{serviceName:a.serviceName,serviceArn:a.serviceArn,imageTag:a.tag,mapKey:a.mapKey,index:r,total:m.matched.length}),d.onECSUpdate?.(`${y}: rolling out image tag ${a.tag}`,a.serviceName);const v=await _(t,e,a.serviceArn,a.serviceName,a.tag,d,void 0,p,f);if(A.debug(N,"Service rollout completed (serviceImageTags)",{serviceName:a.serviceName,success:v.success,...v.success?{taskDefinitionArn:v.data.taskDefinitionArn,imageDigest:v.data.imageDigest,durationMs:v.data.durationMs}:{error:C(v.error.message)}}),!v.success){u.push(`${a.serviceName}: ${C(v.error.message)}`);continue}j(a.serviceName,a.tag,v.data,$,k)}const D=Date.now()-c;if(u.length>0){const r=m.matched.length-u.length,a=m.matched.length,y=u.length===a?`All ${a} ECS service rollouts failed: ${u.join("; ")}`:`Partial rollout: ${r}/${a} services succeeded. Failures: ${u.join("; ")}`;return{kind:"error",error:new Error(y)}}return await F(t,s.appName,k.map(r=>({serviceName:r.serviceName,imageTag:r.imageTag})),d,f),{kind:"completed",data:{target:s.appName,deploymentType:"application",outputs:Object.keys($).length>0?$:void 0,artefacts:k,durationMs:D}}}async function _(t,s,e,i,n,d,f,c,m){const l=Date.now();let $,k,u,D,r;if(c!==void 0)$=c.taskDefinition.previousTaskDefArn,k=c.taskDefinition.taskDefinition,u=c.taskDefinition.containerDefinitions,D=c.taskDefinition.repoUri,r=c.imageDigest;else{const g=await ie(t,s,e,i);if(!g.success)return O(g.error);$=g.data.previousTaskDefArn,k=g.data.taskDefinition,u=g.data.containerDefinitions,D=g.data.repoUri;const b=await t.ecrImageInspector.getImageDigest(D,n,m);if(b.success)r=b.data;else{if(f!==void 0)return O(new Error(`Re-based image tag '${n}' (from supplied '${f}') was not found in ECR for ${i}: ${C(b.error.message)}. The supplied tag's build has no image for ${i} \u2014 supply that service's own tag, or pass --service to roll a single service.`));d.onLog?.(`Could not resolve image digest for ${i} (${C(b.error.message)}); falling back to tag pinning`,"warn")}}const a=g=>r!==void 0?`${g}@${r}`:`${g}:${n}`,y=u.map(g=>g.image?{...g,image:a(V(g.image))}:g),p=await B(t,{clusterArn:s,serviceArn:e,serviceName:i,taskDefinition:k,containerDefinitions:y,previousTaskDefArn:$,...r!==void 0?{imageDigest:r}:{},startedAtMs:l},d);if(!p.success)return O(p.error);const v=`${D}:${n}`,E=de(D);return H({taskDefinitionArn:p.data.newTaskDefArn,previousTaskDefinitionArn:$,imageUri:v,ecrRepositoryArn:E,imageDigest:r,durationMs:p.data.durationMs})}function de(t){const s=t.match(/^(\d{12})\.dkr\.ecr\.([a-z0-9-]+)\.amazonaws\.com\/(.+)$/);if(!s)return"";const[,e,i,n]=s;return`arn:aws:ecr:${i}:${e}:repository/${n}`}export{Se as deployCodeOnly,de as deriveEcrRepositoryArn,ne as lookupContentHashTag,oe as rebaseRollbackTagForService};
@@ -0,0 +1,77 @@
1
+ import type { DeployCallbacks } from "../../types/callbacks.js";
2
+ import type { DeployParams } from "../../types/params.js";
3
+ import type { CloudFormationService } from "../../services/infrastructure/CloudFormationService.js";
4
+ import type { EcsService } from "../../services/infrastructure/EcsService.js";
5
+ import type { EcsServiceResolver } from "../../services/infrastructure/EcsServiceResolver.js";
6
+ /**
7
+ * Read the application's Database-stack connection outputs (endpoint/port
8
+ * keys only). Returns `{}` when the stack or its outputs do not exist, and
9
+ * `undefined` when the read itself failed — callers use that distinction to
10
+ * warn that endpoint stability could not be verified rather than silently
11
+ * treating a failed read as "no endpoints".
12
+ */
13
+ export declare function captureDatabaseConnectionOutputs(cfnService: CloudFormationService, appName: string): Promise<Record<string, string> | undefined>;
14
+ /**
15
+ * Snapshot each live ECS service's current task-definition ARN before the
16
+ * stack loop runs. The post-deploy reconcile compares these against the
17
+ * post-deploy ARNs to tell which services CloudFormation already rolled
18
+ * during the deploy (ARN changed ⇒ tasks relaunched after the Database phase
19
+ * and re-resolved the updated secret) versus which still run pre-deploy tasks
20
+ * (ARN unchanged ⇒ stale connection values). Inferring this from build state
21
+ * is wrong in the primary scenario: a config-only database replacement
22
+ * rebuilds the same content-hash image, so the Compute stack no-ops and never
23
+ * rolls anything. Returns `{}` when no services exist yet and `undefined`
24
+ * when the read failed. The resolver reports export-read failures as an empty
25
+ * service list, so the reconcile treats an empty baseline like a failed read:
26
+ * both restart without the baseline — an extra roll is harmless, a missed one
27
+ * is not.
28
+ */
29
+ export declare function captureServiceTaskDefinitions(ecsResolver: EcsServiceResolver, ecsService: EcsService, appName: string): Promise<Record<string, string> | undefined>;
30
+ export interface DatabaseEndpointReconcileParams {
31
+ appName: string;
32
+ /** Pre-deploy connection-output capture; `undefined` when the read failed. */
33
+ connectionOutputsBefore: Record<string, string> | undefined;
34
+ /**
35
+ * Pre-deploy serviceArn → task-definition ARN baseline; `undefined` when
36
+ * the read failed (every service is then restarted — the safe direction).
37
+ */
38
+ serviceTaskDefinitionsBefore: Record<string, string> | undefined;
39
+ services: {
40
+ cfnService: CloudFormationService;
41
+ ecsService: EcsService;
42
+ ecsResolver: EcsServiceResolver;
43
+ };
44
+ callbacks: DeployCallbacks;
45
+ confirmRestart?: DeployParams["confirmDatabaseEndpointRestart"];
46
+ abortSignal?: AbortSignal;
47
+ }
48
+ export type DatabaseEndpointReconcileOutcome = {
49
+ kind: "not-needed";
50
+ } | {
51
+ kind: "unverified";
52
+ } | {
53
+ kind: "already-current";
54
+ } | {
55
+ kind: "declined";
56
+ } | {
57
+ kind: "reconciled";
58
+ services: string[];
59
+ } | {
60
+ kind: "failed";
61
+ } | {
62
+ kind: "cancelled";
63
+ };
64
+ /**
65
+ * Post-deploy reconcile for a replaced database. A create-only property
66
+ * change (e.g. `snapshotIdentifier`) replaces the database: the credentials
67
+ * secret updates in place (same ARN, new host), so the ECS task definition is
68
+ * byte-identical and CloudFormation makes no ECS change — running tasks keep
69
+ * the stale connection values injected at launch and silently write to the
70
+ * doomed old database until CloudFormation deletes it. This step diffs the
71
+ * Database stack's connection outputs across the deploy and restarts the
72
+ * services whose task-definition ARN did not change during the deploy (those
73
+ * whose ARN moved were already relaunched after the Database phase).
74
+ * Best-effort: every failure warns with a `fjall rollout` hint and never
75
+ * fails the deploy.
76
+ */
77
+ export declare function reconcileDatabaseEndpointChange(params: DatabaseEndpointReconcileParams): Promise<DatabaseEndpointReconcileOutcome>;
@@ -0,0 +1 @@
1
+ import{maskSensitiveOutput as g}from"@fjall/util";import{logger as b}from"@fjall/util/logger";import{getApplicationStackName as R,APPLICATION_STACKS as T}from"../../types/operations.js";import{extractEcsServiceName as N}from"../../aws/utils/arnParser.js";import{isAborted as C}from"../../aws/organisations/types.js";import{resolveServiceTaskDefinition as O}from"./serviceImageTagsRollback.js";import{registerAndRollTaskDefinition as E}from"./taskDefinitionRoll.js";const w="databaseEndpointReconcile",j=["Endpoint","Port"];async function I(a,t){const i=R(t,T.DATABASE),o=await a.getStackOutputs(i);if(!o.success){b.debug(w,"Could not read database stack outputs",{stackName:i,error:g(o.error.message)});return}const e={};for(const d of o.data){const r=d.OutputKey,l=d.OutputValue;r===void 0||l===void 0||j.some(u=>r.endsWith(u))&&(e[r]=l)}return e}async function W(a,t,i){const o=await a.getDeployableClusterAndServices(i);if(!o.success){b.debug(w,"Could not discover services pre-deploy",{appName:i,error:g(o.error.message)});return}const{clusterArn:e,serviceArns:d}=o.data;if(e===void 0||d.length===0)return{};const r=await t.describeServices(e,d);if(!r.success){b.debug(w,"Could not describe services pre-deploy",{appName:i,error:g(r.error.message)});return}const l={};for(const u of r.data)u.serviceArn!==void 0&&u.taskDefinition!==void 0&&(l[u.serviceArn]=u.taskDefinition);return l}function x(a,t){return Object.keys(a).filter(i=>t[i]!==void 0&&t[i]!==a[i]).sort()}async function q(a){const{appName:t,connectionOutputsBefore:i,services:o,callbacks:e,abortSignal:d}=a,r=`Run \`fjall rollout ${t}\` to restart them manually.`,l=`Could not verify the database endpoint was unchanged by this deploy. If the database was replaced (e.g. restored from a snapshot), run \`fjall rollout ${t}\` so running services reconnect to it.`;if(i===void 0)return e.onLog?.(l,"warn"),{kind:"unverified"};const u=await I(o.cfnService,t);if(u===void 0)return e.onLog?.(l,"warn"),{kind:"unverified"};const h=x(i,u);if(h.length===0)return{kind:"not-needed"};if(e.onLog?.(`Database connection outputs changed this deploy (${h.join(", ")}) \u2014 the database was replaced.`,"info"),C(d))return e.onLog?.(`Deploy cancelled before services were restarted onto the new database endpoint. ${r}`,"warn"),{kind:"cancelled"};const k=await o.ecsResolver.getDeployableClusterAndServices(t);if(!k.success)return e.onLog?.(`Could not discover this application's ECS services to restart them onto the new database endpoint: ${g(k.error.message)}. ${r}`,"warn"),{kind:"failed"};const{clusterArn:m,serviceArns:$}=k.data;if(m===void 0||$.length===0)return e.onLog?.(`Database connection outputs changed but no running ECS services were found to restart. If this application has running services, run \`fjall rollout ${t}\` so they reconnect to the new database endpoint.`,"warn"),{kind:"not-needed"};const A=a.serviceTaskDefinitionsBefore,L=A!==void 0&&Object.keys(A).length>0,v=[];let S=0,D=0;const y=(n,s)=>{e.onLog?.(`Could not restart ${n} onto the new database endpoint: ${g(s)}. ${r}`,"warn"),D+=1};for(const n of $){if(C(d))return e.onLog?.(`Deploy cancelled before services were restarted onto the new database endpoint. ${r}`,"warn"),{kind:"cancelled"};const s=N(n)??n,c=await O({ecsService:o.ecsService},m,n,s);if(!c.success){y(s,c.error.message);continue}const f=A?.[n];if(L&&f===void 0){S+=1;continue}if(f!==void 0&&f!==c.data.previousTaskDefArn){S+=1;continue}v.push({serviceArn:n,serviceName:s,resolved:c.data})}if(v.length===0)return D>0?{kind:"failed"}:(e.onLog?.(`Services were already relaunched during this deploy \u2014 no restart needed. If something other than this deploy relaunched them, run \`fjall rollout ${t}\` to be safe.`,"info"),{kind:"already-current"});if(a.confirmRestart!==void 0){const n={appName:t,changedOutputs:h,serviceCount:v.length};let s;try{s=await a.confirmRestart(n)}catch(c){return e.onLog?.(`Restart confirmation failed: ${g(c instanceof Error?c.message:String(c))}. ${r}`,"warn"),{kind:"failed"}}if(!s)return e.onLog?.(`Restart declined \u2014 running containers are still connected to the old database endpoint. ${r}`,"warn"),{kind:"declined"}}e.onLog?.(`Restarting ${v.length} service${v.length===1?"":"s"} so running containers reconnect to the new database\u2026`,"info");const p=[];for(const n of v){if(C(d))return e.onLog?.(`Deploy cancelled before all services were restarted onto the new database endpoint. ${r}`,"warn"),{kind:"cancelled"};let s=n.resolved;if(a.confirmRestart!==void 0){const f=await O({ecsService:o.ecsService},m,n.serviceArn,n.serviceName);if(!f.success){y(n.serviceName,f.error.message);continue}if(f.data.previousTaskDefArn!==n.resolved.previousTaskDefArn){e.onLog?.(`${n.serviceName} was relaunched while awaiting confirmation \u2014 skipping its restart.`,"info");continue}s=f.data}const c=await E({ecsService:o.ecsService},{clusterArn:m,serviceArn:n.serviceArn,serviceName:n.serviceName,taskDefinition:s.taskDefinition,containerDefinitions:s.containerDefinitions,previousTaskDefArn:s.previousTaskDefArn,startedAtMs:Date.now()},e);if(!c.success){y(n.serviceName,c.error.message);continue}p.push(n.serviceName)}return p.length>0&&(b.debug(w,"Restarted services after database replacement",{appName:t,changedOutputs:h,services:p,alreadyRolled:S}),e.onLog?.(`Restarted ${p.length} service${p.length===1?"":"s"} onto the new database endpoint.`,"info")),D>0?{kind:"failed"}:{kind:"reconciled",services:p}}export{I as captureDatabaseConnectionOutputs,W as captureServiceTaskDefinitions,q as reconcileDatabaseEndpointChange};
@@ -17,13 +17,21 @@ export interface DeploySessionContext {
17
17
  * `AdministratorAccess` and the OIDC subject encodes no resource narrowing, so
18
18
  * without this the session is an org-wide admin bearer credential.
19
19
  *
20
- * Two accepted v1 limitations (not app-confinement guarantees):
20
+ * Three accepted v1 limitations (not app-confinement guarantees):
21
21
  * - Session policies do not propagate across `sts:AssumeRole`, so mutation via
22
22
  * the CDK deploy/cfn-exec roles is out of this policy's reach — the real
23
23
  * confinement is the Phase E `FjallDeployBoundary` permission boundary.
24
24
  * - The `stack/${appPascal}*` prefix wildcard over-grants a prefix-collision
25
25
  * sibling app (`Web` vs `WebApp`) — same low-risk, same-account class as the
26
26
  * ECR baseline-prefix note in `buildSecretSession.ts`.
27
+ * - The `-cache` NotResource entry exempts `repository/<appKebab>-cache` from
28
+ * the foreign-ECR Deny. CLI app creation reserves the `-cache` suffix
29
+ * (`hasReservedAppNameSuffix`, `@fjall/util`); the webapp create route does
30
+ * NOT yet enforce it (its pinned @fjall/util predates the helper — wiring
31
+ * lands with the 2.26.x bump), so until then a webapp-created sibling app
32
+ * could take that name. The residual beyond that is a pre-existing
33
+ * customer-created repo of that name in the BYOC account — same accepted
34
+ * class as `cdk-*`.
27
35
  *
28
36
  * DENY-list, not allow-list (deliberate). `fjall deploy` runs `cdk deploy`: the
29
37
  * caller's session performs synth lookups, asset uploads to the CDK asset bucket,
@@ -1 +1 @@
1
- import{success as p,failure as s}from"@fjall/generator";import{toKebab as m,toPascalCase as d}from"@fjall/util";import{MAX_INLINE_POLICY_CHARS as r}from"./buildSecretSession.js";const u=["cloudformation:CreateStack","cloudformation:UpdateStack","cloudformation:DeleteStack","cloudformation:CreateChangeSet","cloudformation:ExecuteChangeSet","cloudformation:DeleteChangeSet","cloudformation:CancelUpdateStack","cloudformation:ContinueUpdateRollback","cloudformation:RollbackStack","cloudformation:SetStackPolicy","cloudformation:UpdateTerminationProtection"],f=["ecr:PutImage","ecr:InitiateLayerUpload","ecr:UploadLayerPart","ecr:CompleteLayerUpload","ecr:BatchDeleteImage","ecr:DeleteRepository","ecr:SetRepositoryPolicy","ecr:DeleteRepositoryPolicy"];function $(a){const{region:o,accountId:e,partition:t}=a,n=d(a.appName),i=m(a.appName),l=[{Sid:"FjallDeployInheritAdmin",Effect:"Allow",Action:"*",Resource:"*"},{Sid:"FjallDeployDenyForeignStackMutation",Effect:"Deny",Action:u,NotResource:[`arn:${t}:cloudformation:${o}:${e}:stack/${n}*/*`,`arn:${t}:cloudformation:${o}:${e}:stack/CDKToolkit/*`]},{Sid:"FjallDeployDenyForeignEcrMutation",Effect:"Deny",Action:f,NotResource:[`arn:${t}:ecr:${o}:${e}:repository/${i}`,`arn:${t}:ecr:${o}:${e}:repository/cdk-*`]}],c=JSON.stringify({Version:"2012-10-17",Statement:l});return c.length>r?s(new Error(`Deploy session policy is ${c.length} chars, exceeding the ${r}-char AWS inline-policy limit.`)):p(c)}export{$ as buildDeploySessionPolicy};
1
+ import{success as p,failure as s}from"@fjall/generator";import{toKebab as m,toPascalCase as d}from"@fjall/util";import{buildCacheRepositoryName as u}from"@fjall/util/docker";import{MAX_INLINE_POLICY_CHARS as i}from"./buildSecretSession.js";const f=["cloudformation:CreateStack","cloudformation:UpdateStack","cloudformation:DeleteStack","cloudformation:CreateChangeSet","cloudformation:ExecuteChangeSet","cloudformation:DeleteChangeSet","cloudformation:CancelUpdateStack","cloudformation:ContinueUpdateRollback","cloudformation:RollbackStack","cloudformation:SetStackPolicy","cloudformation:UpdateTerminationProtection"],y=["ecr:PutImage","ecr:InitiateLayerUpload","ecr:UploadLayerPart","ecr:CompleteLayerUpload","ecr:BatchDeleteImage","ecr:DeleteRepository","ecr:SetRepositoryPolicy","ecr:DeleteRepositoryPolicy"];function k(a){const{region:o,accountId:e,partition:t}=a,n=d(a.appName),c=m(a.appName),l=[{Sid:"FjallDeployInheritAdmin",Effect:"Allow",Action:"*",Resource:"*"},{Sid:"FjallDeployDenyForeignStackMutation",Effect:"Deny",Action:f,NotResource:[`arn:${t}:cloudformation:${o}:${e}:stack/${n}*/*`,`arn:${t}:cloudformation:${o}:${e}:stack/CDKToolkit/*`]},{Sid:"FjallDeployDenyForeignEcrMutation",Effect:"Deny",Action:y,NotResource:[`arn:${t}:ecr:${o}:${e}:repository/${c}`,`arn:${t}:ecr:${o}:${e}:repository/${u(c)}`,`arn:${t}:ecr:${o}:${e}:repository/cdk-*`]}],r=JSON.stringify({Version:"2012-10-17",Statement:l});return r.length>i?s(new Error(`Deploy session policy is ${r.length} chars, exceeding the ${i}-char AWS inline-policy limit.`)):p(r)}export{k as buildDeploySessionPolicy};
@@ -1 +1 @@
1
- import{isAbsolute as K,join as $,dirname as H,resolve as x}from"node:path";import{existsSync as T}from"node:fs";import{success as k,failure as f}from"@fjall/generator";import{deriveContentHashTag as F,maskSensitiveOutput as m,toKebab as I}from"@fjall/util";import{evaluateBakeGuard as U,evaluateResolvedBuildArgValues as G,acknowledgedBuildArgKeys as V,BAKE_GUARD_EXPOSURE_CLAUSE as M}from"@fjall/util/docker";import{logger as P}from"@fjall/util/logger";import{parseDockerServicesFromManifest as W}from"@fjall/util/manifest";import{STEP_IDS as b}from"../../types/stepDefinitions.js";import{resolveBuildSecrets as z,resolveSecretRefValue as X,resolveBuildSecretSessionProvider as Y,sourcedRefsFromBuildArgs as q}from"./buildSecretResolver.js";import{partitionForRegion as J}from"./buildSecretSession.js";import{resolveBuildArgs as Q}from"./buildArgResolver.js";import{validateBuildGroup as Z}from"./buildGroupValidator.js";const C="dockerBuildHelper",E="Building and pushing Docker image";function ee(e,s,r){const t=$(s,e);if(T(t))return{path:t,base:s};if(r!==""&&r!==s){const o=$(r,e);if(T(o))return{path:o,base:r}}return{path:t,base:s}}function te(e,s,r){const t=e.docker.path,o=e.docker.context;let a,i;if(K(t))a=t,i=s;else{const c=ee(t,s,r);a=c.path,i=c.base}return{buildContext:o?K(o)?x(o):x(i,o):x(H(a)),dockerfilePath:a,target:e.docker.target,resolvedViaFallback:i!==s}}function re(e){return`${e.buildContext}|${e.dockerfilePath}|${e.target??""}`}function ne(e,s){return`Dockerfile not found for service '${e}' at ${s}. Checked the synth directory and the working directory. If your Dockerfile lives at the project root with the CDK app in a subdirectory, set an absolute docker.context (the build context must share the Dockerfile's directory).`}function ie(e){return e.startsWith("cn-")?"amazonaws.com.cn":"amazonaws.com"}function oe(e,s,r){return`${e}.dkr.ecr.${s}.${ie(s)}/${I(r)}`}function se(e,s,r){const t=new Map;for(const o of e){const a=te(o,s,r),i=re(a),p=t.get(i);p?p.members.push(o):t.set(i,{identity:a,members:[o]})}return Array.from(t.values())}async function ae(e,s,r,t,o){const a=await e.buildAndPush({appName:s.appName,appPath:s.path,region:r,accountId:t},(i,p,c,v)=>{o.onDockerProgress?.(m(i),p,c,v)});return a.success?k({imageUri:a.data.imageUri,imageTag:a.data.imageTag}):f(a.error)}function L(){return{contentHashTagsByService:{},imageDigestsByService:{}}}async function de(e,s,r,t,o,a,i,p,c,v,y,h){const u=e.members[0];if(!T(e.identity.dockerfilePath))return f(new Error(ne(u.name,e.identity.dockerfilePath)));e.identity.resolvedViaFallback&&i.onLog?.(`Building service '${u.name}' from ${e.identity.dockerfilePath} (resolved at the project root).`,"info");const n=[...u.docker.buildSecrets??[],...h??[]],g=Z({serviceName:u.name,dockerfilePath:e.identity.dockerfilePath,buildContext:e.identity.buildContext,...e.identity.target!==void 0&&{target:e.identity.target},...u.docker.buildArgs!==void 0&&{buildArgs:u.docker.buildArgs},...n.length>0&&{buildSecrets:n},members:e.members.map(d=>({serviceName:d.name,...d.docker.buildArgs!==void 0&&{buildArgs:d.docker.buildArgs},...d.docker.buildSecrets!==void 0&&{buildSecrets:d.docker.buildSecrets}}))});if(!g.success)return f(g.error);const l=U(u.docker.buildArgs,I(r.appName));for(const d of l.warnings)i.onLog?.(m(d.message),"warn");if(l.findings.length>0)return f(new Error(m(l.findings.map(d=>d.message).join(" "))));const R=n,B=q(u.docker.buildArgs),A={appName:r.appName,region:t,accountId:o,partition:J(t)},S=await Y(p,[...R,...B],A);if(!S.success)return f(S.error);const D=S.data,N=await Q({manifestBuildArgs:u.docker.buildArgs,serviceDir:e.identity.buildContext,appDir:r.path,...v!==void 0&&{stage:v},shellEnv:process.env,...y!==void 0&&{flagBuildArgs:y},resolveSourcedValue:(d,j)=>X(D,{id:d,...j},c)});if(!N.success)return f(N.error);const O=N.data.buildArgs;for(const d of N.data.inferredDotenvKeys)i.onLog?.(m(`buildArg "${d}" was auto-declared from a .env file and will be baked into the image \u2014 ${M}.`),"warn");const _=G(O,I(r.appName),V(u.docker.buildArgs));if(_.length>0)return f(new Error(m(_.map(d=>d.message).join(" "))));let w;if(R.length>0){const d=await z(D,R,c);if(!d.success)return f(d.error);w=d.data}try{const d={appName:r.appName,appPath:e.identity.buildContext,region:t,accountId:o,buildContext:e.identity.buildContext,dockerfilePath:e.identity.dockerfilePath,pushByDigest:!0,serviceName:u.name,...e.identity.target!==void 0&&{target:e.identity.target},...Object.keys(O).length>0&&{buildArgs:O},...w!==void 0&&{buildSecrets:w.resolved}};return await ce(e,d,s,a,i)}finally{w!==void 0&&await w.cleanup().catch(()=>i.onLog?.("Build-secret cleanup failed.","warn"))}}async function ce(e,s,r,t,o){const a=await r.buildAndPush(s,(h,u,n,g)=>{o.onDockerProgress?.(m(h),u,n,g)});if(!a.success)return f(a.error);const i=a.data.imageDigest;if(typeof i!="string"||!i.startsWith("sha256:"))return f(new Error(`Buildx push succeeded but returned an invalid digest: ${m(String(i))}`));const p={},c={},v=[];for(const h of e.members){const u=h.docker.target,n=F(i,h.name,u);if(!n.success)return f(n.error);const g=n.data,l=h.name;p[l]=g,c[l]=i,v.push(`${t}:${g}`)}const y=await r.tagByDigest({sourceImage:t,digest:i,tags:v});return y.success?k({contentHashTagsByService:p,imageDigestsByService:c}):f(y.error)}async function Re(e,s,r,t){const o=e.dockerProvider;if(!o)return k(L());const a=s.awsProvider.getAccountId();if(!a)return t.onLog?.("Skipping Docker build \u2014 account ID not available","warn"),k(L());const i=s.awsProvider.getRegion(),p=$(r.path,"cdk.out"),c=W(p),v=oe(a,i,r.appName);if(P.debug(C,"runDockerBuild starting",{appName:r.appName,appPath:r.path,accountId:a,region:i,cdkOutPath:p,manifestServiceCount:c.length,manifestServices:c.map(n=>({name:n.name,path:n.docker.path,context:n.docker.context,target:n.docker.target})),stepId:b.DOCKER_OPERATIONS,stepName:E}),t.onStepStart?.(b.DOCKER_OPERATIONS,E),c.length===0){t.onLog?.("Initialising ECR repository\u2026","info");const n=await o.initialiseECR({appName:r.appName,region:i,accountId:a});n.success||t.onLog?.(`ECR initialisation failed: ${m(n.error.message)}`,"warn"),t.onLog?.("Building and pushing Docker image\u2026","info");const g=await ae(o,r,i,a,t);if(!g.success){P.debug(C,"Docker buildAndPush (legacy) failed",{appName:r.appName,error:m(g.error.message)}),t.onStepComplete?.(b.DOCKER_OPERATIONS,E,"error");const l=new Error(m(g.error.message));return t.onError?.(l),f(l)}return t.onStepComplete?.(b.DOCKER_OPERATIONS,E,"completed"),t.onLog?.(`Docker image pushed: ${g.data.imageUri}`,"info"),k(L())}const y=await o.preflight?.();if(y!==void 0&&!y.success){t.onStepComplete?.(b.DOCKER_OPERATIONS,E,"error");const n=new Error(m(y.error.message));return t.onError?.(n),f(n)}const h=se(c,r.path,e.workingDirectory);t.onLog?.(`Building and pushing ${c.length} service image(s) in ${h.length} group(s)\u2026`,"info");let u=!1;if(o.beginEcrSession!==void 0){const n=await o.beginEcrSession({accountId:a,region:i});if(!n.success){t.onStepComplete?.(b.DOCKER_OPERATIONS,E,"error");const g=new Error(m(n.error.message));return t.onError?.(g),f(g)}u=!0}try{const n={},g={};for(let l=0;l<h.length;l++){const R=h[l],B=R.members.map(S=>S.name).join(", ");t.onLog?.(`Building group ${l+1}/${h.length} (${B})\u2026`,"info");const A=await de(R,o,r,i,a,v,t,s.awsProvider,e.abortSignal,e.options?.environment,e.options?.flagBuildArgs,e.options?.flagBuildSecrets);if(!A.success){P.debug(C,"Docker buildAndPush failed",{appName:r.appName,leader:R.members[0]?.name,members:B,error:m(A.error.message)}),t.onStepComplete?.(b.DOCKER_OPERATIONS,E,"error");const S=new Error(m(A.error.message));return t.onError?.(S),f(S)}for(const[S,D]of Object.entries(A.data.contentHashTagsByService))n[S]=D;for(const[S,D]of Object.entries(A.data.imageDigestsByService))g[S]=D}return P.debug(C,"Docker buildAndPush succeeded",{appName:r.appName,services:c.map(l=>l.name),groupCount:h.length,contentHashTags:n}),t.onStepComplete?.(b.DOCKER_OPERATIONS,E,"completed"),t.onLog?.(`Docker images pushed for ${c.length} service(s)`,"info"),k({contentHashTagsByService:n,imageDigestsByService:g,repoUri:v})}finally{u&&o.endEcrSession!==void 0&&await o.endEcrSession().catch(n=>{P.debug(C,"endEcrSession cleanup failed",{error:m(n instanceof Error?n.message:String(n))})})}}export{E as DOCKER_BUILD_STEP_NAME,L as emptyBuildProducts,Re as runDockerBuild};
1
+ import{isAbsolute as F,join as L,dirname as M,resolve as _}from"node:path";import{existsSync as K}from"node:fs";import{success as N,failure as h}from"@fjall/generator";import{deriveContentHashTag as W,maskSensitiveOutput as b,toKebab as x}from"@fjall/util";import{evaluateBakeGuard as z,evaluateResolvedBuildArgValues as X,acknowledgedBuildArgKeys as Y,buildCacheRepositoryName as H,BAKE_GUARD_EXPOSURE_CLAUSE as q}from"@fjall/util/docker";import{runWithBoundedConcurrency as J,resolveBuildConcurrency as Q}from"@fjall/util/buildConcurrency";import{logger as T}from"@fjall/util/logger";import{parseDockerServicesFromManifest as Z}from"@fjall/util/manifest";import{STEP_IDS as A}from"../../types/stepDefinitions.js";import{isAborted as U}from"../../aws/organisations/types.js";import{resolveBuildSecrets as ee,resolveSecretRefValue as re,resolveBuildSecretSessionProvider as te,sourcedRefsFromBuildArgs as ne}from"./buildSecretResolver.js";import{partitionForRegion as oe}from"./buildSecretSession.js";import{resolveBuildArgs as ie}from"./buildArgResolver.js";import{validateBuildGroup as se}from"./buildGroupValidator.js";import{ensureBuildCacheRepository as ae}from"./ecrCacheRepository.js";const I="dockerBuildHelper",C="Building and pushing Docker image";function de(e,n,t){const r=L(n,e);if(K(r))return{path:r,base:n};if(t!==""&&t!==n){const i=L(t,e);if(K(i))return{path:i,base:t}}return{path:r,base:n}}function ue(e,n,t){const r=e.docker.path,i=e.docker.context;let s,a;if(F(r))s=r,a=n;else{const p=de(r,n,t);s=p.path,a=p.base}return{buildContext:i?F(i)?_(i):_(a,i):_(M(s)),dockerfilePath:s,target:e.docker.target,resolvedViaFallback:a!==n}}function ce(e){return`${e.buildContext}|${e.dockerfilePath}|${e.target??""}`}function le(e,n){return`Dockerfile not found for service '${e}' at ${n}. Checked the synth directory and the working directory. If your Dockerfile lives at the project root with the CDK app in a subdirectory, set an absolute docker.context (the build context must share the Dockerfile's directory).`}function fe(e){return e.startsWith("cn-")?"amazonaws.com.cn":"amazonaws.com"}function G(e,n){return`${e}.dkr.ecr.${n}.${fe(n)}`}function ge(e,n,t){return`${G(e,n)}/${x(t)}`}function me(e,n,t){return`${G(e,n)}/${H(x(t))}`}function pe(e,n,t){const r=new Map;for(const i of e){const s=ue(i,n,t),a=ce(s),m=r.get(a);m?m.members.push(i):r.set(a,{identity:s,members:[i]})}return Array.from(r.values())}async function he(e,n,t,r,i){const s=await e.buildAndPush({appName:n.appName,appPath:n.path,region:t,accountId:r},(a,m,p,S)=>{i.onDockerProgress?.(b(a),m,p,S)});return s.success?N({imageUri:s.data.imageUri,imageTag:s.data.imageTag}):h(s.error)}function j(){return{contentHashTagsByService:{},imageDigestsByService:{}}}async function be(e,n,t,r,i,s,a,m,p,S,w,l,E){const c=e.members[0];if(!K(e.identity.dockerfilePath))return h(new Error(le(c.name,e.identity.dockerfilePath)));e.identity.resolvedViaFallback&&m.onLog?.(`Building service '${c.name}' from ${e.identity.dockerfilePath} (resolved at the project root).`,"info");const y=[...c.docker.buildSecrets??[],...E??[]],v=se({serviceName:c.name,dockerfilePath:e.identity.dockerfilePath,buildContext:e.identity.buildContext,...e.identity.target!==void 0&&{target:e.identity.target},...c.docker.buildArgs!==void 0&&{buildArgs:c.docker.buildArgs},...y.length>0&&{buildSecrets:y},members:e.members.map(o=>({serviceName:o.name,...o.docker.buildArgs!==void 0&&{buildArgs:o.docker.buildArgs},...o.docker.buildSecrets!==void 0&&{buildSecrets:o.docker.buildSecrets}}))});if(!v.success)return h(v.error);const k=z(c.docker.buildArgs,x(t.appName));for(const o of k.warnings)m.onLog?.(b(o.message),"warn");if(k.findings.length>0)return h(new Error(b(k.findings.map(o=>o.message).join(" "))));const d=y,f=ne(c.docker.buildArgs),D={appName:t.appName,region:r,accountId:i,partition:oe(r)},$=await te(p,[...d,...f],D);if(!$.success)return h($.error);const O=$.data,R=await ie({manifestBuildArgs:c.docker.buildArgs,serviceDir:e.identity.buildContext,appDir:t.path,...w!==void 0&&{stage:w},shellEnv:process.env,...l!==void 0&&{flagBuildArgs:l},resolveSourcedValue:(o,P)=>re(O,{id:o,...P},S)});if(!R.success)return h(R.error);const B=R.data.buildArgs;for(const o of R.data.inferredDotenvKeys)m.onLog?.(b(`buildArg "${o}" was auto-declared from a .env file and will be baked into the image \u2014 ${q}.`),"warn");const u=X(B,x(t.appName),Y(c.docker.buildArgs));if(u.length>0)return h(new Error(b(u.map(o=>o.message).join(" "))));let g;if(d.length>0){const o=await ee(O,d,S);if(!o.success)return h(o.error);g=o.data}try{if(U(S))return h(new Error(`Build of service '${c.name}' was aborted before the docker build started`));const o={appName:t.appName,appPath:e.identity.buildContext,region:r,accountId:i,buildContext:e.identity.buildContext,dockerfilePath:e.identity.dockerfilePath,pushByDigest:!0,serviceName:c.name,...a!==void 0&&{cacheRepoUri:a},...e.identity.target!==void 0&&{target:e.identity.target},...Object.keys(B).length>0&&{buildArgs:B},...g!==void 0&&{buildSecrets:g.resolved}};return await ve(e,o,n,s,m)}finally{g!==void 0&&await g.cleanup().catch(()=>m.onLog?.("Build-secret cleanup failed.","warn"))}}async function ve(e,n,t,r,i){const s=await t.buildAndPush(n,(l,E,c,y)=>{i.onDockerProgress?.(b(l),E,c,y)});if(!s.success)return h(s.error);const a=s.data.imageDigest;if(typeof a!="string"||!a.startsWith("sha256:"))return h(new Error(`Buildx push succeeded but returned an invalid digest: ${b(String(a))}`));const m={},p={},S=[];for(const l of e.members){const E=l.docker.target,c=W(a,l.name,E);if(!c.success)return h(c.error);const y=c.data,v=l.name;m[v]=y,p[v]=a,S.push(`${r}:${y}`)}const w=await t.tagByDigest({sourceImage:r,digest:a,tags:S});return w.success?N({contentHashTagsByService:m,imageDigestsByService:p}):h(w.error)}async function Te(e,n,t,r){const i=e.dockerProvider;if(!i)return N(j());const s=n.awsProvider.getAccountId();if(!s)return r.onLog?.("Skipping Docker build \u2014 account ID not available","warn"),N(j());const a=n.awsProvider.getRegion(),m=L(t.path,"cdk.out"),p=Z(m),S=ge(s,a,t.appName);if(T.debug(I,"runDockerBuild starting",{appName:t.appName,appPath:t.path,accountId:s,region:a,cdkOutPath:m,manifestServiceCount:p.length,manifestServices:p.map(d=>({name:d.name,path:d.docker.path,context:d.docker.context,target:d.docker.target})),stepId:A.DOCKER_OPERATIONS,stepName:C}),r.onStepStart?.(A.DOCKER_OPERATIONS,C),p.length===0){r.onLog?.("Initialising ECR repository\u2026","info");const d=await i.initialiseECR({appName:t.appName,region:a,accountId:s});d.success||r.onLog?.(`ECR initialisation failed: ${b(d.error.message)}`,"warn"),r.onLog?.("Building and pushing Docker image\u2026","info");const f=await he(i,t,a,s,r);if(!f.success){T.debug(I,"Docker buildAndPush (legacy) failed",{appName:t.appName,error:b(f.error.message)}),r.onStepComplete?.(A.DOCKER_OPERATIONS,C,"error");const D=new Error(b(f.error.message));return r.onError?.(D),h(D)}return r.onStepComplete?.(A.DOCKER_OPERATIONS,C,"completed"),r.onLog?.(`Docker image pushed: ${f.data.imageUri}`,"info"),N(j())}const w=await i.preflight?.();if(w!==void 0&&!w.success){r.onStepComplete?.(A.DOCKER_OPERATIONS,C,"error");const d=new Error(b(w.error.message));return r.onError?.(d),h(d)}const l=pe(p,t.path,e.workingDirectory);let E=me(s,a,t.appName);const c=await ae(n.awsProvider,H(x(t.appName)),e.abortSignal);c.success||(E=void 0,r.onLog?.(`Building without registry cache \u2014 ${c.error.message}`,"warn")),r.onLog?.(`Building and pushing ${p.length} service image(s) in ${l.length} group(s)\u2026`,"info");let y=!1;if(i.beginEcrSession!==void 0){const d=await i.beginEcrSession({accountId:s,region:a});if(!d.success){r.onStepComplete?.(A.DOCKER_OPERATIONS,C,"error");const f=new Error(b(d.error.message));return r.onError?.(f),h(f)}y=!0}const v=new AbortController,k=()=>v.abort();e.abortSignal!==void 0&&(e.abortSignal.aborted?v.abort():e.abortSignal.addEventListener("abort",k,{once:!0}));try{const d=()=>U(e.abortSignal)?"the deployment was cancelled":"a sibling group failed";let f;const D=Q(l.length),$=l.map((u,g)=>async()=>{const o=u.members.map(V=>V.name).join(", ");if(v.signal.aborted)throw new Error(`Build of group ${g+1}/${l.length} (${o}) was not started \u2014 ${d()}`);r.onLog?.(`Building group ${g+1}/${l.length} (${o})\u2026`,"info");const P=await be(u,i,t,a,s,S,E,r,n.awsProvider,v.signal,e.options?.environment,e.options?.flagBuildArgs,e.options?.flagBuildSecrets);if(!P.success)throw f===void 0&&(f={error:P.error,leader:u.members[0]?.name,members:o}),v.abort(),P.error;return P.data}),O=await J($,D,{abortOnFirstFailure:!0,abortSignal:v.signal});if(f===void 0)for(let u=0;u<l.length;u++){const g=O[u];if(!(g!==void 0&&g.success)){f={error:g!==void 0&&!g.success?g.error:new Error(`Build of group ${u+1}/${l.length} never ran \u2014 ${d()}`),leader:l[u].members[0]?.name,members:l[u].members.map(o=>o.name).join(", ")};break}}if(f!==void 0){T.debug(I,"Docker buildAndPush failed",{appName:t.appName,leader:f.leader,members:f.members,error:b(f.error.message)}),r.onStepComplete?.(A.DOCKER_OPERATIONS,C,"error");const u=new Error(b(f.error.message));return r.onError?.(u),h(u)}const R={},B={};for(const u of O)if(!(u===void 0||!u.success)){for(const[g,o]of Object.entries(u.data.contentHashTagsByService))R[g]=o;for(const[g,o]of Object.entries(u.data.imageDigestsByService))B[g]=o}return T.debug(I,"Docker buildAndPush succeeded",{appName:t.appName,services:p.map(u=>u.name),groupCount:l.length,contentHashTags:R}),r.onStepComplete?.(A.DOCKER_OPERATIONS,C,"completed"),r.onLog?.(`Docker images pushed for ${p.length} service(s)`,"info"),N({contentHashTagsByService:R,imageDigestsByService:B,repoUri:S})}finally{e.abortSignal?.removeEventListener("abort",k),y&&i.endEcrSession!==void 0&&await i.endEcrSession().catch(d=>{T.debug(I,"endEcrSession cleanup failed",{error:b(d instanceof Error?d.message:String(d))})})}}export{C as DOCKER_BUILD_STEP_NAME,j as emptyBuildProducts,Te as runDockerBuild};
@@ -0,0 +1,14 @@
1
+ import { type Result } from "@fjall/generator";
2
+ import type { AwsProvider } from "../../aws/AwsProvider.js";
3
+ /**
4
+ * Idempotently provision the buildx registry-cache repository beside the app's
5
+ * image repository: create-if-absent, then (re)apply the shared untagged-image
6
+ * lifecycle policy. Mirrors the `fjall build` provisioner
7
+ * (`@fjall/cli` EcrBuildOrchestrator → EcrRepositoryService) — both address the
8
+ * same physical repo via `buildCacheRepositoryName` and the shared policy text,
9
+ * so neither path can drift the other.
10
+ *
11
+ * Failure messages are pre-masked at construction — callers may surface
12
+ * `error.message` to log/callback sinks without re-masking.
13
+ */
14
+ export declare function ensureBuildCacheRepository(awsProvider: AwsProvider, cacheRepositoryName: string, abortSignal?: AbortSignal): Promise<Result<void>>;
@@ -0,0 +1 @@
1
+ import{ECRClient as s,CreateRepositoryCommand as u,PutLifecyclePolicyCommand as m}from"@aws-sdk/client-ecr";import{success as y,failure as i}from"@fjall/generator";import{getErrorMessage as n,maskSensitiveOutput as c}from"@fjall/util";import{untaggedLifecyclePolicyText as d,CACHE_REPO_UNTAGGED_RETENTION_DAYS as p}from"@fjall/util/docker";import{logger as f}from"@fjall/util/logger";import{composeSdkAbortSignal as a,extractErrorName as g}from"../../aws/organisations/types.js";const C="EcrCacheRepository";async function O(l,r,t){const o=l.getClient(s);try{await o.send(new u({repositoryName:r,imageScanningConfiguration:{scanOnPush:!0},imageTagMutability:"MUTABLE"}),{abortSignal:a(t)}),f.debug(C,"Created build-cache repository",{cacheRepositoryName:r})}catch(e){if(g(e)!=="RepositoryAlreadyExistsException")return i(new Error(`Could not create build-cache repository '${r}': ${c(n(e))}`))}try{await o.send(new m({repositoryName:r,lifecyclePolicyText:d(p)}),{abortSignal:a(t)})}catch(e){return i(new Error(`Could not set lifecycle policy on build-cache repository '${r}': ${c(n(e))}`))}return y(void 0)}export{O as ensureBuildCacheRepository};